From 36d22d82aa202bb199967e9512281e9a53db42c9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 21:33:14 +0200 Subject: Adding upstream version 115.7.0esr. Signed-off-by: Daniel Baumann --- xpcom/base/AppShutdown.cpp | 469 +++ xpcom/base/AppShutdown.h | 152 + xpcom/base/AutoRestore.h | 42 + xpcom/base/AvailableMemoryTracker.cpp | 194 + xpcom/base/AvailableMemoryTracker.h | 25 + xpcom/base/AvailableMemoryWatcher.cpp | 180 + xpcom/base/AvailableMemoryWatcher.h | 68 + xpcom/base/AvailableMemoryWatcherLinux.cpp | 290 ++ xpcom/base/AvailableMemoryWatcherMac.cpp | 625 +++ xpcom/base/AvailableMemoryWatcherUtils.h | 56 + xpcom/base/AvailableMemoryWatcherWin.cpp | 407 ++ xpcom/base/ClearOnShutdown.cpp | 63 + xpcom/base/ClearOnShutdown.h | 147 + xpcom/base/CodeAddressService.h | 250 ++ xpcom/base/CountingAllocatorBase.h | 158 + xpcom/base/CycleCollectedJSContext.cpp | 928 +++++ xpcom/base/CycleCollectedJSContext.h | 396 ++ xpcom/base/CycleCollectedJSRuntime.cpp | 2075 ++++++++++ xpcom/base/CycleCollectedJSRuntime.h | 528 +++ xpcom/base/Debug.cpp | 21 + xpcom/base/Debug.h | 21 + xpcom/base/DebuggerOnGCRunnable.cpp | 51 + xpcom/base/DebuggerOnGCRunnable.h | 35 + xpcom/base/DeferredFinalize.cpp | 23 + xpcom/base/DeferredFinalize.h | 35 + xpcom/base/EnumeratedArrayCycleCollection.h | 32 + xpcom/base/ErrorList.py | 1406 +++++++ xpcom/base/ErrorNames.cpp | 78 + xpcom/base/ErrorNames.h | 29 + xpcom/base/GkRustUtils.cpp | 16 + xpcom/base/GkRustUtils.h | 18 + xpcom/base/HoldDropJSObjects.cpp | 53 + xpcom/base/HoldDropJSObjects.h | 84 + xpcom/base/IntentionalCrash.h | 71 + xpcom/base/JSONStringWriteFuncs.h | 52 + xpcom/base/JSObjectHolder.cpp | 9 + xpcom/base/JSObjectHolder.h | 42 + xpcom/base/LogCommandLineHandler.cpp | 90 + xpcom/base/LogCommandLineHandler.h | 49 + xpcom/base/LogModulePrefWatcher.cpp | 164 + xpcom/base/LogModulePrefWatcher.h | 39 + xpcom/base/Logging.cpp | 912 +++++ xpcom/base/Logging.h | 322 ++ xpcom/base/MacHelpers.h | 18 + xpcom/base/MacHelpers.mm | 32 + xpcom/base/MacStringHelpers.h | 20 + xpcom/base/MacStringHelpers.mm | 34 + xpcom/base/MemoryInfo.cpp | 105 + xpcom/base/MemoryInfo.h | 81 + xpcom/base/MemoryMapping.cpp | 208 + xpcom/base/MemoryMapping.h | 183 + xpcom/base/MemoryPressureLevelMac.h | 77 + xpcom/base/MemoryReportingProcess.h | 45 + xpcom/base/MemoryTelemetry.cpp | 520 +++ xpcom/base/MemoryTelemetry.h | 72 + xpcom/base/NSPRLogModulesParser.cpp | 66 + xpcom/base/NSPRLogModulesParser.h | 24 + xpcom/base/OwningNonNull.h | 213 + xpcom/base/RLBoxSandboxPool.cpp | 111 + xpcom/base/RLBoxSandboxPool.h | 105 + xpcom/base/RLBoxUtils.h | 70 + xpcom/base/ShutdownPhase.h | 32 + xpcom/base/SizeOfState.h | 67 + xpcom/base/StaticLocalPtr.h | 253 ++ xpcom/base/StaticMonitor.h | 117 + xpcom/base/StaticMutex.h | 84 + xpcom/base/StaticPtr.h | 235 ++ xpcom/base/components.conf | 30 + xpcom/base/moz.build | 261 ++ xpcom/base/nsAlgorithm.h | 54 + xpcom/base/nsAutoRef.h | 489 +++ xpcom/base/nsCOMPtr.cpp | 34 + xpcom/base/nsCOMPtr.h | 1170 ++++++ xpcom/base/nsCRTGlue.cpp | 328 ++ xpcom/base/nsCRTGlue.h | 172 + xpcom/base/nsClassInfoImpl.cpp | 61 + xpcom/base/nsCom.h | 10 + xpcom/base/nsConsoleMessage.cpp | 67 + xpcom/base/nsConsoleMessage.h | 31 + xpcom/base/nsConsoleService.cpp | 569 +++ xpcom/base/nsConsoleService.h | 113 + xpcom/base/nsCrashOnException.cpp | 34 + xpcom/base/nsCrashOnException.h | 23 + xpcom/base/nsCycleCollectionNoteChild.h | 85 + xpcom/base/nsCycleCollectionNoteRootCallback.h | 44 + xpcom/base/nsCycleCollectionParticipant.cpp | 34 + xpcom/base/nsCycleCollectionParticipant.h | 1107 ++++++ xpcom/base/nsCycleCollectionTraversalCallback.h | 70 + xpcom/base/nsCycleCollector.cpp | 4050 ++++++++++++++++++++ xpcom/base/nsCycleCollector.h | 74 + xpcom/base/nsCycleCollectorTraceJSHelpers.cpp | 89 + xpcom/base/nsDebug.h | 384 ++ xpcom/base/nsDebugImpl.cpp | 675 ++++ xpcom/base/nsDebugImpl.h | 42 + xpcom/base/nsDumpUtils.cpp | 488 +++ xpcom/base/nsDumpUtils.h | 184 + xpcom/base/nsError.h | 90 + xpcom/base/nsGZFileWriter.cpp | 97 + xpcom/base/nsGZFileWriter.h | 79 + xpcom/base/nsIAvailableMemoryWatcherBase.idl | 36 + xpcom/base/nsIClassInfoImpl.h | 193 + xpcom/base/nsIConsoleListener.idl | 18 + xpcom/base/nsIConsoleMessage.idl | 52 + xpcom/base/nsIConsoleService.idl | 78 + xpcom/base/nsICycleCollectorListener.idl | 166 + xpcom/base/nsID.cpp | 239 ++ xpcom/base/nsID.h | 173 + xpcom/base/nsIDUtils.h | 32 + xpcom/base/nsIDebug2.idl | 100 + xpcom/base/nsIException.idl | 80 + xpcom/base/nsIInterfaceRequestor.idl | 35 + xpcom/base/nsIInterfaceRequestorUtils.cpp | 32 + xpcom/base/nsIInterfaceRequestorUtils.h | 40 + xpcom/base/nsIMacPreferencesReader.idl | 34 + xpcom/base/nsIMemoryInfoDumper.idl | 167 + xpcom/base/nsIMemoryReporter.idl | 615 +++ xpcom/base/nsIMessageLoop.idl | 36 + xpcom/base/nsINIParser.cpp | 324 ++ xpcom/base/nsINIParser.h | 168 + xpcom/base/nsISecurityConsoleMessage.idl | 20 + xpcom/base/nsISizeOf.h | 39 + xpcom/base/nsISupports.idl | 60 + xpcom/base/nsISupportsImpl.cpp | 144 + xpcom/base/nsISupportsImpl.h | 1539 ++++++++ xpcom/base/nsISupportsUtils.h | 142 + xpcom/base/nsIUUIDGenerator.idl | 39 + xpcom/base/nsIVersionComparator.idl | 48 + xpcom/base/nsIWeakReference.idl | 114 + xpcom/base/nsIWeakReferenceUtils.h | 84 + xpcom/base/nsInterfaceRequestorAgg.cpp | 74 + xpcom/base/nsInterfaceRequestorAgg.h | 35 + xpcom/base/nsMacPreferencesReader.h | 34 + xpcom/base/nsMacPreferencesReader.mm | 79 + xpcom/base/nsMacUtilsImpl.cpp | 544 +++ xpcom/base/nsMacUtilsImpl.h | 55 + xpcom/base/nsMaybeWeakPtr.h | 170 + xpcom/base/nsMemory.h | 62 + xpcom/base/nsMemoryImpl.cpp | 131 + xpcom/base/nsMemoryInfoDumper.cpp | 742 ++++ xpcom/base/nsMemoryInfoDumper.h | 48 + xpcom/base/nsMemoryReporterManager.cpp | 2916 ++++++++++++++ xpcom/base/nsMemoryReporterManager.h | 321 ++ xpcom/base/nsMessageLoop.cpp | 151 + xpcom/base/nsMessageLoop.h | 29 + xpcom/base/nsObjCExceptions.h | 56 + xpcom/base/nsObjCExceptions.mm | 48 + xpcom/base/nsQueryObject.h | 93 + xpcom/base/nsSecurityConsoleMessage.cpp | 37 + xpcom/base/nsSecurityConsoleMessage.h | 33 + xpcom/base/nsSystemInfo.cpp | 1660 ++++++++ xpcom/base/nsSystemInfo.h | 130 + xpcom/base/nsTraceRefcnt.cpp | 1219 ++++++ xpcom/base/nsTraceRefcnt.h | 38 + xpcom/base/nsUUIDGenerator.cpp | 30 + xpcom/base/nsUUIDGenerator.h | 30 + xpcom/base/nsVersionComparator.cpp | 401 ++ xpcom/base/nsVersionComparator.h | 112 + xpcom/base/nsVersionComparatorImpl.cpp | 20 + xpcom/base/nsVersionComparatorImpl.h | 28 + xpcom/base/nsWeakReference.cpp | 151 + xpcom/base/nsWeakReference.h | 68 + xpcom/base/nsWindowsHelpers.h | 348 ++ xpcom/base/nscore.h | 265 ++ xpcom/base/nsrootidl.idl | 105 + xpcom/build/BinaryPath.h | 321 ++ xpcom/build/FileLocation.cpp | 212 + xpcom/build/FileLocation.h | 140 + xpcom/build/IOInterposer.cpp | 532 +++ xpcom/build/IOInterposer.h | 288 ++ xpcom/build/IOInterposerPrivate.h | 117 + xpcom/build/LateWriteChecks.cpp | 263 ++ xpcom/build/LateWriteChecks.h | 78 + xpcom/build/MainThreadIOLogger.cpp | 206 + xpcom/build/MainThreadIOLogger.h | 18 + xpcom/build/NSPRInterposer.cpp | 207 + xpcom/build/NSPRInterposer.h | 28 + xpcom/build/Omnijar.cpp | 198 + xpcom/build/Omnijar.h | 171 + xpcom/build/PoisonIOInterposer.h | 93 + xpcom/build/PoisonIOInterposerBase.cpp | 268 ++ xpcom/build/PoisonIOInterposerMac.cpp | 348 ++ xpcom/build/PoisonIOInterposerStub.cpp | 16 + xpcom/build/PoisonIOInterposerWin.cpp | 508 +++ xpcom/build/Services.py | 170 + xpcom/build/SmallArrayLRUCache.h | 199 + xpcom/build/XPCOM.h | 167 + xpcom/build/XPCOMInit.cpp | 790 ++++ xpcom/build/XPCOMModule.h | 20 + xpcom/build/XPCOMModule.inc | 3 + xpcom/build/XREAppData.h | 231 ++ xpcom/build/XREChildData.h | 44 + xpcom/build/XREShellData.h | 39 + xpcom/build/components.conf | 266 ++ xpcom/build/gen_process_types.py | 34 + xpcom/build/mach_override.c | 793 ++++ xpcom/build/mach_override.h | 121 + xpcom/build/moz.build | 105 + xpcom/build/nsXPCOM.h | 383 ++ xpcom/build/nsXPCOMCID.h | 220 ++ xpcom/build/nsXPCOMCIDInternal.h | 49 + xpcom/build/nsXPCOMPrivate.h | 126 + xpcom/build/nsXULAppAPI.h | 387 ++ xpcom/build/perfprobe.cpp | 215 ++ xpcom/build/perfprobe.h | 198 + xpcom/build/xpcom_alpha.def | 256 ++ xpcom/components/GenericFactory.cpp | 18 + xpcom/components/GenericFactory.h | 38 + xpcom/components/ManifestParser.cpp | 678 ++++ xpcom/components/ManifestParser.h | 23 + xpcom/components/Module.h | 76 + xpcom/components/ModuleUtils.h | 81 + xpcom/components/StaticComponents.cpp.in | 410 ++ xpcom/components/StaticComponents.h | 284 ++ xpcom/components/components.conf | 23 + xpcom/components/gen_static_components.py | 1206 ++++++ xpcom/components/moz.build | 85 + xpcom/components/nsCategoryCache.cpp | 162 + xpcom/components/nsCategoryCache.h | 123 + xpcom/components/nsCategoryManager.cpp | 692 ++++ xpcom/components/nsCategoryManager.h | 145 + xpcom/components/nsCategoryManagerUtils.h | 14 + xpcom/components/nsComponentManager.cpp | 1575 ++++++++ xpcom/components/nsComponentManager.h | 218 ++ xpcom/components/nsComponentManagerUtils.cpp | 259 ++ xpcom/components/nsComponentManagerUtils.h | 170 + xpcom/components/nsICategoryManager.idl | 137 + xpcom/components/nsIClassInfo.idl | 74 + xpcom/components/nsIComponentManager.idl | 117 + xpcom/components/nsIComponentRegistrar.idl | 100 + xpcom/components/nsIFactory.idl | 27 + xpcom/components/nsIServiceManager.idl | 70 + xpcom/components/nsServiceManagerUtils.h | 56 + xpcom/components/test/python.ini | 4 + .../components/test/test_gen_static_components.py | 150 + xpcom/docs/cc-macros.rst | 190 + xpcom/docs/collections.rst | 95 + xpcom/docs/hashtables.rst | 141 + xpcom/docs/hashtables_detailed.rst | 121 + xpcom/docs/huntingleaks.rst | 22 + xpcom/docs/index.rst | 19 + xpcom/docs/logging.rst | 435 +++ xpcom/docs/refptr.rst | 81 + xpcom/docs/stringguide.rst | 1110 ++++++ xpcom/docs/thread-safety.rst | 354 ++ xpcom/docs/writing-xpcom-interface.rst | 287 ++ xpcom/docs/xpidl.rst | 390 ++ xpcom/ds/ArenaAllocator.h | 214 ++ xpcom/ds/ArenaAllocatorExtensions.h | 76 + xpcom/ds/ArrayAlgorithm.h | 109 + xpcom/ds/ArrayIterator.h | 165 + xpcom/ds/Atom.py | 64 + xpcom/ds/AtomArray.h | 19 + xpcom/ds/Dafsa.cpp | 153 + xpcom/ds/Dafsa.h | 54 + xpcom/ds/HTMLAtoms.py | 257 ++ xpcom/ds/IncrementalTokenizer.cpp | 190 + xpcom/ds/IncrementalTokenizer.h | 125 + xpcom/ds/Observer.h | 76 + xpcom/ds/PLDHashTable.cpp | 870 +++++ xpcom/ds/PLDHashTable.h | 807 ++++ xpcom/ds/PerfectHash.h | 50 + xpcom/ds/SimpleEnumerator.h | 73 + xpcom/ds/StaticAtoms.py | 2636 +++++++++++++ xpcom/ds/StickyTimeDuration.h | 239 ++ xpcom/ds/Tokenizer.cpp | 805 ++++ xpcom/ds/Tokenizer.h | 524 +++ xpcom/ds/components.conf | 24 + xpcom/ds/moz.build | 156 + xpcom/ds/nsArray.cpp | 146 + xpcom/ds/nsArray.h | 78 + xpcom/ds/nsArrayEnumerator.cpp | 213 + xpcom/ds/nsArrayEnumerator.h | 31 + xpcom/ds/nsArrayUtils.cpp | 22 + xpcom/ds/nsArrayUtils.h | 34 + xpcom/ds/nsAtom.h | 309 ++ xpcom/ds/nsAtomTable.cpp | 670 ++++ xpcom/ds/nsAtomTable.h | 30 + xpcom/ds/nsBaseHashtable.h | 1029 +++++ xpcom/ds/nsCOMArray.cpp | 252 ++ xpcom/ds/nsCOMArray.h | 398 ++ xpcom/ds/nsCRT.cpp | 123 + xpcom/ds/nsCRT.h | 119 + xpcom/ds/nsCharSeparatedTokenizer.cpp | 10 + xpcom/ds/nsCharSeparatedTokenizer.h | 274 ++ xpcom/ds/nsCheapSets.h | 155 + xpcom/ds/nsClassHashtable.h | 115 + xpcom/ds/nsDeque.cpp | 265 ++ xpcom/ds/nsDeque.h | 538 +++ xpcom/ds/nsEnumeratorUtils.cpp | 248 ++ xpcom/ds/nsEnumeratorUtils.h | 24 + xpcom/ds/nsExpirationTracker.h | 618 +++ xpcom/ds/nsGkAtoms.cpp | 70 + xpcom/ds/nsGkAtoms.h | 192 + xpcom/ds/nsHashKeys.h | 636 +++ xpcom/ds/nsHashPropertyBag.cpp | 366 ++ xpcom/ds/nsHashPropertyBag.h | 80 + xpcom/ds/nsHashtablesFwd.h | 94 + xpcom/ds/nsIArray.idl | 103 + xpcom/ds/nsIArrayExtensions.idl | 51 + xpcom/ds/nsIINIParser.idl | 61 + xpcom/ds/nsIMutableArray.idl | 92 + xpcom/ds/nsINIParserImpl.cpp | 143 + xpcom/ds/nsINIParserImpl.h | 23 + xpcom/ds/nsIObserver.idl | 37 + xpcom/ds/nsIObserverService.idl | 109 + xpcom/ds/nsIPersistentProperties.h | 13 + xpcom/ds/nsIPersistentProperties2.idl | 59 + xpcom/ds/nsIProperties.idl | 46 + xpcom/ds/nsIProperty.idl | 25 + xpcom/ds/nsIPropertyBag.idl | 28 + xpcom/ds/nsIPropertyBag2.idl | 80 + xpcom/ds/nsISerializable.idl | 32 + xpcom/ds/nsISimpleEnumerator.idl | 77 + xpcom/ds/nsIStringEnumerator.idl | 37 + xpcom/ds/nsISupportsIterators.idl | 292 ++ xpcom/ds/nsISupportsPrimitives.idl | 222 ++ xpcom/ds/nsIVariant.idl | 162 + xpcom/ds/nsIWindowsRegKey.idl | 336 ++ xpcom/ds/nsIWritablePropertyBag.idl | 27 + xpcom/ds/nsIWritablePropertyBag2.idl | 22 + xpcom/ds/nsInterfaceHashtable.h | 14 + xpcom/ds/nsMathUtils.h | 109 + xpcom/ds/nsObserverList.cpp | 92 + xpcom/ds/nsObserverList.h | 67 + xpcom/ds/nsObserverService.cpp | 357 ++ xpcom/ds/nsObserverService.h | 56 + xpcom/ds/nsPersistentProperties.cpp | 584 +++ xpcom/ds/nsPersistentProperties.h | 57 + xpcom/ds/nsPointerHashKeys.h | 50 + xpcom/ds/nsProperties.cpp | 61 + xpcom/ds/nsProperties.h | 29 + xpcom/ds/nsQuickSort.cpp | 175 + xpcom/ds/nsQuickSort.h | 39 + xpcom/ds/nsRefCountedHashtable.h | 245 ++ xpcom/ds/nsRefPtrHashtable.h | 12 + xpcom/ds/nsSimpleEnumerator.cpp | 78 + xpcom/ds/nsSimpleEnumerator.h | 22 + xpcom/ds/nsStaticAtomUtils.h | 36 + xpcom/ds/nsStaticNameTable.cpp | 180 + xpcom/ds/nsStaticNameTable.h | 48 + xpcom/ds/nsStringEnumerator.cpp | 311 ++ xpcom/ds/nsStringEnumerator.h | 102 + xpcom/ds/nsSupportsPrimitives.cpp | 603 +++ xpcom/ds/nsSupportsPrimitives.h | 279 ++ xpcom/ds/nsTArray-inl.h | 691 ++++ xpcom/ds/nsTArray.cpp | 28 + xpcom/ds/nsTArray.h | 3345 ++++++++++++++++ xpcom/ds/nsTArrayForwardDeclare.h | 39 + xpcom/ds/nsTHashMap.h | 70 + xpcom/ds/nsTHashSet.h | 193 + xpcom/ds/nsTHashtable.h | 966 +++++ xpcom/ds/nsTObserverArray.cpp | 27 + xpcom/ds/nsTObserverArray.h | 583 +++ xpcom/ds/nsTPriorityQueue.h | 147 + xpcom/ds/nsVariant.cpp | 1876 +++++++++ xpcom/ds/nsVariant.h | 223 ++ xpcom/ds/nsWhitespaceTokenizer.h | 96 + xpcom/ds/nsWindowsRegKey.cpp | 523 +++ xpcom/ds/nsWindowsRegKey.h | 45 + xpcom/ds/test/python.ini | 4 + xpcom/ds/test/test_dafsa.py | 546 +++ xpcom/ds/tools/incremental_dafsa.py | 509 +++ xpcom/ds/tools/make_dafsa.py | 372 ++ xpcom/ds/tools/perfecthash.py | 371 ++ .../geckoprocesstypes/__init__.py | 203 + .../geckoprocesstypes/moz.build | 5 + xpcom/geckoprocesstypes_generator/setup.py | 17 + xpcom/glue/FileUtils.cpp | 579 +++ xpcom/glue/FileUtils.h | 165 + xpcom/glue/MemUtils.cpp | 67 + xpcom/glue/MemUtils.h | 20 + xpcom/glue/XREAppData.cpp | 60 + xpcom/glue/moz.build | 12 + xpcom/glue/objs.mozbuild | 22 + xpcom/glue/standalone/moz.build | 36 + xpcom/glue/standalone/nsXPCOMGlue.cpp | 422 ++ xpcom/idl-parser/setup.py | 18 + xpcom/idl-parser/xpidl/__init__.py | 0 xpcom/idl-parser/xpidl/header.py | 710 ++++ xpcom/idl-parser/xpidl/jsonxpt.py | 282 ++ xpcom/idl-parser/xpidl/moz.build | 11 + xpcom/idl-parser/xpidl/python.ini | 4 + xpcom/idl-parser/xpidl/runtests.py | 257 ++ xpcom/idl-parser/xpidl/rust.py | 670 ++++ xpcom/idl-parser/xpidl/rust_macros.py | 108 + xpcom/idl-parser/xpidl/xpidl.py | 2100 ++++++++++ xpcom/io/Base64.cpp | 780 ++++ xpcom/io/Base64.h | 93 + xpcom/io/CocoaFileUtils.h | 46 + xpcom/io/CocoaFileUtils.mm | 289 ++ xpcom/io/FileDescriptorFile.cpp | 442 +++ xpcom/io/FileDescriptorFile.h | 48 + xpcom/io/FilePreferences.cpp | 373 ++ xpcom/io/FilePreferences.h | 40 + xpcom/io/FileUtilsWin.cpp | 139 + xpcom/io/FileUtilsWin.h | 147 + xpcom/io/FixedBufferOutputStream.cpp | 161 + xpcom/io/FixedBufferOutputStream.h | 78 + xpcom/io/InputStreamLengthHelper.cpp | 258 ++ xpcom/io/InputStreamLengthHelper.h | 57 + xpcom/io/InputStreamLengthWrapper.cpp | 345 ++ xpcom/io/InputStreamLengthWrapper.h | 84 + xpcom/io/NonBlockingAsyncInputStream.cpp | 388 ++ xpcom/io/NonBlockingAsyncInputStream.h | 85 + xpcom/io/SlicedInputStream.cpp | 668 ++++ xpcom/io/SlicedInputStream.h | 102 + xpcom/io/SnappyCompressOutputStream.cpp | 259 ++ xpcom/io/SnappyCompressOutputStream.h | 68 + xpcom/io/SnappyFrameUtils.cpp | 241 ++ xpcom/io/SnappyFrameUtils.h | 80 + xpcom/io/SnappyUncompressInputStream.cpp | 386 ++ xpcom/io/SnappyUncompressInputStream.h | 89 + xpcom/io/SpecialSystemDirectory.cpp | 748 ++++ xpcom/io/SpecialSystemDirectory.h | 62 + xpcom/io/StreamBufferSink.h | 29 + xpcom/io/StreamBufferSinkImpl.h | 49 + xpcom/io/StreamBufferSource.h | 61 + xpcom/io/StreamBufferSourceImpl.h | 82 + xpcom/io/components.conf | 42 + xpcom/io/crc32c.c | 154 + xpcom/io/crc32c.h | 26 + xpcom/io/moz.build | 162 + xpcom/io/nsAnonymousTemporaryFile.cpp | 264 ++ xpcom/io/nsAnonymousTemporaryFile.h | 39 + xpcom/io/nsAppDirectoryServiceDefs.h | 102 + xpcom/io/nsAppFileLocationProvider.cpp | 333 ++ xpcom/io/nsAppFileLocationProvider.h | 45 + xpcom/io/nsBinaryStream.cpp | 1007 +++++ xpcom/io/nsBinaryStream.h | 99 + xpcom/io/nsDirectoryService.cpp | 448 +++ xpcom/io/nsDirectoryService.h | 59 + xpcom/io/nsDirectoryServiceDefs.h | 96 + xpcom/io/nsDirectoryServiceUtils.h | 29 + xpcom/io/nsEscape.cpp | 664 ++++ xpcom/io/nsEscape.h | 243 ++ xpcom/io/nsIAsyncInputStream.idl | 105 + xpcom/io/nsIAsyncOutputStream.idl | 104 + xpcom/io/nsIBinaryInputStream.idl | 118 + xpcom/io/nsIBinaryOutputStream.idl | 103 + xpcom/io/nsICloneableInputStream.idl | 31 + xpcom/io/nsIConverterInputStream.idl | 45 + xpcom/io/nsIConverterOutputStream.idl | 30 + xpcom/io/nsIDirectoryEnumerator.idl | 34 + xpcom/io/nsIDirectoryService.idl | 101 + xpcom/io/nsIFile.idl | 597 +++ xpcom/io/nsIIOUtil.idl | 34 + xpcom/io/nsIInputStream.idl | 172 + xpcom/io/nsIInputStreamLength.idl | 85 + xpcom/io/nsIInputStreamPriority.idl | 17 + xpcom/io/nsIInputStreamTee.idl | 42 + xpcom/io/nsILineInputStream.idl | 26 + xpcom/io/nsILocalFileMac.idl | 212 + xpcom/io/nsILocalFileWin.idl | 98 + xpcom/io/nsIMultiplexInputStream.idl | 35 + xpcom/io/nsIOUtil.cpp | 30 + xpcom/io/nsIOUtil.h | 28 + xpcom/io/nsIObjectInputStream.idl | 56 + xpcom/io/nsIObjectOutputStream.idl | 99 + xpcom/io/nsIOutputStream.idl | 164 + xpcom/io/nsIPipe.idl | 171 + xpcom/io/nsIRandomAccessStream.idl | 62 + xpcom/io/nsISafeOutputStream.idl | 39 + xpcom/io/nsIScriptableBase64Encoder.idl | 32 + xpcom/io/nsIScriptableInputStream.idl | 67 + xpcom/io/nsISeekableStream.idl | 65 + xpcom/io/nsIStorageStream.idl | 69 + xpcom/io/nsIStreamBufferAccess.idl | 88 + xpcom/io/nsIStringStream.idl | 92 + xpcom/io/nsITellableStream.idl | 34 + xpcom/io/nsIUnicharInputStream.idl | 97 + xpcom/io/nsIUnicharLineInputStream.idl | 26 + xpcom/io/nsIUnicharOutputStream.idl | 45 + xpcom/io/nsInputStreamTee.cpp | 341 ++ xpcom/io/nsLinebreakConverter.cpp | 452 +++ xpcom/io/nsLinebreakConverter.h | 141 + xpcom/io/nsLocalFile.h | 124 + xpcom/io/nsLocalFileCommon.cpp | 438 +++ xpcom/io/nsLocalFileCommon.h | 16 + xpcom/io/nsLocalFileUnix.cpp | 2916 ++++++++++++++ xpcom/io/nsLocalFileUnix.h | 104 + xpcom/io/nsLocalFileWin.cpp | 3697 ++++++++++++++++++ xpcom/io/nsLocalFileWin.h | 127 + xpcom/io/nsMultiplexInputStream.cpp | 1557 ++++++++ xpcom/io/nsMultiplexInputStream.h | 27 + xpcom/io/nsNativeCharsetUtils.cpp | 98 + xpcom/io/nsNativeCharsetUtils.h | 52 + xpcom/io/nsPipe.h | 21 + xpcom/io/nsPipe3.cpp | 1884 +++++++++ xpcom/io/nsScriptableBase64Encoder.cpp | 26 + xpcom/io/nsScriptableBase64Encoder.h | 30 + xpcom/io/nsScriptableInputStream.cpp | 117 + xpcom/io/nsScriptableInputStream.h | 46 + xpcom/io/nsSegmentedBuffer.cpp | 170 + xpcom/io/nsSegmentedBuffer.h | 126 + xpcom/io/nsStorageStream.cpp | 680 ++++ xpcom/io/nsStorageStream.h | 79 + xpcom/io/nsStreamUtils.cpp | 976 +++++ xpcom/io/nsStreamUtils.h | 332 ++ xpcom/io/nsStringStream.cpp | 591 +++ xpcom/io/nsStringStream.h | 89 + xpcom/io/nsUnicharInputStream.cpp | 132 + xpcom/io/nsUnicharInputStream.h | 15 + xpcom/io/nsWildCard.cpp | 435 +++ xpcom/io/nsWildCard.h | 63 + xpcom/metrics.yaml | 30 + xpcom/moz.build | 56 + xpcom/reflect/moz.build | 7 + xpcom/reflect/xptcall/README | 6 + xpcom/reflect/xptcall/genstubs.pl | 87 + xpcom/reflect/xptcall/md/moz.build | 12 + xpcom/reflect/xptcall/md/test/README | 6 + xpcom/reflect/xptcall/md/test/clean.bat | 5 + xpcom/reflect/xptcall/md/test/invoke_test.cpp | 187 + xpcom/reflect/xptcall/md/test/mk_invoke.bat | 9 + xpcom/reflect/xptcall/md/test/mk_stub.bat | 9 + xpcom/reflect/xptcall/md/test/moz.build | 11 + xpcom/reflect/xptcall/md/test/stub_test.cpp | 209 + xpcom/reflect/xptcall/md/unix/moz.build | 278 ++ .../reflect/xptcall/md/unix/vtable_layout_x86.cpp | 66 + xpcom/reflect/xptcall/md/unix/xptc_gcc_x86_unix.h | 16 + .../reflect/xptcall/md/unix/xptcinvoke_aarch64.cpp | 168 + .../xptcall/md/unix/xptcinvoke_alpha_openbsd.cpp | 144 + xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp | 417 ++ .../xptcall/md/unix/xptcinvoke_arm_netbsd.cpp | 181 + .../xptcall/md/unix/xptcinvoke_arm_openbsd.cpp | 183 + .../xptcall/md/unix/xptcinvoke_asm_aarch64.S | 92 + .../reflect/xptcall/md/unix/xptcinvoke_asm_ipf32.s | 145 + .../reflect/xptcall/md/unix/xptcinvoke_asm_ipf64.s | 146 + .../reflect/xptcall/md/unix/xptcinvoke_asm_mips.S | 134 + .../xptcall/md/unix/xptcinvoke_asm_mips64.S | 121 + .../reflect/xptcall/md/unix/xptcinvoke_asm_pa32.s | 131 + .../xptcall/md/unix/xptcinvoke_asm_parisc_linux.s | 108 + .../xptcall/md/unix/xptcinvoke_asm_ppc64_linux.S | 167 + .../xptcall/md/unix/xptcinvoke_asm_ppc_aix.s | 129 + .../xptcall/md/unix/xptcinvoke_asm_ppc_aix64.s | 128 + .../md/unix/xptcinvoke_asm_ppc_ibmobj_aix.s | 124 + .../xptcall/md/unix/xptcinvoke_asm_ppc_linux.S | 98 + .../xptcall/md/unix/xptcinvoke_asm_ppc_openbsd.S | 94 + .../xptcall/md/unix/xptcinvoke_asm_ppc_rhapsody.s | 142 + .../xptcall/md/unix/xptcinvoke_asm_riscv64.S | 89 + .../md/unix/xptcinvoke_asm_sparc64_openbsd.s | 86 + .../md/unix/xptcinvoke_asm_sparc_linux_GCC3.s | 53 + .../xptcall/md/unix/xptcinvoke_asm_sparc_netbsd.s | 55 + .../xptcall/md/unix/xptcinvoke_asm_sparc_openbsd.s | 55 + .../xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S | 117 + .../reflect/xptcall/md/unix/xptcinvoke_darwin.cpp | 18 + .../xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp | 97 + xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf32.cpp | 131 + xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf64.cpp | 99 + .../xptcall/md/unix/xptcinvoke_linux_alpha.cpp | 144 + .../xptcall/md/unix/xptcinvoke_linux_s390.cpp | 194 + .../xptcall/md/unix/xptcinvoke_linux_s390x.cpp | 189 + xpcom/reflect/xptcall/md/unix/xptcinvoke_mips.cpp | 99 + .../reflect/xptcall/md/unix/xptcinvoke_mips64.cpp | 144 + xpcom/reflect/xptcall/md/unix/xptcinvoke_pa32.cpp | 148 + .../xptcall/md/unix/xptcinvoke_ppc64_linux.cpp | 140 + .../reflect/xptcall/md/unix/xptcinvoke_ppc_aix.cpp | 73 + .../xptcall/md/unix/xptcinvoke_ppc_aix64.cpp | 62 + .../xptcall/md/unix/xptcinvoke_ppc_linux.cpp | 128 + .../xptcall/md/unix/xptcinvoke_ppc_openbsd.cpp | 109 + .../xptcall/md/unix/xptcinvoke_ppc_rhapsody.cpp | 113 + .../reflect/xptcall/md/unix/xptcinvoke_riscv64.cpp | 106 + .../xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp | 69 + .../xptcall/md/unix/xptcinvoke_sparc_netbsd.cpp | 130 + .../xptcall/md/unix/xptcinvoke_sparc_openbsd.cpp | 127 + .../xptcall/md/unix/xptcinvoke_sparc_solaris.cpp | 130 + .../xptcall/md/unix/xptcinvoke_x86_64_unix.cpp | 76 + .../reflect/xptcall/md/unix/xptcstubs_aarch64.cpp | 238 ++ .../xptcall/md/unix/xptcstubs_alpha_openbsd.cpp | 178 + xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp | 226 ++ .../xptcall/md/unix/xptcstubs_arm_netbsd.cpp | 99 + .../xptcall/md/unix/xptcstubs_arm_openbsd.cpp | 194 + .../xptcall/md/unix/xptcstubs_asm_aarch64.S | 62 + .../reflect/xptcall/md/unix/xptcstubs_asm_ipf32.s | 123 + .../reflect/xptcall/md/unix/xptcstubs_asm_ipf64.s | 124 + xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.S | 116 + .../xptcall/md/unix/xptcstubs_asm_mips.s.m4 | 75 + .../reflect/xptcall/md/unix/xptcstubs_asm_mips64.S | 111 + xpcom/reflect/xptcall/md/unix/xptcstubs_asm_pa32.s | 68 + .../xptcall/md/unix/xptcstubs_asm_parisc_linux.s | 73 + .../xptcall/md/unix/xptcstubs_asm_ppc64_linux.S | 112 + .../xptcall/md/unix/xptcstubs_asm_ppc_aix.s.m4 | 119 + .../xptcall/md/unix/xptcstubs_asm_ppc_aix64.s.m4 | 97 + .../xptcall/md/unix/xptcstubs_asm_ppc_darwin.s.m4 | 114 + .../xptcall/md/unix/xptcstubs_asm_ppc_linux.S | 77 + .../xptcall/md/unix/xptcstubs_asm_ppc_openbsd.S | 72 + .../xptcall/md/unix/xptcstubs_asm_riscv64.S | 53 + .../md/unix/xptcstubs_asm_sparc64_openbsd.s | 50 + .../xptcall/md/unix/xptcstubs_asm_sparc_netbsd.s | 49 + .../xptcall/md/unix/xptcstubs_asm_sparc_openbsd.s | 49 + .../xptcall/md/unix/xptcstubs_asm_sparc_solaris.s | 49 + xpcom/reflect/xptcall/md/unix/xptcstubs_darwin.cpp | 18 + .../xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp | 132 + xpcom/reflect/xptcall/md/unix/xptcstubs_ipf32.cpp | 139 + xpcom/reflect/xptcall/md/unix/xptcstubs_ipf64.cpp | 142 + .../xptcall/md/unix/xptcstubs_linux_alpha.cpp | 179 + .../xptcall/md/unix/xptcstubs_linux_s390.cpp | 178 + .../xptcall/md/unix/xptcstubs_linux_s390x.cpp | 182 + xpcom/reflect/xptcall/md/unix/xptcstubs_mips.cpp | 104 + xpcom/reflect/xptcall/md/unix/xptcstubs_mips64.cpp | 183 + xpcom/reflect/xptcall/md/unix/xptcstubs_pa32.cpp | 141 + .../xptcall/md/unix/xptcstubs_ppc64_linux.cpp | 280 ++ .../reflect/xptcall/md/unix/xptcstubs_ppc_aix.cpp | 181 + .../xptcall/md/unix/xptcstubs_ppc_aix64.cpp | 168 + .../xptcall/md/unix/xptcstubs_ppc_linux.cpp | 211 + .../xptcall/md/unix/xptcstubs_ppc_openbsd.cpp | 194 + .../xptcall/md/unix/xptcstubs_ppc_rhapsody.cpp | 150 + .../reflect/xptcall/md/unix/xptcstubs_riscv64.cpp | 160 + .../xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp | 101 + .../xptcall/md/unix/xptcstubs_sparc_netbsd.cpp | 103 + .../xptcall/md/unix/xptcstubs_sparc_openbsd.cpp | 103 + .../xptcall/md/unix/xptcstubs_sparc_solaris.cpp | 104 + .../xptcall/md/unix/xptcstubs_x86_64_darwin.cpp | 183 + .../xptcall/md/unix/xptcstubs_x86_64_linux.cpp | 209 + xpcom/reflect/xptcall/md/win32/moz.build | 53 + xpcom/reflect/xptcall/md/win32/preprocess.py | 44 + xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp | 45 + .../xptcall/md/win32/xptcinvoke_aarch64.cpp | 141 + .../xptcall/md/win32/xptcinvoke_asm_aarch64.asm | 66 + .../xptcall/md/win32/xptcinvoke_asm_x86_64.asm | 107 + .../xptcall/md/win32/xptcinvoke_asm_x86_64_gnu.s | 110 + .../xptcall/md/win32/xptcinvoke_asm_x86_msvc.asm | 63 + .../reflect/xptcall/md/win32/xptcinvoke_x86_64.cpp | 58 + .../xptcall/md/win32/xptcinvoke_x86_gnu.cpp | 106 + xpcom/reflect/xptcall/md/win32/xptcstubs.cpp | 138 + .../reflect/xptcall/md/win32/xptcstubs_aarch64.cpp | 186 + .../xptcall/md/win32/xptcstubs_asm_aarch64.asm | 306 ++ .../xptcall/md/win32/xptcstubs_asm_x86_64.asm | 335 ++ .../reflect/xptcall/md/win32/xptcstubs_x86_64.cpp | 188 + .../xptcall/md/win32/xptcstubs_x86_64_gnu.cpp | 297 ++ xpcom/reflect/xptcall/moz.build | 20 + xpcom/reflect/xptcall/nsXPTCUtils.h | 41 + xpcom/reflect/xptcall/porting.html | 237 ++ xpcom/reflect/xptcall/xptcall.cpp | 55 + xpcom/reflect/xptcall/xptcall.h | 173 + xpcom/reflect/xptcall/xptcprivate.h | 68 + xpcom/reflect/xptcall/xptcstubsdecl.inc | 761 ++++ xpcom/reflect/xptcall/xptcstubsdef.inc | 252 ++ xpcom/reflect/xptinfo/moz.build | 21 + xpcom/reflect/xptinfo/xptcodegen.py | 646 ++++ xpcom/reflect/xptinfo/xptinfo.cpp | 105 + xpcom/reflect/xptinfo/xptinfo.h | 711 ++++ xpcom/rust/gecko_logger/Cargo.toml | 12 + xpcom/rust/gecko_logger/src/lib.rs | 256 ++ xpcom/rust/gkrust_utils/Cargo.toml | 9 + xpcom/rust/gkrust_utils/cbindgen.toml | 31 + xpcom/rust/gkrust_utils/src/lib.rs | 24 + xpcom/rust/gtest/bench-collections/Bench.cpp | 297 ++ xpcom/rust/gtest/bench-collections/Cargo.toml | 12 + xpcom/rust/gtest/bench-collections/bench.rs | 101 + xpcom/rust/gtest/moz.build | 14 + xpcom/rust/gtest/moz_task/Cargo.toml | 13 + xpcom/rust/gtest/moz_task/TestMozTask.cpp | 14 + xpcom/rust/gtest/moz_task/test.rs | 78 + xpcom/rust/gtest/nsstring/Cargo.toml | 12 + xpcom/rust/gtest/nsstring/TestnsString.cpp | 177 + xpcom/rust/gtest/nsstring/test.rs | 131 + xpcom/rust/gtest/xpcom/Cargo.toml | 14 + xpcom/rust/gtest/xpcom/TestXpcom.cpp | 66 + xpcom/rust/gtest/xpcom/test.rs | 131 + xpcom/rust/moz_task/Cargo.toml | 16 + xpcom/rust/moz_task/src/dispatcher.rs | 153 + xpcom/rust/moz_task/src/event_loop.rs | 66 + xpcom/rust/moz_task/src/executor.rs | 291 ++ xpcom/rust/moz_task/src/lib.rs | 378 ++ xpcom/rust/nserror/Cargo.toml | 11 + xpcom/rust/nserror/src/lib.rs | 79 + xpcom/rust/nsstring/Cargo.toml | 14 + xpcom/rust/nsstring/src/conversions.rs | 751 ++++ xpcom/rust/nsstring/src/lib.rs | 1543 ++++++++ xpcom/rust/xpcom/Cargo.toml | 20 + xpcom/rust/xpcom/src/base.rs | 59 + xpcom/rust/xpcom/src/components.rs | 23 + xpcom/rust/xpcom/src/interfaces/idl.rs | 12 + xpcom/rust/xpcom/src/interfaces/mod.rs | 31 + xpcom/rust/xpcom/src/interfaces/nonidl.rs | 180 + xpcom/rust/xpcom/src/lib.rs | 43 + xpcom/rust/xpcom/src/method.rs | 241 ++ xpcom/rust/xpcom/src/promise.rs | 62 + xpcom/rust/xpcom/src/reexports.rs | 52 + xpcom/rust/xpcom/src/refptr.rs | 388 ++ xpcom/rust/xpcom/src/statics.rs | 75 + xpcom/rust/xpcom/xpcom_macros/Cargo.toml | 16 + xpcom/rust/xpcom/xpcom_macros/src/lib.rs | 818 ++++ xpcom/string/README.html | 11 + xpcom/string/RustRegex.h | 707 ++++ xpcom/string/RustStringAPI.cpp | 123 + xpcom/string/crashtests/1113005-frame.html | 5 + xpcom/string/crashtests/1113005.html | 2 + xpcom/string/crashtests/394275-1.html | 9 + xpcom/string/crashtests/395651-1.html | 30 + xpcom/string/crashtests/crashtests.list | 3 + xpcom/string/moz.build | 62 + xpcom/string/nsASCIIMask.cpp | 38 + xpcom/string/nsASCIIMask.h | 70 + xpcom/string/nsAString.h | 38 + xpcom/string/nsCharTraits.h | 486 +++ xpcom/string/nsDependentString.h | 15 + xpcom/string/nsDependentSubstring.h | 13 + xpcom/string/nsLiteralString.h | 31 + xpcom/string/nsPrintfCString.h | 64 + xpcom/string/nsPromiseFlatString.h | 14 + xpcom/string/nsReadableUtils.cpp | 630 +++ xpcom/string/nsReadableUtils.h | 610 +++ xpcom/string/nsString.h | 171 + xpcom/string/nsStringBuffer.cpp | 162 + xpcom/string/nsStringBuffer.h | 184 + xpcom/string/nsStringFlags.h | 95 + xpcom/string/nsStringFwd.h | 92 + xpcom/string/nsStringIterator.h | 117 + xpcom/string/nsStringStats.cpp | 66 + xpcom/string/nsStringStats.h | 32 + xpcom/string/nsTDependentString.cpp | 50 + xpcom/string/nsTDependentString.h | 126 + xpcom/string/nsTDependentSubstring.cpp | 106 + xpcom/string/nsTDependentSubstring.h | 162 + xpcom/string/nsTLiteralString.cpp | 10 + xpcom/string/nsTLiteralString.h | 113 + xpcom/string/nsTPromiseFlatString.cpp | 26 + xpcom/string/nsTPromiseFlatString.h | 136 + xpcom/string/nsTString.cpp | 42 + xpcom/string/nsTString.h | 447 +++ xpcom/string/nsTStringComparator.cpp | 91 + xpcom/string/nsTStringHasher.h | 30 + xpcom/string/nsTStringRepr.cpp | 273 ++ xpcom/string/nsTStringRepr.h | 546 +++ xpcom/string/nsTSubstring.cpp | 1706 +++++++++ xpcom/string/nsTSubstring.h | 1454 +++++++ xpcom/string/nsTSubstringTuple.cpp | 92 + xpcom/string/nsTSubstringTuple.h | 89 + xpcom/string/nsTextFormatter.cpp | 895 +++++ xpcom/string/nsTextFormatter.h | 172 + xpcom/string/nsUTF8Utils.h | 247 ++ xpcom/system/moz.build | 24 + xpcom/system/nsIBlocklistService.idl | 24 + xpcom/system/nsICrashReporter.idl | 175 + xpcom/system/nsIDeviceSensors.idl | 60 + xpcom/system/nsIGIOService.idl | 88 + xpcom/system/nsIGSettingsService.idl | 30 + xpcom/system/nsIGeolocationProvider.idl | 82 + xpcom/system/nsIHapticFeedback.idl | 22 + xpcom/system/nsIPlatformInfo.idl | 19 + xpcom/system/nsISystemInfo.idl | 40 + xpcom/system/nsIXULAppInfo.idl | 64 + xpcom/system/nsIXULRuntime.idl | 396 ++ xpcom/tests/NotXPCOMTest.idl | 17 + xpcom/tests/RegFactory.cpp | 118 + xpcom/tests/SizeTest01.cpp | 107 + xpcom/tests/SizeTest02.cpp | 87 + xpcom/tests/SizeTest03.cpp | 94 + xpcom/tests/SizeTest04.cpp | 61 + xpcom/tests/SizeTest05.cpp | 65 + xpcom/tests/SizeTest06.cpp | 148 + xpcom/tests/TestArguments.cpp | 16 + xpcom/tests/TestBlockingProcess.cpp | 8 + xpcom/tests/TestHarness.h | 268 ++ xpcom/tests/TestMemoryPressureWatcherLinux.cpp | 65 + xpcom/tests/TestPRIntN.cpp | 39 + xpcom/tests/TestQuickReturn.cpp | 5 + xpcom/tests/TestShutdown.cpp | 37 + xpcom/tests/TestStreamUtils.cpp | 65 + xpcom/tests/TestUnicodeArguments.cpp | 73 + xpcom/tests/TestWinReg.js | 64 + xpcom/tests/TestingAtomList.h | 6 + xpcom/tests/crashtests/bug-1714685.html | 13 + xpcom/tests/crashtests/crashtests.list | 1 + xpcom/tests/gtest/Helpers.cpp | 201 + xpcom/tests/gtest/Helpers.h | 195 + xpcom/tests/gtest/TestAllocReplacement.cpp | 106 + xpcom/tests/gtest/TestArenaAllocator.cpp | 310 ++ xpcom/tests/gtest/TestArrayAlgorithm.cpp | 108 + xpcom/tests/gtest/TestAtoms.cpp | 177 + xpcom/tests/gtest/TestAutoRefCnt.cpp | 66 + .../gtest/TestAvailableMemoryWatcherLinux.cpp | 227 ++ .../tests/gtest/TestAvailableMemoryWatcherMac.cpp | 226 ++ .../tests/gtest/TestAvailableMemoryWatcherWin.cpp | 663 ++++ xpcom/tests/gtest/TestBase64.cpp | 454 +++ xpcom/tests/gtest/TestCOMArray.cpp | 295 ++ xpcom/tests/gtest/TestCOMPtr.cpp | 436 +++ xpcom/tests/gtest/TestCOMPtrEq.cpp | 82 + xpcom/tests/gtest/TestCRT.cpp | 90 + xpcom/tests/gtest/TestCallTemplates.cpp | 115 + xpcom/tests/gtest/TestCloneInputStream.cpp | 236 ++ xpcom/tests/gtest/TestDafsa.cpp | 82 + xpcom/tests/gtest/TestDeadlockDetector.cpp | 314 ++ .../gtest/TestDeadlockDetectorScalability.cpp | 163 + xpcom/tests/gtest/TestDelayedRunnable.cpp | 168 + xpcom/tests/gtest/TestEncoding.cpp | 108 + xpcom/tests/gtest/TestEscape.cpp | 238 ++ xpcom/tests/gtest/TestEventPriorities.cpp | 91 + xpcom/tests/gtest/TestEventTargetQI.cpp | 83 + xpcom/tests/gtest/TestExpirationTracker.cpp | 194 + xpcom/tests/gtest/TestFile.cpp | 576 +++ xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp | 289 ++ xpcom/tests/gtest/TestFilePreferencesUnix.cpp | 208 + xpcom/tests/gtest/TestFilePreferencesWin.cpp | 196 + xpcom/tests/gtest/TestGCPostBarriers.cpp | 162 + xpcom/tests/gtest/TestHandleWatcher.cpp | 580 +++ xpcom/tests/gtest/TestHashtables.cpp | 1617 ++++++++ xpcom/tests/gtest/TestID.cpp | 33 + xpcom/tests/gtest/TestIDUtils.cpp | 36 + xpcom/tests/gtest/TestInputStreamLengthHelper.cpp | 161 + xpcom/tests/gtest/TestJSHolderMap.cpp | 345 ++ xpcom/tests/gtest/TestLogCommandLineHandler.cpp | 183 + xpcom/tests/gtest/TestLogging.cpp | 182 + xpcom/tests/gtest/TestMacNSURLEscaping.mm | 139 + xpcom/tests/gtest/TestMemoryPressure.cpp | 199 + xpcom/tests/gtest/TestMoveString.cpp | 266 ++ xpcom/tests/gtest/TestMozPromise.cpp | 756 ++++ xpcom/tests/gtest/TestMruCache.cpp | 395 ++ xpcom/tests/gtest/TestMultiplexInputStream.cpp | 958 +++++ xpcom/tests/gtest/TestNSPRLogModulesParser.cpp | 167 + .../gtest/TestNonBlockingAsyncInputStream.cpp | 379 ++ xpcom/tests/gtest/TestNsDeque.cpp | 594 +++ xpcom/tests/gtest/TestNsRefPtr.cpp | 444 +++ xpcom/tests/gtest/TestObserverArray.cpp | 573 +++ xpcom/tests/gtest/TestObserverService.cpp | 281 ++ xpcom/tests/gtest/TestOwningNonNull.cpp | 24 + xpcom/tests/gtest/TestPLDHash.cpp | 407 ++ xpcom/tests/gtest/TestPipes.cpp | 1031 +++++ xpcom/tests/gtest/TestPriorityQueue.cpp | 73 + xpcom/tests/gtest/TestQueue.cpp | 186 + xpcom/tests/gtest/TestRWLock.cpp | 214 ++ xpcom/tests/gtest/TestRacingServiceManager.cpp | 260 ++ xpcom/tests/gtest/TestRecursiveMutex.cpp | 25 + xpcom/tests/gtest/TestRustRegex.cpp | 181 + xpcom/tests/gtest/TestSTLWrappers.cpp | 65 + xpcom/tests/gtest/TestSegmentedBuffer.cpp | 41 + xpcom/tests/gtest/TestSlicedInputStream.cpp | 665 ++++ xpcom/tests/gtest/TestSmallArrayLRUCache.cpp | 368 ++ xpcom/tests/gtest/TestSnappyStreams.cpp | 162 + xpcom/tests/gtest/TestStateWatching.cpp | 50 + xpcom/tests/gtest/TestStorageStream.cpp | 130 + xpcom/tests/gtest/TestStringStream.cpp | 100 + xpcom/tests/gtest/TestStrings.cpp | 2801 ++++++++++++++ xpcom/tests/gtest/TestSubstringTuple.cpp | 55 + xpcom/tests/gtest/TestSynchronization.cpp | 324 ++ xpcom/tests/gtest/TestTArray.cpp | 1042 +++++ xpcom/tests/gtest/TestTArray2.cpp | 1423 +++++++ xpcom/tests/gtest/TestTaskQueue.cpp | 215 ++ xpcom/tests/gtest/TestTextFormatter.cpp | 237 ++ xpcom/tests/gtest/TestThreadManager.cpp | 147 + xpcom/tests/gtest/TestThreadMetrics.cpp | 320 ++ xpcom/tests/gtest/TestThreadPool.cpp | 211 + xpcom/tests/gtest/TestThreadPoolListener.cpp | 205 + xpcom/tests/gtest/TestThreadUtils.cpp | 2226 +++++++++++ xpcom/tests/gtest/TestThreads.cpp | 415 ++ xpcom/tests/gtest/TestThreads_mac.mm | 53 + xpcom/tests/gtest/TestThrottledEventQueue.cpp | 613 +++ xpcom/tests/gtest/TestTimeStamp.cpp | 70 + xpcom/tests/gtest/TestTimers.cpp | 924 +++++ xpcom/tests/gtest/TestTokenizer.cpp | 1447 +++++++ xpcom/tests/gtest/TestUTF.cpp | 264 ++ xpcom/tests/gtest/TestVariant.cpp | 156 + xpcom/tests/gtest/UTFStrings.h | 130 + xpcom/tests/gtest/dafsa_test_1.dat | 6 + xpcom/tests/gtest/moz.build | 181 + xpcom/tests/gtest/wikipedia/README.txt | 13 + xpcom/tests/gtest/wikipedia/ar.txt | 70 + xpcom/tests/gtest/wikipedia/de-edit.txt | 487 +++ xpcom/tests/gtest/wikipedia/de.txt | 487 +++ xpcom/tests/gtest/wikipedia/ja.txt | 151 + xpcom/tests/gtest/wikipedia/ko.txt | 110 + xpcom/tests/gtest/wikipedia/ru.txt | 410 ++ xpcom/tests/gtest/wikipedia/th.txt | 412 ++ xpcom/tests/gtest/wikipedia/tr.txt | 245 ++ xpcom/tests/gtest/wikipedia/vi.txt | 333 ++ xpcom/tests/moz.build | 57 + xpcom/tests/resources.h | 19 + xpcom/tests/test.properties | 14 + .../unit/data/SmallApp.app/Contents/Info.plist | 26 + .../unit/data/SmallApp.app/Contents/MacOS/SmallApp | Bin 0 -> 37988 bytes .../tests/unit/data/SmallApp.app/Contents/PkgInfo | 1 + .../Resources/English.lproj/InfoPlist.strings | Bin 0 -> 92 bytes .../English.lproj/MainMenu.nib/designable.nib | 343 ++ .../English.lproj/MainMenu.nib/keyedobjects.nib | Bin 0 -> 3356 bytes xpcom/tests/unit/data/bug121341-2.properties | 9 + xpcom/tests/unit/data/bug121341.properties | 68 + xpcom/tests/unit/data/iniparser01-utf16leBOM.ini | 1 + xpcom/tests/unit/data/iniparser01-utf8BOM.ini | 1 + xpcom/tests/unit/data/iniparser01.ini | 0 xpcom/tests/unit/data/iniparser02-utf16leBOM.ini | Bin 0 -> 6 bytes xpcom/tests/unit/data/iniparser02-utf8BOM.ini | 1 + xpcom/tests/unit/data/iniparser02.ini | 1 + xpcom/tests/unit/data/iniparser03-utf16leBOM.ini | Bin 0 -> 10 bytes xpcom/tests/unit/data/iniparser03-utf8BOM.ini | 1 + xpcom/tests/unit/data/iniparser03.ini | 1 + xpcom/tests/unit/data/iniparser04-utf16leBOM.ini | Bin 0 -> 26 bytes xpcom/tests/unit/data/iniparser04-utf8BOM.ini | 1 + xpcom/tests/unit/data/iniparser04.ini | 1 + xpcom/tests/unit/data/iniparser05-utf16leBOM.ini | Bin 0 -> 34 bytes xpcom/tests/unit/data/iniparser05-utf8BOM.ini | 1 + xpcom/tests/unit/data/iniparser05.ini | 1 + xpcom/tests/unit/data/iniparser06-utf16leBOM.ini | Bin 0 -> 30 bytes xpcom/tests/unit/data/iniparser06-utf8BOM.ini | 2 + xpcom/tests/unit/data/iniparser06.ini | 2 + xpcom/tests/unit/data/iniparser07-utf16leBOM.ini | Bin 0 -> 40 bytes xpcom/tests/unit/data/iniparser07-utf8BOM.ini | 2 + xpcom/tests/unit/data/iniparser07.ini | 2 + xpcom/tests/unit/data/iniparser08-utf16leBOM.ini | Bin 0 -> 42 bytes xpcom/tests/unit/data/iniparser08-utf8BOM.ini | 2 + xpcom/tests/unit/data/iniparser08.ini | 2 + xpcom/tests/unit/data/iniparser09-utf16leBOM.ini | Bin 0 -> 54 bytes xpcom/tests/unit/data/iniparser09-utf8BOM.ini | 2 + xpcom/tests/unit/data/iniparser09.ini | 2 + xpcom/tests/unit/data/iniparser10-utf16leBOM.ini | Bin 0 -> 58 bytes xpcom/tests/unit/data/iniparser10-utf8BOM.ini | 3 + xpcom/tests/unit/data/iniparser10.ini | 3 + xpcom/tests/unit/data/iniparser11-utf16leBOM.ini | Bin 0 -> 76 bytes xpcom/tests/unit/data/iniparser11-utf8BOM.ini | 3 + xpcom/tests/unit/data/iniparser11.ini | 3 + xpcom/tests/unit/data/iniparser12-utf16leBOM.ini | Bin 0 -> 86 bytes xpcom/tests/unit/data/iniparser12-utf8BOM.ini | 3 + xpcom/tests/unit/data/iniparser12.ini | 3 + xpcom/tests/unit/data/iniparser13-utf16leBOM.ini | Bin 0 -> 94 bytes xpcom/tests/unit/data/iniparser13-utf8BOM.ini | 3 + xpcom/tests/unit/data/iniparser13.ini | 3 + xpcom/tests/unit/data/iniparser14-utf16leBOM.ini | Bin 0 -> 160 bytes xpcom/tests/unit/data/iniparser14-utf8BOM.ini | 6 + xpcom/tests/unit/data/iniparser14.ini | 6 + xpcom/tests/unit/data/iniparser15-utf16leBOM.ini | Bin 0 -> 162 bytes xpcom/tests/unit/data/iniparser15-utf8BOM.ini | 6 + xpcom/tests/unit/data/iniparser15.ini | 6 + xpcom/tests/unit/data/iniparser16-utf16leBOM.ini | Bin 0 -> 210 bytes xpcom/tests/unit/data/iniparser16-utf8BOM.ini | 13 + xpcom/tests/unit/data/iniparser16.ini | 13 + xpcom/tests/unit/data/iniparser17.ini | 7 + .../data/presentation.key/.typeAttributes.dict | 0 .../unit/data/presentation.key/Contents/PkgInfo | 1 + .../tests/unit/data/presentation.key/index.apxl.gz | Bin 0 -> 83487 bytes .../unit/data/presentation.key/thumbs/st0.tiff | Bin 0 -> 16654 bytes xpcom/tests/unit/data/process_directive.manifest | 2 + xpcom/tests/unit/head_xpcom.js | 21 + xpcom/tests/unit/test_bug121341.js | 62 + xpcom/tests/unit/test_bug1434856.js | 27 + xpcom/tests/unit/test_bug325418.js | 72 + xpcom/tests/unit/test_bug332389.js | 14 + xpcom/tests/unit/test_bug333505.js | 10 + xpcom/tests/unit/test_bug364285-1.js | 43 + xpcom/tests/unit/test_bug374754.js | 65 + xpcom/tests/unit/test_bug476919.js | 25 + xpcom/tests/unit/test_bug478086.js | 23 + xpcom/tests/unit/test_bug745466.js | 7 + ..._console_service_callFunctionAndLogException.js | 265 ++ xpcom/tests/unit/test_debugger_malloc_size_of.js | 32 + xpcom/tests/unit/test_file_createUnique.js | 29 + xpcom/tests/unit/test_file_equality.js | 37 + xpcom/tests/unit/test_file_renameTo.js | 55 + xpcom/tests/unit/test_getTimers.js | 90 + xpcom/tests/unit/test_hidden_files.js | 24 + xpcom/tests/unit/test_home.js | 18 + xpcom/tests/unit/test_iniParser.js | 476 +++ xpcom/tests/unit/test_ioutil.js | 29 + xpcom/tests/unit/test_localfile.js | 288 ++ xpcom/tests/unit/test_mac_bundle.js | 18 + xpcom/tests/unit/test_mac_xattrs.js | 98 + xpcom/tests/unit/test_notxpcom_scriptable.js | 67 + xpcom/tests/unit/test_nsIMutableArray.js | 131 + xpcom/tests/unit/test_nsIProcess.js | 184 + xpcom/tests/unit/test_nsIProcess_stress.js | 24 + xpcom/tests/unit/test_pipe.js | 55 + xpcom/tests/unit/test_process_directives.js | 20 + xpcom/tests/unit/test_process_directives_child.js | 3 + xpcom/tests/unit/test_seek_multiplex.js | 164 + xpcom/tests/unit/test_storagestream.js | 152 + xpcom/tests/unit/test_streams.js | 170 + xpcom/tests/unit/test_stringstream.js | 20 + xpcom/tests/unit/test_symlinks.js | 139 + xpcom/tests/unit/test_systemInfo.js | 26 + xpcom/tests/unit/test_versioncomparator.js | 55 + xpcom/tests/unit/test_windows_cmdline_file.js | 22 + xpcom/tests/unit/test_windows_registry.js | 237 ++ xpcom/tests/unit/xpcshell.ini | 69 + xpcom/tests/windows/TestCOM.cpp | 97 + xpcom/tests/windows/TestNtPathToDosPath.cpp | 176 + xpcom/tests/windows/TestPoisonIOInterposer.cpp | 152 + xpcom/tests/windows/moz.build | 17 + xpcom/threads/AbstractThread.cpp | 359 ++ xpcom/threads/AbstractThread.h | 129 + xpcom/threads/BlockingResourceBase.cpp | 547 +++ xpcom/threads/BlockingResourceBase.h | 339 ++ xpcom/threads/CPUUsageWatcher.cpp | 252 ++ xpcom/threads/CPUUsageWatcher.h | 100 + xpcom/threads/CondVar.h | 139 + xpcom/threads/DataMutex.h | 130 + xpcom/threads/DeadlockDetector.h | 359 ++ xpcom/threads/DelayedRunnable.cpp | 113 + xpcom/threads/DelayedRunnable.h | 53 + xpcom/threads/EventQueue.cpp | 131 + xpcom/threads/EventQueue.h | 136 + xpcom/threads/EventTargetCapability.h | 95 + xpcom/threads/IdlePeriodState.cpp | 255 ++ xpcom/threads/IdlePeriodState.h | 201 + xpcom/threads/IdleTaskRunner.cpp | 280 ++ xpcom/threads/IdleTaskRunner.h | 122 + xpcom/threads/InputTaskManager.cpp | 156 + xpcom/threads/InputTaskManager.h | 141 + xpcom/threads/LazyIdleThread.cpp | 126 + xpcom/threads/LazyIdleThread.h | 93 + xpcom/threads/LeakRefPtr.h | 48 + xpcom/threads/MainThreadIdlePeriod.cpp | 78 + xpcom/threads/MainThreadIdlePeriod.h | 31 + xpcom/threads/MainThreadUtils.h | 60 + xpcom/threads/Monitor.h | 316 ++ xpcom/threads/MozPromise.h | 1763 +++++++++ xpcom/threads/MozPromiseInlines.h | 48 + xpcom/threads/Mutex.h | 452 +++ xpcom/threads/PerformanceCounter.cpp | 73 + xpcom/threads/PerformanceCounter.h | 139 + xpcom/threads/Queue.h | 265 ++ xpcom/threads/RWLock.cpp | 28 + xpcom/threads/RWLock.h | 243 ++ xpcom/threads/RecursiveMutex.cpp | 85 + xpcom/threads/RecursiveMutex.h | 120 + xpcom/threads/ReentrantMonitor.h | 251 ++ xpcom/threads/SchedulerGroup.cpp | 125 + xpcom/threads/SchedulerGroup.h | 87 + xpcom/threads/SharedThreadPool.cpp | 221 ++ xpcom/threads/SharedThreadPool.h | 130 + xpcom/threads/SpinEventLoopUntil.h | 191 + xpcom/threads/StateMirroring.h | 393 ++ xpcom/threads/StateWatching.h | 302 ++ xpcom/threads/SyncRunnable.h | 157 + xpcom/threads/SynchronizedEventQueue.cpp | 26 + xpcom/threads/SynchronizedEventQueue.h | 131 + xpcom/threads/TaskCategory.h | 47 + xpcom/threads/TaskController.cpp | 1072 ++++++ xpcom/threads/TaskController.h | 445 +++ xpcom/threads/TaskDispatcher.h | 304 ++ xpcom/threads/TaskQueue.cpp | 347 ++ xpcom/threads/TaskQueue.h | 281 ++ xpcom/threads/ThreadBound.h | 143 + xpcom/threads/ThreadDelay.cpp | 38 + xpcom/threads/ThreadDelay.h | 16 + xpcom/threads/ThreadEventQueue.cpp | 324 ++ xpcom/threads/ThreadEventQueue.h | 95 + xpcom/threads/ThreadEventTarget.cpp | 136 + xpcom/threads/ThreadEventTarget.h | 63 + xpcom/threads/ThreadLocalVariables.cpp | 16 + xpcom/threads/ThrottledEventQueue.cpp | 459 +++ xpcom/threads/ThrottledEventQueue.h | 118 + xpcom/threads/TimerThread.cpp | 1512 ++++++++ xpcom/threads/TimerThread.h | 243 ++ xpcom/threads/VsyncTaskManager.cpp | 22 + xpcom/threads/VsyncTaskManager.h | 26 + xpcom/threads/WinHandleWatcher.cpp | 303 ++ xpcom/threads/WinHandleWatcher.h | 117 + xpcom/threads/components.conf | 29 + xpcom/threads/moz.build | 148 + xpcom/threads/nsEnvironment.cpp | 136 + xpcom/threads/nsEnvironment.h | 34 + xpcom/threads/nsICancelableRunnable.h | 40 + xpcom/threads/nsIDirectTaskDispatcher.idl | 57 + xpcom/threads/nsIDiscardableRunnable.h | 41 + xpcom/threads/nsIEnvironment.idl | 54 + xpcom/threads/nsIEventTarget.idl | 227 ++ xpcom/threads/nsIIdlePeriod.idl | 32 + xpcom/threads/nsIIdleRunnable.h | 48 + xpcom/threads/nsINamed.idl | 24 + xpcom/threads/nsIProcess.idl | 112 + xpcom/threads/nsIRunnable.idl | 45 + xpcom/threads/nsISerialEventTarget.idl | 27 + xpcom/threads/nsISupportsPriority.idl | 45 + xpcom/threads/nsITargetShutdownTask.h | 37 + xpcom/threads/nsIThread.idl | 222 ++ xpcom/threads/nsIThreadInternal.idl | 110 + xpcom/threads/nsIThreadManager.idl | 173 + xpcom/threads/nsIThreadPool.idl | 115 + xpcom/threads/nsIThreadShutdown.idl | 57 + xpcom/threads/nsITimer.idl | 376 ++ xpcom/threads/nsMemoryPressure.cpp | 104 + xpcom/threads/nsMemoryPressure.h | 77 + xpcom/threads/nsProcess.h | 82 + xpcom/threads/nsProcessCommon.cpp | 600 +++ xpcom/threads/nsProxyRelease.cpp | 30 + xpcom/threads/nsProxyRelease.h | 390 ++ xpcom/threads/nsThread.cpp | 1609 ++++++++ xpcom/threads/nsThread.h | 400 ++ xpcom/threads/nsThreadManager.cpp | 798 ++++ xpcom/threads/nsThreadManager.h | 117 + xpcom/threads/nsThreadPool.cpp | 611 +++ xpcom/threads/nsThreadPool.h | 68 + xpcom/threads/nsThreadSyncDispatch.h | 65 + xpcom/threads/nsThreadUtils.cpp | 768 ++++ xpcom/threads/nsThreadUtils.h | 1925 ++++++++++ xpcom/threads/nsTimerImpl.cpp | 820 ++++ xpcom/threads/nsTimerImpl.h | 231 ++ xpcom/windbgdlg/Makefile.in | 6 + xpcom/windbgdlg/moz.build | 12 + xpcom/windbgdlg/windbgdlg.cpp | 113 + xpcom/xpcom-config.h.in | 13 + xpcom/xpidl/Makefile.in | 6 + xpcom/xpidl/moz.build | 5 + 1093 files changed, 230360 insertions(+) create mode 100644 xpcom/base/AppShutdown.cpp create mode 100644 xpcom/base/AppShutdown.h create mode 100644 xpcom/base/AutoRestore.h create mode 100644 xpcom/base/AvailableMemoryTracker.cpp create mode 100644 xpcom/base/AvailableMemoryTracker.h create mode 100644 xpcom/base/AvailableMemoryWatcher.cpp create mode 100644 xpcom/base/AvailableMemoryWatcher.h create mode 100644 xpcom/base/AvailableMemoryWatcherLinux.cpp create mode 100644 xpcom/base/AvailableMemoryWatcherMac.cpp create mode 100644 xpcom/base/AvailableMemoryWatcherUtils.h create mode 100644 xpcom/base/AvailableMemoryWatcherWin.cpp create mode 100644 xpcom/base/ClearOnShutdown.cpp create mode 100644 xpcom/base/ClearOnShutdown.h create mode 100644 xpcom/base/CodeAddressService.h create mode 100644 xpcom/base/CountingAllocatorBase.h create mode 100644 xpcom/base/CycleCollectedJSContext.cpp create mode 100644 xpcom/base/CycleCollectedJSContext.h create mode 100644 xpcom/base/CycleCollectedJSRuntime.cpp create mode 100644 xpcom/base/CycleCollectedJSRuntime.h create mode 100644 xpcom/base/Debug.cpp create mode 100644 xpcom/base/Debug.h create mode 100644 xpcom/base/DebuggerOnGCRunnable.cpp create mode 100644 xpcom/base/DebuggerOnGCRunnable.h create mode 100644 xpcom/base/DeferredFinalize.cpp create mode 100644 xpcom/base/DeferredFinalize.h create mode 100644 xpcom/base/EnumeratedArrayCycleCollection.h create mode 100755 xpcom/base/ErrorList.py create mode 100644 xpcom/base/ErrorNames.cpp create mode 100644 xpcom/base/ErrorNames.h create mode 100644 xpcom/base/GkRustUtils.cpp create mode 100644 xpcom/base/GkRustUtils.h create mode 100644 xpcom/base/HoldDropJSObjects.cpp create mode 100644 xpcom/base/HoldDropJSObjects.h create mode 100644 xpcom/base/IntentionalCrash.h create mode 100644 xpcom/base/JSONStringWriteFuncs.h create mode 100644 xpcom/base/JSObjectHolder.cpp create mode 100644 xpcom/base/JSObjectHolder.h create mode 100644 xpcom/base/LogCommandLineHandler.cpp create mode 100644 xpcom/base/LogCommandLineHandler.h create mode 100644 xpcom/base/LogModulePrefWatcher.cpp create mode 100644 xpcom/base/LogModulePrefWatcher.h create mode 100644 xpcom/base/Logging.cpp create mode 100644 xpcom/base/Logging.h create mode 100644 xpcom/base/MacHelpers.h create mode 100644 xpcom/base/MacHelpers.mm create mode 100644 xpcom/base/MacStringHelpers.h create mode 100644 xpcom/base/MacStringHelpers.mm create mode 100644 xpcom/base/MemoryInfo.cpp create mode 100644 xpcom/base/MemoryInfo.h create mode 100644 xpcom/base/MemoryMapping.cpp create mode 100644 xpcom/base/MemoryMapping.h create mode 100644 xpcom/base/MemoryPressureLevelMac.h create mode 100644 xpcom/base/MemoryReportingProcess.h create mode 100644 xpcom/base/MemoryTelemetry.cpp create mode 100644 xpcom/base/MemoryTelemetry.h create mode 100644 xpcom/base/NSPRLogModulesParser.cpp create mode 100644 xpcom/base/NSPRLogModulesParser.h create mode 100644 xpcom/base/OwningNonNull.h create mode 100644 xpcom/base/RLBoxSandboxPool.cpp create mode 100644 xpcom/base/RLBoxSandboxPool.h create mode 100644 xpcom/base/RLBoxUtils.h create mode 100644 xpcom/base/ShutdownPhase.h create mode 100644 xpcom/base/SizeOfState.h create mode 100644 xpcom/base/StaticLocalPtr.h create mode 100644 xpcom/base/StaticMonitor.h create mode 100644 xpcom/base/StaticMutex.h create mode 100644 xpcom/base/StaticPtr.h create mode 100644 xpcom/base/components.conf create mode 100644 xpcom/base/moz.build create mode 100644 xpcom/base/nsAlgorithm.h create mode 100644 xpcom/base/nsAutoRef.h create mode 100644 xpcom/base/nsCOMPtr.cpp create mode 100644 xpcom/base/nsCOMPtr.h create mode 100644 xpcom/base/nsCRTGlue.cpp create mode 100644 xpcom/base/nsCRTGlue.h create mode 100644 xpcom/base/nsClassInfoImpl.cpp create mode 100644 xpcom/base/nsCom.h create mode 100644 xpcom/base/nsConsoleMessage.cpp create mode 100644 xpcom/base/nsConsoleMessage.h create mode 100644 xpcom/base/nsConsoleService.cpp create mode 100644 xpcom/base/nsConsoleService.h create mode 100644 xpcom/base/nsCrashOnException.cpp create mode 100644 xpcom/base/nsCrashOnException.h create mode 100644 xpcom/base/nsCycleCollectionNoteChild.h create mode 100644 xpcom/base/nsCycleCollectionNoteRootCallback.h create mode 100644 xpcom/base/nsCycleCollectionParticipant.cpp create mode 100644 xpcom/base/nsCycleCollectionParticipant.h create mode 100644 xpcom/base/nsCycleCollectionTraversalCallback.h create mode 100644 xpcom/base/nsCycleCollector.cpp create mode 100644 xpcom/base/nsCycleCollector.h create mode 100644 xpcom/base/nsCycleCollectorTraceJSHelpers.cpp create mode 100644 xpcom/base/nsDebug.h create mode 100644 xpcom/base/nsDebugImpl.cpp create mode 100644 xpcom/base/nsDebugImpl.h create mode 100644 xpcom/base/nsDumpUtils.cpp create mode 100644 xpcom/base/nsDumpUtils.h create mode 100644 xpcom/base/nsError.h create mode 100644 xpcom/base/nsGZFileWriter.cpp create mode 100644 xpcom/base/nsGZFileWriter.h create mode 100644 xpcom/base/nsIAvailableMemoryWatcherBase.idl create mode 100644 xpcom/base/nsIClassInfoImpl.h create mode 100644 xpcom/base/nsIConsoleListener.idl create mode 100644 xpcom/base/nsIConsoleMessage.idl create mode 100644 xpcom/base/nsIConsoleService.idl create mode 100644 xpcom/base/nsICycleCollectorListener.idl create mode 100644 xpcom/base/nsID.cpp create mode 100644 xpcom/base/nsID.h create mode 100644 xpcom/base/nsIDUtils.h create mode 100644 xpcom/base/nsIDebug2.idl create mode 100644 xpcom/base/nsIException.idl create mode 100644 xpcom/base/nsIInterfaceRequestor.idl create mode 100644 xpcom/base/nsIInterfaceRequestorUtils.cpp create mode 100644 xpcom/base/nsIInterfaceRequestorUtils.h create mode 100644 xpcom/base/nsIMacPreferencesReader.idl create mode 100644 xpcom/base/nsIMemoryInfoDumper.idl create mode 100644 xpcom/base/nsIMemoryReporter.idl create mode 100644 xpcom/base/nsIMessageLoop.idl create mode 100644 xpcom/base/nsINIParser.cpp create mode 100644 xpcom/base/nsINIParser.h create mode 100644 xpcom/base/nsISecurityConsoleMessage.idl create mode 100644 xpcom/base/nsISizeOf.h create mode 100644 xpcom/base/nsISupports.idl create mode 100644 xpcom/base/nsISupportsImpl.cpp create mode 100644 xpcom/base/nsISupportsImpl.h create mode 100644 xpcom/base/nsISupportsUtils.h create mode 100644 xpcom/base/nsIUUIDGenerator.idl create mode 100644 xpcom/base/nsIVersionComparator.idl create mode 100644 xpcom/base/nsIWeakReference.idl create mode 100644 xpcom/base/nsIWeakReferenceUtils.h create mode 100644 xpcom/base/nsInterfaceRequestorAgg.cpp create mode 100644 xpcom/base/nsInterfaceRequestorAgg.h create mode 100644 xpcom/base/nsMacPreferencesReader.h create mode 100644 xpcom/base/nsMacPreferencesReader.mm create mode 100644 xpcom/base/nsMacUtilsImpl.cpp create mode 100644 xpcom/base/nsMacUtilsImpl.h create mode 100644 xpcom/base/nsMaybeWeakPtr.h create mode 100644 xpcom/base/nsMemory.h create mode 100644 xpcom/base/nsMemoryImpl.cpp create mode 100644 xpcom/base/nsMemoryInfoDumper.cpp create mode 100644 xpcom/base/nsMemoryInfoDumper.h create mode 100644 xpcom/base/nsMemoryReporterManager.cpp create mode 100644 xpcom/base/nsMemoryReporterManager.h create mode 100644 xpcom/base/nsMessageLoop.cpp create mode 100644 xpcom/base/nsMessageLoop.h create mode 100644 xpcom/base/nsObjCExceptions.h create mode 100644 xpcom/base/nsObjCExceptions.mm create mode 100644 xpcom/base/nsQueryObject.h create mode 100644 xpcom/base/nsSecurityConsoleMessage.cpp create mode 100644 xpcom/base/nsSecurityConsoleMessage.h create mode 100644 xpcom/base/nsSystemInfo.cpp create mode 100644 xpcom/base/nsSystemInfo.h create mode 100644 xpcom/base/nsTraceRefcnt.cpp create mode 100644 xpcom/base/nsTraceRefcnt.h create mode 100644 xpcom/base/nsUUIDGenerator.cpp create mode 100644 xpcom/base/nsUUIDGenerator.h create mode 100644 xpcom/base/nsVersionComparator.cpp create mode 100644 xpcom/base/nsVersionComparator.h create mode 100644 xpcom/base/nsVersionComparatorImpl.cpp create mode 100644 xpcom/base/nsVersionComparatorImpl.h create mode 100644 xpcom/base/nsWeakReference.cpp create mode 100644 xpcom/base/nsWeakReference.h create mode 100644 xpcom/base/nsWindowsHelpers.h create mode 100644 xpcom/base/nscore.h create mode 100644 xpcom/base/nsrootidl.idl create mode 100644 xpcom/build/BinaryPath.h create mode 100644 xpcom/build/FileLocation.cpp create mode 100644 xpcom/build/FileLocation.h create mode 100644 xpcom/build/IOInterposer.cpp create mode 100644 xpcom/build/IOInterposer.h create mode 100644 xpcom/build/IOInterposerPrivate.h create mode 100644 xpcom/build/LateWriteChecks.cpp create mode 100644 xpcom/build/LateWriteChecks.h create mode 100644 xpcom/build/MainThreadIOLogger.cpp create mode 100644 xpcom/build/MainThreadIOLogger.h create mode 100644 xpcom/build/NSPRInterposer.cpp create mode 100644 xpcom/build/NSPRInterposer.h create mode 100644 xpcom/build/Omnijar.cpp create mode 100644 xpcom/build/Omnijar.h create mode 100644 xpcom/build/PoisonIOInterposer.h create mode 100644 xpcom/build/PoisonIOInterposerBase.cpp create mode 100644 xpcom/build/PoisonIOInterposerMac.cpp create mode 100644 xpcom/build/PoisonIOInterposerStub.cpp create mode 100644 xpcom/build/PoisonIOInterposerWin.cpp create mode 100644 xpcom/build/Services.py create mode 100644 xpcom/build/SmallArrayLRUCache.h create mode 100644 xpcom/build/XPCOM.h create mode 100644 xpcom/build/XPCOMInit.cpp create mode 100644 xpcom/build/XPCOMModule.h create mode 100644 xpcom/build/XPCOMModule.inc create mode 100644 xpcom/build/XREAppData.h create mode 100644 xpcom/build/XREChildData.h create mode 100644 xpcom/build/XREShellData.h create mode 100644 xpcom/build/components.conf create mode 100644 xpcom/build/gen_process_types.py create mode 100644 xpcom/build/mach_override.c create mode 100644 xpcom/build/mach_override.h create mode 100644 xpcom/build/moz.build create mode 100644 xpcom/build/nsXPCOM.h create mode 100644 xpcom/build/nsXPCOMCID.h create mode 100644 xpcom/build/nsXPCOMCIDInternal.h create mode 100644 xpcom/build/nsXPCOMPrivate.h create mode 100644 xpcom/build/nsXULAppAPI.h create mode 100644 xpcom/build/perfprobe.cpp create mode 100644 xpcom/build/perfprobe.h create mode 100644 xpcom/build/xpcom_alpha.def create mode 100644 xpcom/components/GenericFactory.cpp create mode 100644 xpcom/components/GenericFactory.h create mode 100644 xpcom/components/ManifestParser.cpp create mode 100644 xpcom/components/ManifestParser.h create mode 100644 xpcom/components/Module.h create mode 100644 xpcom/components/ModuleUtils.h create mode 100644 xpcom/components/StaticComponents.cpp.in create mode 100644 xpcom/components/StaticComponents.h create mode 100644 xpcom/components/components.conf create mode 100644 xpcom/components/gen_static_components.py create mode 100644 xpcom/components/moz.build create mode 100644 xpcom/components/nsCategoryCache.cpp create mode 100644 xpcom/components/nsCategoryCache.h create mode 100644 xpcom/components/nsCategoryManager.cpp create mode 100644 xpcom/components/nsCategoryManager.h create mode 100644 xpcom/components/nsCategoryManagerUtils.h create mode 100644 xpcom/components/nsComponentManager.cpp create mode 100644 xpcom/components/nsComponentManager.h create mode 100644 xpcom/components/nsComponentManagerUtils.cpp create mode 100644 xpcom/components/nsComponentManagerUtils.h create mode 100644 xpcom/components/nsICategoryManager.idl create mode 100644 xpcom/components/nsIClassInfo.idl create mode 100644 xpcom/components/nsIComponentManager.idl create mode 100644 xpcom/components/nsIComponentRegistrar.idl create mode 100644 xpcom/components/nsIFactory.idl create mode 100644 xpcom/components/nsIServiceManager.idl create mode 100644 xpcom/components/nsServiceManagerUtils.h create mode 100644 xpcom/components/test/python.ini create mode 100644 xpcom/components/test/test_gen_static_components.py create mode 100644 xpcom/docs/cc-macros.rst create mode 100644 xpcom/docs/collections.rst create mode 100644 xpcom/docs/hashtables.rst create mode 100644 xpcom/docs/hashtables_detailed.rst create mode 100644 xpcom/docs/huntingleaks.rst create mode 100644 xpcom/docs/index.rst create mode 100644 xpcom/docs/logging.rst create mode 100644 xpcom/docs/refptr.rst create mode 100644 xpcom/docs/stringguide.rst create mode 100644 xpcom/docs/thread-safety.rst create mode 100644 xpcom/docs/writing-xpcom-interface.rst create mode 100644 xpcom/docs/xpidl.rst create mode 100644 xpcom/ds/ArenaAllocator.h create mode 100644 xpcom/ds/ArenaAllocatorExtensions.h create mode 100644 xpcom/ds/ArrayAlgorithm.h create mode 100644 xpcom/ds/ArrayIterator.h create mode 100644 xpcom/ds/Atom.py create mode 100644 xpcom/ds/AtomArray.h create mode 100644 xpcom/ds/Dafsa.cpp create mode 100644 xpcom/ds/Dafsa.h create mode 100644 xpcom/ds/HTMLAtoms.py create mode 100644 xpcom/ds/IncrementalTokenizer.cpp create mode 100644 xpcom/ds/IncrementalTokenizer.h create mode 100644 xpcom/ds/Observer.h create mode 100644 xpcom/ds/PLDHashTable.cpp create mode 100644 xpcom/ds/PLDHashTable.h create mode 100644 xpcom/ds/PerfectHash.h create mode 100644 xpcom/ds/SimpleEnumerator.h create mode 100644 xpcom/ds/StaticAtoms.py create mode 100644 xpcom/ds/StickyTimeDuration.h create mode 100644 xpcom/ds/Tokenizer.cpp create mode 100644 xpcom/ds/Tokenizer.h create mode 100644 xpcom/ds/components.conf create mode 100644 xpcom/ds/moz.build create mode 100644 xpcom/ds/nsArray.cpp create mode 100644 xpcom/ds/nsArray.h create mode 100644 xpcom/ds/nsArrayEnumerator.cpp create mode 100644 xpcom/ds/nsArrayEnumerator.h create mode 100644 xpcom/ds/nsArrayUtils.cpp create mode 100644 xpcom/ds/nsArrayUtils.h create mode 100644 xpcom/ds/nsAtom.h create mode 100644 xpcom/ds/nsAtomTable.cpp create mode 100644 xpcom/ds/nsAtomTable.h create mode 100644 xpcom/ds/nsBaseHashtable.h create mode 100644 xpcom/ds/nsCOMArray.cpp create mode 100644 xpcom/ds/nsCOMArray.h create mode 100644 xpcom/ds/nsCRT.cpp create mode 100644 xpcom/ds/nsCRT.h create mode 100644 xpcom/ds/nsCharSeparatedTokenizer.cpp create mode 100644 xpcom/ds/nsCharSeparatedTokenizer.h create mode 100644 xpcom/ds/nsCheapSets.h create mode 100644 xpcom/ds/nsClassHashtable.h create mode 100644 xpcom/ds/nsDeque.cpp create mode 100644 xpcom/ds/nsDeque.h create mode 100644 xpcom/ds/nsEnumeratorUtils.cpp create mode 100644 xpcom/ds/nsEnumeratorUtils.h create mode 100644 xpcom/ds/nsExpirationTracker.h create mode 100644 xpcom/ds/nsGkAtoms.cpp create mode 100644 xpcom/ds/nsGkAtoms.h create mode 100644 xpcom/ds/nsHashKeys.h create mode 100644 xpcom/ds/nsHashPropertyBag.cpp create mode 100644 xpcom/ds/nsHashPropertyBag.h create mode 100644 xpcom/ds/nsHashtablesFwd.h create mode 100644 xpcom/ds/nsIArray.idl create mode 100644 xpcom/ds/nsIArrayExtensions.idl create mode 100644 xpcom/ds/nsIINIParser.idl create mode 100644 xpcom/ds/nsIMutableArray.idl create mode 100644 xpcom/ds/nsINIParserImpl.cpp create mode 100644 xpcom/ds/nsINIParserImpl.h create mode 100644 xpcom/ds/nsIObserver.idl create mode 100644 xpcom/ds/nsIObserverService.idl create mode 100644 xpcom/ds/nsIPersistentProperties.h create mode 100644 xpcom/ds/nsIPersistentProperties2.idl create mode 100644 xpcom/ds/nsIProperties.idl create mode 100644 xpcom/ds/nsIProperty.idl create mode 100644 xpcom/ds/nsIPropertyBag.idl create mode 100644 xpcom/ds/nsIPropertyBag2.idl create mode 100644 xpcom/ds/nsISerializable.idl create mode 100644 xpcom/ds/nsISimpleEnumerator.idl create mode 100644 xpcom/ds/nsIStringEnumerator.idl create mode 100644 xpcom/ds/nsISupportsIterators.idl create mode 100644 xpcom/ds/nsISupportsPrimitives.idl create mode 100644 xpcom/ds/nsIVariant.idl create mode 100644 xpcom/ds/nsIWindowsRegKey.idl create mode 100644 xpcom/ds/nsIWritablePropertyBag.idl create mode 100644 xpcom/ds/nsIWritablePropertyBag2.idl create mode 100644 xpcom/ds/nsInterfaceHashtable.h create mode 100644 xpcom/ds/nsMathUtils.h create mode 100644 xpcom/ds/nsObserverList.cpp create mode 100644 xpcom/ds/nsObserverList.h create mode 100644 xpcom/ds/nsObserverService.cpp create mode 100644 xpcom/ds/nsObserverService.h create mode 100644 xpcom/ds/nsPersistentProperties.cpp create mode 100644 xpcom/ds/nsPersistentProperties.h create mode 100644 xpcom/ds/nsPointerHashKeys.h create mode 100644 xpcom/ds/nsProperties.cpp create mode 100644 xpcom/ds/nsProperties.h create mode 100644 xpcom/ds/nsQuickSort.cpp create mode 100644 xpcom/ds/nsQuickSort.h create mode 100644 xpcom/ds/nsRefCountedHashtable.h create mode 100644 xpcom/ds/nsRefPtrHashtable.h create mode 100644 xpcom/ds/nsSimpleEnumerator.cpp create mode 100644 xpcom/ds/nsSimpleEnumerator.h create mode 100644 xpcom/ds/nsStaticAtomUtils.h create mode 100644 xpcom/ds/nsStaticNameTable.cpp create mode 100644 xpcom/ds/nsStaticNameTable.h create mode 100644 xpcom/ds/nsStringEnumerator.cpp create mode 100644 xpcom/ds/nsStringEnumerator.h create mode 100644 xpcom/ds/nsSupportsPrimitives.cpp create mode 100644 xpcom/ds/nsSupportsPrimitives.h create mode 100644 xpcom/ds/nsTArray-inl.h create mode 100644 xpcom/ds/nsTArray.cpp create mode 100644 xpcom/ds/nsTArray.h create mode 100644 xpcom/ds/nsTArrayForwardDeclare.h create mode 100644 xpcom/ds/nsTHashMap.h create mode 100644 xpcom/ds/nsTHashSet.h create mode 100644 xpcom/ds/nsTHashtable.h create mode 100644 xpcom/ds/nsTObserverArray.cpp create mode 100644 xpcom/ds/nsTObserverArray.h create mode 100644 xpcom/ds/nsTPriorityQueue.h create mode 100644 xpcom/ds/nsVariant.cpp create mode 100644 xpcom/ds/nsVariant.h create mode 100644 xpcom/ds/nsWhitespaceTokenizer.h create mode 100644 xpcom/ds/nsWindowsRegKey.cpp create mode 100644 xpcom/ds/nsWindowsRegKey.h create mode 100644 xpcom/ds/test/python.ini create mode 100644 xpcom/ds/test/test_dafsa.py create mode 100644 xpcom/ds/tools/incremental_dafsa.py create mode 100644 xpcom/ds/tools/make_dafsa.py create mode 100644 xpcom/ds/tools/perfecthash.py create mode 100644 xpcom/geckoprocesstypes_generator/geckoprocesstypes/__init__.py create mode 100644 xpcom/geckoprocesstypes_generator/geckoprocesstypes/moz.build create mode 100644 xpcom/geckoprocesstypes_generator/setup.py create mode 100644 xpcom/glue/FileUtils.cpp create mode 100644 xpcom/glue/FileUtils.h create mode 100644 xpcom/glue/MemUtils.cpp create mode 100644 xpcom/glue/MemUtils.h create mode 100644 xpcom/glue/XREAppData.cpp create mode 100644 xpcom/glue/moz.build create mode 100644 xpcom/glue/objs.mozbuild create mode 100644 xpcom/glue/standalone/moz.build create mode 100644 xpcom/glue/standalone/nsXPCOMGlue.cpp create mode 100644 xpcom/idl-parser/setup.py create mode 100644 xpcom/idl-parser/xpidl/__init__.py create mode 100644 xpcom/idl-parser/xpidl/header.py create mode 100644 xpcom/idl-parser/xpidl/jsonxpt.py create mode 100644 xpcom/idl-parser/xpidl/moz.build create mode 100644 xpcom/idl-parser/xpidl/python.ini create mode 100755 xpcom/idl-parser/xpidl/runtests.py create mode 100644 xpcom/idl-parser/xpidl/rust.py create mode 100644 xpcom/idl-parser/xpidl/rust_macros.py create mode 100755 xpcom/idl-parser/xpidl/xpidl.py create mode 100644 xpcom/io/Base64.cpp create mode 100644 xpcom/io/Base64.h create mode 100644 xpcom/io/CocoaFileUtils.h create mode 100644 xpcom/io/CocoaFileUtils.mm create mode 100644 xpcom/io/FileDescriptorFile.cpp create mode 100644 xpcom/io/FileDescriptorFile.h create mode 100644 xpcom/io/FilePreferences.cpp create mode 100644 xpcom/io/FilePreferences.h create mode 100644 xpcom/io/FileUtilsWin.cpp create mode 100644 xpcom/io/FileUtilsWin.h create mode 100644 xpcom/io/FixedBufferOutputStream.cpp create mode 100644 xpcom/io/FixedBufferOutputStream.h create mode 100644 xpcom/io/InputStreamLengthHelper.cpp create mode 100644 xpcom/io/InputStreamLengthHelper.h create mode 100644 xpcom/io/InputStreamLengthWrapper.cpp create mode 100644 xpcom/io/InputStreamLengthWrapper.h create mode 100644 xpcom/io/NonBlockingAsyncInputStream.cpp create mode 100644 xpcom/io/NonBlockingAsyncInputStream.h create mode 100644 xpcom/io/SlicedInputStream.cpp create mode 100644 xpcom/io/SlicedInputStream.h create mode 100644 xpcom/io/SnappyCompressOutputStream.cpp create mode 100644 xpcom/io/SnappyCompressOutputStream.h create mode 100644 xpcom/io/SnappyFrameUtils.cpp create mode 100644 xpcom/io/SnappyFrameUtils.h create mode 100644 xpcom/io/SnappyUncompressInputStream.cpp create mode 100644 xpcom/io/SnappyUncompressInputStream.h create mode 100644 xpcom/io/SpecialSystemDirectory.cpp create mode 100644 xpcom/io/SpecialSystemDirectory.h create mode 100644 xpcom/io/StreamBufferSink.h create mode 100644 xpcom/io/StreamBufferSinkImpl.h create mode 100644 xpcom/io/StreamBufferSource.h create mode 100644 xpcom/io/StreamBufferSourceImpl.h create mode 100644 xpcom/io/components.conf create mode 100644 xpcom/io/crc32c.c create mode 100644 xpcom/io/crc32c.h create mode 100644 xpcom/io/moz.build create mode 100644 xpcom/io/nsAnonymousTemporaryFile.cpp create mode 100644 xpcom/io/nsAnonymousTemporaryFile.h create mode 100644 xpcom/io/nsAppDirectoryServiceDefs.h create mode 100644 xpcom/io/nsAppFileLocationProvider.cpp create mode 100644 xpcom/io/nsAppFileLocationProvider.h create mode 100644 xpcom/io/nsBinaryStream.cpp create mode 100644 xpcom/io/nsBinaryStream.h create mode 100644 xpcom/io/nsDirectoryService.cpp create mode 100644 xpcom/io/nsDirectoryService.h create mode 100644 xpcom/io/nsDirectoryServiceDefs.h create mode 100644 xpcom/io/nsDirectoryServiceUtils.h create mode 100644 xpcom/io/nsEscape.cpp create mode 100644 xpcom/io/nsEscape.h create mode 100644 xpcom/io/nsIAsyncInputStream.idl create mode 100644 xpcom/io/nsIAsyncOutputStream.idl create mode 100644 xpcom/io/nsIBinaryInputStream.idl create mode 100644 xpcom/io/nsIBinaryOutputStream.idl create mode 100644 xpcom/io/nsICloneableInputStream.idl create mode 100644 xpcom/io/nsIConverterInputStream.idl create mode 100644 xpcom/io/nsIConverterOutputStream.idl create mode 100644 xpcom/io/nsIDirectoryEnumerator.idl create mode 100644 xpcom/io/nsIDirectoryService.idl create mode 100644 xpcom/io/nsIFile.idl create mode 100644 xpcom/io/nsIIOUtil.idl create mode 100644 xpcom/io/nsIInputStream.idl create mode 100644 xpcom/io/nsIInputStreamLength.idl create mode 100644 xpcom/io/nsIInputStreamPriority.idl create mode 100644 xpcom/io/nsIInputStreamTee.idl create mode 100644 xpcom/io/nsILineInputStream.idl create mode 100644 xpcom/io/nsILocalFileMac.idl create mode 100644 xpcom/io/nsILocalFileWin.idl create mode 100644 xpcom/io/nsIMultiplexInputStream.idl create mode 100644 xpcom/io/nsIOUtil.cpp create mode 100644 xpcom/io/nsIOUtil.h create mode 100644 xpcom/io/nsIObjectInputStream.idl create mode 100644 xpcom/io/nsIObjectOutputStream.idl create mode 100644 xpcom/io/nsIOutputStream.idl create mode 100644 xpcom/io/nsIPipe.idl create mode 100644 xpcom/io/nsIRandomAccessStream.idl create mode 100644 xpcom/io/nsISafeOutputStream.idl create mode 100644 xpcom/io/nsIScriptableBase64Encoder.idl create mode 100644 xpcom/io/nsIScriptableInputStream.idl create mode 100644 xpcom/io/nsISeekableStream.idl create mode 100644 xpcom/io/nsIStorageStream.idl create mode 100644 xpcom/io/nsIStreamBufferAccess.idl create mode 100644 xpcom/io/nsIStringStream.idl create mode 100644 xpcom/io/nsITellableStream.idl create mode 100644 xpcom/io/nsIUnicharInputStream.idl create mode 100644 xpcom/io/nsIUnicharLineInputStream.idl create mode 100644 xpcom/io/nsIUnicharOutputStream.idl create mode 100644 xpcom/io/nsInputStreamTee.cpp create mode 100644 xpcom/io/nsLinebreakConverter.cpp create mode 100644 xpcom/io/nsLinebreakConverter.h create mode 100644 xpcom/io/nsLocalFile.h create mode 100644 xpcom/io/nsLocalFileCommon.cpp create mode 100644 xpcom/io/nsLocalFileCommon.h create mode 100644 xpcom/io/nsLocalFileUnix.cpp create mode 100644 xpcom/io/nsLocalFileUnix.h create mode 100644 xpcom/io/nsLocalFileWin.cpp create mode 100644 xpcom/io/nsLocalFileWin.h create mode 100644 xpcom/io/nsMultiplexInputStream.cpp create mode 100644 xpcom/io/nsMultiplexInputStream.h create mode 100644 xpcom/io/nsNativeCharsetUtils.cpp create mode 100644 xpcom/io/nsNativeCharsetUtils.h create mode 100644 xpcom/io/nsPipe.h create mode 100644 xpcom/io/nsPipe3.cpp create mode 100644 xpcom/io/nsScriptableBase64Encoder.cpp create mode 100644 xpcom/io/nsScriptableBase64Encoder.h create mode 100644 xpcom/io/nsScriptableInputStream.cpp create mode 100644 xpcom/io/nsScriptableInputStream.h create mode 100644 xpcom/io/nsSegmentedBuffer.cpp create mode 100644 xpcom/io/nsSegmentedBuffer.h create mode 100644 xpcom/io/nsStorageStream.cpp create mode 100644 xpcom/io/nsStorageStream.h create mode 100644 xpcom/io/nsStreamUtils.cpp create mode 100644 xpcom/io/nsStreamUtils.h create mode 100644 xpcom/io/nsStringStream.cpp create mode 100644 xpcom/io/nsStringStream.h create mode 100644 xpcom/io/nsUnicharInputStream.cpp create mode 100644 xpcom/io/nsUnicharInputStream.h create mode 100644 xpcom/io/nsWildCard.cpp create mode 100644 xpcom/io/nsWildCard.h create mode 100644 xpcom/metrics.yaml create mode 100644 xpcom/moz.build create mode 100644 xpcom/reflect/moz.build create mode 100644 xpcom/reflect/xptcall/README create mode 100644 xpcom/reflect/xptcall/genstubs.pl create mode 100644 xpcom/reflect/xptcall/md/moz.build create mode 100644 xpcom/reflect/xptcall/md/test/README create mode 100755 xpcom/reflect/xptcall/md/test/clean.bat create mode 100644 xpcom/reflect/xptcall/md/test/invoke_test.cpp create mode 100755 xpcom/reflect/xptcall/md/test/mk_invoke.bat create mode 100755 xpcom/reflect/xptcall/md/test/mk_stub.bat create mode 100644 xpcom/reflect/xptcall/md/test/moz.build create mode 100644 xpcom/reflect/xptcall/md/test/stub_test.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/moz.build create mode 100644 xpcom/reflect/xptcall/md/unix/vtable_layout_x86.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptc_gcc_x86_unix.h create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_aarch64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_alpha_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_netbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_arm_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_aarch64.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf32.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ipf64.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_mips64.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_pa32.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_parisc_linux.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc64_linux.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_aix64.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_ibmobj_aix.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_linux.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_openbsd.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_ppc_rhapsody.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_riscv64.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc64_openbsd.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_linux_GCC3.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_netbsd.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_sparc_openbsd.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_asm_x86_64_unix.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_darwin.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_gcc_x86_unix.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf32.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_ipf64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_alpha.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390x.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_mips.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_mips64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_pa32.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc64_linux.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_aix64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_linux.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_ppc_rhapsody.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_riscv64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc64_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_netbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_sparc_solaris.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcinvoke_x86_64_unix.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_aarch64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_alpha_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_arm.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_arm_netbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_arm_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_aarch64.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf32.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ipf64.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips.s.m4 create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_mips64.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_pa32.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_parisc_linux.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc64_linux.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix.s.m4 create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_aix64.s.m4 create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_darwin.s.m4 create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_linux.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_ppc_openbsd.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_riscv64.S create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc64_openbsd.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_netbsd.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_openbsd.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_asm_sparc_solaris.s create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_darwin.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_gcc_x86_unix.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_ipf32.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_ipf64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_linux_alpha.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_linux_s390x.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_mips.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_mips64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_pa32.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_ppc64_linux.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_aix64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_linux.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_ppc_rhapsody.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_riscv64.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_sparc64_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_netbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_openbsd.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_sparc_solaris.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_darwin.cpp create mode 100644 xpcom/reflect/xptcall/md/unix/xptcstubs_x86_64_linux.cpp create mode 100644 xpcom/reflect/xptcall/md/win32/moz.build create mode 100644 xpcom/reflect/xptcall/md/win32/preprocess.py create mode 100644 xpcom/reflect/xptcall/md/win32/xptcinvoke.cpp create mode 100644 xpcom/reflect/xptcall/md/win32/xptcinvoke_aarch64.cpp create mode 100644 xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_aarch64.asm create mode 100644 xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64.asm create mode 100644 xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_64_gnu.s create mode 100644 xpcom/reflect/xptcall/md/win32/xptcinvoke_asm_x86_msvc.asm create mode 100644 xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_64.cpp create mode 100644 xpcom/reflect/xptcall/md/win32/xptcinvoke_x86_gnu.cpp create mode 100644 xpcom/reflect/xptcall/md/win32/xptcstubs.cpp create mode 100644 xpcom/reflect/xptcall/md/win32/xptcstubs_aarch64.cpp create mode 100644 xpcom/reflect/xptcall/md/win32/xptcstubs_asm_aarch64.asm create mode 100644 xpcom/reflect/xptcall/md/win32/xptcstubs_asm_x86_64.asm create mode 100644 xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64.cpp create mode 100644 xpcom/reflect/xptcall/md/win32/xptcstubs_x86_64_gnu.cpp create mode 100644 xpcom/reflect/xptcall/moz.build create mode 100644 xpcom/reflect/xptcall/nsXPTCUtils.h create mode 100644 xpcom/reflect/xptcall/porting.html create mode 100644 xpcom/reflect/xptcall/xptcall.cpp create mode 100644 xpcom/reflect/xptcall/xptcall.h create mode 100644 xpcom/reflect/xptcall/xptcprivate.h create mode 100644 xpcom/reflect/xptcall/xptcstubsdecl.inc create mode 100644 xpcom/reflect/xptcall/xptcstubsdef.inc create mode 100644 xpcom/reflect/xptinfo/moz.build create mode 100644 xpcom/reflect/xptinfo/xptcodegen.py create mode 100644 xpcom/reflect/xptinfo/xptinfo.cpp create mode 100644 xpcom/reflect/xptinfo/xptinfo.h create mode 100644 xpcom/rust/gecko_logger/Cargo.toml create mode 100644 xpcom/rust/gecko_logger/src/lib.rs create mode 100644 xpcom/rust/gkrust_utils/Cargo.toml create mode 100644 xpcom/rust/gkrust_utils/cbindgen.toml create mode 100644 xpcom/rust/gkrust_utils/src/lib.rs create mode 100644 xpcom/rust/gtest/bench-collections/Bench.cpp create mode 100644 xpcom/rust/gtest/bench-collections/Cargo.toml create mode 100644 xpcom/rust/gtest/bench-collections/bench.rs create mode 100644 xpcom/rust/gtest/moz.build create mode 100644 xpcom/rust/gtest/moz_task/Cargo.toml create mode 100644 xpcom/rust/gtest/moz_task/TestMozTask.cpp create mode 100644 xpcom/rust/gtest/moz_task/test.rs create mode 100644 xpcom/rust/gtest/nsstring/Cargo.toml create mode 100644 xpcom/rust/gtest/nsstring/TestnsString.cpp create mode 100644 xpcom/rust/gtest/nsstring/test.rs create mode 100644 xpcom/rust/gtest/xpcom/Cargo.toml create mode 100644 xpcom/rust/gtest/xpcom/TestXpcom.cpp create mode 100644 xpcom/rust/gtest/xpcom/test.rs create mode 100644 xpcom/rust/moz_task/Cargo.toml create mode 100644 xpcom/rust/moz_task/src/dispatcher.rs create mode 100644 xpcom/rust/moz_task/src/event_loop.rs create mode 100644 xpcom/rust/moz_task/src/executor.rs create mode 100644 xpcom/rust/moz_task/src/lib.rs create mode 100644 xpcom/rust/nserror/Cargo.toml create mode 100644 xpcom/rust/nserror/src/lib.rs create mode 100644 xpcom/rust/nsstring/Cargo.toml create mode 100644 xpcom/rust/nsstring/src/conversions.rs create mode 100644 xpcom/rust/nsstring/src/lib.rs create mode 100644 xpcom/rust/xpcom/Cargo.toml create mode 100644 xpcom/rust/xpcom/src/base.rs create mode 100644 xpcom/rust/xpcom/src/components.rs create mode 100644 xpcom/rust/xpcom/src/interfaces/idl.rs create mode 100644 xpcom/rust/xpcom/src/interfaces/mod.rs create mode 100644 xpcom/rust/xpcom/src/interfaces/nonidl.rs create mode 100644 xpcom/rust/xpcom/src/lib.rs create mode 100644 xpcom/rust/xpcom/src/method.rs create mode 100644 xpcom/rust/xpcom/src/promise.rs create mode 100644 xpcom/rust/xpcom/src/reexports.rs create mode 100644 xpcom/rust/xpcom/src/refptr.rs create mode 100644 xpcom/rust/xpcom/src/statics.rs create mode 100644 xpcom/rust/xpcom/xpcom_macros/Cargo.toml create mode 100644 xpcom/rust/xpcom/xpcom_macros/src/lib.rs create mode 100644 xpcom/string/README.html create mode 100644 xpcom/string/RustRegex.h create mode 100644 xpcom/string/RustStringAPI.cpp create mode 100644 xpcom/string/crashtests/1113005-frame.html create mode 100644 xpcom/string/crashtests/1113005.html create mode 100644 xpcom/string/crashtests/394275-1.html create mode 100644 xpcom/string/crashtests/395651-1.html create mode 100644 xpcom/string/crashtests/crashtests.list create mode 100644 xpcom/string/moz.build create mode 100644 xpcom/string/nsASCIIMask.cpp create mode 100644 xpcom/string/nsASCIIMask.h create mode 100644 xpcom/string/nsAString.h create mode 100644 xpcom/string/nsCharTraits.h create mode 100644 xpcom/string/nsDependentString.h create mode 100644 xpcom/string/nsDependentSubstring.h create mode 100644 xpcom/string/nsLiteralString.h create mode 100644 xpcom/string/nsPrintfCString.h create mode 100644 xpcom/string/nsPromiseFlatString.h create mode 100644 xpcom/string/nsReadableUtils.cpp create mode 100644 xpcom/string/nsReadableUtils.h create mode 100644 xpcom/string/nsString.h create mode 100644 xpcom/string/nsStringBuffer.cpp create mode 100644 xpcom/string/nsStringBuffer.h create mode 100644 xpcom/string/nsStringFlags.h create mode 100644 xpcom/string/nsStringFwd.h create mode 100644 xpcom/string/nsStringIterator.h create mode 100644 xpcom/string/nsStringStats.cpp create mode 100644 xpcom/string/nsStringStats.h create mode 100644 xpcom/string/nsTDependentString.cpp create mode 100644 xpcom/string/nsTDependentString.h create mode 100644 xpcom/string/nsTDependentSubstring.cpp create mode 100644 xpcom/string/nsTDependentSubstring.h create mode 100644 xpcom/string/nsTLiteralString.cpp create mode 100644 xpcom/string/nsTLiteralString.h create mode 100644 xpcom/string/nsTPromiseFlatString.cpp create mode 100644 xpcom/string/nsTPromiseFlatString.h create mode 100644 xpcom/string/nsTString.cpp create mode 100644 xpcom/string/nsTString.h create mode 100644 xpcom/string/nsTStringComparator.cpp create mode 100644 xpcom/string/nsTStringHasher.h create mode 100644 xpcom/string/nsTStringRepr.cpp create mode 100644 xpcom/string/nsTStringRepr.h create mode 100644 xpcom/string/nsTSubstring.cpp create mode 100644 xpcom/string/nsTSubstring.h create mode 100644 xpcom/string/nsTSubstringTuple.cpp create mode 100644 xpcom/string/nsTSubstringTuple.h create mode 100644 xpcom/string/nsTextFormatter.cpp create mode 100644 xpcom/string/nsTextFormatter.h create mode 100644 xpcom/string/nsUTF8Utils.h create mode 100644 xpcom/system/moz.build create mode 100644 xpcom/system/nsIBlocklistService.idl create mode 100644 xpcom/system/nsICrashReporter.idl create mode 100644 xpcom/system/nsIDeviceSensors.idl create mode 100644 xpcom/system/nsIGIOService.idl create mode 100644 xpcom/system/nsIGSettingsService.idl create mode 100644 xpcom/system/nsIGeolocationProvider.idl create mode 100644 xpcom/system/nsIHapticFeedback.idl create mode 100644 xpcom/system/nsIPlatformInfo.idl create mode 100644 xpcom/system/nsISystemInfo.idl create mode 100644 xpcom/system/nsIXULAppInfo.idl create mode 100644 xpcom/system/nsIXULRuntime.idl create mode 100644 xpcom/tests/NotXPCOMTest.idl create mode 100644 xpcom/tests/RegFactory.cpp create mode 100644 xpcom/tests/SizeTest01.cpp create mode 100644 xpcom/tests/SizeTest02.cpp create mode 100644 xpcom/tests/SizeTest03.cpp create mode 100644 xpcom/tests/SizeTest04.cpp create mode 100644 xpcom/tests/SizeTest05.cpp create mode 100644 xpcom/tests/SizeTest06.cpp create mode 100644 xpcom/tests/TestArguments.cpp create mode 100644 xpcom/tests/TestBlockingProcess.cpp create mode 100644 xpcom/tests/TestHarness.h create mode 100644 xpcom/tests/TestMemoryPressureWatcherLinux.cpp create mode 100644 xpcom/tests/TestPRIntN.cpp create mode 100644 xpcom/tests/TestQuickReturn.cpp create mode 100644 xpcom/tests/TestShutdown.cpp create mode 100644 xpcom/tests/TestStreamUtils.cpp create mode 100644 xpcom/tests/TestUnicodeArguments.cpp create mode 100644 xpcom/tests/TestWinReg.js create mode 100644 xpcom/tests/TestingAtomList.h create mode 100644 xpcom/tests/crashtests/bug-1714685.html create mode 100644 xpcom/tests/crashtests/crashtests.list create mode 100644 xpcom/tests/gtest/Helpers.cpp create mode 100644 xpcom/tests/gtest/Helpers.h create mode 100644 xpcom/tests/gtest/TestAllocReplacement.cpp create mode 100644 xpcom/tests/gtest/TestArenaAllocator.cpp create mode 100644 xpcom/tests/gtest/TestArrayAlgorithm.cpp create mode 100644 xpcom/tests/gtest/TestAtoms.cpp create mode 100644 xpcom/tests/gtest/TestAutoRefCnt.cpp create mode 100644 xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp create mode 100644 xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp create mode 100644 xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp create mode 100644 xpcom/tests/gtest/TestBase64.cpp create mode 100644 xpcom/tests/gtest/TestCOMArray.cpp create mode 100644 xpcom/tests/gtest/TestCOMPtr.cpp create mode 100644 xpcom/tests/gtest/TestCOMPtrEq.cpp create mode 100644 xpcom/tests/gtest/TestCRT.cpp create mode 100644 xpcom/tests/gtest/TestCallTemplates.cpp create mode 100644 xpcom/tests/gtest/TestCloneInputStream.cpp create mode 100644 xpcom/tests/gtest/TestDafsa.cpp create mode 100644 xpcom/tests/gtest/TestDeadlockDetector.cpp create mode 100644 xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp create mode 100644 xpcom/tests/gtest/TestDelayedRunnable.cpp create mode 100644 xpcom/tests/gtest/TestEncoding.cpp create mode 100644 xpcom/tests/gtest/TestEscape.cpp create mode 100644 xpcom/tests/gtest/TestEventPriorities.cpp create mode 100644 xpcom/tests/gtest/TestEventTargetQI.cpp create mode 100644 xpcom/tests/gtest/TestExpirationTracker.cpp create mode 100644 xpcom/tests/gtest/TestFile.cpp create mode 100644 xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp create mode 100644 xpcom/tests/gtest/TestFilePreferencesUnix.cpp create mode 100644 xpcom/tests/gtest/TestFilePreferencesWin.cpp create mode 100644 xpcom/tests/gtest/TestGCPostBarriers.cpp create mode 100644 xpcom/tests/gtest/TestHandleWatcher.cpp create mode 100644 xpcom/tests/gtest/TestHashtables.cpp create mode 100644 xpcom/tests/gtest/TestID.cpp create mode 100644 xpcom/tests/gtest/TestIDUtils.cpp create mode 100644 xpcom/tests/gtest/TestInputStreamLengthHelper.cpp create mode 100644 xpcom/tests/gtest/TestJSHolderMap.cpp create mode 100644 xpcom/tests/gtest/TestLogCommandLineHandler.cpp create mode 100644 xpcom/tests/gtest/TestLogging.cpp create mode 100644 xpcom/tests/gtest/TestMacNSURLEscaping.mm create mode 100644 xpcom/tests/gtest/TestMemoryPressure.cpp create mode 100644 xpcom/tests/gtest/TestMoveString.cpp create mode 100644 xpcom/tests/gtest/TestMozPromise.cpp create mode 100644 xpcom/tests/gtest/TestMruCache.cpp create mode 100644 xpcom/tests/gtest/TestMultiplexInputStream.cpp create mode 100644 xpcom/tests/gtest/TestNSPRLogModulesParser.cpp create mode 100644 xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp create mode 100644 xpcom/tests/gtest/TestNsDeque.cpp create mode 100644 xpcom/tests/gtest/TestNsRefPtr.cpp create mode 100644 xpcom/tests/gtest/TestObserverArray.cpp create mode 100644 xpcom/tests/gtest/TestObserverService.cpp create mode 100644 xpcom/tests/gtest/TestOwningNonNull.cpp create mode 100644 xpcom/tests/gtest/TestPLDHash.cpp create mode 100644 xpcom/tests/gtest/TestPipes.cpp create mode 100644 xpcom/tests/gtest/TestPriorityQueue.cpp create mode 100644 xpcom/tests/gtest/TestQueue.cpp create mode 100644 xpcom/tests/gtest/TestRWLock.cpp create mode 100644 xpcom/tests/gtest/TestRacingServiceManager.cpp create mode 100644 xpcom/tests/gtest/TestRecursiveMutex.cpp create mode 100644 xpcom/tests/gtest/TestRustRegex.cpp create mode 100644 xpcom/tests/gtest/TestSTLWrappers.cpp create mode 100644 xpcom/tests/gtest/TestSegmentedBuffer.cpp create mode 100644 xpcom/tests/gtest/TestSlicedInputStream.cpp create mode 100644 xpcom/tests/gtest/TestSmallArrayLRUCache.cpp create mode 100644 xpcom/tests/gtest/TestSnappyStreams.cpp create mode 100644 xpcom/tests/gtest/TestStateWatching.cpp create mode 100644 xpcom/tests/gtest/TestStorageStream.cpp create mode 100644 xpcom/tests/gtest/TestStringStream.cpp create mode 100644 xpcom/tests/gtest/TestStrings.cpp create mode 100644 xpcom/tests/gtest/TestSubstringTuple.cpp create mode 100644 xpcom/tests/gtest/TestSynchronization.cpp create mode 100644 xpcom/tests/gtest/TestTArray.cpp create mode 100644 xpcom/tests/gtest/TestTArray2.cpp create mode 100644 xpcom/tests/gtest/TestTaskQueue.cpp create mode 100644 xpcom/tests/gtest/TestTextFormatter.cpp create mode 100644 xpcom/tests/gtest/TestThreadManager.cpp create mode 100644 xpcom/tests/gtest/TestThreadMetrics.cpp create mode 100644 xpcom/tests/gtest/TestThreadPool.cpp create mode 100644 xpcom/tests/gtest/TestThreadPoolListener.cpp create mode 100644 xpcom/tests/gtest/TestThreadUtils.cpp create mode 100644 xpcom/tests/gtest/TestThreads.cpp create mode 100644 xpcom/tests/gtest/TestThreads_mac.mm create mode 100644 xpcom/tests/gtest/TestThrottledEventQueue.cpp create mode 100644 xpcom/tests/gtest/TestTimeStamp.cpp create mode 100644 xpcom/tests/gtest/TestTimers.cpp create mode 100644 xpcom/tests/gtest/TestTokenizer.cpp create mode 100644 xpcom/tests/gtest/TestUTF.cpp create mode 100644 xpcom/tests/gtest/TestVariant.cpp create mode 100644 xpcom/tests/gtest/UTFStrings.h create mode 100644 xpcom/tests/gtest/dafsa_test_1.dat create mode 100644 xpcom/tests/gtest/moz.build create mode 100644 xpcom/tests/gtest/wikipedia/README.txt create mode 100644 xpcom/tests/gtest/wikipedia/ar.txt create mode 100644 xpcom/tests/gtest/wikipedia/de-edit.txt create mode 100644 xpcom/tests/gtest/wikipedia/de.txt create mode 100644 xpcom/tests/gtest/wikipedia/ja.txt create mode 100644 xpcom/tests/gtest/wikipedia/ko.txt create mode 100644 xpcom/tests/gtest/wikipedia/ru.txt create mode 100644 xpcom/tests/gtest/wikipedia/th.txt create mode 100644 xpcom/tests/gtest/wikipedia/tr.txt create mode 100644 xpcom/tests/gtest/wikipedia/vi.txt create mode 100644 xpcom/tests/moz.build create mode 100644 xpcom/tests/resources.h create mode 100644 xpcom/tests/test.properties create mode 100644 xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist create mode 100755 xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp create mode 100644 xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo create mode 100644 xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings create mode 100644 xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib create mode 100644 xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib create mode 100644 xpcom/tests/unit/data/bug121341-2.properties create mode 100644 xpcom/tests/unit/data/bug121341.properties create mode 100644 xpcom/tests/unit/data/iniparser01-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser01-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser01.ini create mode 100644 xpcom/tests/unit/data/iniparser02-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser02-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser02.ini create mode 100644 xpcom/tests/unit/data/iniparser03-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser03-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser03.ini create mode 100644 xpcom/tests/unit/data/iniparser04-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser04-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser04.ini create mode 100644 xpcom/tests/unit/data/iniparser05-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser05-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser05.ini create mode 100644 xpcom/tests/unit/data/iniparser06-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser06-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser06.ini create mode 100644 xpcom/tests/unit/data/iniparser07-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser07-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser07.ini create mode 100644 xpcom/tests/unit/data/iniparser08-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser08-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser08.ini create mode 100644 xpcom/tests/unit/data/iniparser09-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser09-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser09.ini create mode 100644 xpcom/tests/unit/data/iniparser10-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser10-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser10.ini create mode 100644 xpcom/tests/unit/data/iniparser11-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser11-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser11.ini create mode 100644 xpcom/tests/unit/data/iniparser12-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser12-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser12.ini create mode 100644 xpcom/tests/unit/data/iniparser13-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser13-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser13.ini create mode 100644 xpcom/tests/unit/data/iniparser14-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser14-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser14.ini create mode 100644 xpcom/tests/unit/data/iniparser15-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser15-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser15.ini create mode 100644 xpcom/tests/unit/data/iniparser16-utf16leBOM.ini create mode 100644 xpcom/tests/unit/data/iniparser16-utf8BOM.ini create mode 100644 xpcom/tests/unit/data/iniparser16.ini create mode 100644 xpcom/tests/unit/data/iniparser17.ini create mode 100644 xpcom/tests/unit/data/presentation.key/.typeAttributes.dict create mode 100644 xpcom/tests/unit/data/presentation.key/Contents/PkgInfo create mode 100644 xpcom/tests/unit/data/presentation.key/index.apxl.gz create mode 100644 xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff create mode 100644 xpcom/tests/unit/data/process_directive.manifest create mode 100644 xpcom/tests/unit/head_xpcom.js create mode 100644 xpcom/tests/unit/test_bug121341.js create mode 100644 xpcom/tests/unit/test_bug1434856.js create mode 100644 xpcom/tests/unit/test_bug325418.js create mode 100644 xpcom/tests/unit/test_bug332389.js create mode 100644 xpcom/tests/unit/test_bug333505.js create mode 100644 xpcom/tests/unit/test_bug364285-1.js create mode 100644 xpcom/tests/unit/test_bug374754.js create mode 100644 xpcom/tests/unit/test_bug476919.js create mode 100644 xpcom/tests/unit/test_bug478086.js create mode 100644 xpcom/tests/unit/test_bug745466.js create mode 100644 xpcom/tests/unit/test_console_service_callFunctionAndLogException.js create mode 100644 xpcom/tests/unit/test_debugger_malloc_size_of.js create mode 100644 xpcom/tests/unit/test_file_createUnique.js create mode 100644 xpcom/tests/unit/test_file_equality.js create mode 100644 xpcom/tests/unit/test_file_renameTo.js create mode 100644 xpcom/tests/unit/test_getTimers.js create mode 100644 xpcom/tests/unit/test_hidden_files.js create mode 100644 xpcom/tests/unit/test_home.js create mode 100644 xpcom/tests/unit/test_iniParser.js create mode 100644 xpcom/tests/unit/test_ioutil.js create mode 100644 xpcom/tests/unit/test_localfile.js create mode 100644 xpcom/tests/unit/test_mac_bundle.js create mode 100644 xpcom/tests/unit/test_mac_xattrs.js create mode 100644 xpcom/tests/unit/test_notxpcom_scriptable.js create mode 100644 xpcom/tests/unit/test_nsIMutableArray.js create mode 100644 xpcom/tests/unit/test_nsIProcess.js create mode 100644 xpcom/tests/unit/test_nsIProcess_stress.js create mode 100644 xpcom/tests/unit/test_pipe.js create mode 100644 xpcom/tests/unit/test_process_directives.js create mode 100644 xpcom/tests/unit/test_process_directives_child.js create mode 100644 xpcom/tests/unit/test_seek_multiplex.js create mode 100644 xpcom/tests/unit/test_storagestream.js create mode 100644 xpcom/tests/unit/test_streams.js create mode 100644 xpcom/tests/unit/test_stringstream.js create mode 100644 xpcom/tests/unit/test_symlinks.js create mode 100644 xpcom/tests/unit/test_systemInfo.js create mode 100644 xpcom/tests/unit/test_versioncomparator.js create mode 100644 xpcom/tests/unit/test_windows_cmdline_file.js create mode 100644 xpcom/tests/unit/test_windows_registry.js create mode 100644 xpcom/tests/unit/xpcshell.ini create mode 100644 xpcom/tests/windows/TestCOM.cpp create mode 100644 xpcom/tests/windows/TestNtPathToDosPath.cpp create mode 100644 xpcom/tests/windows/TestPoisonIOInterposer.cpp create mode 100644 xpcom/tests/windows/moz.build create mode 100644 xpcom/threads/AbstractThread.cpp create mode 100644 xpcom/threads/AbstractThread.h create mode 100644 xpcom/threads/BlockingResourceBase.cpp create mode 100644 xpcom/threads/BlockingResourceBase.h create mode 100644 xpcom/threads/CPUUsageWatcher.cpp create mode 100644 xpcom/threads/CPUUsageWatcher.h create mode 100644 xpcom/threads/CondVar.h create mode 100644 xpcom/threads/DataMutex.h create mode 100644 xpcom/threads/DeadlockDetector.h create mode 100644 xpcom/threads/DelayedRunnable.cpp create mode 100644 xpcom/threads/DelayedRunnable.h create mode 100644 xpcom/threads/EventQueue.cpp create mode 100644 xpcom/threads/EventQueue.h create mode 100644 xpcom/threads/EventTargetCapability.h create mode 100644 xpcom/threads/IdlePeriodState.cpp create mode 100644 xpcom/threads/IdlePeriodState.h create mode 100644 xpcom/threads/IdleTaskRunner.cpp create mode 100644 xpcom/threads/IdleTaskRunner.h create mode 100644 xpcom/threads/InputTaskManager.cpp create mode 100644 xpcom/threads/InputTaskManager.h create mode 100644 xpcom/threads/LazyIdleThread.cpp create mode 100644 xpcom/threads/LazyIdleThread.h create mode 100644 xpcom/threads/LeakRefPtr.h create mode 100644 xpcom/threads/MainThreadIdlePeriod.cpp create mode 100644 xpcom/threads/MainThreadIdlePeriod.h create mode 100644 xpcom/threads/MainThreadUtils.h create mode 100644 xpcom/threads/Monitor.h create mode 100644 xpcom/threads/MozPromise.h create mode 100644 xpcom/threads/MozPromiseInlines.h create mode 100644 xpcom/threads/Mutex.h create mode 100644 xpcom/threads/PerformanceCounter.cpp create mode 100644 xpcom/threads/PerformanceCounter.h create mode 100644 xpcom/threads/Queue.h create mode 100644 xpcom/threads/RWLock.cpp create mode 100644 xpcom/threads/RWLock.h create mode 100644 xpcom/threads/RecursiveMutex.cpp create mode 100644 xpcom/threads/RecursiveMutex.h create mode 100644 xpcom/threads/ReentrantMonitor.h create mode 100644 xpcom/threads/SchedulerGroup.cpp create mode 100644 xpcom/threads/SchedulerGroup.h create mode 100644 xpcom/threads/SharedThreadPool.cpp create mode 100644 xpcom/threads/SharedThreadPool.h create mode 100644 xpcom/threads/SpinEventLoopUntil.h create mode 100644 xpcom/threads/StateMirroring.h create mode 100644 xpcom/threads/StateWatching.h create mode 100644 xpcom/threads/SyncRunnable.h create mode 100644 xpcom/threads/SynchronizedEventQueue.cpp create mode 100644 xpcom/threads/SynchronizedEventQueue.h create mode 100644 xpcom/threads/TaskCategory.h create mode 100644 xpcom/threads/TaskController.cpp create mode 100644 xpcom/threads/TaskController.h create mode 100644 xpcom/threads/TaskDispatcher.h create mode 100644 xpcom/threads/TaskQueue.cpp create mode 100644 xpcom/threads/TaskQueue.h create mode 100644 xpcom/threads/ThreadBound.h create mode 100644 xpcom/threads/ThreadDelay.cpp create mode 100644 xpcom/threads/ThreadDelay.h create mode 100644 xpcom/threads/ThreadEventQueue.cpp create mode 100644 xpcom/threads/ThreadEventQueue.h create mode 100644 xpcom/threads/ThreadEventTarget.cpp create mode 100644 xpcom/threads/ThreadEventTarget.h create mode 100644 xpcom/threads/ThreadLocalVariables.cpp create mode 100644 xpcom/threads/ThrottledEventQueue.cpp create mode 100644 xpcom/threads/ThrottledEventQueue.h create mode 100644 xpcom/threads/TimerThread.cpp create mode 100644 xpcom/threads/TimerThread.h create mode 100644 xpcom/threads/VsyncTaskManager.cpp create mode 100644 xpcom/threads/VsyncTaskManager.h create mode 100644 xpcom/threads/WinHandleWatcher.cpp create mode 100644 xpcom/threads/WinHandleWatcher.h create mode 100644 xpcom/threads/components.conf create mode 100644 xpcom/threads/moz.build create mode 100644 xpcom/threads/nsEnvironment.cpp create mode 100644 xpcom/threads/nsEnvironment.h create mode 100644 xpcom/threads/nsICancelableRunnable.h create mode 100644 xpcom/threads/nsIDirectTaskDispatcher.idl create mode 100644 xpcom/threads/nsIDiscardableRunnable.h create mode 100644 xpcom/threads/nsIEnvironment.idl create mode 100644 xpcom/threads/nsIEventTarget.idl create mode 100644 xpcom/threads/nsIIdlePeriod.idl create mode 100644 xpcom/threads/nsIIdleRunnable.h create mode 100644 xpcom/threads/nsINamed.idl create mode 100644 xpcom/threads/nsIProcess.idl create mode 100644 xpcom/threads/nsIRunnable.idl create mode 100644 xpcom/threads/nsISerialEventTarget.idl create mode 100644 xpcom/threads/nsISupportsPriority.idl create mode 100644 xpcom/threads/nsITargetShutdownTask.h create mode 100644 xpcom/threads/nsIThread.idl create mode 100644 xpcom/threads/nsIThreadInternal.idl create mode 100644 xpcom/threads/nsIThreadManager.idl create mode 100644 xpcom/threads/nsIThreadPool.idl create mode 100644 xpcom/threads/nsIThreadShutdown.idl create mode 100644 xpcom/threads/nsITimer.idl create mode 100644 xpcom/threads/nsMemoryPressure.cpp create mode 100644 xpcom/threads/nsMemoryPressure.h create mode 100644 xpcom/threads/nsProcess.h create mode 100644 xpcom/threads/nsProcessCommon.cpp create mode 100644 xpcom/threads/nsProxyRelease.cpp create mode 100644 xpcom/threads/nsProxyRelease.h create mode 100644 xpcom/threads/nsThread.cpp create mode 100644 xpcom/threads/nsThread.h create mode 100644 xpcom/threads/nsThreadManager.cpp create mode 100644 xpcom/threads/nsThreadManager.h create mode 100644 xpcom/threads/nsThreadPool.cpp create mode 100644 xpcom/threads/nsThreadPool.h create mode 100644 xpcom/threads/nsThreadSyncDispatch.h create mode 100644 xpcom/threads/nsThreadUtils.cpp create mode 100644 xpcom/threads/nsThreadUtils.h create mode 100644 xpcom/threads/nsTimerImpl.cpp create mode 100644 xpcom/threads/nsTimerImpl.h create mode 100644 xpcom/windbgdlg/Makefile.in create mode 100644 xpcom/windbgdlg/moz.build create mode 100644 xpcom/windbgdlg/windbgdlg.cpp create mode 100644 xpcom/xpcom-config.h.in create mode 100644 xpcom/xpidl/Makefile.in create mode 100644 xpcom/xpidl/moz.build (limited to 'xpcom') diff --git a/xpcom/base/AppShutdown.cpp b/xpcom/base/AppShutdown.cpp new file mode 100644 index 0000000000..c68dedef31 --- /dev/null +++ b/xpcom/base/AppShutdown.cpp @@ -0,0 +1,469 @@ +/* -*- 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 "ShutdownPhase.h" +#ifdef XP_WIN +# include +# include "mozilla/PreXULSkeletonUI.h" +#else +# include +#endif + +#include "ProfilerControl.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/Printf.h" +#include "mozilla/scache/StartupCache.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/StaticPrefs_toolkit.h" +#include "mozilla/LateWriteChecks.h" +#include "mozilla/Services.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsAppRunner.h" +#include "nsDirectoryServiceUtils.h" +#include "nsExceptionHandler.h" +#include "nsICertStorage.h" +#include "nsThreadUtils.h" + +#include "AppShutdown.h" + +// TODO: understand why on Android we cannot include this and if we should +#ifndef ANDROID +# include "nsTerminator.h" +#endif +#include "prenv.h" + +#ifdef MOZ_BACKGROUNDTASKS +# include "mozilla/BackgroundTasks.h" +#endif + +namespace mozilla { + +const char* sPhaseObserverKeys[] = { + nullptr, // NotInShutdown + "quit-application", // AppShutdownConfirmed + "profile-change-net-teardown", // AppShutdownNetTeardown + "profile-change-teardown", // AppShutdownTeardown + "profile-before-change", // AppShutdown + "profile-before-change-qm", // AppShutdownQM + "profile-before-change-telemetry", // AppShutdownTelemetry + "xpcom-will-shutdown", // XPCOMWillShutdown + "xpcom-shutdown", // XPCOMShutdown + "xpcom-shutdown-threads", // XPCOMShutdownThreads + nullptr, // XPCOMShutdownFinal + nullptr // CCPostLastCycleCollection +}; + +static_assert(sizeof(sPhaseObserverKeys) / sizeof(sPhaseObserverKeys[0]) == + (size_t)ShutdownPhase::ShutdownPhase_Length); + +const char* sPhaseReadableNames[] = {"NotInShutdown", + "AppShutdownConfirmed", + "AppShutdownNetTeardown", + "AppShutdownTeardown", + "AppShutdown", + "AppShutdownQM", + "AppShutdownTelemetry", + "XPCOMWillShutdown", + "XPCOMShutdown", + "XPCOMShutdownThreads", + "XPCOMShutdownFinal", + "CCPostLastCycleCollection"}; + +static_assert(sizeof(sPhaseReadableNames) / sizeof(sPhaseReadableNames[0]) == + (size_t)ShutdownPhase::ShutdownPhase_Length); + +#ifndef ANDROID +static nsTerminator* sTerminator = nullptr; +#endif + +static ShutdownPhase sFastShutdownPhase = ShutdownPhase::NotInShutdown; +static ShutdownPhase sLateWriteChecksPhase = ShutdownPhase::NotInShutdown; +static AppShutdownMode sShutdownMode = AppShutdownMode::Normal; +static Atomic sCurrentShutdownPhase( + ShutdownPhase::NotInShutdown); +static int sExitCode = 0; + +// These environment variable strings are all deliberately copied and leaked +// due to requirements of PR_SetEnv and similar. +static char* sSavedXulAppFile = nullptr; +#ifdef XP_WIN +static wchar_t* sSavedProfDEnvVar = nullptr; +static wchar_t* sSavedProfLDEnvVar = nullptr; +#else +static char* sSavedProfDEnvVar = nullptr; +static char* sSavedProfLDEnvVar = nullptr; +#endif + +ShutdownPhase GetShutdownPhaseFromPrefValue(int32_t aPrefValue) { + switch (aPrefValue) { + case 1: + return ShutdownPhase::CCPostLastCycleCollection; + case 2: + return ShutdownPhase::XPCOMShutdownThreads; + case 3: + return ShutdownPhase::XPCOMShutdown; + // NOTE: the remaining values from the ShutdownPhase enum will be added + // when we're at least reasonably confident that the world won't come + // crashing down if we do a fast shutdown at that point. + } + return ShutdownPhase::NotInShutdown; +} + +ShutdownPhase AppShutdown::GetCurrentShutdownPhase() { + return sCurrentShutdownPhase; +} + +bool AppShutdown::IsInOrBeyond(ShutdownPhase aPhase) { + return (sCurrentShutdownPhase >= aPhase); +} + +int AppShutdown::GetExitCode() { return sExitCode; } + +void AppShutdown::SaveEnvVarsForPotentialRestart() { + const char* s = PR_GetEnv("XUL_APP_FILE"); + if (s) { + sSavedXulAppFile = Smprintf("%s=%s", "XUL_APP_FILE", s).release(); + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedXulAppFile); + } +} + +const char* AppShutdown::GetObserverKey(ShutdownPhase aPhase) { + return sPhaseObserverKeys[static_cast>( + aPhase)]; +} + +const char* AppShutdown::GetShutdownPhaseName(ShutdownPhase aPhase) { + return sPhaseReadableNames[static_cast>( + aPhase)]; +} + +void AppShutdown::MaybeDoRestart() { + if (sShutdownMode == AppShutdownMode::Restart) { + StopLateWriteChecks(); + + // Since we'll be launching our child while we're still alive, make sure + // we've unlocked the profile first, otherwise the child could hit its + // profile lock check before we've exited and thus released our lock. + UnlockProfile(); + + if (sSavedXulAppFile) { + PR_SetEnv(sSavedXulAppFile); + } + +#ifdef XP_WIN + if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) { + SetEnvironmentVariableW(L"XRE_PROFILE_PATH", sSavedProfDEnvVar); + } + if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) { + SetEnvironmentVariableW(L"XRE_PROFILE_LOCAL_PATH", sSavedProfLDEnvVar); + } + Unused << NotePreXULSkeletonUIRestarting(); +#else + if (sSavedProfDEnvVar && !EnvHasValue("XRE_PROFILE_PATH")) { + PR_SetEnv(sSavedProfDEnvVar); + } + if (sSavedProfLDEnvVar && !EnvHasValue("XRE_PROFILE_LOCAL_PATH")) { + PR_SetEnv(sSavedProfLDEnvVar); + } +#endif + + LaunchChild(true); + } +} + +#ifdef XP_WIN +wchar_t* CopyPathIntoNewWCString(nsIFile* aFile) { + wchar_t* result = nullptr; + nsAutoString resStr; + aFile->GetPath(resStr); + if (resStr.Length() > 0) { + result = (wchar_t*)malloc((resStr.Length() + 1) * sizeof(wchar_t)); + if (result) { + wcscpy(result, resStr.get()); + result[resStr.Length()] = 0; + } + } + + return result; +} +#endif + +void AppShutdown::Init(AppShutdownMode aMode, int aExitCode, + AppShutdownReason aReason) { + if (sShutdownMode == AppShutdownMode::Normal) { + sShutdownMode = aMode; + } + AppShutdown::AnnotateShutdownReason(aReason); + + sExitCode = aExitCode; + +#ifndef ANDROID + sTerminator = new nsTerminator(); +#endif + + // Late-write checks needs to find the profile directory, so it has to + // be initialized before services::Shutdown or (because of + // xpcshell tests replacing the service) modules being unloaded. + InitLateWriteChecks(); + + int32_t fastShutdownPref = StaticPrefs::toolkit_shutdown_fastShutdownStage(); + sFastShutdownPhase = GetShutdownPhaseFromPrefValue(fastShutdownPref); + int32_t lateWriteChecksPref = + StaticPrefs::toolkit_shutdown_lateWriteChecksStage(); + sLateWriteChecksPhase = GetShutdownPhaseFromPrefValue(lateWriteChecksPref); + + // Very early shutdowns can happen before the startup cache is even + // initialized; don't bother initializing it during shutdown. + if (auto* cache = scache::StartupCache::GetSingletonNoInit()) { + cache->MaybeInitShutdownWrite(); + } +} + +void AppShutdown::MaybeFastShutdown(ShutdownPhase aPhase) { + // For writes which we want to ensure are recorded, we don't want to trip + // the late write checking code. Anything that writes to disk and which + // we don't want to skip should be listed out explicitly in this section. + if (aPhase == sFastShutdownPhase || aPhase == sLateWriteChecksPhase) { + if (auto* cache = scache::StartupCache::GetSingletonNoInit()) { + cache->EnsureShutdownWriteComplete(); + } + + nsresult rv; + nsCOMPtr certStorage = + do_GetService("@mozilla.org/security/certstorage;1", &rv); + if (NS_SUCCEEDED(rv)) { + SpinEventLoopUntil("AppShutdown::MaybeFastShutdown"_ns, [&]() { + int32_t remainingOps; + nsresult rv = certStorage->GetRemainingOperationCount(&remainingOps); + NS_ASSERTION(NS_SUCCEEDED(rv), + "nsICertStorage::getRemainingOperationCount failed during " + "shutdown"); + return NS_FAILED(rv) || remainingOps <= 0; + }); + } + } + if (aPhase == sFastShutdownPhase) { + StopLateWriteChecks(); + RecordShutdownEndTimeStamp(); + MaybeDoRestart(); + + profiler_shutdown(IsFastShutdown::Yes); + + DoImmediateExit(sExitCode); + } else if (aPhase == sLateWriteChecksPhase) { +#ifdef XP_MACOSX + OnlyReportDirtyWrites(); +#endif /* XP_MACOSX */ + BeginLateWriteChecks(); + } +} + +void AppShutdown::OnShutdownConfirmed() { + // If we're restarting, we need to save environment variables correctly + // while everything is still alive to do so. + if (sShutdownMode == AppShutdownMode::Restart) { + nsCOMPtr profD; + nsCOMPtr profLD; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profD)); + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, + getter_AddRefs(profLD)); +#ifdef XP_WIN + sSavedProfDEnvVar = CopyPathIntoNewWCString(profD); + sSavedProfLDEnvVar = CopyPathIntoNewWCString(profLD); +#else + nsAutoCString profDStr; + profD->GetNativePath(profDStr); + sSavedProfDEnvVar = + Smprintf("XRE_PROFILE_PATH=%s", profDStr.get()).release(); + nsAutoCString profLDStr; + profLD->GetNativePath(profLDStr); + sSavedProfLDEnvVar = + Smprintf("XRE_PROFILE_LOCAL_PATH=%s", profLDStr.get()).release(); +#endif + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfDEnvVar); + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(sSavedProfLDEnvVar); + } +} + +void AppShutdown::DoImmediateExit(int aExitCode) { +#ifdef XP_WIN + HANDLE process = ::GetCurrentProcess(); + if (::TerminateProcess(process, aExitCode)) { + ::WaitForSingleObject(process, INFINITE); + } + MOZ_CRASH("TerminateProcess failed."); +#else + _exit(aExitCode); +#endif +} + +bool AppShutdown::IsRestarting() { + return sShutdownMode == AppShutdownMode::Restart; +} + +void AppShutdown::AnnotateShutdownReason(AppShutdownReason aReason) { + auto key = CrashReporter::Annotation::ShutdownReason; + nsCString reasonStr; + switch (aReason) { + case AppShutdownReason::AppClose: + reasonStr = "AppClose"_ns; + break; + case AppShutdownReason::AppRestart: + reasonStr = "AppRestart"_ns; + break; + case AppShutdownReason::OSForceClose: + reasonStr = "OSForceClose"_ns; + break; + case AppShutdownReason::OSSessionEnd: + reasonStr = "OSSessionEnd"_ns; + break; + case AppShutdownReason::OSShutdown: + reasonStr = "OSShutdown"_ns; + break; + case AppShutdownReason::WinUnexpectedMozQuit: + reasonStr = "WinUnexpectedMozQuit"_ns; + break; + default: + MOZ_ASSERT_UNREACHABLE("We should know the given reason for shutdown."); + reasonStr = "Unknown"_ns; + break; + } + CrashReporter::AnnotateCrashReport(key, reasonStr); +} + +#ifdef DEBUG +static bool sNotifyingShutdownObservers = false; +static bool sAdvancingShutdownPhase = false; + +bool AppShutdown::IsNoOrLegalShutdownTopic(const char* aTopic) { + if (!XRE_IsParentProcess()) { + // Until we know what to do with AppShutdown for child processes, + // we ignore them for now. See bug 1697745. + return true; + } + ShutdownPhase phase = GetShutdownPhaseFromTopic(aTopic); + return phase == ShutdownPhase::NotInShutdown || + (sNotifyingShutdownObservers && phase == sCurrentShutdownPhase); +} +#endif + +void AppShutdown::AdvanceShutdownPhaseInternal( + ShutdownPhase aPhase, bool doNotify, const char16_t* aNotificationData, + const nsCOMPtr& aNotificationSubject) { + AssertIsOnMainThread(); +#ifdef DEBUG + // Prevent us from re-entrance + MOZ_ASSERT(!sAdvancingShutdownPhase); + sAdvancingShutdownPhase = true; + auto exit = MakeScopeExit([] { sAdvancingShutdownPhase = false; }); +#endif + + // We ensure that we can move only forward. We cannot + // MOZ_ASSERT here as there are some tests that fire + // notifications out of shutdown order. + // See for example test_sss_sanitizeOnShutdown.js + if (sCurrentShutdownPhase >= aPhase) { + return; + } + + nsCOMPtr thread = do_GetCurrentThread(); + + // AppShutdownConfirmed is special in some ways as + // - we can be called on top of a nested event loop (and it is the phase for + // which SpinEventLoopUntilOrQuit breaks, so we want to return soon) + // - we can be called from a sync marionette function that wants immediate + // feedback, too + // - in general, AppShutdownConfirmed will fire the "quit-application" + // notification which in turn will cause an event to be dispatched that + // runs all the rest of our shutdown sequence which we do not want to be + // processed on top of the running event. + // Thus we never do any NS_ProcessPendingEvents for it. + bool mayProcessPending = (aPhase > ShutdownPhase::AppShutdownConfirmed); + + // Give runnables dispatched between two calls to AdvanceShutdownPhase + // a chance to run before actually advancing the phase. As we excluded + // AppShutdownConfirmed above we can be sure that the processing is + // covered by the terminator's timer of the previous phase during normal + // shutdown (except out-of-order calls from some test). + // Note that this affects only main thread runnables, such that the correct + // way of ensuring shutdown processing remains to have an async shutdown + // blocker. + if (mayProcessPending && thread) { + NS_ProcessPendingEvents(thread); + } + + // From now on any IsInOrBeyond checks will find the new phase set. + sCurrentShutdownPhase = aPhase; + +#ifndef ANDROID + if (sTerminator) { + sTerminator->AdvancePhase(aPhase); + } +#endif + + AppShutdown::MaybeFastShutdown(aPhase); + + // This will null out the gathered pointers for this phase synchronously. + // Note that we keep the old order here to avoid breakage, so be aware that + // the notifications fired below will find these already cleared in case + // you expected the opposite. + mozilla::KillClearOnShutdown(aPhase); + + // Empty our MT event queue to process any side effects thereof. + if (mayProcessPending && thread) { + NS_ProcessPendingEvents(thread); + } + + if (doNotify) { + const char* aTopic = AppShutdown::GetObserverKey(aPhase); + if (aTopic) { + nsCOMPtr obsService = + mozilla::services::GetObserverService(); + if (obsService) { +#ifdef DEBUG + sNotifyingShutdownObservers = true; + auto reset = MakeScopeExit([] { sNotifyingShutdownObservers = false; }); +#endif + obsService->NotifyObservers(aNotificationSubject, aTopic, + aNotificationData); + // Empty our MT event queue again after the notification has finished + if (mayProcessPending && thread) { + NS_ProcessPendingEvents(thread); + } + } + } + } +} + +/** + * XXX: Before tackling bug 1697745 we need the + * possibility to advance the phase without notification + * in the content process. + */ +void AppShutdown::AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase) { + AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ false, nullptr, nullptr); +} + +void AppShutdown::AdvanceShutdownPhase( + ShutdownPhase aPhase, const char16_t* aNotificationData, + const nsCOMPtr& aNotificationSubject) { + AdvanceShutdownPhaseInternal(aPhase, /* doNotify */ true, aNotificationData, + aNotificationSubject); +} + +ShutdownPhase AppShutdown::GetShutdownPhaseFromTopic(const char* aTopic) { + for (size_t i = 0; i < ArrayLength(sPhaseObserverKeys); ++i) { + if (sPhaseObserverKeys[i] && !strcmp(sPhaseObserverKeys[i], aTopic)) { + return static_cast(i); + } + } + return ShutdownPhase::NotInShutdown; +} + +} // namespace mozilla diff --git a/xpcom/base/AppShutdown.h b/xpcom/base/AppShutdown.h new file mode 100644 index 0000000000..6056338900 --- /dev/null +++ b/xpcom/base/AppShutdown.h @@ -0,0 +1,152 @@ +/* -*- 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/. */ + +#ifndef AppShutdown_h +#define AppShutdown_h + +#include +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "ShutdownPhase.h" + +namespace mozilla { + +enum class AppShutdownMode { + Normal, + Restart, +}; + +enum class AppShutdownReason { + // No reason. + Unknown, + // Normal application shutdown. + AppClose, + // The application wants to restart. + AppRestart, + // The OS is force closing us. + OSForceClose, + // The user is logging off from the OS session, the system may stay alive. + OSSessionEnd, + // The system is shutting down (and maybe restarting). + OSShutdown, + // We unexpectedly received MOZ_WM_APP_QUIT, see bug 1827807. + WinUnexpectedMozQuit, +}; + +class AppShutdown { + public: + static ShutdownPhase GetCurrentShutdownPhase(); + static bool IsInOrBeyond(ShutdownPhase aPhase); + + /** + * Returns the current exit code that the process will be terminated with. + */ + static int GetExitCode(); + + /** + * Save environment variables that we might need if the app initiates a + * restart later in its lifecycle. + */ + static void SaveEnvVarsForPotentialRestart(); + + /** + * Init the shutdown with the requested shutdown mode, exit code and optional + * a reason (if missing it will be derived from aMode). + */ + static void Init(AppShutdownMode aMode, int aExitCode, + AppShutdownReason aReason); + + /** + * Confirm that we are in fact going to be shutting down. + */ + static void OnShutdownConfirmed(); + + /** + * If we've attempted to initiate a restart, this call will set up the + * necessary environment variables and launch the new process. + */ + static void MaybeDoRestart(); + + /** + * The _exit() call is not a safe way to terminate your own process on + * Windows, because _exit runs DLL detach callbacks which run static + * destructors for xul.dll. + * + * This method terminates the current process without those issues. + * + * Optionally a custom exit code can be supplied. + */ + static void DoImmediateExit(int aExitCode = 0); + + /** + * True if the application is currently attempting to shut down in order to + * restart. + */ + static bool IsRestarting(); + + /** + * Wrapper for shutdown notifications that informs the terminator before + * we notify other observers. Calls MaybeFastShutdown. + */ + static void AdvanceShutdownPhase( + ShutdownPhase aPhase, const char16_t* aNotificationData = nullptr, + const nsCOMPtr& aNotificationSubject = + nsCOMPtr(nullptr)); + + /** + * XXX: Before tackling bug 1697745 we need the + * possibility to advance the phase without notification + * in the content process. + */ + static void AdvanceShutdownPhaseWithoutNotify(ShutdownPhase aPhase); + + /** + * Map shutdown phase to observer key + */ + static const char* GetObserverKey(ShutdownPhase aPhase); + + /** + * Map shutdown phase to readable name + */ + static const char* GetShutdownPhaseName(ShutdownPhase aPhase); + + /** + * Map observer topic key to shutdown phase + */ + static ShutdownPhase GetShutdownPhaseFromTopic(const char* aTopic); + +#ifdef DEBUG + /** + * Check, if we are allowed to send a shutdown notification. + * Shutdown specific topics are only allowed during calls to + * AdvanceShutdownPhase itself. + */ + static bool IsNoOrLegalShutdownTopic(const char* aTopic); +#endif + + private: + /** + * Set the shutdown reason annotation. + */ + static void AnnotateShutdownReason(AppShutdownReason aReason); + + /** + * This will perform a fast shutdown via _exit(0) or similar if the user's + * prefs are configured to do so at this phase. + */ + static void MaybeFastShutdown(ShutdownPhase aPhase); + + /** + * Internal helper function, uses MaybeFastShutdown. + */ + static void AdvanceShutdownPhaseInternal( + ShutdownPhase aPhase, bool doNotify, const char16_t* aNotificationData, + const nsCOMPtr& aNotificationSubject); +}; + +} // namespace mozilla + +#endif // AppShutdown_h diff --git a/xpcom/base/AutoRestore.h b/xpcom/base/AutoRestore.h new file mode 100644 index 0000000000..fc585da14b --- /dev/null +++ b/xpcom/base/AutoRestore.h @@ -0,0 +1,42 @@ +/* -*- 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/. */ + +/* functions for restoring saved values at the end of a C++ scope */ + +#ifndef mozilla_AutoRestore_h_ +#define mozilla_AutoRestore_h_ + +#include "mozilla/Attributes.h" // MOZ_STACK_CLASS + +namespace mozilla { + +/** + * Save the current value of a variable and restore it when the object + * goes out of scope. For example: + * { + * AutoRestore savePainting(mIsPainting); + * mIsPainting = true; + * + * // ... your code here ... + * + * // mIsPainting is reset to its old value at the end of this block + * } + */ +template +class MOZ_RAII AutoRestore { + private: + T& mLocation; + T mValue; + + public: + explicit AutoRestore(T& aValue) : mLocation(aValue), mValue(aValue) {} + ~AutoRestore() { mLocation = mValue; } + T SavedValue() const { return mValue; } +}; + +} // namespace mozilla + +#endif /* !defined(mozilla_AutoRestore_h_) */ diff --git a/xpcom/base/AvailableMemoryTracker.cpp b/xpcom/base/AvailableMemoryTracker.cpp new file mode 100644 index 0000000000..5e75f26ac6 --- /dev/null +++ b/xpcom/base/AvailableMemoryTracker.cpp @@ -0,0 +1,194 @@ +/* -*- 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/AvailableMemoryTracker.h" + +#if defined(XP_WIN) +# include "mozilla/WindowsVersion.h" +# include "nsIMemoryReporter.h" +#endif + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIRunnable.h" +#include "nsISupports.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +#include "mozilla/Mutex.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" + +#if defined(MOZ_MEMORY) +# include "mozmemory.h" +#endif // MOZ_MEMORY + +using namespace mozilla; + +Atomic sNumLowPhysicalMemEvents; + +namespace { + +#if defined(XP_WIN) + +# if (NTDDI_VERSION < NTDDI_WINBLUE) || \ + (NTDDI_VERSION == NTDDI_WINBLUE && !defined(WINBLUE_KBSPRING14)) +// Definitions for heap optimization that require the Windows SDK to target the +// Windows 8.1 Update +static const HEAP_INFORMATION_CLASS HeapOptimizeResources = + static_cast(3); + +static const DWORD HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION = 1; + +typedef struct _HEAP_OPTIMIZE_RESOURCES_INFORMATION { + DWORD Version; + DWORD Flags; +} HEAP_OPTIMIZE_RESOURCES_INFORMATION, *PHEAP_OPTIMIZE_RESOURCES_INFORMATION; +# endif + +static int64_t LowMemoryEventsPhysicalDistinguishedAmount() { + return sNumLowPhysicalMemEvents; +} + +class LowEventsReporter final : public nsIMemoryReporter { + ~LowEventsReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + // clang-format off + MOZ_COLLECT_REPORT( + "low-memory-events/physical", KIND_OTHER, UNITS_COUNT_CUMULATIVE, + LowMemoryEventsPhysicalDistinguishedAmount(), +"Number of low-physical-memory events fired since startup. We fire such an " +"event when a windows low memory resource notification is signaled. The " +"machine will start to page if it runs out of physical memory. This may " +"cause it to run slowly, but it shouldn't cause it to crash."); + // clang-format on + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(LowEventsReporter, nsIMemoryReporter) + +#endif // defined(XP_WIN) + +/** + * This runnable is executed in response to a memory-pressure event; we spin + * the event-loop when receiving the memory-pressure event in the hope that + * other observers will synchronously free some memory that we'll be able to + * purge here. + */ +class nsJemallocFreeDirtyPagesRunnable final : public Runnable { + ~nsJemallocFreeDirtyPagesRunnable() = default; + +#if defined(XP_WIN) + void OptimizeSystemHeap(); +#endif + + public: + NS_DECL_NSIRUNNABLE + + nsJemallocFreeDirtyPagesRunnable() + : Runnable("nsJemallocFreeDirtyPagesRunnable") {} +}; + +NS_IMETHODIMP +nsJemallocFreeDirtyPagesRunnable::Run() { + MOZ_ASSERT(NS_IsMainThread()); + +#if defined(MOZ_MEMORY) + jemalloc_free_dirty_pages(); +#endif + +#if defined(XP_WIN) + OptimizeSystemHeap(); +#endif + + return NS_OK; +} + +#if defined(XP_WIN) +void nsJemallocFreeDirtyPagesRunnable::OptimizeSystemHeap() { + // HeapSetInformation exists prior to Windows 8.1, but the + // HeapOptimizeResources information class does not. + if (IsWin8Point1OrLater()) { + HEAP_OPTIMIZE_RESOURCES_INFORMATION heapOptInfo = { + HEAP_OPTIMIZE_RESOURCES_CURRENT_VERSION}; + + ::HeapSetInformation(nullptr, HeapOptimizeResources, &heapOptInfo, + sizeof(heapOptInfo)); + } +} +#endif // defined(XP_WIN) + +/** + * The memory pressure watcher is used for listening to memory-pressure events + * and reacting upon them. We use one instance per process currently only for + * cleaning up dirty unused pages held by jemalloc. + */ +class nsMemoryPressureWatcher final : public nsIObserver { + ~nsMemoryPressureWatcher() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + void Init(); +}; + +NS_IMPL_ISUPPORTS(nsMemoryPressureWatcher, nsIObserver) + +/** + * Initialize and subscribe to the memory-pressure events. We subscribe to the + * observer service in this method and not in the constructor because we need + * to hold a strong reference to 'this' before calling the observer service. + */ +void nsMemoryPressureWatcher::Init() { + nsCOMPtr os = services::GetObserverService(); + + if (os) { + os->AddObserver(this, "memory-pressure", /* ownsWeak */ false); + } +} + +/** + * Reacts to all types of memory-pressure events, launches a runnable to + * free dirty pages held by jemalloc. + */ +NS_IMETHODIMP +nsMemoryPressureWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(!strcmp(aTopic, "memory-pressure"), "Unknown topic"); + + nsCOMPtr runnable = new nsJemallocFreeDirtyPagesRunnable(); + + NS_DispatchToMainThread(runnable); + + return NS_OK; +} + +} // namespace + +namespace mozilla { +namespace AvailableMemoryTracker { + +void Init() { + // The watchers are held alive by the observer service. + RefPtr watcher = new nsMemoryPressureWatcher(); + watcher->Init(); + +#if defined(XP_WIN) + RegisterLowMemoryEventsPhysicalDistinguishedAmount( + LowMemoryEventsPhysicalDistinguishedAmount); +#endif // defined(XP_WIN) +} + +} // namespace AvailableMemoryTracker +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryTracker.h b/xpcom/base/AvailableMemoryTracker.h new file mode 100644 index 0000000000..3d2a048cb3 --- /dev/null +++ b/xpcom/base/AvailableMemoryTracker.h @@ -0,0 +1,25 @@ +/* -*- 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/. */ + +#ifndef mozilla_AvailableMemoryTracker_h +#define mozilla_AvailableMemoryTracker_h + +namespace mozilla { +namespace AvailableMemoryTracker { + +// The AvailableMemoryTracker launches a memory pressure watcher on all +// platforms to react to low-memory situations and on Windows it implements +// the full functionality used to monitor how much memory is available. +// +// Init() requires the observer service to be already available so cannot be +// called too early during initialization. + +void Init(); + +} // namespace AvailableMemoryTracker +} // namespace mozilla + +#endif // ifndef mozilla_AvailableMemoryTracker_h diff --git a/xpcom/base/AvailableMemoryWatcher.cpp b/xpcom/base/AvailableMemoryWatcher.cpp new file mode 100644 index 0000000000..44e0f7d220 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcher.cpp @@ -0,0 +1,180 @@ +/* -*- 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 "AvailableMemoryWatcher.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "nsExceptionHandler.h" +#include "nsMemoryPressure.h" +#include "nsXULAppAPI.h" + +namespace mozilla { + +// Use this class as the initial value of +// nsAvailableMemoryWatcherBase::mCallback until RegisterCallback() is called +// so that nsAvailableMemoryWatcherBase does not have to check if its callback +// object is valid or not. +class NullTabUnloader final : public nsITabUnloader { + ~NullTabUnloader() = default; + + public: + NullTabUnloader() = default; + + NS_DECL_ISUPPORTS + NS_DECL_NSITABUNLOADER +}; + +NS_IMPL_ISUPPORTS(NullTabUnloader, nsITabUnloader) + +NS_IMETHODIMP NullTabUnloader::UnloadTabAsync() { + return NS_ERROR_NOT_IMPLEMENTED; +} + +StaticRefPtr + nsAvailableMemoryWatcherBase::sSingleton; + +/*static*/ +already_AddRefed +nsAvailableMemoryWatcherBase::GetSingleton() { + if (!sSingleton) { + sSingleton = CreateAvailableMemoryWatcher(); + ClearOnShutdown(&sSingleton); + } + + return do_AddRef(sSingleton); +} + +NS_IMPL_ISUPPORTS(nsAvailableMemoryWatcherBase, nsIAvailableMemoryWatcherBase); + +nsAvailableMemoryWatcherBase::nsAvailableMemoryWatcherBase() + : mNumOfTabUnloading(0), + mNumOfMemoryPressure(0), + mTabUnloader(new NullTabUnloader), + mInteracting(false) { + MOZ_ASSERT(XRE_IsParentProcess(), + "Watching memory only in the main process."); +} + +const char* const nsAvailableMemoryWatcherBase::kObserverTopics[] = { + // Use this shutdown phase to make sure the instance is destroyed in GTest + "xpcom-shutdown", + "user-interaction-active", + "user-interaction-inactive", +}; + +nsresult nsAvailableMemoryWatcherBase::Init() { + MOZ_ASSERT(NS_IsMainThread(), + "nsAvailableMemoryWatcherBase needs to be initialized " + "in the main thread."); + + if (mObserverSvc) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + mObserverSvc = services::GetObserverService(); + MOZ_ASSERT(mObserverSvc); + + for (auto topic : kObserverTopics) { + nsresult rv = mObserverSvc->AddObserver(this, topic, + /* ownsWeak */ false); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +void nsAvailableMemoryWatcherBase::Shutdown() { + for (auto topic : kObserverTopics) { + mObserverSvc->RemoveObserver(this, topic); + } +} + +NS_IMETHODIMP +nsAvailableMemoryWatcherBase::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (strcmp(aTopic, "xpcom-shutdown") == 0) { + Shutdown(); + } else if (strcmp(aTopic, "user-interaction-inactive") == 0) { + mInteracting = false; +#ifdef MOZ_CRASHREPORTER + CrashReporter::SetInactiveStateStart(); +#endif + } else if (strcmp(aTopic, "user-interaction-active") == 0) { + mInteracting = true; +#ifdef MOZ_CRASHREPORTER + CrashReporter::ClearInactiveStateStart(); +#endif + } + return NS_OK; +} + +nsresult nsAvailableMemoryWatcherBase::RegisterTabUnloader( + nsITabUnloader* aTabUnloader) { + mTabUnloader = aTabUnloader; + return NS_OK; +} + +nsresult nsAvailableMemoryWatcherBase::OnUnloadAttemptCompleted( + nsresult aResult) { + switch (aResult) { + // A tab was unloaded successfully. + case NS_OK: + ++mNumOfTabUnloading; + break; + + // There was no unloadable tab. + case NS_ERROR_NOT_AVAILABLE: + ++mNumOfMemoryPressure; + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + break; + + // There was a pending task to unload a tab. + case NS_ERROR_ABORT: + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected aResult"); + break; + } + return NS_OK; +} + +void nsAvailableMemoryWatcherBase::UpdateLowMemoryTimeStamp() { + if (mLowMemoryStart.IsNull()) { + mLowMemoryStart = TimeStamp::NowLoRes(); + } +} + +void nsAvailableMemoryWatcherBase::RecordTelemetryEventOnHighMemory() { + Telemetry::SetEventRecordingEnabled("memory_watcher"_ns, true); + Telemetry::RecordEvent( + Telemetry::EventID::Memory_watcher_OnHighMemory_Stats, + Some(nsPrintfCString( + "%u,%u,%f", mNumOfTabUnloading, mNumOfMemoryPressure, + (TimeStamp::NowLoRes() - mLowMemoryStart).ToSeconds())), + Nothing()); + mNumOfTabUnloading = mNumOfMemoryPressure = 0; + mLowMemoryStart = TimeStamp(); +} + +// Define the fallback method for a platform for which a platform-specific +// CreateAvailableMemoryWatcher() is not defined. +#if defined(ANDROID) || \ + !defined(XP_WIN) && !defined(XP_DARWIN) && !defined(XP_LINUX) +already_AddRefed CreateAvailableMemoryWatcher() { + RefPtr instance(new nsAvailableMemoryWatcherBase); + return do_AddRef(instance); +} +#endif + +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryWatcher.h b/xpcom/base/AvailableMemoryWatcher.h new file mode 100644 index 0000000000..0468ad0fa3 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcher.h @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +#ifndef mozilla_AvailableMemoryWatcher_h +#define mozilla_AvailableMemoryWatcher_h + +#include "mozilla/ipc/CrashReporterHost.h" +#include "mozilla/UniquePtr.h" +#include "MemoryPressureLevelMac.h" +#include "nsCOMPtr.h" +#include "nsIAvailableMemoryWatcherBase.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" + +namespace mozilla { + +// This class implements a platform-independent part to watch the system's +// memory situation and invoke the registered callbacks when we detect +// a low-memory situation or a high-memory situation. +// The actual logic to monitor the memory status is implemented in a subclass +// of nsAvailableMemoryWatcherBase per platform. +class nsAvailableMemoryWatcherBase : public nsIAvailableMemoryWatcherBase, + public nsIObserver { + static StaticRefPtr sSingleton; + static const char* const kObserverTopics[]; + + TimeStamp mLowMemoryStart; + + protected: + uint32_t mNumOfTabUnloading; + uint32_t mNumOfMemoryPressure; + + nsCOMPtr mTabUnloader; + nsCOMPtr mObserverSvc; + // Do not change this value off the main thread. + bool mInteracting; + + virtual ~nsAvailableMemoryWatcherBase() = default; + virtual nsresult Init(); + void Shutdown(); + void UpdateLowMemoryTimeStamp(); + void RecordTelemetryEventOnHighMemory(); + + public: + static already_AddRefed GetSingleton(); + + nsAvailableMemoryWatcherBase(); + +#if defined(XP_DARWIN) + virtual void OnMemoryPressureChanged(MacMemoryPressureLevel aNewLevel){}; + virtual void AddChildAnnotations( + const UniquePtr& aCrashReporter){}; +#endif + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIAVAILABLEMEMORYWATCHERBASE + NS_DECL_NSIOBSERVER +}; + +// Method to create a platform-specific object +already_AddRefed CreateAvailableMemoryWatcher(); + +} // namespace mozilla + +#endif // ifndef mozilla_AvailableMemoryWatcher_h diff --git a/xpcom/base/AvailableMemoryWatcherLinux.cpp b/xpcom/base/AvailableMemoryWatcherLinux.cpp new file mode 100644 index 0000000000..d2be46c84d --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcherLinux.cpp @@ -0,0 +1,290 @@ +/* -*- 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 "AvailableMemoryWatcher.h" +#include "AvailableMemoryWatcherUtils.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/Unused.h" +#include "nsAppRunner.h" +#include "nsIObserverService.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsIThread.h" +#include "nsMemoryPressure.h" + +namespace mozilla { + +// Linux has no native low memory detection. This class creates a timer that +// polls for low memory and sends a low memory notification if it notices a +// memory pressure event. +class nsAvailableMemoryWatcher final : public nsITimerCallback, + public nsINamed, + public nsAvailableMemoryWatcherBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSIOBSERVER + NS_DECL_NSINAMED + + nsresult Init() override; + nsAvailableMemoryWatcher(); + + void HandleLowMemory(); + void MaybeHandleHighMemory(); + + private: + ~nsAvailableMemoryWatcher() = default; + void StartPolling(const MutexAutoLock&); + void StopPolling(const MutexAutoLock&); + void ShutDown(); + void UpdateCrashAnnotation(const MutexAutoLock&); + static bool IsMemoryLow(); + + nsCOMPtr mTimer MOZ_GUARDED_BY(mMutex); + nsCOMPtr mThread MOZ_GUARDED_BY(mMutex); + + bool mPolling MOZ_GUARDED_BY(mMutex); + bool mUnderMemoryPressure MOZ_GUARDED_BY(mMutex); + + // We might tell polling to start/stop from our polling thread + // or from the main thread during ::Observe(). + Mutex mMutex; + + // Polling interval to check for low memory. In high memory scenarios, + // default to 5000 ms between each check. + static const uint32_t kHighMemoryPollingIntervalMS = 5000; + + // Polling interval to check for low memory. Default to 1000 ms between each + // check. Use this interval when memory is low, + static const uint32_t kLowMemoryPollingIntervalMS = 1000; +}; + +// A modern version of linux should keep memory information in the +// /proc/meminfo path. +static const char* kMeminfoPath = "/proc/meminfo"; + +nsAvailableMemoryWatcher::nsAvailableMemoryWatcher() + : mPolling(false), + mUnderMemoryPressure(false), + mMutex("Memory Poller mutex") {} + +nsresult nsAvailableMemoryWatcher::Init() { + nsresult rv = nsAvailableMemoryWatcherBase::Init(); + if (NS_FAILED(rv)) { + return rv; + } + MutexAutoLock lock(mMutex); + mTimer = NS_NewTimer(); + nsCOMPtr thread; + // We have to make our own thread here instead of using the background pool, + // because some low memory scenarios can cause the background pool to fill. + rv = NS_NewNamedThread("MemoryPoller", getter_AddRefs(thread)); + if (NS_FAILED(rv)) { + NS_WARNING("Couldn't make a thread for nsAvailableMemoryWatcher."); + // In this scenario we can't poll for low memory, since we can't dispatch + // to our memory watcher thread. + return rv; + } + mThread = thread; + + // Set the crash annotation to its initial state. + UpdateCrashAnnotation(lock); + + StartPolling(lock); + + return NS_OK; +} + +already_AddRefed CreateAvailableMemoryWatcher() { + RefPtr watcher(new nsAvailableMemoryWatcher); + + if (NS_FAILED(watcher->Init())) { + return do_AddRef(new nsAvailableMemoryWatcherBase); + } + + return watcher.forget(); +} + +NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher, + nsAvailableMemoryWatcherBase, nsITimerCallback, + nsIObserver, nsINamed); + +void nsAvailableMemoryWatcher::StopPolling(const MutexAutoLock&) + MOZ_REQUIRES(mMutex) { + if (mPolling && mTimer) { + // stop dispatching memory checks to the thread. + mTimer->Cancel(); + mPolling = false; + } +} + +// Check /proc/meminfo for low memory. Largely C method for reading +// /proc/meminfo. +/* static */ +bool nsAvailableMemoryWatcher::IsMemoryLow() { + MemoryInfo memInfo{0, 0}; + bool aResult = false; + + nsresult rv = ReadMemoryFile(kMeminfoPath, memInfo); + + if (NS_FAILED(rv) || memInfo.memAvailable == 0) { + // If memAvailable cannot be found, then we are using an older system. + // We can't accurately poll on this. + return aResult; + } + unsigned long memoryAsPercentage = + (memInfo.memAvailable * 100) / memInfo.memTotal; + + if (memoryAsPercentage <= + StaticPrefs::browser_low_commit_space_threshold_percent() || + memInfo.memAvailable < + StaticPrefs::browser_low_commit_space_threshold_mb() * 1024) { + aResult = true; + } + + return aResult; +} + +void nsAvailableMemoryWatcher::ShutDown() { + nsCOMPtr thread; + { + MutexAutoLock lock(mMutex); + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + thread = mThread.forget(); + } + // thread->Shutdown() spins a nested event loop while waiting for the thread + // to end. But the thread might execute some previously dispatched event that + // wants to lock our mutex, too, before arriving at the shutdown event. + if (thread) { + thread->Shutdown(); + } +} + +// We will use this to poll for low memory. +NS_IMETHODIMP +nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) { + MutexAutoLock lock(mMutex); + if (!mThread) { + // If we've made it this far and there's no |mThread|, + // we might have failed to dispatch it for some reason. + MOZ_ASSERT(mThread); + return NS_ERROR_FAILURE; + } + nsresult rv = mThread->Dispatch( + NS_NewRunnableFunction("MemoryPoller", [self = RefPtr{this}]() { + if (self->IsMemoryLow()) { + self->HandleLowMemory(); + } else { + self->MaybeHandleHighMemory(); + } + })); + + if NS_FAILED (rv) { + NS_WARNING("Cannot dispatch memory polling event."); + } + return NS_OK; +} + +void nsAvailableMemoryWatcher::HandleLowMemory() { + MutexAutoLock lock(mMutex); + if (!mTimer) { + // We have been shut down from outside while in flight. + return; + } + if (!mUnderMemoryPressure) { + mUnderMemoryPressure = true; + UpdateCrashAnnotation(lock); + // Poll more frequently under memory pressure. + StartPolling(lock); + } + UpdateLowMemoryTimeStamp(); + // We handle low memory offthread, but we want to unload + // tabs only from the main thread, so we will dispatch this + // back to the main thread. + NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsAvailableMemoryWatcher::OnLowMemory", + [self = RefPtr{this}]() { self->mTabUnloader->UnloadTabAsync(); })); +} + +void nsAvailableMemoryWatcher::UpdateCrashAnnotation(const MutexAutoLock&) + MOZ_REQUIRES(mMutex) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::LinuxUnderMemoryPressure, + mUnderMemoryPressure); +} + +// If memory is not low, we may need to dispatch an +// event for it if we have been under memory pressure. +// We can also adjust our polling interval. +void nsAvailableMemoryWatcher::MaybeHandleHighMemory() { + MutexAutoLock lock(mMutex); + if (!mTimer) { + // We have been shut down from outside while in flight. + return; + } + if (mUnderMemoryPressure) { + RecordTelemetryEventOnHighMemory(); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure); + mUnderMemoryPressure = false; + UpdateCrashAnnotation(lock); + } + StartPolling(lock); +} + +// When we change the polling interval, we will need to restart the timer +// on the new interval. +void nsAvailableMemoryWatcher::StartPolling(const MutexAutoLock& aLock) + MOZ_REQUIRES(mMutex) { + uint32_t pollingInterval = mUnderMemoryPressure + ? kLowMemoryPollingIntervalMS + : kHighMemoryPollingIntervalMS; + if (!mPolling) { + // Restart the timer with the new interval if it has stopped. + // For testing, use a small polling interval. + if (NS_SUCCEEDED( + mTimer->InitWithCallback(this, gIsGtest ? 10 : pollingInterval, + nsITimer::TYPE_REPEATING_SLACK))) { + mPolling = true; + } + } else { + mTimer->SetDelay(gIsGtest ? 10 : pollingInterval); + } +} + +// Observe events for shutting down and starting/stopping the timer. +NS_IMETHODIMP +nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData); + if (NS_FAILED(rv)) { + return rv; + } + + if (strcmp(aTopic, "xpcom-shutdown") == 0) { + ShutDown(); + } else { + MutexAutoLock lock(mMutex); + if (mTimer) { + if (strcmp(aTopic, "user-interaction-active") == 0) { + StartPolling(lock); + } else if (strcmp(aTopic, "user-interaction-inactive") == 0) { + StopPolling(lock); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAvailableMemoryWatcher::GetName(nsACString& aName) { + aName.AssignLiteral("nsAvailableMemoryWatcher"); + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryWatcherMac.cpp b/xpcom/base/AvailableMemoryWatcherMac.cpp new file mode 100644 index 0000000000..452b32a149 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcherMac.cpp @@ -0,0 +1,625 @@ +/* -*- 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 +#include +#include + +#include "AvailableMemoryWatcher.h" +#include "Logging.h" +#include "mozilla/Preferences.h" +#include "nsICrashReporter.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" +#include "nsPrintfCString.h" + +#define MP_LOG(...) MOZ_LOG(gMPLog, mozilla::LogLevel::Debug, (__VA_ARGS__)) +static mozilla::LazyLogModule gMPLog("MemoryPressure"); + +namespace mozilla { + +/* + * The Mac AvailableMemoryWatcher works as follows. When the OS memory pressure + * level changes on macOS, nsAvailableMemoryWatcher::OnMemoryPressureChanged() + * is called with the new memory pressure level. The level is represented in + * Gecko by a MacMemoryPressureLevel instance and represents the states of + * normal, warning, or critical which correspond to the native levels. When the + * browser launches, the initial level is determined using a sysctl. Which + * actions are taken in the browser in response to memory pressure, and the + * level (warning or critical) which trigger the reponse is configurable with + * prefs to make it easier to perform experiments to study how the response + * affects the user experience. + * + * By default, the browser responds by attempting to reduce memory use when the + * OS transitions to the critical level and while it stays in the critical + * level. i.e., "critical" OS memory pressure is the default threshold for the + * low memory response. Setting pref "browser.lowMemoryResponseOnWarn" to true + * changes the memory response to occur at the "warning" level which is less + * severe than "critical". When entering the critical level, we begin polling + * the memory pressure level every 'n' milliseconds (specified via the pref + * "browser.lowMemoryPollingIntervalMS"). Each time the poller wakes up and + * finds the OS still under memory pressure, the low memory response is + * executed. + * + * By default, the memory pressure response is, in order, to + * 1) call nsITabUnloader::UnloadTabAsync(), + * 2) if no tabs could be unloaded, issue a Gecko + * MemoryPressureState::LowMemory notification. + * The response can be changed via the pref "browser.lowMemoryResponseMask" to + * limit the actions to only tab unloading or Gecko memory pressure + * notifications. + * + * Polling occurs on the main thread because, at each polling interval, we + * call into the tab unloader which requires being on the main thread. + * Polling only occurs while under OS memory pressure at the critical (by + * default) level. + */ +class nsAvailableMemoryWatcher final : public nsITimerCallback, + public nsINamed, + public nsAvailableMemoryWatcherBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + nsAvailableMemoryWatcher(); + nsresult Init() override; + + void OnMemoryPressureChanged(MacMemoryPressureLevel aLevel) override; + void AddChildAnnotations( + const UniquePtr& aCrashReporter) override; + + private: + ~nsAvailableMemoryWatcher(){}; + + void OnMemoryPressureChangedInternal(MacMemoryPressureLevel aNewLevel, + bool aIsInitialLevel); + + // Override OnUnloadAttemptCompleted() so that we can control whether + // or not a Gecko memory-pressure event is sent after a tab unload attempt. + // This method is called externally by the tab unloader after a tab unload + // attempt. It is used internally when tab unloading is disabled in + // mResponseMask. + nsresult OnUnloadAttemptCompleted(nsresult aResult) override; + + void OnShutdown(); + void OnPrefChange(); + + void InitParentAnnotations(); + void UpdateParentAnnotations(); + + void AddParentAnnotation(CrashReporter::Annotation aAnnotation, + nsAutoCString aString) { + CrashReporter::AnnotateCrashReport(aAnnotation, aString); + } + void AddParentAnnotation(CrashReporter::Annotation aAnnotation, + uint32_t aData) { + CrashReporter::AnnotateCrashReport(aAnnotation, aData); + } + + void LowMemoryResponse(); + void StartPolling(); + void StopPolling(); + void RestartPolling(); + inline bool IsPolling() { return mTimer; } + + void ReadSysctls(); + + // This enum represents the allowed values for the pref that controls + // the low memory response - "browser.lowMemoryResponseMask". Specifically, + // whether or not we unload tabs and/or issue the Gecko "memory-pressure" + // internal notification. For tab unloading, the pref + // "browser.tabs.unloadOnLowMemory" must also be set. + enum ResponseMask { + eNone = 0x0, + eTabUnload = 0x1, + eInternalMemoryPressure = 0x2, + eAll = 0x3, + }; + static constexpr char kResponseMask[] = "browser.lowMemoryResponseMask"; + static const uint32_t kResponseMaskDefault; + static const uint32_t kResponseMaskMax; + + // Pref for controlling how often we wake up during an OS memory pressure + // time period. At each wakeup, we unload tabs and issue the Gecko + // "memory-pressure" internal notification. When not under OS memory pressure, + // polling is disabled. + static constexpr char kPollingIntervalMS[] = + "browser.lowMemoryPollingIntervalMS"; + static const uint32_t kPollingIntervalMaxMS; + static const uint32_t kPollingIntervalMinMS; + static const uint32_t kPollingIntervalDefaultMS; + + static constexpr char kResponseOnWarn[] = "browser.lowMemoryResponseOnWarn"; + static const bool kResponseLevelOnWarnDefault = false; + + // Init has been called. + bool mInitialized; + + // The memory pressure reported to the application by macOS. + MacMemoryPressureLevel mLevel; + + // The OS memory pressure level that triggers the response. + MacMemoryPressureLevel mResponseLevel; + + // The value of the kern.memorystatus_vm_pressure_level sysctl. The OS + // notifies the application when the memory pressure level changes, + // but the sysctl value can be read at any time. Unofficially, the sysctl + // value corresponds to the OS memory pressure level with 4=>critical, + // 2=>warning, and 1=>normal (values from kernel event.h file). + uint32_t mLevelSysctl; + static const int kSysctlLevelNormal = 0x1; + static const int kSysctlLevelWarning = 0x2; + static const int kSysctlLevelCritical = 0x4; + + // The value of the kern.memorystatus_level sysctl. Unofficially, + // this is the percentage of available memory. (Also readable + // via the undocumented memorystatus_get_level syscall.) + int mAvailMemSysctl; + + // The string representation of `mLevel`. i.e., normal, warning, or critical. + // Set to "unset" until a memory pressure change is reported to the process + // by the OS. + nsAutoCString mLevelStr; + + // Timestamps for memory pressure level changes. Specifically, the Unix + // time in string form. Saved as Unix time to allow comparisons with + // the crash time. + nsAutoCString mNormalTimeStr; + nsAutoCString mWarningTimeStr; + nsAutoCString mCriticalTimeStr; + + nsCOMPtr mTimer; // non-null indicates the timer is active + + // Saved pref values. + uint32_t mPollingInterval; + uint32_t mResponseMask; +}; + +const uint32_t nsAvailableMemoryWatcher::kResponseMaskDefault = + ResponseMask::eAll; +const uint32_t nsAvailableMemoryWatcher::kResponseMaskMax = ResponseMask::eAll; + +// 10 seconds +const uint32_t nsAvailableMemoryWatcher::kPollingIntervalDefaultMS = 10'000; +// 10 minutes +const uint32_t nsAvailableMemoryWatcher::kPollingIntervalMaxMS = 600'000; +// 100 milliseconds +const uint32_t nsAvailableMemoryWatcher::kPollingIntervalMinMS = 100; + +NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher, + nsAvailableMemoryWatcherBase, nsIObserver, + nsITimerCallback, nsINamed) + +nsAvailableMemoryWatcher::nsAvailableMemoryWatcher() + : mInitialized(false), + mLevel(MacMemoryPressureLevel::Value::eUnset), + mResponseLevel(MacMemoryPressureLevel::Value::eCritical), + mLevelSysctl(0xFFFFFFFF), + mAvailMemSysctl(-1), + mLevelStr("Unset"), + mNormalTimeStr("Unset"), + mWarningTimeStr("Unset"), + mCriticalTimeStr("Unset"), + mPollingInterval(0), + mResponseMask(ResponseMask::eAll) {} + +nsresult nsAvailableMemoryWatcher::Init() { + nsresult rv = nsAvailableMemoryWatcherBase::Init(); + if (NS_FAILED(rv)) { + return rv; + } + + // Users of nsAvailableMemoryWatcher should use + // nsAvailableMemoryWatcherBase::GetSingleton() and not call Init directly. + MOZ_ASSERT(!mInitialized); + if (mInitialized) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // Read polling frequency pref + mPollingInterval = + Preferences::GetUint(kPollingIntervalMS, kPollingIntervalDefaultMS); + mPollingInterval = std::clamp(mPollingInterval, kPollingIntervalMinMS, + kPollingIntervalMaxMS); + + // Read response bitmask pref which (along with the main tab unloading + // preference) controls whether or not tab unloading and Gecko (internal) + // memory pressure notifications will be sent. The main tab unloading + // preference must also be enabled for tab unloading to occur. + mResponseMask = Preferences::GetUint(kResponseMask, kResponseMaskDefault); + if (mResponseMask > kResponseMaskMax) { + mResponseMask = kResponseMaskMax; + } + + // Read response level pref + if (Preferences::GetBool(kResponseOnWarn, kResponseLevelOnWarnDefault)) { + mResponseLevel = MacMemoryPressureLevel::Value::eWarning; + } else { + mResponseLevel = MacMemoryPressureLevel::Value::eCritical; + } + + ReadSysctls(); + MP_LOG("Initial memory pressure sysctl: %d", mLevelSysctl); + MP_LOG("Initial available memory sysctl: %d", mAvailMemSysctl); + + // Set the initial state of all annotations for parent crash reports. + // Content process crash reports are set when a crash occurs and + // AddChildAnnotations() is called. + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressure, mLevelStr); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressureNormalTime, mNormalTimeStr); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressureWarningTime, mWarningTimeStr); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressureCriticalTime, + mCriticalTimeStr); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacMemoryPressureSysctl, mLevelSysctl); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MacAvailableMemorySysctl, mAvailMemSysctl); + + // To support running experiments, handle pref + // changes without requiring a browser restart. + rv = Preferences::AddStrongObserver(this, kResponseMask); + if (NS_FAILED(rv)) { + NS_WARNING( + nsPrintfCString("Failed to add %s observer", kResponseMask).get()); + } + rv = Preferences::AddStrongObserver(this, kPollingIntervalMS); + if (NS_FAILED(rv)) { + NS_WARNING( + nsPrintfCString("Failed to add %s observer", kPollingIntervalMS).get()); + } + rv = Preferences::AddStrongObserver(this, kResponseOnWarn); + if (NS_FAILED(rv)) { + NS_WARNING( + nsPrintfCString("Failed to add %s observer", kResponseOnWarn).get()); + } + + // Use the memory pressure sysctl to initialize our memory pressure state. + MacMemoryPressureLevel initialLevel; + switch (mLevelSysctl) { + case kSysctlLevelNormal: + initialLevel = MacMemoryPressureLevel::Value::eNormal; + break; + case kSysctlLevelWarning: + initialLevel = MacMemoryPressureLevel::Value::eWarning; + break; + case kSysctlLevelCritical: + initialLevel = MacMemoryPressureLevel::Value::eCritical; + break; + default: + initialLevel = MacMemoryPressureLevel::Value::eUnexpected; + } + + OnMemoryPressureChangedInternal(initialLevel, /* aIsInitialLevel */ true); + mInitialized = true; + return NS_OK; +} + +already_AddRefed CreateAvailableMemoryWatcher() { + // Users of nsAvailableMemoryWatcher should use + // nsAvailableMemoryWatcherBase::GetSingleton(). + RefPtr watcher(new nsAvailableMemoryWatcher()); + watcher->Init(); + return watcher.forget(); +} + +// Update the memory pressure level, level change timestamps, and sysctl +// level crash report annotations. +void nsAvailableMemoryWatcher::UpdateParentAnnotations() { + // Generate a string representation of the current Unix time. + time_t timeChanged = time(NULL); + nsAutoCString timeChangedString; + timeChangedString = + nsPrintfCString("%" PRIu64, static_cast(timeChanged)); + + nsAutoCString pressureLevelString; + Maybe pressureLevelKey; + + switch (mLevel.GetValue()) { + case MacMemoryPressureLevel::Value::eNormal: + mNormalTimeStr = timeChangedString; + pressureLevelString = "Normal"; + pressureLevelKey.emplace( + CrashReporter::Annotation::MacMemoryPressureNormalTime); + break; + case MacMemoryPressureLevel::Value::eWarning: + mWarningTimeStr = timeChangedString; + pressureLevelString = "Warning"; + pressureLevelKey.emplace( + CrashReporter::Annotation::MacMemoryPressureWarningTime); + break; + case MacMemoryPressureLevel::Value::eCritical: + mCriticalTimeStr = timeChangedString; + pressureLevelString = "Critical"; + pressureLevelKey.emplace( + CrashReporter::Annotation::MacMemoryPressureCriticalTime); + break; + default: + pressureLevelString = "Unexpected"; + break; + } + + // Save the current memory pressure level. + AddParentAnnotation(CrashReporter::Annotation::MacMemoryPressure, + pressureLevelString); + + // Save the time we transitioned to the current memory pressure level. + if (pressureLevelKey.isSome()) { + AddParentAnnotation(pressureLevelKey.value(), timeChangedString); + } + + AddParentAnnotation(CrashReporter::Annotation::MacMemoryPressureSysctl, + mLevelSysctl); + AddParentAnnotation(CrashReporter::Annotation::MacAvailableMemorySysctl, + mAvailMemSysctl); +} + +void nsAvailableMemoryWatcher::ReadSysctls() { + // Pressure level + uint32_t level; + size_t size = sizeof(level); + if (sysctlbyname("kern.memorystatus_vm_pressure_level", &level, &size, NULL, + 0) == -1) { + MP_LOG("Failure reading memory pressure sysctl"); + } + mLevelSysctl = level; + + // Available memory percent + int availPercent; + size = sizeof(availPercent); + if (sysctlbyname("kern.memorystatus_level", &availPercent, &size, NULL, 0) == + -1) { + MP_LOG("Failure reading available memory level"); + } + mAvailMemSysctl = availPercent; +} + +/* virtual */ +void nsAvailableMemoryWatcher::OnMemoryPressureChanged( + MacMemoryPressureLevel aNewLevel) { + MOZ_ASSERT(mInitialized); + OnMemoryPressureChangedInternal(aNewLevel, /* aIsInitialLevel */ false); +} + +void nsAvailableMemoryWatcher::OnMemoryPressureChangedInternal( + MacMemoryPressureLevel aNewLevel, bool aIsInitialLevel) { + MOZ_ASSERT(mInitialized || aIsInitialLevel); + MP_LOG("MemoryPressureChange: existing level: %s, new level: %s", + mLevel.ToString(), aNewLevel.ToString()); + + // If 'aNewLevel' is not one of normal, warning, or critical, ASSERT + // here so we can debug this scenario. For non-debug builds, ignore + // the unexpected value which will be logged in crash reports. + MOZ_ASSERT(aNewLevel.IsNormal() || aNewLevel.IsWarningOrAbove()); + + if (mLevel == aNewLevel) { + return; + } + + // Start the memory pressure response if the new level is high enough + // and the existing level was not. + if ((mLevel < mResponseLevel) && (aNewLevel >= mResponseLevel)) { + UpdateLowMemoryTimeStamp(); + LowMemoryResponse(); + if (mResponseMask) { + StartPolling(); + } + } + + // End the memory pressure reponse if the new level is not high enough. + if ((mLevel >= mResponseLevel) && (aNewLevel < mResponseLevel)) { + RecordTelemetryEventOnHighMemory(); + StopPolling(); + MP_LOG("Issuing MemoryPressureState::NoPressure"); + NS_NotifyOfMemoryPressure(MemoryPressureState::NoPressure); + } + + mLevel = aNewLevel; + + if (!aIsInitialLevel) { + // Sysctls are already read by ::Init(). + ReadSysctls(); + MP_LOG("level sysctl: %d, available memory: %d percent", mLevelSysctl, + mAvailMemSysctl); + } + UpdateParentAnnotations(); +} + +/* virtual */ +// Add all annotations to the provided crash reporter instance. +void nsAvailableMemoryWatcher::AddChildAnnotations( + const UniquePtr& aCrashReporter) { + aCrashReporter->AddAnnotation(CrashReporter::Annotation::MacMemoryPressure, + mLevelStr); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacMemoryPressureNormalTime, mNormalTimeStr); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacMemoryPressureWarningTime, mWarningTimeStr); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacMemoryPressureCriticalTime, + mCriticalTimeStr); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacMemoryPressureSysctl, mLevelSysctl); + aCrashReporter->AddAnnotation( + CrashReporter::Annotation::MacAvailableMemorySysctl, mAvailMemSysctl); +} + +void nsAvailableMemoryWatcher::LowMemoryResponse() { + if (mResponseMask & ResponseMask::eTabUnload) { + MP_LOG("Attempting tab unload"); + mTabUnloader->UnloadTabAsync(); + } else { + // Re-use OnUnloadAttemptCompleted() to issue the internal + // memory pressure event. + OnUnloadAttemptCompleted(NS_ERROR_NOT_AVAILABLE); + } +} + +NS_IMETHODIMP +nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mLevel >= mResponseLevel); + LowMemoryResponse(); + return NS_OK; +} + +// Override OnUnloadAttemptCompleted() so that we can issue Gecko memory +// pressure notifications only if eInternalMemoryPressure is set in +// mResponseMask. When called from the tab unloader, an |aResult| value of +// NS_OK indicates the tab unloader successfully unloaded a tab. +// NS_ERROR_NOT_AVAILABLE indicates the tab unloader did not unload any tabs. +NS_IMETHODIMP +nsAvailableMemoryWatcher::OnUnloadAttemptCompleted(nsresult aResult) { + switch (aResult) { + // A tab was unloaded successfully. + case NS_OK: + MP_LOG("Tab unloaded"); + ++mNumOfTabUnloading; + break; + + // Either the tab unloader found no unloadable tabs OR we've been called + // locally to explicitly issue the internal memory pressure event because + // tab unloading is disabled in |mResponseMask|. In either case, attempt + // to reduce memory use using the internal memory pressure notification. + case NS_ERROR_NOT_AVAILABLE: + if (mResponseMask & ResponseMask::eInternalMemoryPressure) { + ++mNumOfMemoryPressure; + MP_LOG("Tab not unloaded"); + MP_LOG("Issuing MemoryPressureState::LowMemory"); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + } + break; + + // There was a pending task to unload a tab. + case NS_ERROR_ABORT: + break; + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected aResult"); + break; + } + return NS_OK; +} + +NS_IMETHODIMP +nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData); + if (NS_FAILED(rv)) { + return rv; + } + + if (strcmp(aTopic, "xpcom-shutdown") == 0) { + OnShutdown(); + } else if (strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { + OnPrefChange(); + } + return NS_OK; +} + +void nsAvailableMemoryWatcher::OnShutdown() { + StopPolling(); + Preferences::RemoveObserver(this, kResponseMask); + Preferences::RemoveObserver(this, kPollingIntervalMS); +} + +void nsAvailableMemoryWatcher::OnPrefChange() { + MP_LOG("OnPrefChange()"); + // Handle the polling interval changing. + uint32_t pollingInterval = Preferences::GetUint(kPollingIntervalMS); + if (pollingInterval != mPollingInterval) { + mPollingInterval = std::clamp(pollingInterval, kPollingIntervalMinMS, + kPollingIntervalMaxMS); + RestartPolling(); + } + + // Handle the response mask changing. + uint32_t responseMask = Preferences::GetUint(kResponseMask); + if (mResponseMask != responseMask) { + mResponseMask = std::min(responseMask, kResponseMaskMax); + + // Do we need to turn on polling? + if (mResponseMask && (mLevel >= mResponseLevel) && !IsPolling()) { + StartPolling(); + } + + // Do we need to turn off polling? + if (!mResponseMask && IsPolling()) { + StopPolling(); + } + } + + // Handle the response level changing. + MacMemoryPressureLevel newResponseLevel; + if (Preferences::GetBool(kResponseOnWarn, kResponseLevelOnWarnDefault)) { + newResponseLevel = MacMemoryPressureLevel::Value::eWarning; + } else { + newResponseLevel = MacMemoryPressureLevel::Value::eCritical; + } + if (newResponseLevel == mResponseLevel) { + return; + } + + // Do we need to turn on polling? + if (mResponseMask && (newResponseLevel <= mLevel)) { + UpdateLowMemoryTimeStamp(); + LowMemoryResponse(); + StartPolling(); + } + + // Do we need to turn off polling? + if (IsPolling() && (newResponseLevel > mLevel)) { + RecordTelemetryEventOnHighMemory(); + StopPolling(); + MP_LOG("Issuing MemoryPressureState::NoPressure"); + NS_NotifyOfMemoryPressure(MemoryPressureState::NoPressure); + } + mResponseLevel = newResponseLevel; +} + +void nsAvailableMemoryWatcher::StartPolling() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mTimer) { + MP_LOG("Starting poller"); + mTimer = NS_NewTimer(); + if (mTimer) { + mTimer->InitWithCallback(this, mPollingInterval, + nsITimer::TYPE_REPEATING_SLACK); + } + } +} + +void nsAvailableMemoryWatcher::StopPolling() { + MOZ_ASSERT(NS_IsMainThread()); + if (mTimer) { + MP_LOG("Pausing poller"); + mTimer->Cancel(); + mTimer = nullptr; + } +} + +void nsAvailableMemoryWatcher::RestartPolling() { + if (IsPolling()) { + StopPolling(); + StartPolling(); + } else { + MOZ_ASSERT(!mTimer); + } +} + +NS_IMETHODIMP +nsAvailableMemoryWatcher::GetName(nsACString& aName) { + aName.AssignLiteral("nsAvailableMemoryWatcher"); + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/base/AvailableMemoryWatcherUtils.h b/xpcom/base/AvailableMemoryWatcherUtils.h new file mode 100644 index 0000000000..2d70fe5fd0 --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcherUtils.h @@ -0,0 +1,56 @@ +/* -*- 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/. */ + +#ifndef mozilla_AvailableMemoryWatcherUtils_h +#define mozilla_AvailableMemoryWatcherUtils_h + +#include "mozilla/Attributes.h" +#include "nsISupportsUtils.h" // For nsresult + +namespace mozilla { + +struct MemoryInfo { + unsigned long memTotal; + unsigned long memAvailable; +}; +// Check /proc/meminfo for low memory. Largely C method for reading +// /proc/meminfo. +MOZ_MAYBE_UNUSED +static nsresult ReadMemoryFile(const char* meminfoPath, MemoryInfo& aResult) { + FILE* fd; + if ((fd = fopen(meminfoPath, "r")) == nullptr) { + // Meminfo somehow unreachable + return NS_ERROR_FAILURE; + } + + char buff[128]; + + /* The first few lines of meminfo look something like this: + * MemTotal: 65663448 kB + * MemFree: 57368112 kB + * MemAvailable: 61852700 kB + * We mostly care about the available versus the total. We calculate our + * memory thresholds using this, and when memory drops below 5% we consider + * this to be a memory pressure event. In practice these lines aren't + * necessarily in order, but we can simply search for MemTotal + * and MemAvailable. + */ + char namebuffer[20]; + while ((fgets(buff, sizeof(buff), fd)) != nullptr) { + if (strstr(buff, "MemTotal:")) { + sscanf(buff, "%s %lu ", namebuffer, &aResult.memTotal); + } + if (strstr(buff, "MemAvailable:")) { + sscanf(buff, "%s %lu ", namebuffer, &aResult.memAvailable); + } + } + fclose(fd); + return NS_OK; +} + +} // namespace mozilla + +#endif // ifndef mozilla_AvailableMemoryWatcherUtils_h diff --git a/xpcom/base/AvailableMemoryWatcherWin.cpp b/xpcom/base/AvailableMemoryWatcherWin.cpp new file mode 100644 index 0000000000..42e590e93a --- /dev/null +++ b/xpcom/base/AvailableMemoryWatcherWin.cpp @@ -0,0 +1,407 @@ +/* -*- 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 "AvailableMemoryWatcher.h" +#include "mozilla/Atomics.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_browser.h" +#include "nsAppRunner.h" +#include "nsExceptionHandler.h" +#include "nsICrashReporter.h" +#include "nsIObserver.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" +#include "nsServiceManagerUtils.h" +#include "nsWindowsHelpers.h" + +#include + +extern mozilla::Atomic + sNumLowPhysicalMemEvents; + +namespace mozilla { + +// This class is used to monitor low memory events delivered by Windows via +// memory resource notification objects. When we enter a low memory scenario +// the LowMemoryCallback() is invoked by Windows. This initial call triggers +// an nsITimer that polls to see when the low memory condition has been lifted. +// When it has, we'll stop polling and start waiting for the next +// LowMemoryCallback(). Meanwhile, the polling may be stopped and restarted by +// user-interaction events from the observer service. +class nsAvailableMemoryWatcher final : public nsITimerCallback, + public nsINamed, + public nsAvailableMemoryWatcherBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + nsAvailableMemoryWatcher(); + nsresult Init() override; + + private: + static VOID CALLBACK LowMemoryCallback(PVOID aContext, BOOLEAN aIsTimer); + static void RecordLowMemoryEvent(); + static bool IsCommitSpaceLow(); + + ~nsAvailableMemoryWatcher(); + bool RegisterMemoryResourceHandler(const MutexAutoLock& aLock) + MOZ_REQUIRES(mMutex); + void UnregisterMemoryResourceHandler(const MutexAutoLock&) + MOZ_REQUIRES(mMutex); + void MaybeSaveMemoryReport(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void Shutdown(const MutexAutoLock& aLock) MOZ_REQUIRES(mMutex); + bool ListenForLowMemory(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void OnLowMemory(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void OnHighMemory(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void StartPollingIfUserInteracting(const MutexAutoLock& aLock) + MOZ_REQUIRES(mMutex); + void StopPolling(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void StopPollingIfUserIdle(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + void OnUserInteracting(const MutexAutoLock&) MOZ_REQUIRES(mMutex); + + // The publicly available methods (::Observe() and ::Notify()) are called on + // the main thread while the ::LowMemoryCallback() method is called by an + // external thread. All functions called from those must acquire a lock on + // this mutex before accessing the object's fields to prevent races. + Mutex mMutex; + nsCOMPtr mTimer MOZ_GUARDED_BY(mMutex); + nsAutoHandle mLowMemoryHandle MOZ_GUARDED_BY(mMutex); + HANDLE mWaitHandle MOZ_GUARDED_BY(mMutex); + bool mPolling MOZ_GUARDED_BY(mMutex); + + // Indicates whether to start a timer when user interaction is notified. + // This flag is needed because the low-memory callback may be triggered when + // the user is inactive and we want to delay-start the timer. + bool mNeedToRestartTimerOnUserInteracting MOZ_GUARDED_BY(mMutex); + // Indicate that the available commit space is low. The timer handler needs + // this flag because it is triggered by the low physical memory regardless + // of the available commit space. + bool mUnderMemoryPressure MOZ_GUARDED_BY(mMutex); + + bool mSavedReport MOZ_GUARDED_BY(mMutex); + bool mIsShutdown MOZ_GUARDED_BY(mMutex); + + // Members below this line are used only in the main thread. + // No lock is needed. + + // Don't fire a low-memory notification more often than this interval. + uint32_t mPollingInterval; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsAvailableMemoryWatcher, + nsAvailableMemoryWatcherBase, nsIObserver, + nsITimerCallback, nsINamed) + +nsAvailableMemoryWatcher::nsAvailableMemoryWatcher() + : mMutex("low memory callback mutex"), + mWaitHandle(nullptr), + mPolling(false), + mNeedToRestartTimerOnUserInteracting(false), + mUnderMemoryPressure(false), + mSavedReport(false), + mIsShutdown(false), + mPollingInterval(0) {} + +nsresult nsAvailableMemoryWatcher::Init() { + nsresult rv = nsAvailableMemoryWatcherBase::Init(); + if (NS_FAILED(rv)) { + return rv; + } + + MutexAutoLock lock(mMutex); + mTimer = NS_NewTimer(); + if (!mTimer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // Use a very short interval for GTest to verify the timer's behavior. + mPollingInterval = gIsGtest ? 10 : 10000; + + if (!RegisterMemoryResourceHandler(lock)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsAvailableMemoryWatcher::~nsAvailableMemoryWatcher() { + // These handles should have been released during the shutdown phase. + MOZ_ASSERT(!mLowMemoryHandle); + MOZ_ASSERT(!mWaitHandle); +} + +// static +VOID CALLBACK nsAvailableMemoryWatcher::LowMemoryCallback(PVOID aContext, + BOOLEAN aIsTimer) { + if (aIsTimer) { + return; + } + + // The |watcher| was addref'ed when we registered the wait handle in + // ListenForLowMemory(). It is decremented when exiting the function, + // so please make sure we unregister the wait handle after this line. + RefPtr watcher = + already_AddRefed( + static_cast(aContext)); + + MutexAutoLock lock(watcher->mMutex); + if (watcher->mIsShutdown) { + // mWaitHandle should have been unregistered during shutdown + MOZ_ASSERT(!watcher->mWaitHandle); + return; + } + + ::UnregisterWait(watcher->mWaitHandle); + watcher->mWaitHandle = nullptr; + + // On Windows, memory allocations fails when the available commit space is + // not sufficient. It's possible that this callback function is invoked + // but there is still commit space enough for the application to continue + // to run. In such a case, there is no strong need to trigger the memory + // pressure event. So we trigger the event only when the available commit + // space is low. + if (IsCommitSpaceLow()) { + watcher->OnLowMemory(lock); + } else { + // Since we have unregistered the callback, we need to start a timer to + // continue watching memory. + watcher->StartPollingIfUserInteracting(lock); + } +} + +// static +void nsAvailableMemoryWatcher::RecordLowMemoryEvent() { + sNumLowPhysicalMemEvents++; + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::LowPhysicalMemoryEvents, + sNumLowPhysicalMemEvents); +} + +bool nsAvailableMemoryWatcher::RegisterMemoryResourceHandler( + const MutexAutoLock& aLock) { + mLowMemoryHandle.own( + ::CreateMemoryResourceNotification(LowMemoryResourceNotification)); + + if (!mLowMemoryHandle) { + return false; + } + + return ListenForLowMemory(aLock); +} + +void nsAvailableMemoryWatcher::UnregisterMemoryResourceHandler( + const MutexAutoLock&) { + if (mWaitHandle) { + bool res = ::UnregisterWait(mWaitHandle); + if (res || ::GetLastError() != ERROR_IO_PENDING) { + // We decrement the refcount only when we're sure the LowMemoryCallback() + // callback won't be invoked, otherwise the callback will do it + this->Release(); + } + mWaitHandle = nullptr; + } + + mLowMemoryHandle.reset(); +} + +void nsAvailableMemoryWatcher::Shutdown(const MutexAutoLock& aLock) { + mIsShutdown = true; + mNeedToRestartTimerOnUserInteracting = false; + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + UnregisterMemoryResourceHandler(aLock); +} + +bool nsAvailableMemoryWatcher::ListenForLowMemory(const MutexAutoLock&) { + if (mLowMemoryHandle && !mWaitHandle) { + // We're giving ownership of this object to the LowMemoryCallback(). We + // increment the count here so that the object is kept alive until the + // callback decrements it. + this->AddRef(); + bool res = ::RegisterWaitForSingleObject( + &mWaitHandle, mLowMemoryHandle, LowMemoryCallback, this, INFINITE, + WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE); + if (!res) { + // We couldn't register the callback, decrement the count + this->Release(); + } + // Once we register the wait handle, we no longer need to start + // the timer because we can continue watching memory via callback. + mNeedToRestartTimerOnUserInteracting = false; + return res; + } + + return false; +} + +void nsAvailableMemoryWatcher::MaybeSaveMemoryReport(const MutexAutoLock&) { + if (mSavedReport) { + return; + } + + if (nsCOMPtr cr = + do_GetService("@mozilla.org/toolkit/crash-reporter;1")) { + mSavedReport = NS_SUCCEEDED(cr->SaveMemoryReport()); + } +} + +void nsAvailableMemoryWatcher::OnLowMemory(const MutexAutoLock& aLock) { + mUnderMemoryPressure = true; + RecordLowMemoryEvent(); + + if (NS_IsMainThread()) { + MaybeSaveMemoryReport(aLock); + UpdateLowMemoryTimeStamp(); + { + // Don't invoke UnloadTabAsync() with the lock to avoid deadlock + // because nsAvailableMemoryWatcher::Notify may be invoked while + // running the method. + MutexAutoUnlock unlock(mMutex); + mTabUnloader->UnloadTabAsync(); + } + } else { + // SaveMemoryReport and mTabUnloader needs to be run in the main thread + // (See nsMemoryReporterManager::GetReportsForThisProcessExtended) + NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsAvailableMemoryWatcher::OnLowMemory", [self = RefPtr{this}]() { + { + MutexAutoLock lock(self->mMutex); + self->MaybeSaveMemoryReport(lock); + self->UpdateLowMemoryTimeStamp(); + } + self->mTabUnloader->UnloadTabAsync(); + })); + } + + StartPollingIfUserInteracting(aLock); +} + +void nsAvailableMemoryWatcher::OnHighMemory(const MutexAutoLock& aLock) { + MOZ_ASSERT(NS_IsMainThread()); + + if (mUnderMemoryPressure) { + RecordTelemetryEventOnHighMemory(); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure); + } + + mUnderMemoryPressure = false; + mSavedReport = false; // Will save a new report if memory gets low again + StopPolling(aLock); + ListenForLowMemory(aLock); +} + +// static +bool nsAvailableMemoryWatcher::IsCommitSpaceLow() { + // Other options to get the available page file size: + // - GetPerformanceInfo + // Too slow, don't use it. + // - PdhCollectQueryData and PdhGetRawCounterValue + // Faster than GetPerformanceInfo, but slower than GlobalMemoryStatusEx. + // - NtQuerySystemInformation(SystemMemoryUsageInformation) + // Faster than GlobalMemoryStatusEx, but undocumented. + MEMORYSTATUSEX memStatus = {sizeof(memStatus)}; + if (!::GlobalMemoryStatusEx(&memStatus)) { + return false; + } + + constexpr size_t kBytesPerMB = 1024 * 1024; + return (memStatus.ullAvailPageFile / kBytesPerMB) < + StaticPrefs::browser_low_commit_space_threshold_mb(); +} + +void nsAvailableMemoryWatcher::StartPollingIfUserInteracting( + const MutexAutoLock&) { + // When the user is inactive, we mark the flag to delay-start + // the timer when the user becomes active later. + mNeedToRestartTimerOnUserInteracting = true; + + if (mInteracting && !mPolling) { + if (NS_SUCCEEDED(mTimer->InitWithCallback( + this, mPollingInterval, nsITimer::TYPE_REPEATING_SLACK))) { + mPolling = true; + } + } +} + +void nsAvailableMemoryWatcher::StopPolling(const MutexAutoLock&) { + mTimer->Cancel(); + mPolling = false; +} + +void nsAvailableMemoryWatcher::StopPollingIfUserIdle( + const MutexAutoLock& aLock) { + if (!mInteracting) { + StopPolling(aLock); + } +} + +void nsAvailableMemoryWatcher::OnUserInteracting(const MutexAutoLock& aLock) { + if (mNeedToRestartTimerOnUserInteracting) { + StartPollingIfUserInteracting(aLock); + } +} + +// Timer callback, polls the low memory resource notification to detect when +// we've freed enough memory or if we have to send more memory pressure events. +// Polling stops automatically when the user is not interacting with the UI. +NS_IMETHODIMP +nsAvailableMemoryWatcher::Notify(nsITimer* aTimer) { + MutexAutoLock lock(mMutex); + StopPollingIfUserIdle(lock); + + if (IsCommitSpaceLow()) { + OnLowMemory(lock); + } else { + OnHighMemory(lock); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAvailableMemoryWatcher::GetName(nsACString& aName) { + aName.AssignLiteral("nsAvailableMemoryWatcher"); + return NS_OK; +} + +// Observer service callback, used to stop the polling timer when the user +// stops interacting with Firefox and resuming it when they interact again. +// Also used to shut down the service if the application is quitting. +NS_IMETHODIMP +nsAvailableMemoryWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + nsresult rv = nsAvailableMemoryWatcherBase::Observe(aSubject, aTopic, aData); + if (NS_FAILED(rv)) { + return rv; + } + + MutexAutoLock lock(mMutex); + + if (strcmp(aTopic, "xpcom-shutdown") == 0) { + Shutdown(lock); + } else if (strcmp(aTopic, "user-interaction-active") == 0) { + OnUserInteracting(lock); + } + + return NS_OK; +} + +already_AddRefed CreateAvailableMemoryWatcher() { + RefPtr watcher(new nsAvailableMemoryWatcher); + if (NS_FAILED(watcher->Init())) { + return do_AddRef(new nsAvailableMemoryWatcherBase); // fallback + } + return watcher.forget(); +} + +} // namespace mozilla diff --git a/xpcom/base/ClearOnShutdown.cpp b/xpcom/base/ClearOnShutdown.cpp new file mode 100644 index 0000000000..700785736d --- /dev/null +++ b/xpcom/base/ClearOnShutdown.cpp @@ -0,0 +1,63 @@ +/* -*- 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/ClearOnShutdown.h" + +namespace mozilla { +namespace ClearOnShutdown_Internal { + +Array, + static_cast(ShutdownPhase::ShutdownPhase_Length)> + sShutdownObservers; +ShutdownPhase sCurrentClearOnShutdownPhase = ShutdownPhase::NotInShutdown; + +void InsertIntoShutdownList(ShutdownObserver* aObserver, ShutdownPhase aPhase) { + // Adding a ClearOnShutdown for a "past" phase is an error. + if (PastShutdownPhase(aPhase)) { + MOZ_ASSERT(false, "ClearOnShutdown for phase that already was cleared"); + aObserver->Shutdown(); + delete aObserver; + return; + } + + if (!(sShutdownObservers[static_cast(aPhase)])) { + sShutdownObservers[static_cast(aPhase)] = new ShutdownList(); + } + sShutdownObservers[static_cast(aPhase)]->insertBack(aObserver); +} + +} // namespace ClearOnShutdown_Internal + +// Called by AdvanceShutdownPhase each time we switch a phase. Will null out +// pointers added by ClearOnShutdown for all phases up to and including aPhase. +void KillClearOnShutdown(ShutdownPhase aPhase) { + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + // Shutdown only goes one direction... + MOZ_ASSERT(!PastShutdownPhase(aPhase)); + + // Set the phase before notifying observers to make sure that they can't run + // any code which isn't allowed to run after the start of this phase. + sCurrentClearOnShutdownPhase = aPhase; + + // It's impossible to add an entry for a "past" phase; this is blocked in + // ClearOnShutdown, but clear them out anyways in case there are phases + // that weren't passed to KillClearOnShutdown. + for (size_t phase = static_cast(ShutdownPhase::First); + phase <= static_cast(aPhase); phase++) { + if (sShutdownObservers[static_cast(phase)]) { + while (ShutdownObserver* observer = + sShutdownObservers[static_cast(phase)]->popLast()) { + observer->Shutdown(); + delete observer; + } + sShutdownObservers[static_cast(phase)] = nullptr; + } + } +} + +} // namespace mozilla diff --git a/xpcom/base/ClearOnShutdown.h b/xpcom/base/ClearOnShutdown.h new file mode 100644 index 0000000000..f927e3334a --- /dev/null +++ b/xpcom/base/ClearOnShutdown.h @@ -0,0 +1,147 @@ +/* -*- 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/. */ + +#ifndef mozilla_ClearOnShutdown_h +#define mozilla_ClearOnShutdown_h + +#include "mozilla/LinkedList.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Array.h" +#include "ShutdownPhase.h" +#include "MainThreadUtils.h" + +#include + +/* + * This header exports two public methods in the mozilla namespace: + * + * template + * void ClearOnShutdown(SmartPtr *aPtr, + * aPhase=ShutdownPhase::XPCOMShutdownFinal) + * + * This function takes a pointer to a smart pointer and nulls the smart pointer + * on shutdown (and a particular phase of shutdown as needed). If a phase + * is specified, the ptr will be cleared at the start of that phase. Also, + * if a phase has already occurred when ClearOnShutdown() is called it will + * cause a MOZ_ASSERT. In case a phase is not explicitly cleared we will + * clear it on the next phase that occurs. + * + * This is useful if you have a global smart pointer object which you don't + * want to "leak" on shutdown. + * + * Although ClearOnShutdown will work with any smart pointer (i.e., nsCOMPtr, + * RefPtr, StaticRefPtr, and StaticAutoPtr), you probably want to + * use it only with StaticRefPtr and StaticAutoPtr. There is no way to undo a + * call to ClearOnShutdown, so you can call it only on smart pointers which you + * know will live until the program shuts down. In practice, these are likely + * global variables, which should be Static{Ref,Auto}Ptr. + * + * template + * void RunOnShutdown(CallableT&& aCallable, + * aPhase = ShutdownPhase::XPCOMShutdownFinal) + * + * This function takes a callable and executes it upon shutdown at the start of + * the specified phase. If the phase has already occurred when RunOnShutdown() + * is called, it will cause a MOZ_ASSERT. In case a phase is not explicitly + * cleared, we will clear it on the next phase that occurs. + * + * ClearOnShutdown and RunOnShutdown are both currently main-thread only because + * we don't want to accidentally free an object from a different thread than the + * one it was created on. + */ + +namespace mozilla { + +namespace ClearOnShutdown_Internal { + +class ShutdownObserver : public LinkedListElement { + public: + virtual void Shutdown() = 0; + virtual ~ShutdownObserver() = default; +}; + +template +class PointerClearer : public ShutdownObserver { + public: + explicit PointerClearer(SmartPtr* aPtr) : mPtr(aPtr) {} + + virtual void Shutdown() override { + if (mPtr) { + *mPtr = nullptr; + } + } + + private: + SmartPtr* mPtr; +}; + +class FunctionInvoker : public ShutdownObserver { + public: + template + explicit FunctionInvoker(CallableT&& aCallable) + : mCallable(std::forward(aCallable)) {} + + virtual void Shutdown() override { + if (!mCallable) { + return; + } + + mCallable(); + } + + private: + std::function mCallable; +}; + +void InsertIntoShutdownList(ShutdownObserver* aShutdownObserver, + ShutdownPhase aPhase); + +typedef LinkedList ShutdownList; +extern Array, + static_cast(ShutdownPhase::ShutdownPhase_Length)> + sShutdownObservers; +extern ShutdownPhase sCurrentClearOnShutdownPhase; + +} // namespace ClearOnShutdown_Internal + +template +inline void ClearOnShutdown( + SmartPtr* aPtr, ShutdownPhase aPhase = ShutdownPhase::XPCOMShutdownFinal) { + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPhase != ShutdownPhase::ShutdownPhase_Length); + + InsertIntoShutdownList(new PointerClearer(aPtr), aPhase); +} + +template +inline void RunOnShutdown( + CallableT&& aCallable, + ShutdownPhase aPhase = ShutdownPhase::XPCOMShutdownFinal) { + using namespace ClearOnShutdown_Internal; + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aPhase != ShutdownPhase::ShutdownPhase_Length); + + InsertIntoShutdownList( + new FunctionInvoker(std::forward(aCallable)), aPhase); +} + +inline bool PastShutdownPhase(ShutdownPhase aPhase) { + MOZ_ASSERT(NS_IsMainThread()); + + return size_t(ClearOnShutdown_Internal::sCurrentClearOnShutdownPhase) >= + size_t(aPhase); +} + +// Called by AdvanceShutdownPhase each time we switch a phase. Will null out +// pointers added by ClearOnShutdown for all phases up to and including aPhase. +void KillClearOnShutdown(ShutdownPhase aPhase); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/CodeAddressService.h b/xpcom/base/CodeAddressService.h new file mode 100644 index 0000000000..821bf2c73c --- /dev/null +++ b/xpcom/base/CodeAddressService.h @@ -0,0 +1,250 @@ +/* -*- 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/. */ + +#ifndef CodeAddressService_h__ +#define CodeAddressService_h__ + +#include +#include +#include +#include "mozilla/AllocPolicy.h" +#include "mozilla/Assertions.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HashTable.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/StackWalk.h" + +namespace mozilla { + +namespace detail { + +template +class CodeAddressServiceAllocPolicy : public AllocPolicy { + public: + char* strdup_(const char* aStr) { + char* s = AllocPolicy::template pod_malloc(strlen(aStr) + 1); + if (!s) { + MOZ_CRASH("CodeAddressService OOM"); + } + strcpy(s, aStr); + return s; + } +}; + +// Default implementation of DescribeCodeAddressLock. +struct DefaultDescribeCodeAddressLock { + static void Unlock() {} + static void Lock() {} + // Because CodeAddressService asserts that IsLocked() is true, returning true + // here is a sensible default when there is no relevant lock. + static bool IsLocked() { return true; } +}; + +} // namespace detail + +// This class is used to print details about code locations. +// +// |AllocPolicy_| must adhere to the description in mfbt/AllocPolicy.h. +// +// |DescribeCodeAddressLock| is needed when the callers may be holding a lock +// used by MozDescribeCodeAddress. |DescribeCodeAddressLock| must implement +// static methods IsLocked(), Unlock() and Lock(). +template +class CodeAddressService + : private detail::CodeAddressServiceAllocPolicy { + protected: + // GetLocation() is the key function in this class. It's basically a wrapper + // around MozDescribeCodeAddress. + // + // However, MozDescribeCodeAddress is very slow on some platforms, and we + // have lots of repeated (i.e. same PC) calls to it. So we do some caching + // of results. Each cached result includes two strings (|mFunction| and + // |mLibrary|), so we also optimize them for space in the following ways. + // + // - The number of distinct library names is small, e.g. a few dozen. There + // is lots of repetition, especially of libxul. So we intern them in their + // own table, which saves space over duplicating them for each cache entry. + // + // - The number of distinct function names is much higher, so we duplicate + // them in each cache entry. That's more space-efficient than interning + // because entries containing single-occurrence function names are quickly + // overwritten, and their copies released. In addition, empty function + // names are common, so we use nullptr to represent them compactly. + + using AllocPolicy = detail::CodeAddressServiceAllocPolicy; + using StringHashSet = HashSet; + + StringHashSet mLibraryStrings; + + struct Entry : private AllocPolicy { + const void* mPc; + char* mFunction; // owned by the Entry; may be null + const char* mLibrary; // owned by mLibraryStrings; never null + // in a non-empty entry is in use + ptrdiff_t mLOffset; + char* mFileName; // owned by the Entry; may be null + uint32_t mLineNo : 31; + uint32_t mInUse : 1; // is the entry used? + + Entry() + : mPc(0), + mFunction(nullptr), + mLibrary(nullptr), + mLOffset(0), + mFileName(nullptr), + mLineNo(0), + mInUse(0) {} + + ~Entry() { + // We don't free mLibrary because it's externally owned. + AllocPolicy::free_(mFunction); + AllocPolicy::free_(mFileName); + } + + void Replace(const void* aPc, const char* aFunction, const char* aLibrary, + ptrdiff_t aLOffset, const char* aFileName, + unsigned long aLineNo) { + mPc = aPc; + + // Convert "" to nullptr. Otherwise, make a copy of the name. + AllocPolicy::free_(mFunction); + mFunction = !aFunction[0] ? nullptr : AllocPolicy::strdup_(aFunction); + AllocPolicy::free_(mFileName); + mFileName = !aFileName[0] ? nullptr : AllocPolicy::strdup_(aFileName); + + mLibrary = aLibrary; + mLOffset = aLOffset; + mLineNo = aLineNo; + + mInUse = 1; + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + // Don't measure mLibrary because it's externally owned. + size_t n = 0; + n += aMallocSizeOf(mFunction); + n += aMallocSizeOf(mFileName); + return n; + } + }; + + const char* InternLibraryString(const char* aString) { + auto p = mLibraryStrings.lookupForAdd(aString); + if (p) { + return *p; + } + + const char* newString = AllocPolicy::strdup_(aString); + if (!mLibraryStrings.add(p, newString)) { + MOZ_CRASH("CodeAddressService OOM"); + } + return newString; + } + + Entry& GetEntry(const void* aPc) { + MOZ_ASSERT(DescribeCodeAddressLock::IsLocked()); + + uint32_t index = HashGeneric(aPc) & kMask; + MOZ_ASSERT(index < kNumEntries); + Entry& entry = mEntries[index]; + + if (!entry.mInUse || entry.mPc != aPc) { + mNumCacheMisses++; + + // MozDescribeCodeAddress can (on Linux) acquire a lock inside + // the shared library loader. Another thread might call malloc + // while holding that lock (when loading a shared library). So + // we have to exit the lock around this call. For details, see + // https://bugzilla.mozilla.org/show_bug.cgi?id=363334#c3 + MozCodeAddressDetails details; + { + DescribeCodeAddressLock::Unlock(); + (void)MozDescribeCodeAddress(const_cast(aPc), &details); + DescribeCodeAddressLock::Lock(); + } + + const char* library = InternLibraryString(details.library); + entry.Replace(aPc, details.function, library, details.loffset, + details.filename, details.lineno); + + } else { + mNumCacheHits++; + } + + MOZ_ASSERT(entry.mPc == aPc); + + return entry; + } + + // A direct-mapped cache. When doing dmd::Analyze() just after starting + // desktop Firefox (which is similar to analyzing after a longer-running + // session, thanks to the limit on how many records we print), a cache with + // 2^24 entries (which approximates an infinite-entry cache) has a ~91% hit + // rate. A cache with 2^12 entries has a ~83% hit rate, and takes up ~85 KiB + // (on 32-bit platforms) or ~150 KiB (on 64-bit platforms). + static const size_t kNumEntries = 1 << 12; + static const size_t kMask = kNumEntries - 1; + Entry mEntries[kNumEntries]; + + size_t mNumCacheHits; + size_t mNumCacheMisses; + + public: + CodeAddressService() + : mLibraryStrings(64), mEntries(), mNumCacheHits(0), mNumCacheMisses(0) {} + + ~CodeAddressService() { + for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) { + AllocPolicy::free_(const_cast(iter.get())); + } + } + + // Returns the minimum number of characters necessary to format the frame + // information, without the terminating null. The buffer will be truncated + // if the returned value is greater than aBufLen-1. + int GetLocation(uint32_t aFrameNumber, const void* aPc, char* aBuf, + size_t aBufLen) { + Entry& entry = GetEntry(aPc); + return MozFormatCodeAddress(aBuf, aBufLen, aFrameNumber, entry.mPc, + entry.mFunction, entry.mLibrary, entry.mLOffset, + entry.mFileName, entry.mLineNo); + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + for (uint32_t i = 0; i < kNumEntries; i++) { + n += mEntries[i].SizeOfExcludingThis(aMallocSizeOf); + } + + n += mLibraryStrings.shallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mLibraryStrings.iter(); !iter.done(); iter.next()) { + n += aMallocSizeOf(iter.get()); + } + + return n; + } + + size_t CacheCapacity() const { return kNumEntries; } + + size_t CacheCount() const { + size_t n = 0; + for (size_t i = 0; i < kNumEntries; i++) { + if (mEntries[i].mInUse) { + n++; + } + } + return n; + } + + size_t NumCacheHits() const { return mNumCacheHits; } + size_t NumCacheMisses() const { return mNumCacheMisses; } +}; + +} // namespace mozilla + +#endif // CodeAddressService_h__ diff --git a/xpcom/base/CountingAllocatorBase.h b/xpcom/base/CountingAllocatorBase.h new file mode 100644 index 0000000000..02d74f3223 --- /dev/null +++ b/xpcom/base/CountingAllocatorBase.h @@ -0,0 +1,158 @@ +/* -*- 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/. */ + +#ifndef CountingAllocatorBase_h +#define CountingAllocatorBase_h + +#include +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/mozalloc.h" +#include "nsIMemoryReporter.h" + +namespace mozilla { + +// This CRTP class handles several details of wrapping allocators and should +// be preferred to manually counting with MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC +// and MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE. The typical use is in a memory +// reporter for a particular third party library: +// +// class MyMemoryReporter : public CountingAllocatorBase +// { +// ... +// NS_IMETHOD +// CollectReports(nsIHandleReportCallback* aHandleReport, +// nsISupports* aData, bool aAnonymize) override +// { +// MOZ_COLLECT_REPORT( +// "explicit/path/to/somewhere", KIND_HEAP, UNITS_BYTES, +// MemoryAllocated(), +// "A description of what we are reporting."); +// +// return NS_OK; +// } +// }; +// +// ...somewhere later in the code... +// SetThirdPartyMemoryFunctions(MyMemoryReporter::CountingAlloc, +// MyMemoryReporter::CountingFree); +template +class CountingAllocatorBase { + public: + CountingAllocatorBase() { +#ifdef DEBUG + // There must be only one instance of this class, due to |sAmount| being + // static. + static bool hasRun = false; + MOZ_ASSERT(!hasRun); + hasRun = true; +#endif + } + + static size_t MemoryAllocated() { return sAmount; } + + static void* CountingMalloc(size_t size) { + void* p = malloc(size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void* CountingCalloc(size_t nmemb, size_t size) { + void* p = calloc(nmemb, size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void* CountingRealloc(void* p, size_t size) { + size_t oldsize = MallocSizeOfOnFree(p); + void* pnew = realloc(p, size); + if (pnew) { + size_t newsize = MallocSizeOfOnAlloc(pnew); + sAmount += newsize - oldsize; + } else if (size == 0) { + // We asked for a 0-sized (re)allocation of some existing pointer + // and received NULL in return. 0-sized allocations are permitted + // to either return NULL or to allocate a unique object per call (!). + // For a malloc implementation that chooses the second strategy, + // that allocation may fail (unlikely, but possible). + // + // Given a NULL return value and an allocation size of 0, then, we + // don't know if that means the original pointer was freed or if + // the allocation of the unique object failed. If the original + // pointer was freed, then we have nothing to do here. If the + // allocation of the unique object failed, the original pointer is + // still valid and we ought to undo the decrement from above. + // However, we have no way of knowing how the underlying realloc + // implementation is behaving. Assuming that the original pointer + // was freed is the safest course of action. We do, however, need + // to note that we freed memory. + sAmount -= oldsize; + } else { + // realloc failed. The amount allocated hasn't changed. + } + return pnew; + } + + // Some library code expects that realloc(x, 0) will free x, which is not + // the behavior of the version of jemalloc we're using, so this wrapped + // version of realloc is needed. + static void* CountingFreeingRealloc(void* p, size_t size) { + if (size == 0) { + CountingFree(p); + return nullptr; + } + return CountingRealloc(p, size); + } + + static void CountingFree(void* p) { + sAmount -= MallocSizeOfOnFree(p); + free(p); + } + + // Infallible-allocation wrappers for the counting malloc/calloc/realloc + // functions, for clients that don't safely handle allocation failures + // themselves. + static void* InfallibleCountingMalloc(size_t size) { + void* p = moz_xmalloc(size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void* InfallibleCountingCalloc(size_t nmemb, size_t size) { + void* p = moz_xcalloc(nmemb, size); + sAmount += MallocSizeOfOnAlloc(p); + return p; + } + + static void* InfallibleCountingRealloc(void* p, size_t size) { + size_t oldsize = MallocSizeOfOnFree(p); + void* pnew = moz_xrealloc(p, size); + if (pnew) { + size_t newsize = MallocSizeOfOnAlloc(pnew); + sAmount += newsize - oldsize; + } else if (size == 0) { + // See comment in CountingRealloc above. + sAmount -= oldsize; + } else { + // realloc failed. The amount allocated hasn't changed. + } + return pnew; + } + + private: + // |sAmount| can be (implicitly) accessed by multiple threads, so it + // must be thread-safe. It may be written during GC, so accesses are not + // recorded. + typedef Atomic AmountType; + static inline AmountType sAmount{0}; + + MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(MallocSizeOfOnAlloc) + MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(MallocSizeOfOnFree) +}; + +} // namespace mozilla + +#endif // CountingAllocatorBase_h diff --git a/xpcom/base/CycleCollectedJSContext.cpp b/xpcom/base/CycleCollectedJSContext.cpp new file mode 100644 index 0000000000..9e2f89a5b4 --- /dev/null +++ b/xpcom/base/CycleCollectedJSContext.cpp @@ -0,0 +1,928 @@ +/* -*- 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/CycleCollectedJSContext.h" + +#include +#include + +#include "js/Debug.h" +#include "js/GCAPI.h" +#include "js/Utility.h" +#include "jsapi.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/DebuggerOnGCRunnable.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/TimelineMarker.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/DOMException.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/FinalizationRegistryBinding.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/PromiseBinding.h" +#include "mozilla/dom/PromiseDebugging.h" +#include "mozilla/dom/PromiseRejectionEvent.h" +#include "mozilla/dom/PromiseRejectionEventBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/UserActivation.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDOMJSUtils.h" +#include "nsDOMMutationObserver.h" +#include "nsJSUtils.h" +#include "nsPIDOMWindow.h" +#include "nsStringBuffer.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "nsWrapperCache.h" +#include "xpcpublic.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { + +CycleCollectedJSContext::CycleCollectedJSContext() + : mRuntime(nullptr), + mJSContext(nullptr), + mDoingStableStates(false), + mTargetedMicroTaskRecursionDepth(0), + mMicroTaskLevel(0), + mSuppressionGeneration(0), + mDebuggerRecursionDepth(0), + mMicroTaskRecursionDepth(0), + mFinalizationRegistryCleanup(this) { + MOZ_COUNT_CTOR(CycleCollectedJSContext); + + nsCOMPtr thread = do_GetCurrentThread(); + mOwningThread = thread.forget().downcast().take(); + MOZ_RELEASE_ASSERT(mOwningThread); +} + +CycleCollectedJSContext::~CycleCollectedJSContext() { + MOZ_COUNT_DTOR(CycleCollectedJSContext); + // If the allocation failed, here we are. + if (!mJSContext) { + return; + } + + JS::SetHostCleanupFinalizationRegistryCallback(mJSContext, nullptr, nullptr); + + JS_SetContextPrivate(mJSContext, nullptr); + + mRuntime->SetContext(nullptr); + mRuntime->Shutdown(mJSContext); + + // Last chance to process any events. + CleanupIDBTransactions(mBaseRecursionDepth); + MOZ_ASSERT(mPendingIDBTransactions.IsEmpty()); + + ProcessStableStateQueue(); + MOZ_ASSERT(mStableStateEvents.IsEmpty()); + + // Clear mPendingException first, since it might be cycle collected. + mPendingException = nullptr; + + MOZ_ASSERT(mDebuggerMicroTaskQueue.empty()); + MOZ_ASSERT(mPendingMicroTaskRunnables.empty()); + + mUncaughtRejections.reset(); + mConsumedRejections.reset(); + + mAboutToBeNotifiedRejectedPromises.Clear(); + mPendingUnhandledRejections.Clear(); + + mFinalizationRegistryCleanup.Destroy(); + + JS_DestroyContext(mJSContext); + mJSContext = nullptr; + + nsCycleCollector_forgetJSContext(); + + mozilla::dom::DestroyScriptSettings(); + + mOwningThread->SetScriptObserver(nullptr); + NS_RELEASE(mOwningThread); + + delete mRuntime; + mRuntime = nullptr; +} + +nsresult CycleCollectedJSContext::Initialize(JSRuntime* aParentRuntime, + uint32_t aMaxBytes) { + MOZ_ASSERT(!mJSContext); + + mozilla::dom::InitScriptSettings(); + mJSContext = JS_NewContext(aMaxBytes, aParentRuntime); + if (!mJSContext) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mRuntime = CreateRuntime(mJSContext); + mRuntime->SetContext(this); + + mOwningThread->SetScriptObserver(this); + // The main thread has a base recursion depth of 0, workers of 1. + mBaseRecursionDepth = RecursionDepth(); + + NS_GetCurrentThread()->SetCanInvokeJS(true); + + JS::SetJobQueue(mJSContext, this); + JS::SetPromiseRejectionTrackerCallback(mJSContext, + PromiseRejectionTrackerCallback, this); + mUncaughtRejections.init(mJSContext, + JS::GCVector( + js::SystemAllocPolicy())); + mConsumedRejections.init(mJSContext, + JS::GCVector( + js::SystemAllocPolicy())); + + mFinalizationRegistryCleanup.Init(); + + // Cast to PerThreadAtomCache for dom::GetAtomCache(JSContext*). + JS_SetContextPrivate(mJSContext, static_cast(this)); + + nsCycleCollector_registerJSContext(this); + + return NS_OK; +} + +/* static */ +CycleCollectedJSContext* CycleCollectedJSContext::GetFor(JSContext* aCx) { + // Cast from void* matching JS_SetContextPrivate. + auto atomCache = static_cast(JS_GetContextPrivate(aCx)); + // Down cast. + return static_cast(atomCache); +} + +size_t CycleCollectedJSContext::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + return 0; +} + +class PromiseJobRunnable final : public MicroTaskRunnable { + public: + PromiseJobRunnable(JS::HandleObject aPromise, JS::HandleObject aCallback, + JS::HandleObject aCallbackGlobal, + JS::HandleObject aAllocationSite, + nsIGlobalObject* aIncumbentGlobal) + : mCallback(new PromiseJobCallback(aCallback, aCallbackGlobal, + aAllocationSite, aIncumbentGlobal)), + mPropagateUserInputEventHandling(false) { + MOZ_ASSERT(js::IsFunctionObject(aCallback)); + + if (aPromise) { + JS::PromiseUserInputEventHandlingState state = + JS::GetPromiseUserInputEventHandlingState(aPromise); + mPropagateUserInputEventHandling = + state == + JS::PromiseUserInputEventHandlingState::HadUserInteractionAtCreation; + } + } + + virtual ~PromiseJobRunnable() = default; + + protected: + MOZ_CAN_RUN_SCRIPT + virtual void Run(AutoSlowOperation& aAso) override { + JSObject* callback = mCallback->CallbackPreserveColor(); + nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr; + if (global && !global->IsDying()) { + // Propagate the user input event handling bit if needed. + nsCOMPtr win = do_QueryInterface(global); + RefPtr doc; + if (win) { + doc = win->GetExtantDoc(); + } + AutoHandlingUserInputStatePusher userInpStatePusher( + mPropagateUserInputEventHandling); + + mCallback->Call("promise callback"); + aAso.CheckForInterrupt(); + } + // Now that mCallback is no longer needed, clear any pointers it contains to + // JS GC things. This removes any storebuffer entries associated with those + // pointers, which can cause problems by taking up memory and by triggering + // minor GCs. This otherwise would not happen until the next minor GC or + // cycle collection. + mCallback->Reset(); + } + + virtual bool Suppressed() override { + JSObject* callback = mCallback->CallbackPreserveColor(); + nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr; + return global && global->IsInSyncOperation(); + } + + private: + const RefPtr mCallback; + bool mPropagateUserInputEventHandling; +}; + +JSObject* CycleCollectedJSContext::getIncumbentGlobal(JSContext* aCx) { + nsIGlobalObject* global = mozilla::dom::GetIncumbentGlobal(); + if (global) { + return global->GetGlobalJSObject(); + } + return nullptr; +} + +bool CycleCollectedJSContext::enqueuePromiseJob( + JSContext* aCx, JS::HandleObject aPromise, JS::HandleObject aJob, + JS::HandleObject aAllocationSite, JS::HandleObject aIncumbentGlobal) { + MOZ_ASSERT(aCx == Context()); + MOZ_ASSERT(Get() == this); + + nsIGlobalObject* global = nullptr; + if (aIncumbentGlobal) { + global = xpc::NativeGlobal(aIncumbentGlobal); + } + JS::RootedObject jobGlobal(aCx, JS::CurrentGlobalOrNull(aCx)); + RefPtr runnable = new PromiseJobRunnable( + aPromise, aJob, jobGlobal, aAllocationSite, global); + DispatchToMicroTask(runnable.forget()); + return true; +} + +// Used only by the SpiderMonkey Debugger API, and even then only via +// JS::AutoDebuggerJobQueueInterruption, to ensure that the debuggee's queue is +// not affected; see comments in js/public/Promise.h. +void CycleCollectedJSContext::runJobs(JSContext* aCx) { + MOZ_ASSERT(aCx == Context()); + MOZ_ASSERT(Get() == this); + PerformMicroTaskCheckPoint(); +} + +bool CycleCollectedJSContext::empty() const { + // This is our override of JS::JobQueue::empty. Since that interface is only + // concerned with the ordinary microtask queue, not the debugger microtask + // queue, we only report on the former. + return mPendingMicroTaskRunnables.empty(); +} + +// Preserve a debuggee's microtask queue while it is interrupted by the +// debugger. See the comments for JS::AutoDebuggerJobQueueInterruption. +class CycleCollectedJSContext::SavedMicroTaskQueue + : public JS::JobQueue::SavedJobQueue { + public: + explicit SavedMicroTaskQueue(CycleCollectedJSContext* ccjs) : ccjs(ccjs) { + ccjs->mDebuggerRecursionDepth++; + ccjs->mPendingMicroTaskRunnables.swap(mQueue); + } + + ~SavedMicroTaskQueue() { + // The JS Debugger attempts to maintain the invariant that microtasks which + // occur durring debugger operation are completely flushed from the task + // queue before returning control to the debuggee, in order to avoid + // micro-tasks generated during debugging from interfering with regular + // operation. + // + // While the vast majority of microtasks can be reliably flushed, + // synchronous operations (see nsAutoSyncOperation) such as printing and + // alert diaglogs suppress the execution of some microtasks. + // + // When PerformMicroTaskCheckpoint is run while microtasks are suppressed, + // any suppressed microtasks are gathered into a new SuppressedMicroTasks + // runnable, which is enqueued on exit from PerformMicroTaskCheckpoint. As a + // result, AutoDebuggerJobQueueInterruption::runJobs is not able to + // correctly guarantee that the microtask queue is totally empty in the + // presence of sync operations. + // + // Previous versions of this code release-asserted that the queue was empty, + // causing user observable crashes (Bug 1849675). To avoid this, we instead + // choose to move suspended microtasks from the SavedMicroTaskQueue to the + // main microtask queue in this destructor. This means that jobs enqueued + // during synchnronous events under debugger control may produce events + // which run outside the debugger, but this is viewed as strictly + // preferrable to crashing. + MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.size() <= 1); + MOZ_RELEASE_ASSERT(ccjs->mDebuggerRecursionDepth); + RefPtr maybeSuppressedTasks; + + // Handle the case where there is a SuppressedMicroTask still in the queue. + if (!ccjs->mPendingMicroTaskRunnables.empty()) { + maybeSuppressedTasks = ccjs->mPendingMicroTaskRunnables.front(); + ccjs->mPendingMicroTaskRunnables.pop_front(); + } + + MOZ_RELEASE_ASSERT(ccjs->mPendingMicroTaskRunnables.empty()); + ccjs->mDebuggerRecursionDepth--; + ccjs->mPendingMicroTaskRunnables.swap(mQueue); + + // Re-enqueue the suppressed task now that we've put the original microtask + // queue back. + if (maybeSuppressedTasks) { + ccjs->mPendingMicroTaskRunnables.push_back(maybeSuppressedTasks); + } + } + + private: + CycleCollectedJSContext* ccjs; + std::deque> mQueue; +}; + +js::UniquePtr +CycleCollectedJSContext::saveJobQueue(JSContext* cx) { + auto saved = js::MakeUnique(this); + if (!saved) { + // When MakeUnique's allocation fails, the SavedMicroTaskQueue constructor + // is never called, so mPendingMicroTaskRunnables is still initialized. + JS_ReportOutOfMemory(cx); + return nullptr; + } + + return saved; +} + +/* static */ +void CycleCollectedJSContext::PromiseRejectionTrackerCallback( + JSContext* aCx, bool aMutedErrors, JS::HandleObject aPromise, + JS::PromiseRejectionHandlingState state, void* aData) { + CycleCollectedJSContext* self = static_cast(aData); + + MOZ_ASSERT(aCx == self->Context()); + MOZ_ASSERT(Get() == self); + + // TODO: Bug 1549351 - Promise rejection event should not be sent for + // cross-origin scripts + + PromiseArray& aboutToBeNotified = self->mAboutToBeNotifiedRejectedPromises; + PromiseHashtable& unhandled = self->mPendingUnhandledRejections; + uint64_t promiseID = JS::GetPromiseID(aPromise); + + if (state == JS::PromiseRejectionHandlingState::Unhandled) { + PromiseDebugging::AddUncaughtRejection(aPromise); + if (!aMutedErrors) { + RefPtr promise = + Promise::CreateFromExisting(xpc::NativeGlobal(aPromise), aPromise); + aboutToBeNotified.AppendElement(promise); + unhandled.InsertOrUpdate(promiseID, std::move(promise)); + } + } else { + PromiseDebugging::AddConsumedRejection(aPromise); + for (size_t i = 0; i < aboutToBeNotified.Length(); i++) { + if (aboutToBeNotified[i] && + aboutToBeNotified[i]->PromiseObj() == aPromise) { + // To avoid large amounts of memmoves, we don't shrink the vector + // here. Instead, we filter out nullptrs when iterating over the + // vector later. + aboutToBeNotified[i] = nullptr; + DebugOnly isFound = unhandled.Remove(promiseID); + MOZ_ASSERT(isFound); + return; + } + } + RefPtr promise; + unhandled.Remove(promiseID, getter_AddRefs(promise)); + if (!promise && !aMutedErrors) { + nsIGlobalObject* global = xpc::NativeGlobal(aPromise); + if (nsCOMPtr owner = do_QueryInterface(global)) { + RootedDictionary init(aCx); + init.mPromise = Promise::CreateFromExisting(global, aPromise); + init.mReason = JS::GetPromiseResult(aPromise); + + RefPtr event = + PromiseRejectionEvent::Constructor(owner, u"rejectionhandled"_ns, + init); + + RefPtr asyncDispatcher = + new AsyncEventDispatcher(owner, event); + asyncDispatcher->PostDOMEvent(); + } + } + } +} + +already_AddRefed CycleCollectedJSContext::GetPendingException() + const { + MOZ_ASSERT(mJSContext); + + nsCOMPtr out = mPendingException; + return out.forget(); +} + +void CycleCollectedJSContext::SetPendingException(Exception* aException) { + MOZ_ASSERT(mJSContext); + mPendingException = aException; +} + +std::deque>& +CycleCollectedJSContext::GetMicroTaskQueue() { + MOZ_ASSERT(mJSContext); + return mPendingMicroTaskRunnables; +} + +std::deque>& +CycleCollectedJSContext::GetDebuggerMicroTaskQueue() { + MOZ_ASSERT(mJSContext); + return mDebuggerMicroTaskQueue; +} + +void CycleCollectedJSContext::ProcessStableStateQueue() { + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + // When run, one event can add another event to the mStableStateEvents, as + // such you can't use iterators here. + for (uint32_t i = 0; i < mStableStateEvents.Length(); ++i) { + nsCOMPtr event = std::move(mStableStateEvents[i]); + event->Run(); + } + + mStableStateEvents.Clear(); + mDoingStableStates = false; +} + +void CycleCollectedJSContext::CleanupIDBTransactions(uint32_t aRecursionDepth) { + MOZ_ASSERT(mJSContext); + MOZ_RELEASE_ASSERT(!mDoingStableStates); + mDoingStableStates = true; + + nsTArray localQueue = + std::move(mPendingIDBTransactions); + + localQueue.RemoveLastElements( + localQueue.end() - + std::remove_if(localQueue.begin(), localQueue.end(), + [aRecursionDepth](PendingIDBTransactionData& data) { + if (data.mRecursionDepth != aRecursionDepth) { + return false; + } + + { + nsCOMPtr transaction = + std::move(data.mTransaction); + transaction->Run(); + } + + return true; + })); + + // If mPendingIDBTransactions has events in it now, they were added from + // something we called, so they belong at the end of the queue. + localQueue.AppendElements(std::move(mPendingIDBTransactions)); + mPendingIDBTransactions = std::move(localQueue); + mDoingStableStates = false; +} + +void CycleCollectedJSContext::BeforeProcessTask(bool aMightBlock) { + // If ProcessNextEvent was called during a microtask callback, we + // must process any pending microtasks before blocking in the event loop, + // otherwise we may deadlock until an event enters the queue later. + if (aMightBlock && PerformMicroTaskCheckPoint()) { + // If any microtask was processed, we post a dummy event in order to + // force the ProcessNextEvent call not to block. This is required + // to support nested event loops implemented using a pattern like + // "while (condition) thread.processNextEvent(true)", in case the + // condition is triggered here by a Promise "then" callback. + NS_DispatchToMainThread(new Runnable("BeforeProcessTask")); + } +} + +void CycleCollectedJSContext::AfterProcessTask(uint32_t aRecursionDepth) { + MOZ_ASSERT(mJSContext); + + // See HTML 6.1.4.2 Processing model + + // Step 4.1: Execute microtasks. + PerformMicroTaskCheckPoint(); + + // Step 4.2 Execute any events that were waiting for a stable state. + ProcessStableStateQueue(); + + // This should be a fast test so that it won't affect the next task + // processing. + MaybePokeGC(); +} + +void CycleCollectedJSContext::AfterProcessMicrotasks() { + MOZ_ASSERT(mJSContext); + // Notify unhandled promise rejections: + // https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises + if (mAboutToBeNotifiedRejectedPromises.Length()) { + RefPtr runnable = new NotifyUnhandledRejections( + std::move(mAboutToBeNotifiedRejectedPromises)); + NS_DispatchToCurrentThread(runnable); + } + // Cleanup Indexed Database transactions: + // https://html.spec.whatwg.org/multipage/webappapis.html#perform-a-microtask-checkpoint + CleanupIDBTransactions(RecursionDepth()); + + // Clear kept alive objects in JS WeakRef. + // https://whatpr.org/html/4571/webappapis.html#perform-a-microtask-checkpoint + // + // ECMAScript implementations are expected to call ClearKeptObjects when a + // synchronous sequence of ECMAScript execution completes. + // + // https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects + JS::ClearKeptObjects(mJSContext); +} + +void CycleCollectedJSContext::MaybePokeGC() { + // Worker-compatible check to see if we want to do an idle-time minor + // GC. + class IdleTimeGCTaskRunnable : public mozilla::IdleRunnable { + public: + using mozilla::IdleRunnable::IdleRunnable; + + public: + IdleTimeGCTaskRunnable() : IdleRunnable("IdleTimeGCTask") {} + + NS_IMETHOD Run() override { + CycleCollectedJSRuntime* ccrt = CycleCollectedJSRuntime::Get(); + if (ccrt) { + ccrt->RunIdleTimeGCTask(); + } + return NS_OK; + } + }; + + if (Runtime()->IsIdleGCTaskNeeded()) { + nsCOMPtr gc_task = new IdleTimeGCTaskRunnable(); + NS_DispatchToCurrentThreadQueue(gc_task.forget(), EventQueuePriority::Idle); + Runtime()->SetPendingIdleGCTask(); + } +} + +uint32_t CycleCollectedJSContext::RecursionDepth() const { + // Debugger interruptions are included in the recursion depth so that debugger + // microtask checkpoints do not run IDB transactions which were initiated + // before the interruption. + return mOwningThread->RecursionDepth() + mDebuggerRecursionDepth; +} + +void CycleCollectedJSContext::RunInStableState( + already_AddRefed&& aRunnable) { + MOZ_ASSERT(mJSContext); + mStableStateEvents.AppendElement(std::move(aRunnable)); +} + +void CycleCollectedJSContext::AddPendingIDBTransaction( + already_AddRefed&& aTransaction) { + MOZ_ASSERT(mJSContext); + + PendingIDBTransactionData data; + data.mTransaction = aTransaction; + + MOZ_ASSERT(mOwningThread); + data.mRecursionDepth = RecursionDepth(); + + // There must be an event running to get here. +#ifndef MOZ_WIDGET_COCOA + MOZ_ASSERT(data.mRecursionDepth > mBaseRecursionDepth); +#else + // XXX bug 1261143 + // Recursion depth should be greater than mBaseRecursionDepth, + // or the runnable will stay in the queue forever. + if (data.mRecursionDepth <= mBaseRecursionDepth) { + data.mRecursionDepth = mBaseRecursionDepth + 1; + } +#endif + + mPendingIDBTransactions.AppendElement(std::move(data)); +} + +void CycleCollectedJSContext::DispatchToMicroTask( + already_AddRefed aRunnable) { + RefPtr runnable(aRunnable); + + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(runnable); + + JS::JobQueueMayNotBeEmpty(Context()); + + LogMicroTaskRunnable::LogDispatch(runnable.get()); + mPendingMicroTaskRunnables.push_back(std::move(runnable)); +} + +class AsyncMutationHandler final : public mozilla::Runnable { + public: + AsyncMutationHandler() : mozilla::Runnable("AsyncMutationHandler") {} + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See + // bug 1535398. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + NS_IMETHOD Run() override { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->PerformMicroTaskCheckPoint(); + } + return NS_OK; + } +}; + +SuppressedMicroTasks::SuppressedMicroTasks(CycleCollectedJSContext* aContext) + : mContext(aContext), + mSuppressionGeneration(aContext->mSuppressionGeneration) {} + +bool SuppressedMicroTasks::Suppressed() { + if (mSuppressionGeneration == mContext->mSuppressionGeneration) { + return true; + } + + for (std::deque>::reverse_iterator it = + mSuppressedMicroTaskRunnables.rbegin(); + it != mSuppressedMicroTaskRunnables.rend(); ++it) { + mContext->GetMicroTaskQueue().push_front(*it); + } + mContext->mSuppressedMicroTasks = nullptr; + + return false; +} + +bool CycleCollectedJSContext::PerformMicroTaskCheckPoint(bool aForce) { + if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) { + AfterProcessMicrotasks(); + // Nothing to do, return early. + return false; + } + + uint32_t currentDepth = RecursionDepth(); + if (mMicroTaskRecursionDepth >= currentDepth && !aForce) { + // We are already executing microtasks for the current recursion depth. + return false; + } + + if (mTargetedMicroTaskRecursionDepth != 0 && + mTargetedMicroTaskRecursionDepth + mDebuggerRecursionDepth != + currentDepth) { + return false; + } + + if (NS_IsMainThread() && !nsContentUtils::IsSafeToRunScript()) { + // Special case for main thread where DOM mutations may happen when + // it is not safe to run scripts. + nsContentUtils::AddScriptRunner(new AsyncMutationHandler()); + return false; + } + + mozilla::AutoRestore restore(mMicroTaskRecursionDepth); + MOZ_ASSERT(aForce ? currentDepth == 0 : currentDepth > 0); + mMicroTaskRecursionDepth = currentDepth; + + AUTO_PROFILER_TRACING_MARKER("JS", "Perform microtasks", JS); + + bool didProcess = false; + AutoSlowOperation aso; + + for (;;) { + RefPtr runnable; + if (!mDebuggerMicroTaskQueue.empty()) { + runnable = std::move(mDebuggerMicroTaskQueue.front()); + mDebuggerMicroTaskQueue.pop_front(); + } else if (!mPendingMicroTaskRunnables.empty()) { + runnable = std::move(mPendingMicroTaskRunnables.front()); + mPendingMicroTaskRunnables.pop_front(); + } else { + break; + } + + if (runnable->Suppressed()) { + // Microtasks in worker shall never be suppressed. + // Otherwise, mPendingMicroTaskRunnables will be replaced later with + // all suppressed tasks in mDebuggerMicroTaskQueue unexpectedly. + MOZ_ASSERT(NS_IsMainThread()); + JS::JobQueueMayNotBeEmpty(Context()); + if (runnable != mSuppressedMicroTasks) { + if (!mSuppressedMicroTasks) { + mSuppressedMicroTasks = new SuppressedMicroTasks(this); + } + mSuppressedMicroTasks->mSuppressedMicroTaskRunnables.push_back( + runnable); + } + } else { + if (mPendingMicroTaskRunnables.empty() && + mDebuggerMicroTaskQueue.empty() && !mSuppressedMicroTasks) { + JS::JobQueueIsEmpty(Context()); + } + didProcess = true; + + LogMicroTaskRunnable::Run log(runnable.get()); + runnable->Run(aso); + runnable = nullptr; + } + } + + // Put back the suppressed microtasks so that they will be run later. + // Note, it is possible that we end up keeping these suppressed tasks around + // for some time, but no longer than spinning the event loop nestedly + // (sync XHR, alert, etc.) + if (mSuppressedMicroTasks) { + mPendingMicroTaskRunnables.push_back(mSuppressedMicroTasks); + } + + AfterProcessMicrotasks(); + + return didProcess; +} + +void CycleCollectedJSContext::PerformDebuggerMicroTaskCheckpoint() { + // Don't do normal microtask handling checks here, since whoever is calling + // this method is supposed to know what they are doing. + + AutoSlowOperation aso; + for (;;) { + // For a debugger microtask checkpoint, we always use the debugger microtask + // queue. + std::deque>* microtaskQueue = + &GetDebuggerMicroTaskQueue(); + + if (microtaskQueue->empty()) { + break; + } + + RefPtr runnable = std::move(microtaskQueue->front()); + MOZ_ASSERT(runnable); + + LogMicroTaskRunnable::Run log(runnable.get()); + + // This function can re-enter, so we remove the element before calling. + microtaskQueue->pop_front(); + + if (mPendingMicroTaskRunnables.empty() && mDebuggerMicroTaskQueue.empty()) { + JS::JobQueueIsEmpty(Context()); + } + runnable->Run(aso); + runnable = nullptr; + } + + AfterProcessMicrotasks(); +} + +NS_IMETHODIMP CycleCollectedJSContext::NotifyUnhandledRejections::Run() { + for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) { + CycleCollectedJSContext* cccx = CycleCollectedJSContext::Get(); + NS_ENSURE_STATE(cccx); + + RefPtr& promise = mUnhandledRejections[i]; + if (!promise) { + continue; + } + + JS::RootingContext* cx = cccx->RootingCx(); + JS::RootedObject promiseObj(cx, promise->PromiseObj()); + MOZ_ASSERT(JS::IsPromiseObject(promiseObj)); + + // Only fire unhandledrejection if the promise is still not handled; + uint64_t promiseID = JS::GetPromiseID(promiseObj); + if (!JS::GetPromiseIsHandled(promiseObj)) { + if (nsCOMPtr target = + do_QueryInterface(promise->GetParentObject())) { + RootedDictionary init(cx); + init.mPromise = promise; + init.mReason = JS::GetPromiseResult(promiseObj); + init.mCancelable = true; + + RefPtr event = + PromiseRejectionEvent::Constructor(target, u"unhandledrejection"_ns, + init); + // We don't use the result of dispatching event here to check whether to + // report the Promise to console. + target->DispatchEvent(*event); + } + } + + cccx = CycleCollectedJSContext::Get(); + NS_ENSURE_STATE(cccx); + if (!JS::GetPromiseIsHandled(promiseObj)) { + DebugOnly isFound = + cccx->mPendingUnhandledRejections.Remove(promiseID); + MOZ_ASSERT(isFound); + } + + // If a rejected promise is being handled in "unhandledrejection" event + // handler, it should be removed from the table in + // PromiseRejectionTrackerCallback. + MOZ_ASSERT(!cccx->mPendingUnhandledRejections.Lookup(promiseID)); + } + return NS_OK; +} + +nsresult CycleCollectedJSContext::NotifyUnhandledRejections::Cancel() { + CycleCollectedJSContext* cccx = CycleCollectedJSContext::Get(); + NS_ENSURE_STATE(cccx); + + for (size_t i = 0; i < mUnhandledRejections.Length(); ++i) { + RefPtr& promise = mUnhandledRejections[i]; + if (!promise) { + continue; + } + + JS::RootedObject promiseObj(cccx->RootingCx(), promise->PromiseObj()); + cccx->mPendingUnhandledRejections.Remove(JS::GetPromiseID(promiseObj)); + } + return NS_OK; +} + +class FinalizationRegistryCleanup::CleanupRunnable + : public DiscardableRunnable { + public: + explicit CleanupRunnable(FinalizationRegistryCleanup* aCleanupWork) + : DiscardableRunnable("CleanupRunnable"), mCleanupWork(aCleanupWork) {} + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See + // bug 1535398. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + NS_IMETHOD Run() override { + mCleanupWork->DoCleanup(); + return NS_OK; + } + + private: + FinalizationRegistryCleanup* mCleanupWork; +}; + +FinalizationRegistryCleanup::FinalizationRegistryCleanup( + CycleCollectedJSContext* aContext) + : mContext(aContext) {} + +void FinalizationRegistryCleanup::Destroy() { + // This must happen before the CycleCollectedJSContext destructor calls + // JS_DestroyContext(). + mCallbacks.reset(); +} + +void FinalizationRegistryCleanup::Init() { + JSContext* cx = mContext->Context(); + mCallbacks.init(cx); + JS::SetHostCleanupFinalizationRegistryCallback(cx, QueueCallback, this); +} + +/* static */ +void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup, + JSObject* aIncumbentGlobal, + void* aData) { + FinalizationRegistryCleanup* cleanup = + static_cast(aData); + cleanup->QueueCallback(aDoCleanup, aIncumbentGlobal); +} + +void FinalizationRegistryCleanup::QueueCallback(JSFunction* aDoCleanup, + JSObject* aIncumbentGlobal) { + bool firstCallback = mCallbacks.empty(); + + MOZ_ALWAYS_TRUE(mCallbacks.append(Callback{aDoCleanup, aIncumbentGlobal})); + + if (firstCallback) { + RefPtr cleanup = new CleanupRunnable(this); + NS_DispatchToCurrentThread(cleanup.forget()); + } +} + +void FinalizationRegistryCleanup::DoCleanup() { + if (mCallbacks.empty()) { + return; + } + + JS::RootingContext* cx = mContext->RootingCx(); + + JS::Rooted callbacks(cx); + std::swap(callbacks.get(), mCallbacks.get()); + + for (const Callback& callback : callbacks) { + JS::ExposeObjectToActiveJS( + JS_GetFunctionObject(callback.mCallbackFunction)); + JS::ExposeObjectToActiveJS(callback.mIncumbentGlobal); + + JS::RootedObject functionObj( + cx, JS_GetFunctionObject(callback.mCallbackFunction)); + JS::RootedObject globalObj(cx, JS::GetNonCCWObjectGlobal(functionObj)); + + nsIGlobalObject* incumbentGlobal = + xpc::NativeGlobal(callback.mIncumbentGlobal); + if (!incumbentGlobal) { + continue; + } + + RefPtr cleanupCallback( + new FinalizationRegistryCleanupCallback(functionObj, globalObj, nullptr, + incumbentGlobal)); + + nsIGlobalObject* global = + xpc::NativeGlobal(cleanupCallback->CallbackPreserveColor()); + if (global) { + cleanupCallback->Call("FinalizationRegistryCleanup::DoCleanup"); + } + } +} + +void FinalizationRegistryCleanup::Callback::trace(JSTracer* trc) { + JS::TraceRoot(trc, &mCallbackFunction, "mCallbackFunction"); + JS::TraceRoot(trc, &mIncumbentGlobal, "mIncumbentGlobal"); +} + +} // namespace mozilla diff --git a/xpcom/base/CycleCollectedJSContext.h b/xpcom/base/CycleCollectedJSContext.h new file mode 100644 index 0000000000..bbe47a57a5 --- /dev/null +++ b/xpcom/base/CycleCollectedJSContext.h @@ -0,0 +1,396 @@ +/* -*- 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/. */ + +#ifndef mozilla_CycleCollectedJSContext_h +#define mozilla_CycleCollectedJSContext_h + +#include + +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/AtomList.h" +#include "mozilla/dom/Promise.h" +#include "js/GCVector.h" +#include "js/Promise.h" + +#include "nsCOMPtr.h" +#include "nsRefPtrHashtable.h" +#include "nsTArray.h" + +class nsCycleCollectionNoteRootCallback; +class nsIRunnable; +class nsThread; + +namespace mozilla { +class AutoSlowOperation; + +class CycleCollectedJSContext; +class CycleCollectedJSRuntime; + +namespace dom { +class Exception; +class WorkerJSContext; +class WorkletJSContext; +} // namespace dom + +// Contains various stats about the cycle collection. +struct CycleCollectorResults { + CycleCollectorResults() { + // Initialize here so when we increment mNumSlices the first time we're + // not using uninitialized memory. + Init(); + } + + void Init() { + mForcedGC = false; + mSuspectedAtCCStart = 0; + mMergedZones = false; + mAnyManual = false; + mVisitedRefCounted = 0; + mVisitedGCed = 0; + mFreedRefCounted = 0; + mFreedGCed = 0; + mFreedJSZones = 0; + mNumSlices = 1; + // mNumSlices is initialized to one, because we call Init() after the + // per-slice increment of mNumSlices has already occurred. + } + + bool mForcedGC; + bool mMergedZones; + // mAnyManual is true if any slice was manually triggered, and at shutdown. + bool mAnyManual; + uint32_t mSuspectedAtCCStart; + uint32_t mVisitedRefCounted; + uint32_t mVisitedGCed; + uint32_t mFreedRefCounted; + uint32_t mFreedGCed; + uint32_t mFreedJSZones; + uint32_t mNumSlices; +}; + +class MicroTaskRunnable { + public: + MicroTaskRunnable() = default; + NS_INLINE_DECL_REFCOUNTING(MicroTaskRunnable) + MOZ_CAN_RUN_SCRIPT virtual void Run(AutoSlowOperation& aAso) = 0; + virtual bool Suppressed() { return false; } + + protected: + virtual ~MicroTaskRunnable() = default; +}; + +// Store the suppressed mictotasks in another microtask so that operations +// for the microtask queue as a whole keep working. +class SuppressedMicroTasks : public MicroTaskRunnable { + public: + explicit SuppressedMicroTasks(CycleCollectedJSContext* aContext); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY void Run(AutoSlowOperation& aAso) final {} + virtual bool Suppressed(); + + CycleCollectedJSContext* mContext; + uint64_t mSuppressionGeneration; + std::deque> mSuppressedMicroTaskRunnables; +}; + +// Support for JS FinalizationRegistry objects, which allow a JS callback to be +// registered that is called when objects die. +// +// We keep a vector of functions that call back into the JS engine along +// with their associated incumbent globals, one per FinalizationRegistry object +// that has pending cleanup work. These are run in their own task. +class FinalizationRegistryCleanup { + public: + explicit FinalizationRegistryCleanup(CycleCollectedJSContext* aContext); + void Init(); + void Destroy(); + void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal); + MOZ_CAN_RUN_SCRIPT void DoCleanup(); + + private: + static void QueueCallback(JSFunction* aDoCleanup, JSObject* aIncumbentGlobal, + void* aData); + + class CleanupRunnable; + + struct Callback { + JSFunction* mCallbackFunction; + JSObject* mIncumbentGlobal; + void trace(JSTracer* trc); + }; + + // This object is part of CycleCollectedJSContext, so it's safe to have a raw + // pointer to its containing context here. + CycleCollectedJSContext* mContext; + + using CallbackVector = JS::GCVector; + JS::PersistentRooted mCallbacks; +}; + +class CycleCollectedJSContext : dom::PerThreadAtomCache, private JS::JobQueue { + friend class CycleCollectedJSRuntime; + friend class SuppressedMicroTasks; + + protected: + CycleCollectedJSContext(); + virtual ~CycleCollectedJSContext(); + + MOZ_IS_CLASS_INIT + nsresult Initialize(JSRuntime* aParentRuntime, uint32_t aMaxBytes); + + virtual CycleCollectedJSRuntime* CreateRuntime(JSContext* aCx) = 0; + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + static void PromiseRejectionTrackerCallback( + JSContext* aCx, bool aMutedErrors, JS::Handle aPromise, + JS::PromiseRejectionHandlingState state, void* aData); + + void AfterProcessMicrotasks(); + + public: + void ProcessStableStateQueue(); + + private: + void CleanupIDBTransactions(uint32_t aRecursionDepth); + + public: + virtual dom::WorkerJSContext* GetAsWorkerJSContext() { return nullptr; } + virtual dom::WorkletJSContext* GetAsWorkletJSContext() { return nullptr; } + + CycleCollectedJSRuntime* Runtime() const { + MOZ_ASSERT(mRuntime); + return mRuntime; + } + + already_AddRefed GetPendingException() const; + void SetPendingException(dom::Exception* aException); + + std::deque>& GetMicroTaskQueue(); + std::deque>& GetDebuggerMicroTaskQueue(); + + JSContext* Context() const { + MOZ_ASSERT(mJSContext); + return mJSContext; + } + + JS::RootingContext* RootingCx() const { + MOZ_ASSERT(mJSContext); + return JS::RootingContext::get(mJSContext); + } + + void SetTargetedMicroTaskRecursionDepth(uint32_t aDepth) { + mTargetedMicroTaskRecursionDepth = aDepth; + } + + void UpdateMicroTaskSuppressionGeneration() { ++mSuppressionGeneration; } + + protected: + JSContext* MaybeContext() const { return mJSContext; } + + public: + // nsThread entrypoints + // + // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate + // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now. + // But we really should! + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void BeforeProcessTask(bool aMightBlock); + // MOZ_CAN_RUN_SCRIPT_BOUNDARY so we don't need to annotate + // nsThread::ProcessNextEvent and all its callers MOZ_CAN_RUN_SCRIPT for now. + // But we really should! + MOZ_CAN_RUN_SCRIPT_BOUNDARY + virtual void AfterProcessTask(uint32_t aRecursionDepth); + + // Check whether any eager thresholds have been reached, which would mean + // an idle GC task (minor or major) would be useful. + virtual void MaybePokeGC(); + + uint32_t RecursionDepth() const; + + // Run in stable state (call through nsContentUtils) + void RunInStableState(already_AddRefed&& aRunnable); + + void AddPendingIDBTransaction(already_AddRefed&& aTransaction); + + // Get the CycleCollectedJSContext for a JSContext. + // Returns null only if Initialize() has not completed on or during + // destruction of the CycleCollectedJSContext. + static CycleCollectedJSContext* GetFor(JSContext* aCx); + + // Get the current thread's CycleCollectedJSContext. Returns null if there + // isn't one. + static CycleCollectedJSContext* Get(); + + // Queue an async microtask to the current main or worker thread. + virtual void DispatchToMicroTask( + already_AddRefed aRunnable); + + // Call EnterMicroTask when you're entering JS execution. + // Usually the best way to do this is to use nsAutoMicroTask. + void EnterMicroTask() { ++mMicroTaskLevel; } + + MOZ_CAN_RUN_SCRIPT + void LeaveMicroTask() { + if (--mMicroTaskLevel == 0) { + PerformMicroTaskCheckPoint(); + } + } + + uint32_t MicroTaskLevel() const { return mMicroTaskLevel; } + + void SetMicroTaskLevel(uint32_t aLevel) { mMicroTaskLevel = aLevel; } + + MOZ_CAN_RUN_SCRIPT + bool PerformMicroTaskCheckPoint(bool aForce = false); + + MOZ_CAN_RUN_SCRIPT + void PerformDebuggerMicroTaskCheckpoint(); + + bool IsInStableOrMetaStableState() const { return mDoingStableStates; } + + // Storage for watching rejected promises waiting for some client to + // consume their rejection. + // Promises in this list have been rejected in the last turn of the + // event loop without the rejection being handled. + // Note that this can contain nullptrs in place of promises removed because + // they're consumed before it'd be reported. + JS::PersistentRooted> + mUncaughtRejections; + + // Promises in this list have previously been reported as rejected + // (because they were in the above list), but the rejection was handled + // in the last turn of the event loop. + JS::PersistentRooted> + mConsumedRejections; + nsTArray> + mUncaughtRejectionObservers; + + virtual bool IsSystemCaller() const = 0; + + // Unused on main thread. Used by AutoJSAPI on Worker and Worklet threads. + virtual void ReportError(JSErrorReport* aReport, + JS::ConstUTF8CharsZ aToStringResult) { + MOZ_ASSERT_UNREACHABLE("Not supported"); + } + + private: + // JS::JobQueue implementation: see js/public/Promise.h. + // SpiderMonkey uses some of these methods to enqueue promise resolution jobs. + // Others protect the debuggee microtask queue from the debugger's + // interruptions; see the comments on JS::AutoDebuggerJobQueueInterruption for + // details. + JSObject* getIncumbentGlobal(JSContext* cx) override; + bool enqueuePromiseJob(JSContext* cx, JS::Handle promise, + JS::Handle job, + JS::Handle allocationSite, + JS::Handle incumbentGlobal) override; + // MOZ_CAN_RUN_SCRIPT_BOUNDARY for now so we don't have to change SpiderMonkey + // headers. The caller presumably knows this can run script (like everything + // in SpiderMonkey!) and will deal. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + void runJobs(JSContext* cx) override; + bool empty() const override; + class SavedMicroTaskQueue; + js::UniquePtr saveJobQueue(JSContext*) override; + + private: + CycleCollectedJSRuntime* mRuntime; + + JSContext* mJSContext; + + nsCOMPtr mPendingException; + nsThread* mOwningThread; // Manual refcounting to avoid include hell. + + struct PendingIDBTransactionData { + nsCOMPtr mTransaction; + uint32_t mRecursionDepth; + }; + + nsTArray> mStableStateEvents; + nsTArray mPendingIDBTransactions; + uint32_t mBaseRecursionDepth; + bool mDoingStableStates; + + // If set to none 0, microtasks will be processed only when recursion depth + // is the set value. + uint32_t mTargetedMicroTaskRecursionDepth; + + uint32_t mMicroTaskLevel; + + std::deque> mPendingMicroTaskRunnables; + std::deque> mDebuggerMicroTaskQueue; + RefPtr mSuppressedMicroTasks; + uint64_t mSuppressionGeneration; + + // How many times the debugger has interrupted execution, possibly creating + // microtask checkpoints in places that they would not normally occur. + uint32_t mDebuggerRecursionDepth; + + uint32_t mMicroTaskRecursionDepth; + + // This implements about-to-be-notified rejected promises list in the spec. + // https://html.spec.whatwg.org/multipage/webappapis.html#about-to-be-notified-rejected-promises-list + typedef nsTArray> PromiseArray; + PromiseArray mAboutToBeNotifiedRejectedPromises; + + // This is for the "outstanding rejected promises weak set" in the spec, + // https://html.spec.whatwg.org/multipage/webappapis.html#outstanding-rejected-promises-weak-set + // We use different data structure and opposite logic here to achieve the same + // effect. Basically this is used for tracking the rejected promise that does + // NOT need firing a rejectionhandled event. We will check the table to see if + // firing rejectionhandled event is required when a rejected promise is being + // handled. + // + // The rejected promise will be stored in the table if + // - it is unhandled, and + // - The unhandledrejection is not yet fired. + // + // And be removed when + // - it is handled, or + // - A unhandledrejection is fired and it isn't being handled in event + // handler. + typedef nsRefPtrHashtable PromiseHashtable; + PromiseHashtable mPendingUnhandledRejections; + + class NotifyUnhandledRejections final : public CancelableRunnable { + public: + explicit NotifyUnhandledRejections(PromiseArray&& aPromises) + : CancelableRunnable("NotifyUnhandledRejections"), + mUnhandledRejections(std::move(aPromises)) {} + + NS_IMETHOD Run() final; + + nsresult Cancel() final; + + private: + PromiseArray mUnhandledRejections; + }; + + FinalizationRegistryCleanup mFinalizationRegistryCleanup; +}; + +class MOZ_STACK_CLASS nsAutoMicroTask { + public: + nsAutoMicroTask() { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->EnterMicroTask(); + } + } + MOZ_CAN_RUN_SCRIPT ~nsAutoMicroTask() { + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + if (ccjs) { + ccjs->LeaveMicroTask(); + } + } +}; + +} // namespace mozilla + +#endif // mozilla_CycleCollectedJSContext_h diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp new file mode 100644 index 0000000000..4e2557f51e --- /dev/null +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -0,0 +1,2075 @@ +/* -*- 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/. */ + +// We're dividing JS objects into 3 categories: +// +// 1. "real" roots, held by the JS engine itself or rooted through the root +// and lock JS APIs. Roots from this category are considered black in the +// cycle collector, any cycle they participate in is uncollectable. +// +// 2. certain roots held by C++ objects that are guaranteed to be alive. +// Roots from this category are considered black in the cycle collector, +// and any cycle they participate in is uncollectable. These roots are +// traced from TraceNativeBlackRoots. +// +// 3. all other roots held by C++ objects that participate in cycle collection, +// held by us (see TraceNativeGrayRoots). Roots from this category are +// considered grey in the cycle collector; whether or not they are collected +// depends on the objects that hold them. +// +// Note that if a root is in multiple categories the fact that it is in +// category 1 or 2 that takes precedence, so it will be considered black. +// +// During garbage collection we switch to an additional mark color (gray) when +// tracing inside TraceNativeGrayRoots. This allows us to walk those roots later +// on and add all objects reachable only from them to the cycle collector. +// +// Phases: +// +// 1. marking of the roots in category 1 by having the JS GC do its marking +// 2. marking of the roots in category 2 by having the JS GC call us back +// (via JS_SetExtraGCRootsTracer) and running TraceNativeBlackRoots +// 3. marking of the roots in category 3 by +// TraceNativeGrayRootsInCollectingZones using an additional color (gray). +// 4. end of GC, GC can sweep its heap +// +// At some later point, when the cycle collector runs: +// +// 5. walk gray objects and add them to the cycle collector, cycle collect +// +// JS objects that are part of cycles the cycle collector breaks will be +// collected by the next JS GC. +// +// If WantAllTraces() is false the cycle collector will not traverse roots +// from category 1 or any JS objects held by them. Any JS objects they hold +// will already be marked by the JS GC and will thus be colored black +// themselves. Any C++ objects they hold will have a missing (untraversed) +// edge from the JS object to the C++ object and so it will be marked black +// too. This decreases the number of objects that the cycle collector has to +// deal with. +// To improve debugging, if WantAllTraces() is true all JS objects are +// traversed. + +#include "mozilla/CycleCollectedJSRuntime.h" + +#include +#include + +#include "js/Debug.h" +#include "js/RealmOptions.h" +#include "js/friend/DumpFunctions.h" // js::DumpHeap +#include "js/GCAPI.h" +#include "js/HeapAPI.h" +#include "js/Object.h" // JS::GetClass, JS::GetCompartment, JS::GetPrivate +#include "js/PropertyAndElement.h" // JS_DefineProperty +#include "js/Warnings.h" // JS::SetWarningReporter +#include "js/ShadowRealmCallbacks.h" +#include "js/SliceBudget.h" +#include "jsfriendapi.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DebuggerOnGCRunnable.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/PerfStats.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPrefs_javascript.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimelineConsumers.h" +#include "mozilla/TimelineMarker.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/AutoEntryScript.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseBinding.h" +#include "mozilla/dom/PromiseDebugging.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/ShadowRealmGlobalScope.h" +#include "mozilla/dom/RegisterShadowRealmBindings.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDOMJSUtils.h" +#include "nsExceptionHandler.h" +#include "nsJSUtils.h" +#include "nsStringBuffer.h" +#include "nsWrapperCache.h" +#include "prenv.h" + +#if defined(XP_MACOSX) +# include "nsMacUtilsImpl.h" +#endif + +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "xpcpublic.h" + +#ifdef NIGHTLY_BUILD +// For performance reasons, we make the JS Dev Error Interceptor a Nightly-only +// feature. +# define MOZ_JS_DEV_ERROR_INTERCEPTOR = 1 +#endif // NIGHTLY_BUILD + +using namespace mozilla; +using namespace mozilla::dom; + +namespace mozilla { + +struct DeferredFinalizeFunctionHolder { + DeferredFinalizeFunction run; + void* data; +}; + +class IncrementalFinalizeRunnable : public DiscardableRunnable { + typedef AutoTArray DeferredFinalizeArray; + typedef CycleCollectedJSRuntime::DeferredFinalizerTable + DeferredFinalizerTable; + + CycleCollectedJSRuntime* mRuntime; + DeferredFinalizeArray mDeferredFinalizeFunctions; + uint32_t mFinalizeFunctionToRun; + bool mReleasing; + + static const PRTime SliceMillis = 5; /* ms */ + + public: + IncrementalFinalizeRunnable(CycleCollectedJSRuntime* aRt, + DeferredFinalizerTable& aFinalizerTable); + virtual ~IncrementalFinalizeRunnable(); + + void ReleaseNow(bool aLimited); + + NS_DECL_NSIRUNNABLE +}; + +} // namespace mozilla + +struct NoteWeakMapChildrenTracer : public JS::CallbackTracer { + NoteWeakMapChildrenTracer(JSRuntime* aRt, + nsCycleCollectionNoteRootCallback& aCb) + : JS::CallbackTracer(aRt, JS::TracerKind::Callback), + mCb(aCb), + mTracedAny(false), + mMap(nullptr), + mKey(nullptr), + mKeyDelegate(nullptr) {} + void onChild(JS::GCCellPtr aThing, const char* name) override; + nsCycleCollectionNoteRootCallback& mCb; + bool mTracedAny; + JSObject* mMap; + JS::GCCellPtr mKey; + JSObject* mKeyDelegate; +}; + +void NoteWeakMapChildrenTracer::onChild(JS::GCCellPtr aThing, + const char* name) { + if (aThing.is()) { + return; + } + + if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) { + return; + } + + if (JS::IsCCTraceKind(aThing.kind())) { + mCb.NoteWeakMapping(mMap, mKey, mKeyDelegate, aThing); + mTracedAny = true; + } else { + JS::TraceChildren(this, aThing); + } +} + +struct NoteWeakMapsTracer : public js::WeakMapTracer { + NoteWeakMapsTracer(JSRuntime* aRt, nsCycleCollectionNoteRootCallback& aCccb) + : js::WeakMapTracer(aRt), mCb(aCccb), mChildTracer(aRt, aCccb) {} + void trace(JSObject* aMap, JS::GCCellPtr aKey, JS::GCCellPtr aValue) override; + nsCycleCollectionNoteRootCallback& mCb; + NoteWeakMapChildrenTracer mChildTracer; +}; + +void NoteWeakMapsTracer::trace(JSObject* aMap, JS::GCCellPtr aKey, + JS::GCCellPtr aValue) { + // If nothing that could be held alive by this entry is marked gray, return. + if ((!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) && + MOZ_LIKELY(!mCb.WantAllTraces())) { + if (!aValue || !JS::GCThingIsMarkedGrayInCC(aValue) || + aValue.is()) { + return; + } + } + + // The cycle collector can only properly reason about weak maps if it can + // reason about the liveness of their keys, which in turn requires that + // the key can be represented in the cycle collector graph. All existing + // uses of weak maps use either objects or scripts as keys, which are okay. + MOZ_ASSERT(JS::IsCCTraceKind(aKey.kind())); + + // As an emergency fallback for non-debug builds, if the key is not + // representable in the cycle collector graph, we treat it as marked. This + // can cause leaks, but is preferable to ignoring the binding, which could + // cause the cycle collector to free live objects. + if (!JS::IsCCTraceKind(aKey.kind())) { + aKey = nullptr; + } + + JSObject* kdelegate = nullptr; + if (aKey.is()) { + kdelegate = js::UncheckedUnwrapWithoutExpose(&aKey.as()); + } + + if (JS::IsCCTraceKind(aValue.kind())) { + mCb.NoteWeakMapping(aMap, aKey, kdelegate, aValue); + } else { + mChildTracer.mTracedAny = false; + mChildTracer.mMap = aMap; + mChildTracer.mKey = aKey; + mChildTracer.mKeyDelegate = kdelegate; + + if (!aValue.is()) { + JS::TraceChildren(&mChildTracer, aValue); + } + + // The delegate could hold alive the key, so report something to the CC + // if we haven't already. + if (!mChildTracer.mTracedAny && aKey && JS::GCThingIsMarkedGrayInCC(aKey) && + kdelegate) { + mCb.NoteWeakMapping(aMap, aKey, kdelegate, nullptr); + } + } +} + +// Report whether the key or value of a weak mapping entry are gray but need to +// be marked black. +static void ShouldWeakMappingEntryBeBlack(JSObject* aMap, JS::GCCellPtr aKey, + JS::GCCellPtr aValue, + bool* aKeyShouldBeBlack, + bool* aValueShouldBeBlack) { + *aKeyShouldBeBlack = false; + *aValueShouldBeBlack = false; + + // If nothing that could be held alive by this entry is marked gray, return. + bool keyMightNeedMarking = aKey && JS::GCThingIsMarkedGrayInCC(aKey); + bool valueMightNeedMarking = aValue && JS::GCThingIsMarkedGrayInCC(aValue) && + aValue.kind() != JS::TraceKind::String; + if (!keyMightNeedMarking && !valueMightNeedMarking) { + return; + } + + if (!JS::IsCCTraceKind(aKey.kind())) { + aKey = nullptr; + } + + if (keyMightNeedMarking && aKey.is()) { + JSObject* kdelegate = + js::UncheckedUnwrapWithoutExpose(&aKey.as()); + if (kdelegate && !JS::ObjectIsMarkedGray(kdelegate) && + (!aMap || !JS::ObjectIsMarkedGray(aMap))) { + *aKeyShouldBeBlack = true; + } + } + + if (aValue && JS::GCThingIsMarkedGrayInCC(aValue) && + (!aKey || !JS::GCThingIsMarkedGrayInCC(aKey)) && + (!aMap || !JS::ObjectIsMarkedGray(aMap)) && + aValue.kind() != JS::TraceKind::Shape) { + *aValueShouldBeBlack = true; + } +} + +struct FixWeakMappingGrayBitsTracer : public js::WeakMapTracer { + explicit FixWeakMappingGrayBitsTracer(JSRuntime* aRt) + : js::WeakMapTracer(aRt) {} + + void FixAll() { + do { + mAnyMarked = false; + js::TraceWeakMaps(this); + } while (mAnyMarked); + } + + void trace(JSObject* aMap, JS::GCCellPtr aKey, + JS::GCCellPtr aValue) override { + bool keyShouldBeBlack; + bool valueShouldBeBlack; + ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack, + &valueShouldBeBlack); + if (keyShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aKey)) { + mAnyMarked = true; + } + + if (valueShouldBeBlack && JS::UnmarkGrayGCThingRecursively(aValue)) { + mAnyMarked = true; + } + } + + MOZ_INIT_OUTSIDE_CTOR bool mAnyMarked; +}; + +#ifdef DEBUG +// Check whether weak maps are marked correctly according to the logic above. +struct CheckWeakMappingGrayBitsTracer : public js::WeakMapTracer { + explicit CheckWeakMappingGrayBitsTracer(JSRuntime* aRt) + : js::WeakMapTracer(aRt), mFailed(false) {} + + static bool Check(JSRuntime* aRt) { + CheckWeakMappingGrayBitsTracer tracer(aRt); + js::TraceWeakMaps(&tracer); + return !tracer.mFailed; + } + + void trace(JSObject* aMap, JS::GCCellPtr aKey, + JS::GCCellPtr aValue) override { + bool keyShouldBeBlack; + bool valueShouldBeBlack; + ShouldWeakMappingEntryBeBlack(aMap, aKey, aValue, &keyShouldBeBlack, + &valueShouldBeBlack); + + if (keyShouldBeBlack) { + fprintf(stderr, "Weak mapping key %p of map %p should be black\n", + aKey.asCell(), aMap); + mFailed = true; + } + + if (valueShouldBeBlack) { + fprintf(stderr, "Weak mapping value %p of map %p should be black\n", + aValue.asCell(), aMap); + mFailed = true; + } + } + + bool mFailed; +}; +#endif // DEBUG + +static void CheckParticipatesInCycleCollection(JS::GCCellPtr aThing, + const char* aName, + void* aClosure) { + bool* cycleCollectionEnabled = static_cast(aClosure); + + if (*cycleCollectionEnabled) { + return; + } + + if (JS::IsCCTraceKind(aThing.kind()) && JS::GCThingIsMarkedGrayInCC(aThing)) { + *cycleCollectionEnabled = true; + } +} + +NS_IMETHODIMP +JSGCThingParticipant::TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) { + auto runtime = reinterpret_cast( + reinterpret_cast(this) - + offsetof(CycleCollectedJSRuntime, mGCThingCycleCollectorGlobal)); + + JS::GCCellPtr cellPtr(aPtr, JS::GCThingTraceKind(aPtr)); + runtime->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_FULL, cellPtr, + aCb); + return NS_OK; +} + +// NB: This is only used to initialize the participant in +// CycleCollectedJSRuntime. It should never be used directly. +static JSGCThingParticipant sGCThingCycleCollectorGlobal; + +NS_IMETHODIMP +JSZoneParticipant::TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) { + auto runtime = reinterpret_cast( + reinterpret_cast(this) - + offsetof(CycleCollectedJSRuntime, mJSZoneCycleCollectorGlobal)); + + MOZ_ASSERT(!aCb.WantAllTraces()); + JS::Zone* zone = static_cast(aPtr); + + runtime->TraverseZone(zone, aCb); + return NS_OK; +} + +struct TraversalTracer : public JS::CallbackTracer { + TraversalTracer(JSRuntime* aRt, nsCycleCollectionTraversalCallback& aCb) + : JS::CallbackTracer(aRt, JS::TracerKind::Callback, + JS::TraceOptions(JS::WeakMapTraceAction::Skip, + JS::WeakEdgeTraceAction::Trace)), + mCb(aCb) {} + void onChild(JS::GCCellPtr aThing, const char* name) override; + nsCycleCollectionTraversalCallback& mCb; +}; + +void TraversalTracer::onChild(JS::GCCellPtr aThing, const char* name) { + // Checking strings and symbols for being gray is rather slow, and we don't + // need either of them for the cycle collector. + if (aThing.is() || aThing.is()) { + return; + } + + // Don't traverse non-gray objects, unless we want all traces. + if (!JS::GCThingIsMarkedGrayInCC(aThing) && !mCb.WantAllTraces()) { + return; + } + + /* + * This function needs to be careful to avoid stack overflow. Normally, when + * IsCCTraceKind is true, the recursion terminates immediately as we just add + * |thing| to the CC graph. So overflow is only possible when there are long + * or cyclic chains of non-IsCCTraceKind GC things. Places where this can + * occur use special APIs to handle such chains iteratively. + */ + if (JS::IsCCTraceKind(aThing.kind())) { + if (MOZ_UNLIKELY(mCb.WantDebugInfo())) { + char buffer[200]; + context().getEdgeName(name, buffer, sizeof(buffer)); + mCb.NoteNextEdgeName(buffer); + } + mCb.NoteJSChild(aThing); + return; + } + + // Allow re-use of this tracer inside trace callback. + JS::AutoClearTracingContext actc(this); + + if (aThing.is()) { + // The maximum depth of traversal when tracing a Shape is unbounded, due to + // the parent pointers on the shape. + JS_TraceShapeCycleCollectorChildren(this, aThing); + } else { + JS::TraceChildren(this, aThing); + } +} + +/* + * The cycle collection participant for a Zone is intended to produce the same + * results as if all of the gray GCthings in a zone were merged into a single + * node, except for self-edges. This avoids the overhead of representing all of + * the GCthings in the zone in the cycle collector graph, which should be much + * faster if many of the GCthings in the zone are gray. + * + * Zone merging should not always be used, because it is a conservative + * approximation of the true cycle collector graph that can incorrectly identify + * some garbage objects as being live. For instance, consider two cycles that + * pass through a zone, where one is garbage and the other is live. If we merge + * the entire zone, the cycle collector will think that both are alive. + * + * We don't have to worry about losing track of a garbage cycle, because any + * such garbage cycle incorrectly identified as live must contain at least one + * C++ to JS edge, and XPConnect will always add the C++ object to the CC graph. + * (This is in contrast to pure C++ garbage cycles, which must always be + * properly identified, because we clear the purple buffer during every CC, + * which may contain the last reference to a garbage cycle.) + */ + +// NB: This is only used to initialize the participant in +// CycleCollectedJSRuntime. It should never be used directly. +static const JSZoneParticipant sJSZoneCycleCollectorGlobal; + +static void JSObjectsTenuredCb(JSContext* aContext, void* aData) { + static_cast(aData)->JSObjectsTenured(); +} + +static void MozCrashWarningReporter(JSContext*, JSErrorReport*) { + MOZ_CRASH("Why is someone touching JSAPI without an AutoJSAPI?"); +} + +JSHolderMap::Entry::Entry() : Entry(nullptr, nullptr, nullptr) {} + +JSHolderMap::Entry::Entry(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone) + : mHolder(aHolder), + mTracer(aTracer) +#ifdef DEBUG + , + mZone(aZone) +#endif +{ +} + +void JSHolderMap::EntryVectorIter::Settle() { + if (Done()) { + return; + } + + Entry* entry = &mIter.Get(); + + // If the entry has been cleared, remove it and shrink the vector. + if (!entry->mHolder && !mHolderMap.RemoveEntry(mVector, entry)) { + // We removed the last entry, so reset the iterator to an empty one. + mIter = EntryVector().Iter(); + MOZ_ASSERT(Done()); + } +} + +JSHolderMap::Iter::Iter(JSHolderMap& aMap, WhichHolders aWhich) + : mHolderMap(aMap), mIter(aMap, aMap.mAnyZoneJSHolders) { + MOZ_RELEASE_ASSERT(!mHolderMap.mHasIterator); + mHolderMap.mHasIterator = true; + + // Populate vector of zones to iterate after the any-zone holders. + for (auto i = aMap.mPerZoneJSHolders.iter(); !i.done(); i.next()) { + JS::Zone* zone = i.get().key(); + if (aWhich == AllHolders || JS::NeedGrayRootsForZone(i.get().key())) { + MOZ_ALWAYS_TRUE(mZones.append(zone)); + } + } + + Settle(); +} + +void JSHolderMap::Iter::Settle() { + while (mIter.Done()) { + if (mZone && mIter.Vector().IsEmpty()) { + mHolderMap.mPerZoneJSHolders.remove(mZone); + } + + mZone = nullptr; + if (mZones.empty()) { + break; + } + + mZone = mZones.popCopy(); + EntryVector& vector = *mHolderMap.mPerZoneJSHolders.lookup(mZone)->value(); + new (&mIter) EntryVectorIter(mHolderMap, vector); + } +} + +void JSHolderMap::Iter::UpdateForRemovals() { + mIter.Settle(); + Settle(); +} + +JSHolderMap::JSHolderMap() : mJSHolderMap(256) {} + +bool JSHolderMap::RemoveEntry(EntryVector& aJSHolders, Entry* aEntry) { + MOZ_ASSERT(aEntry); + MOZ_ASSERT(!aEntry->mHolder); + + // Remove all dead entries from the end of the vector. + while (!aJSHolders.GetLast().mHolder && &aJSHolders.GetLast() != aEntry) { + aJSHolders.PopLast(); + } + + // Swap the element we want to remove with the last one and update the hash + // table. + Entry* lastEntry = &aJSHolders.GetLast(); + if (aEntry != lastEntry) { + MOZ_ASSERT(lastEntry->mHolder); + *aEntry = *lastEntry; + MOZ_ASSERT(mJSHolderMap.has(aEntry->mHolder)); + MOZ_ALWAYS_TRUE(mJSHolderMap.put(aEntry->mHolder, aEntry)); + } + + aJSHolders.PopLast(); + + // Return whether aEntry is still in the vector. + return aEntry != lastEntry; +} + +bool JSHolderMap::Has(void* aHolder) const { return mJSHolderMap.has(aHolder); } + +nsScriptObjectTracer* JSHolderMap::Get(void* aHolder) const { + auto ptr = mJSHolderMap.lookup(aHolder); + if (!ptr) { + return nullptr; + } + + Entry* entry = ptr->value(); + MOZ_ASSERT(entry->mHolder == aHolder); + return entry->mTracer; +} + +nsScriptObjectTracer* JSHolderMap::Extract(void* aHolder) { + MOZ_ASSERT(aHolder); + + auto ptr = mJSHolderMap.lookup(aHolder); + if (!ptr) { + return nullptr; + } + + Entry* entry = ptr->value(); + MOZ_ASSERT(entry->mHolder == aHolder); + nsScriptObjectTracer* tracer = entry->mTracer; + + // Clear the entry's contents. It will be removed the next time iteration + // visits this entry. + *entry = Entry(); + + mJSHolderMap.remove(ptr); + + return tracer; +} + +void JSHolderMap::Put(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone) { + MOZ_ASSERT(aHolder); + MOZ_ASSERT(aTracer); + + // Don't associate multi-zone holders with a zone, even if one is supplied. + if (!aTracer->IsSingleZoneJSHolder()) { + aZone = nullptr; + } + + auto ptr = mJSHolderMap.lookupForAdd(aHolder); + if (ptr) { + Entry* entry = ptr->value(); +#ifdef DEBUG + MOZ_ASSERT(entry->mHolder == aHolder); + MOZ_ASSERT(entry->mTracer == aTracer, + "Don't call HoldJSObjects in superclass ctors"); + if (aZone) { + if (entry->mZone) { + MOZ_ASSERT(entry->mZone == aZone); + } else { + entry->mZone = aZone; + } + } +#endif + entry->mTracer = aTracer; + return; + } + + EntryVector* vector = &mAnyZoneJSHolders; + if (aZone) { + auto ptr = mPerZoneJSHolders.lookupForAdd(aZone); + if (!ptr) { + MOZ_ALWAYS_TRUE( + mPerZoneJSHolders.add(ptr, aZone, MakeUnique())); + } + vector = ptr->value().get(); + } + + vector->InfallibleAppend(Entry{aHolder, aTracer, aZone}); + MOZ_ALWAYS_TRUE(mJSHolderMap.add(ptr, aHolder, &vector->GetLast())); +} + +size_t JSHolderMap::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + + // We're deliberately not measuring anything hanging off the entries in + // mJSHolders. + n += mJSHolderMap.shallowSizeOfExcludingThis(aMallocSizeOf); + n += mAnyZoneJSHolders.SizeOfExcludingThis(aMallocSizeOf); + n += mPerZoneJSHolders.shallowSizeOfExcludingThis(aMallocSizeOf); + for (auto i = mPerZoneJSHolders.iter(); !i.done(); i.next()) { + n += i.get().value()->SizeOfExcludingThis(aMallocSizeOf); + } + + return n; +} + +static bool InitializeShadowRealm(JSContext* aCx, + JS::Handle aGlobal) { + MOZ_ASSERT(StaticPrefs::javascript_options_experimental_shadow_realms()); + + JSAutoRealm ar(aCx, aGlobal); + return dom::RegisterShadowRealmBindings(aCx, aGlobal); +} + +CycleCollectedJSRuntime::CycleCollectedJSRuntime(JSContext* aCx) + : mContext(nullptr), + mGCThingCycleCollectorGlobal(sGCThingCycleCollectorGlobal), + mJSZoneCycleCollectorGlobal(sJSZoneCycleCollectorGlobal), + mJSRuntime(JS_GetRuntime(aCx)), + mHasPendingIdleGCTask(false), + mPrevGCSliceCallback(nullptr), + mPrevGCNurseryCollectionCallback(nullptr), + mOutOfMemoryState(OOMState::OK), + mLargeAllocationFailureState(OOMState::OK) +#ifdef DEBUG + , + mShutdownCalled(false) +#endif +{ + MOZ_COUNT_CTOR(CycleCollectedJSRuntime); + MOZ_ASSERT(aCx); + MOZ_ASSERT(mJSRuntime); + +#if defined(XP_MACOSX) + if (!XRE_IsParentProcess()) { + nsMacUtilsImpl::EnableTCSMIfAvailable(); + } +#endif + + if (!JS_AddExtraGCRootsTracer(aCx, TraceBlackJS, this)) { + MOZ_CRASH("JS_AddExtraGCRootsTracer failed"); + } + JS_SetGrayGCRootsTracer(aCx, TraceGrayJS, this); + JS_SetGCCallback(aCx, GCCallback, this); + mPrevGCSliceCallback = JS::SetGCSliceCallback(aCx, GCSliceCallback); + + if (NS_IsMainThread()) { + // We would like to support all threads here, but the way timeline consumers + // are set up currently, you can either add a marker for one specific + // docshell, or for every consumer globally. We would like to add a marker + // for every consumer observing anything on this thread, but that is not + // currently possible. For now, add global markers only when we are on the + // main thread, since the UI for this tracing data only displays data + // relevant to the main-thread. + mPrevGCNurseryCollectionCallback = + JS::SetGCNurseryCollectionCallback(aCx, GCNurseryCollectionCallback); + } + + JS_SetObjectsTenuredCallback(aCx, JSObjectsTenuredCb, this); + JS::SetOutOfMemoryCallback(aCx, OutOfMemoryCallback, this); + JS::SetWaitCallback(mJSRuntime, BeforeWaitCallback, AfterWaitCallback, + sizeof(dom::AutoYieldJSThreadExecution)); + JS::SetWarningReporter(aCx, MozCrashWarningReporter); + JS::SetShadowRealmInitializeGlobalCallback(aCx, InitializeShadowRealm); + JS::SetShadowRealmGlobalCreationCallback(aCx, dom::NewShadowRealmGlobal); + + js::AutoEnterOOMUnsafeRegion::setAnnotateOOMAllocationSizeCallback( + CrashReporter::AnnotateOOMAllocationSize); + + static js::DOMCallbacks DOMcallbacks = {InstanceClassHasProtoAtDepth}; + SetDOMCallbacks(aCx, &DOMcallbacks); + js::SetScriptEnvironmentPreparer(aCx, &mEnvironmentPreparer); + + JS::dbg::SetDebuggerMallocSizeOf(aCx, moz_malloc_size_of); + +#ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR + JS_SetErrorInterceptorCallback(mJSRuntime, &mErrorInterceptor); +#endif // MOZ_JS_DEV_ERROR_INTERCEPTOR + + JS_SetDestroyZoneCallback(aCx, OnZoneDestroyed); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +class JSLeakTracer : public JS::CallbackTracer { + public: + explicit JSLeakTracer(JSRuntime* aRuntime) + : JS::CallbackTracer(aRuntime, JS::TracerKind::Callback, + JS::WeakMapTraceAction::TraceKeysAndValues) {} + + private: + void onChild(JS::GCCellPtr thing, const char* name) override { + const char* kindName = JS::GCTraceKindToAscii(thing.kind()); + size_t size = JS::GCTraceKindSize(thing.kind()); + MOZ_LOG_CTOR(thing.asCell(), kindName, size); + } +}; +#endif + +void CycleCollectedJSRuntime::Shutdown(JSContext* cx) { +#ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR + mErrorInterceptor.Shutdown(mJSRuntime); +#endif // MOZ_JS_DEV_ERROR_INTERCEPTOR + + // There should not be any roots left to trace at this point. Ensure any that + // remain are flagged as leaks. +#ifdef NS_BUILD_REFCNT_LOGGING + JSLeakTracer tracer(Runtime()); + TraceNativeBlackRoots(&tracer); + TraceAllNativeGrayRoots(&tracer); +#endif + +#ifdef DEBUG + mShutdownCalled = true; +#endif + + JS_SetDestroyZoneCallback(cx, nullptr); +} + +CycleCollectedJSRuntime::~CycleCollectedJSRuntime() { + MOZ_COUNT_DTOR(CycleCollectedJSRuntime); + MOZ_ASSERT(!mDeferredFinalizerTable.Count()); + MOZ_ASSERT(!mFinalizeRunnable); + MOZ_ASSERT(mShutdownCalled); +} + +void CycleCollectedJSRuntime::SetContext(CycleCollectedJSContext* aContext) { + MOZ_ASSERT(!mContext || !aContext, "Don't replace the context!"); + mContext = aContext; +} + +size_t CycleCollectedJSRuntime::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + return mJSHolders.SizeOfExcludingThis(aMallocSizeOf); +} + +void CycleCollectedJSRuntime::UnmarkSkippableJSHolders() { + for (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) { + entry->mTracer->CanSkip(entry->mHolder, true); + } +} + +void CycleCollectedJSRuntime::DescribeGCThing( + bool aIsMarked, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const { + if (!aCb.WantDebugInfo()) { + aCb.DescribeGCedNode(aIsMarked, "JS Object"); + return; + } + + char name[72]; + uint64_t compartmentAddress = 0; + if (aThing.is()) { + JSObject* obj = &aThing.as(); + compartmentAddress = (uint64_t)JS::GetCompartment(obj); + const JSClass* clasp = JS::GetClass(obj); + + // Give the subclass a chance to do something + if (DescribeCustomObjects(obj, clasp, name)) { + // Nothing else to do! + } else if (js::IsFunctionObject(obj)) { + JSFunction* fun = JS_GetObjectFunction(obj); + JSString* str = JS_GetFunctionDisplayId(fun); + if (str) { + JSLinearString* linear = JS_ASSERT_STRING_IS_LINEAR(str); + nsAutoString chars; + AssignJSLinearString(chars, linear); + NS_ConvertUTF16toUTF8 fname(chars); + SprintfLiteral(name, "JS Object (Function - %s)", fname.get()); + } else { + SprintfLiteral(name, "JS Object (Function)"); + } + } else { + SprintfLiteral(name, "JS Object (%s)", clasp->name); + } + } else { + SprintfLiteral(name, "%s", JS::GCTraceKindToAscii(aThing.kind())); + } + + // Disable printing global for objects while we figure out ObjShrink fallout. + aCb.DescribeGCedNode(aIsMarked, name, compartmentAddress); +} + +void CycleCollectedJSRuntime::NoteGCThingJSChildren( + JS::GCCellPtr aThing, nsCycleCollectionTraversalCallback& aCb) const { + TraversalTracer trc(mJSRuntime, aCb); + JS::TraceChildren(&trc, aThing); +} + +void CycleCollectedJSRuntime::NoteGCThingXPCOMChildren( + const JSClass* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const { + MOZ_ASSERT(aClasp); + MOZ_ASSERT(aClasp == JS::GetClass(aObj)); + + JS::Rooted obj(RootingCx(), aObj); + + if (NoteCustomGCThingXPCOMChildren(aClasp, obj, aCb)) { + // Nothing else to do! + return; + } + + // XXX This test does seem fragile, we should probably allowlist classes + // that do hold a strong reference, but that might not be possible. + if (aClasp->slot0IsISupports()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "JS::GetObjectISupports(obj)"); + aCb.NoteXPCOMChild(JS::GetObjectISupports(obj)); + return; + } + + const DOMJSClass* domClass = GetDOMClass(aClasp); + if (domClass) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "UnwrapDOMObject(obj)"); + // It's possible that our object is an unforgeable holder object, in + // which case it doesn't actually have a C++ DOM object associated with + // it. Use UnwrapPossiblyNotInitializedDOMObject, which produces null in + // that case, since NoteXPCOMChild/NoteNativeChild are null-safe. + if (domClass->mDOMObjectIsISupports) { + aCb.NoteXPCOMChild( + UnwrapPossiblyNotInitializedDOMObject(obj)); + } else if (domClass->mParticipant) { + aCb.NoteNativeChild(UnwrapPossiblyNotInitializedDOMObject(obj), + domClass->mParticipant); + } + return; + } + + if (IsRemoteObjectProxy(obj)) { + auto handler = + static_cast(js::GetProxyHandler(obj)); + return handler->NoteChildren(obj, aCb); + } + + JS::Value value = js::MaybeGetScriptPrivate(obj); + if (!value.isUndefined()) { + aCb.NoteXPCOMChild(static_cast(value.toPrivate())); + } +} + +void CycleCollectedJSRuntime::TraverseGCThing( + TraverseSelect aTs, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) { + bool isMarkedGray = JS::GCThingIsMarkedGrayInCC(aThing); + + if (aTs == TRAVERSE_FULL) { + DescribeGCThing(!isMarkedGray, aThing, aCb); + } + + // If this object is alive, then all of its children are alive. For JS + // objects, the black-gray invariant ensures the children are also marked + // black. For C++ objects, the ref count from this object will keep them + // alive. Thus we don't need to trace our children, unless we are debugging + // using WantAllTraces. + if (!isMarkedGray && !aCb.WantAllTraces()) { + return; + } + + if (aTs == TRAVERSE_FULL) { + NoteGCThingJSChildren(aThing, aCb); + } + + if (aThing.is()) { + JSObject* obj = &aThing.as(); + NoteGCThingXPCOMChildren(JS::GetClass(obj), obj, aCb); + } +} + +struct TraverseObjectShimClosure { + nsCycleCollectionTraversalCallback& cb; + CycleCollectedJSRuntime* self; +}; + +void CycleCollectedJSRuntime::TraverseZone( + JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb) { + /* + * We treat the zone as being gray. We handle non-gray GCthings in the + * zone by not reporting their children to the CC. The black-gray invariant + * ensures that any JS children will also be non-gray, and thus don't need to + * be added to the graph. For C++ children, not representing the edge from the + * non-gray JS GCthings to the C++ object will keep the child alive. + * + * We don't allow zone merging in a WantAllTraces CC, because then these + * assumptions don't hold. + */ + aCb.DescribeGCedNode(false, "JS Zone"); + + /* + * Every JS child of everything in the zone is either in the zone + * or is a cross-compartment wrapper. In the former case, we don't need to + * represent these edges in the CC graph because JS objects are not ref + * counted. In the latter case, the JS engine keeps a map of these wrappers, + * which we iterate over. Edges between compartments in the same zone will add + * unnecessary loop edges to the graph (bug 842137). + */ + TraversalTracer trc(mJSRuntime, aCb); + js::TraceGrayWrapperTargets(&trc, aZone); + + /* + * To find C++ children of things in the zone, we scan every JS Object in + * the zone. Only JS Objects can have C++ children. + */ + TraverseObjectShimClosure closure = {aCb, this}; + js::IterateGrayObjects(aZone, TraverseObjectShim, &closure); +} + +/* static */ +void CycleCollectedJSRuntime::TraverseObjectShim( + void* aData, JS::GCCellPtr aThing, const JS::AutoRequireNoGC& nogc) { + TraverseObjectShimClosure* closure = + static_cast(aData); + + MOZ_ASSERT(aThing.is()); + closure->self->TraverseGCThing(CycleCollectedJSRuntime::TRAVERSE_CPP, aThing, + closure->cb); +} + +void CycleCollectedJSRuntime::TraverseNativeRoots( + nsCycleCollectionNoteRootCallback& aCb) { + // NB: This is here just to preserve the existing XPConnect order. I doubt it + // would hurt to do this after the JS holders. + TraverseAdditionalNativeRoots(aCb); + + for (JSHolderMap::Iter entry(mJSHolders); !entry.Done(); entry.Next()) { + void* holder = entry->mHolder; + nsScriptObjectTracer* tracer = entry->mTracer; + + bool noteRoot = false; + if (MOZ_UNLIKELY(aCb.WantAllTraces())) { + noteRoot = true; + } else { + tracer->Trace(holder, + TraceCallbackFunc(CheckParticipatesInCycleCollection), + ¬eRoot); + } + + if (noteRoot) { + aCb.NoteNativeRoot(holder, tracer); + } + } +} + +/* static */ +void CycleCollectedJSRuntime::TraceBlackJS(JSTracer* aTracer, void* aData) { + CycleCollectedJSRuntime* self = static_cast(aData); + + self->TraceNativeBlackRoots(aTracer); +} + +/* static */ +bool CycleCollectedJSRuntime::TraceGrayJS(JSTracer* aTracer, + js::SliceBudget& budget, + void* aData) { + CycleCollectedJSRuntime* self = static_cast(aData); + + // Mark these roots as gray so the CC can walk them later. + + JSHolderMap::WhichHolders which = JSHolderMap::AllHolders; + + // Only trace holders in collecting zones when marking, except if we are + // collecting the atoms zone since any holder may point into that zone. + if (aTracer->isMarkingTracer() && + !JS::AtomsZoneIsCollecting(self->Runtime())) { + which = JSHolderMap::HoldersRequiredForGrayMarking; + } + + return self->TraceNativeGrayRoots(aTracer, which, budget); +} + +/* static */ +void CycleCollectedJSRuntime::GCCallback(JSContext* aContext, + JSGCStatus aStatus, + JS::GCReason aReason, void* aData) { + CycleCollectedJSRuntime* self = static_cast(aData); + + MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext); + MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self); + + self->OnGC(aContext, aStatus, aReason); +} + +/* static */ +void CycleCollectedJSRuntime::GCSliceCallback(JSContext* aContext, + JS::GCProgress aProgress, + const JS::GCDescription& aDesc) { + CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext); + + if (profiler_thread_is_being_profiled_for_markers()) { + if (aProgress == JS::GC_CYCLE_END) { + struct GCMajorMarker { + static constexpr mozilla::Span MarkerTypeName() { + return mozilla::MakeStringSpan("GCMajor"); + } + static void StreamJSONMarkerData( + mozilla::baseprofiler::SpliceableJSONWriter& aWriter, + const mozilla::ProfilerString8View& aTimingJSON) { + if (aTimingJSON.Length() != 0) { + aWriter.SplicedJSONProperty("timings", aTimingJSON); + } else { + aWriter.NullProperty("timings"); + } + } + static mozilla::MarkerSchema MarkerTypeDisplay() { + using MS = mozilla::MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable, + MS::Location::TimelineMemory}; + schema.AddStaticLabelValue( + "Description", + "Summary data for an entire major GC, encompassing a set of " + "incremental slices. The main thread is not blocked for the " + "entire major GC interval, only for the individual slices."); + // No display instructions here, there is special handling in the + // front-end. + return schema; + } + }; + + profiler_add_marker("GCMajor", baseprofiler::category::GCCC, + MarkerTiming::Interval(aDesc.startTime(aContext), + aDesc.endTime(aContext)), + GCMajorMarker{}, + ProfilerString8View::WrapNullTerminatedString( + aDesc.formatJSONProfiler(aContext).get())); + } else if (aProgress == JS::GC_SLICE_END) { + struct GCSliceMarker { + static constexpr mozilla::Span MarkerTypeName() { + return mozilla::MakeStringSpan("GCSlice"); + } + static void StreamJSONMarkerData( + mozilla::baseprofiler::SpliceableJSONWriter& aWriter, + const mozilla::ProfilerString8View& aTimingJSON) { + if (aTimingJSON.Length() != 0) { + aWriter.SplicedJSONProperty("timings", aTimingJSON); + } else { + aWriter.NullProperty("timings"); + } + } + static mozilla::MarkerSchema MarkerTypeDisplay() { + using MS = mozilla::MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable, + MS::Location::TimelineMemory}; + schema.AddStaticLabelValue( + "Description", + "One slice of an incremental garbage collection (GC). The main " + "thread is blocked during this time."); + // No display instructions here, there is special handling in the + // front-end. + return schema; + } + }; + + profiler_add_marker("GCSlice", baseprofiler::category::GCCC, + MarkerTiming::Interval(aDesc.lastSliceStart(aContext), + aDesc.lastSliceEnd(aContext)), + GCSliceMarker{}, + ProfilerString8View::WrapNullTerminatedString( + aDesc.sliceToJSONProfiler(aContext).get())); + } + } + + if (aProgress == JS::GC_CYCLE_END && + JS::dbg::FireOnGarbageCollectionHookRequired(aContext)) { + JS::GCReason reason = aDesc.reason_; + Unused << NS_WARN_IF( + NS_FAILED(DebuggerOnGCRunnable::Enqueue(aContext, aDesc)) && + reason != JS::GCReason::SHUTDOWN_CC && + reason != JS::GCReason::DESTROY_RUNTIME && + reason != JS::GCReason::XPCONNECT_SHUTDOWN); + } + + if (self->mPrevGCSliceCallback) { + self->mPrevGCSliceCallback(aContext, aProgress, aDesc); + } +} + +class MinorGCMarker : public TimelineMarker { + private: + JS::GCReason mReason; + + public: + MinorGCMarker(MarkerTracingType aTracingType, JS::GCReason aReason) + : TimelineMarker("MinorGC", aTracingType, MarkerStackRequest::NO_STACK), + mReason(aReason) { + MOZ_ASSERT(aTracingType == MarkerTracingType::START || + aTracingType == MarkerTracingType::END); + } + + MinorGCMarker(JS::GCNurseryProgress aProgress, JS::GCReason aReason) + : TimelineMarker( + "MinorGC", + aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START + ? MarkerTracingType::START + : MarkerTracingType::END, + MarkerStackRequest::NO_STACK), + mReason(aReason) {} + + virtual void AddDetails(JSContext* aCx, + dom::ProfileTimelineMarker& aMarker) override { + TimelineMarker::AddDetails(aCx, aMarker); + + if (GetTracingType() == MarkerTracingType::START) { + auto reason = JS::ExplainGCReason(mReason); + aMarker.mCauseName.Construct(NS_ConvertUTF8toUTF16(reason)); + } + } + + virtual UniquePtr Clone() override { + auto clone = MakeUnique(GetTracingType(), mReason); + clone->SetCustomTime(GetTime()); + return UniquePtr(std::move(clone)); + } +}; + +/* static */ +void CycleCollectedJSRuntime::GCNurseryCollectionCallback( + JSContext* aContext, JS::GCNurseryProgress aProgress, + JS::GCReason aReason) { + CycleCollectedJSRuntime* self = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext); + MOZ_ASSERT(NS_IsMainThread()); + + if (!TimelineConsumers::IsEmpty()) { + UniquePtr abstractMarker( + MakeUnique(aProgress, aReason)); + TimelineConsumers::AddMarkerForAllObservedDocShells(abstractMarker); + } + + TimeStamp now = TimeStamp::Now(); + if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_START) { + self->mLatestNurseryCollectionStart = now; + } else if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END) { + PerfStats::RecordMeasurement(PerfStats::Metric::MinorGC, + now - self->mLatestNurseryCollectionStart); + } + + if (aProgress == JS::GCNurseryProgress::GC_NURSERY_COLLECTION_END && + profiler_thread_is_being_profiled_for_markers()) { + struct GCMinorMarker { + static constexpr mozilla::Span MarkerTypeName() { + return mozilla::MakeStringSpan("GCMinor"); + } + static void StreamJSONMarkerData( + mozilla::baseprofiler::SpliceableJSONWriter& aWriter, + const mozilla::ProfilerString8View& aTimingJSON) { + if (aTimingJSON.Length() != 0) { + aWriter.SplicedJSONProperty("nursery", aTimingJSON); + } else { + aWriter.NullProperty("nursery"); + } + } + static mozilla::MarkerSchema MarkerTypeDisplay() { + using MS = mozilla::MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable, + MS::Location::TimelineMemory}; + schema.AddStaticLabelValue( + "Description", + "A minor GC (aka nursery collection) to clear out the buffer used " + "for recent allocations and move surviving data to the tenured " + "(long-lived) heap."); + // No display instructions here, there is special handling in the + // front-end. + return schema; + } + }; + + profiler_add_marker( + "GCMinor", baseprofiler::category::GCCC, + MarkerTiming::Interval(self->mLatestNurseryCollectionStart, now), + GCMinorMarker{}, + ProfilerString8View::WrapNullTerminatedString( + JS::MinorGcToJSON(aContext).get())); + } + + if (self->mPrevGCNurseryCollectionCallback) { + self->mPrevGCNurseryCollectionCallback(aContext, aProgress, aReason); + } +} + +/* static */ +void CycleCollectedJSRuntime::OutOfMemoryCallback(JSContext* aContext, + void* aData) { + CycleCollectedJSRuntime* self = static_cast(aData); + + MOZ_ASSERT(CycleCollectedJSContext::Get()->Context() == aContext); + MOZ_ASSERT(CycleCollectedJSContext::Get()->Runtime() == self); + + self->OnOutOfMemory(); +} + +/* static */ +void* CycleCollectedJSRuntime::BeforeWaitCallback(uint8_t* aMemory) { + MOZ_ASSERT(aMemory); + + // aMemory is stack allocated memory to contain our RAII object. This allows + // for us to avoid allocations on the heap during this callback. + return new (aMemory) dom::AutoYieldJSThreadExecution; +} + +/* static */ +void CycleCollectedJSRuntime::AfterWaitCallback(void* aCookie) { + MOZ_ASSERT(aCookie); + static_cast(aCookie) + ->~AutoYieldJSThreadExecution(); +} + +struct JsGcTracer : public TraceCallbacks { + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JS::TraceEdge(static_cast(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JS::TraceEdge(static_cast(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JS::TraceEdge(static_cast(aClosure), aPtr, aName); + } + virtual void Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const override { + aPtr->TraceWrapper(static_cast(aClosure), aName); + } + virtual void Trace(JS::TenuredHeap* aPtr, const char* aName, + void* aClosure) const override { + JS::TraceEdge(static_cast(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JS::TraceEdge(static_cast(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JS::TraceEdge(static_cast(aClosure), aPtr, aName); + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JS::TraceEdge(static_cast(aClosure), aPtr, aName); + } +}; + +void mozilla::TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer) { + nsXPCOMCycleCollectionParticipant* participant = nullptr; + CallQueryInterface(aHolder, &participant); + participant->Trace(aHolder, JsGcTracer(), aTracer); +} + +#if defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) || defined(DEBUG) +# define CHECK_SINGLE_ZONE_JS_HOLDERS +#endif + +#ifdef CHECK_SINGLE_ZONE_JS_HOLDERS + +// A tracer that checks that a JS holder only holds JS GC things in a single +// JS::Zone. +struct CheckZoneTracer : public TraceCallbacks { + const char* mClassName; + mutable JS::Zone* mZone; + + explicit CheckZoneTracer(const char* aClassName, JS::Zone* aZone = nullptr) + : mClassName(aClassName), mZone(aZone) {} + + void checkZone(JS::Zone* aZone, const char* aName) const { + if (JS::IsAtomsZone(aZone)) { + // Any holder may contain pointers into the atoms zone. + return; + } + + if (!mZone) { + mZone = aZone; + return; + } + + if (aZone == mZone) { + return; + } + + // Most JS holders only contain pointers to GC things in a single zone. We + // group holders by referent zone where possible, allowing us to improve GC + // performance by only tracing holders for zones that are being collected. + // + // Additionally, pointers from any holder into the atoms zone are allowed + // since all holders are traced when we collect the atoms zone. + // + // If you added a holder that has pointers into multiple zones do not + // use NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS. + MOZ_CRASH_UNSAFE_PRINTF( + "JS holder %s contains pointers to GC things in more than one zone (" + "found in %s)\n", + mClassName, aName); + } + + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JS::Value value = aPtr->unbarrieredGet(); + if (value.isGCThing()) { + checkZone(JS::GetGCThingZone(value.toGCCellPtr()), aName); + } + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + jsid id = aPtr->unbarrieredGet(); + if (id.isGCThing()) { + MOZ_ASSERT(JS::IsAtomsZone(JS::GetTenuredGCThingZone(id.toGCCellPtr()))); + } + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JSObject* obj = aPtr->unbarrieredGet(); + if (obj) { + checkZone(js::GetObjectZoneFromAnyThread(obj), aName); + } + } + virtual void Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const override { + JSObject* obj = aPtr->GetWrapperPreserveColor(); + if (obj) { + checkZone(js::GetObjectZoneFromAnyThread(obj), aName); + } + } + virtual void Trace(JS::TenuredHeap* aPtr, const char* aName, + void* aClosure) const override { + JSObject* obj = aPtr->unbarrieredGetPtr(); + if (obj) { + checkZone(js::GetObjectZoneFromAnyThread(obj), aName); + } + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JSString* str = aPtr->unbarrieredGet(); + if (str) { + checkZone(JS::GetStringZone(str), aName); + } + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JSScript* script = aPtr->unbarrieredGet(); + if (script) { + checkZone(JS::GetTenuredGCThingZone(JS::GCCellPtr(script)), aName); + } + } + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override { + JSFunction* fun = aPtr->unbarrieredGet(); + if (fun) { + checkZone(js::GetObjectZoneFromAnyThread(JS_GetFunctionObject(fun)), + aName); + } + } +}; + +static inline void CheckHolderIsSingleZone( + void* aHolder, nsCycleCollectionParticipant* aParticipant, + JS::Zone* aZone) { + CheckZoneTracer tracer(aParticipant->ClassName(), aZone); + aParticipant->Trace(aHolder, tracer, nullptr); +} + +#endif + +static inline bool ShouldCheckSingleZoneHolders() { +#if defined(DEBUG) + return true; +#elif defined(NIGHTLY_BUILD) || defined(MOZ_DEV_EDITION) + // Don't check every time to avoid performance impact. + return rand() % 256 == 0; +#else + return false; +#endif +} + +#ifdef NS_BUILD_REFCNT_LOGGING +void CycleCollectedJSRuntime::TraceAllNativeGrayRoots(JSTracer* aTracer) { + MOZ_RELEASE_ASSERT(mHolderIter.isNothing()); + js::SliceBudget budget = js::SliceBudget::unlimited(); + MOZ_ALWAYS_TRUE( + TraceNativeGrayRoots(aTracer, JSHolderMap::AllHolders, budget)); +} +#endif + +bool CycleCollectedJSRuntime::TraceNativeGrayRoots( + JSTracer* aTracer, JSHolderMap::WhichHolders aWhich, + js::SliceBudget& aBudget) { + if (!mHolderIter) { + // NB: This is here just to preserve the existing XPConnect order. I doubt + // it would hurt to do this after the JS holders. + TraceAdditionalNativeGrayRoots(aTracer); + + mHolderIter.emplace(mJSHolders, aWhich); + aBudget.stepAndForceCheck(); + } else { + // Holders may have been removed between slices, so we may need to update + // the iterator. + mHolderIter->UpdateForRemovals(); + } + + bool finished = TraceJSHolders(aTracer, *mHolderIter, aBudget); + if (finished) { + mHolderIter.reset(); + } + + return finished; +} + +bool CycleCollectedJSRuntime::TraceJSHolders(JSTracer* aTracer, + JSHolderMap::Iter& aIter, + js::SliceBudget& aBudget) { + bool checkSingleZoneHolders = ShouldCheckSingleZoneHolders(); + + while (!aIter.Done() && !aBudget.isOverBudget()) { + void* holder = aIter->mHolder; + nsScriptObjectTracer* tracer = aIter->mTracer; + +#ifdef CHECK_SINGLE_ZONE_JS_HOLDERS + if (checkSingleZoneHolders && tracer->IsSingleZoneJSHolder()) { + CheckHolderIsSingleZone(holder, tracer, aIter.Zone()); + } +#else + Unused << checkSingleZoneHolders; +#endif + + tracer->Trace(holder, JsGcTracer(), aTracer); + + aIter.Next(); + aBudget.step(); + } + + return aIter.Done(); +} + +void CycleCollectedJSRuntime::AddJSHolder(void* aHolder, + nsScriptObjectTracer* aTracer, + JS::Zone* aZone) { + mJSHolders.Put(aHolder, aTracer, aZone); +} + +struct ClearJSHolder : public TraceCallbacks { + virtual void Trace(JS::Heap* aPtr, const char*, + void*) const override { + aPtr->setUndefined(); + } + + virtual void Trace(JS::Heap* aPtr, const char*, void*) const override { + *aPtr = JS::PropertyKey::Void(); + } + + virtual void Trace(JS::Heap* aPtr, const char*, + void*) const override { + *aPtr = nullptr; + } + + virtual void Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const override { + aPtr->ClearWrapper(); + } + + virtual void Trace(JS::TenuredHeap* aPtr, const char*, + void*) const override { + *aPtr = nullptr; + } + + virtual void Trace(JS::Heap* aPtr, const char*, + void*) const override { + *aPtr = nullptr; + } + + virtual void Trace(JS::Heap* aPtr, const char*, + void*) const override { + *aPtr = nullptr; + } + + virtual void Trace(JS::Heap* aPtr, const char*, + void*) const override { + *aPtr = nullptr; + } +}; + +void CycleCollectedJSRuntime::RemoveJSHolder(void* aHolder) { + nsScriptObjectTracer* tracer = mJSHolders.Extract(aHolder); + if (tracer) { + // Bug 1531951: The analysis can't see through the virtual call but we know + // that the ClearJSHolder tracer will never GC. + JS::AutoSuppressGCAnalysis nogc; + tracer->Trace(aHolder, ClearJSHolder(), nullptr); + } +} + +#ifdef DEBUG +static void AssertNoGcThing(JS::GCCellPtr aGCThing, const char* aName, + void* aClosure) { + MOZ_ASSERT(!aGCThing); +} + +void CycleCollectedJSRuntime::AssertNoObjectsToTrace(void* aPossibleJSHolder) { + nsScriptObjectTracer* tracer = mJSHolders.Get(aPossibleJSHolder); + if (tracer) { + tracer->Trace(aPossibleJSHolder, TraceCallbackFunc(AssertNoGcThing), + nullptr); + } +} +#endif + +nsCycleCollectionParticipant* CycleCollectedJSRuntime::GCThingParticipant() { + return &mGCThingCycleCollectorGlobal; +} + +nsCycleCollectionParticipant* CycleCollectedJSRuntime::ZoneParticipant() { + return &mJSZoneCycleCollectorGlobal; +} + +nsresult CycleCollectedJSRuntime::TraverseRoots( + nsCycleCollectionNoteRootCallback& aCb) { + TraverseNativeRoots(aCb); + + NoteWeakMapsTracer trc(mJSRuntime, aCb); + js::TraceWeakMaps(&trc); + + return NS_OK; +} + +bool CycleCollectedJSRuntime::UsefulToMergeZones() const { return false; } + +void CycleCollectedJSRuntime::FixWeakMappingGrayBits() const { + MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime), + "Don't call FixWeakMappingGrayBits during a GC."); + FixWeakMappingGrayBitsTracer fixer(mJSRuntime); + fixer.FixAll(); +} + +void CycleCollectedJSRuntime::CheckGrayBits() const { + MOZ_ASSERT(!JS::IsIncrementalGCInProgress(mJSRuntime), + "Don't call CheckGrayBits during a GC."); + +#ifndef ANDROID + // Bug 1346874 - The gray state check is expensive. Android tests are already + // slow enough that this check can easily push them over the threshold to a + // timeout. + + MOZ_ASSERT(js::CheckGrayMarkingState(mJSRuntime)); + MOZ_ASSERT(CheckWeakMappingGrayBitsTracer::Check(mJSRuntime)); +#endif +} + +bool CycleCollectedJSRuntime::AreGCGrayBitsValid() const { + return js::AreGCGrayBitsValid(mJSRuntime); +} + +void CycleCollectedJSRuntime::GarbageCollect(JS::GCOptions aOptions, + JS::GCReason aReason) const { + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + JS::PrepareForFullGC(cx); + JS::NonIncrementalGC(cx, aOptions, aReason); +} + +void CycleCollectedJSRuntime::JSObjectsTenured() { + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + for (auto iter = mNurseryObjects.Iter(); !iter.Done(); iter.Next()) { + nsWrapperCache* cache = iter.Get(); + JSObject* wrapper = cache->GetWrapperMaybeDead(); + MOZ_DIAGNOSTIC_ASSERT(wrapper); + if (!JS::ObjectIsTenured(wrapper)) { + MOZ_ASSERT(!cache->PreservingWrapper()); + js::gc::FinalizeDeadNurseryObject(cx, wrapper); + } + } + + mNurseryObjects.Clear(); +} + +void CycleCollectedJSRuntime::NurseryWrapperAdded(nsWrapperCache* aCache) { + MOZ_ASSERT(aCache); + MOZ_ASSERT(aCache->GetWrapperMaybeDead()); + MOZ_ASSERT(!JS::ObjectIsTenured(aCache->GetWrapperMaybeDead())); + mNurseryObjects.InfallibleAppend(aCache); +} + +void CycleCollectedJSRuntime::DeferredFinalize( + DeferredFinalizeAppendFunction aAppendFunc, DeferredFinalizeFunction aFunc, + void* aThing) { + // Tell the analysis that the function pointers will not GC. + JS::AutoSuppressGCAnalysis suppress; + mDeferredFinalizerTable.WithEntryHandle(aFunc, [&](auto&& entry) { + if (entry) { + aAppendFunc(entry.Data(), aThing); + } else { + entry.Insert(aAppendFunc(nullptr, aThing)); + } + }); +} + +void CycleCollectedJSRuntime::DeferredFinalize(nsISupports* aSupports) { + typedef DeferredFinalizerImpl Impl; + DeferredFinalize(Impl::AppendDeferredFinalizePointer, Impl::DeferredFinalize, + aSupports); +} + +void CycleCollectedJSRuntime::DumpJSHeap(FILE* aFile) { + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + + mozilla::MallocSizeOf mallocSizeOf = + PR_GetEnv("MOZ_GC_LOG_SIZE") ? moz_malloc_size_of : nullptr; + js::DumpHeap(cx, aFile, js::CollectNurseryBeforeDump, mallocSizeOf); +} + +IncrementalFinalizeRunnable::IncrementalFinalizeRunnable( + CycleCollectedJSRuntime* aRt, DeferredFinalizerTable& aFinalizers) + : DiscardableRunnable("IncrementalFinalizeRunnable"), + mRuntime(aRt), + mFinalizeFunctionToRun(0), + mReleasing(false) { + for (auto iter = aFinalizers.Iter(); !iter.Done(); iter.Next()) { + DeferredFinalizeFunction& function = iter.Key(); + void*& data = iter.Data(); + + DeferredFinalizeFunctionHolder* holder = + mDeferredFinalizeFunctions.AppendElement(); + holder->run = function; + holder->data = data; + + iter.Remove(); + } + MOZ_ASSERT(mDeferredFinalizeFunctions.Length()); +} + +IncrementalFinalizeRunnable::~IncrementalFinalizeRunnable() { + MOZ_ASSERT(!mDeferredFinalizeFunctions.Length()); + MOZ_ASSERT(!mRuntime); +} + +void IncrementalFinalizeRunnable::ReleaseNow(bool aLimited) { + if (mReleasing) { + NS_WARNING("Re-entering ReleaseNow"); + return; + } + { + AUTO_PROFILER_LABEL("IncrementalFinalizeRunnable::ReleaseNow", + GCCC_Finalize); + + mozilla::AutoRestore ar(mReleasing); + mReleasing = true; + MOZ_ASSERT(mDeferredFinalizeFunctions.Length() != 0, + "We should have at least ReleaseSliceNow to run"); + MOZ_ASSERT(mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length(), + "No more finalizers to run?"); + + TimeDuration sliceTime = TimeDuration::FromMilliseconds(SliceMillis); + TimeStamp started = aLimited ? TimeStamp::Now() : TimeStamp(); + bool timeout = false; + do { + const DeferredFinalizeFunctionHolder& function = + mDeferredFinalizeFunctions[mFinalizeFunctionToRun]; + if (aLimited) { + bool done = false; + while (!timeout && !done) { + /* + * We don't want to read the clock too often, so we try to + * release slices of 100 items. + */ + done = function.run(100, function.data); + timeout = TimeStamp::Now() - started >= sliceTime; + } + if (done) { + ++mFinalizeFunctionToRun; + } + if (timeout) { + break; + } + } else { + while (!function.run(UINT32_MAX, function.data)) + ; + ++mFinalizeFunctionToRun; + } + } while (mFinalizeFunctionToRun < mDeferredFinalizeFunctions.Length()); + } + + if (mFinalizeFunctionToRun == mDeferredFinalizeFunctions.Length()) { + MOZ_ASSERT(mRuntime->mFinalizeRunnable == this); + mDeferredFinalizeFunctions.Clear(); + CycleCollectedJSRuntime* runtime = mRuntime; + mRuntime = nullptr; + // NB: This may delete this! + runtime->mFinalizeRunnable = nullptr; + } +} + +NS_IMETHODIMP +IncrementalFinalizeRunnable::Run() { + if (!mDeferredFinalizeFunctions.Length()) { + /* These items were already processed synchronously in JSGC_END. */ + MOZ_ASSERT(!mRuntime); + return NS_OK; + } + + MOZ_ASSERT(mRuntime->mFinalizeRunnable == this); + TimeStamp start = TimeStamp::Now(); + ReleaseNow(true); + + if (mDeferredFinalizeFunctions.Length()) { + nsresult rv = NS_DispatchToCurrentThread(this); + if (NS_FAILED(rv)) { + ReleaseNow(false); + } + } else { + MOZ_ASSERT(!mRuntime); + } + + uint32_t duration = (uint32_t)((TimeStamp::Now() - start).ToMilliseconds()); + Telemetry::Accumulate(Telemetry::DEFERRED_FINALIZE_ASYNC, duration); + + return NS_OK; +} + +void CycleCollectedJSRuntime::FinalizeDeferredThings( + DeferredFinalizeType aType) { + // If mFinalizeRunnable isn't null, we didn't finalize everything from the + // previous GC. + if (mFinalizeRunnable) { + if (aType == FinalizeLater) { + // We need to defer all finalization until we return to the event loop, + // so leave things alone. Any new objects to be finalized from the current + // GC will be handled by the existing mFinalizeRunnable. + return; + } + MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeNow); + // If we're finalizing incrementally, we don't want finalizers to build up, + // so try to finish them off now. + // If we're finalizing synchronously, also go ahead and clear them out, + // so we make sure as much as possible is freed. + mFinalizeRunnable->ReleaseNow(false); + if (mFinalizeRunnable) { + // If we re-entered ReleaseNow, we couldn't delete mFinalizeRunnable and + // we need to just continue processing it. + return; + } + } + + // If there's nothing to finalize, don't create a new runnable. + if (mDeferredFinalizerTable.Count() == 0) { + return; + } + + mFinalizeRunnable = + new IncrementalFinalizeRunnable(this, mDeferredFinalizerTable); + + // Everything should be gone now. + MOZ_ASSERT(mDeferredFinalizerTable.Count() == 0); + + if (aType == FinalizeNow) { + mFinalizeRunnable->ReleaseNow(false); + MOZ_ASSERT(!mFinalizeRunnable); + } else { + MOZ_ASSERT(aType == FinalizeIncrementally || aType == FinalizeLater); + NS_DispatchToCurrentThreadQueue(do_AddRef(mFinalizeRunnable), 2500, + EventQueuePriority::Idle); + } +} + +const char* CycleCollectedJSRuntime::OOMStateToString( + const OOMState aOomState) const { + switch (aOomState) { + case OOMState::OK: + return "OK"; + case OOMState::Reporting: + return "Reporting"; + case OOMState::Reported: + return "Reported"; + case OOMState::Recovered: + return "Recovered"; + default: + MOZ_ASSERT_UNREACHABLE("OOMState holds an invalid value"); + return "Unknown"; + } +} + +bool CycleCollectedJSRuntime::OOMReported() { + return mOutOfMemoryState == OOMState::Reported; +} + +void CycleCollectedJSRuntime::AnnotateAndSetOutOfMemory(OOMState* aStatePtr, + OOMState aNewState) { + *aStatePtr = aNewState; + CrashReporter::Annotation annotation = + (aStatePtr == &mOutOfMemoryState) + ? CrashReporter::Annotation::JSOutOfMemory + : CrashReporter::Annotation::JSLargeAllocationFailure; + + CrashReporter::AnnotateCrashReport( + annotation, nsDependentCString(OOMStateToString(aNewState))); +} + +void CycleCollectedJSRuntime::OnGC(JSContext* aContext, JSGCStatus aStatus, + JS::GCReason aReason) { + switch (aStatus) { + case JSGC_BEGIN: + MOZ_RELEASE_ASSERT(mHolderIter.isNothing()); + nsCycleCollector_prepareForGarbageCollection(); + PrepareWaitingZonesForGC(); + break; + case JSGC_END: { + MOZ_RELEASE_ASSERT(mHolderIter.isNothing()); + if (mOutOfMemoryState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Recovered); + } + if (mLargeAllocationFailureState == OOMState::Reported) { + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, + OOMState::Recovered); + } + + DeferredFinalizeType finalizeType; + if (JS_IsExceptionPending(aContext)) { + // There is a pending exception. The finalizers are not set up to run + // in that state, so don't run the finalizer until we've returned to the + // event loop. + finalizeType = FinalizeLater; + } else if (JS::InternalGCReason(aReason)) { + if (aReason == JS::GCReason::DESTROY_RUNTIME) { + // We're shutting down, so we need to destroy things immediately. + finalizeType = FinalizeNow; + } else { + // We may be in the middle of running some code that the JIT has + // assumed can't have certain kinds of side effects. Finalizers can do + // all sorts of things, such as run JS, so we want to run them later, + // after we've returned to the event loop. + finalizeType = FinalizeLater; + } + } else if (JS::WasIncrementalGC(mJSRuntime)) { + // The GC was incremental, so we probably care about pauses. Try to + // break up finalization, but it is okay if we do some now. + finalizeType = FinalizeIncrementally; + } else { + // If we're running a synchronous GC, we probably want to free things as + // quickly as possible. This can happen during testing or if memory is + // low. + finalizeType = FinalizeNow; + } + FinalizeDeferredThings(finalizeType); + + break; + } + default: + MOZ_CRASH(); + } + + CustomGCCallback(aStatus); +} + +void CycleCollectedJSRuntime::OnOutOfMemory() { + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reporting); + CustomOutOfMemoryCallback(); + AnnotateAndSetOutOfMemory(&mOutOfMemoryState, OOMState::Reported); +} + +void CycleCollectedJSRuntime::SetLargeAllocationFailure(OOMState aNewState) { + AnnotateAndSetOutOfMemory(&mLargeAllocationFailureState, aNewState); +} + +void CycleCollectedJSRuntime::PrepareWaitingZonesForGC() { + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + if (mZonesWaitingForGC.Count() == 0) { + JS::PrepareForFullGC(cx); + } else { + for (const auto& key : mZonesWaitingForGC) { + JS::PrepareZoneForGC(cx, key); + } + mZonesWaitingForGC.Clear(); + } +} + +/* static */ +void CycleCollectedJSRuntime::OnZoneDestroyed(JS::GCContext* aGcx, + JS::Zone* aZone) { + // Remove the zone from the set of zones waiting for GC, if present. This can + // happen if a zone is added to the set during an incremental GC in which it + // is later destroyed. + CycleCollectedJSRuntime* runtime = Get(); + runtime->mZonesWaitingForGC.Remove(aZone); +} + +void CycleCollectedJSRuntime::EnvironmentPreparer::invoke( + JS::HandleObject global, js::ScriptEnvironmentPreparer::Closure& closure) { + MOZ_ASSERT(JS_IsGlobalObject(global)); + nsIGlobalObject* nativeGlobal = xpc::NativeGlobal(global); + + // Not much we can do if we simply don't have a usable global here... + NS_ENSURE_TRUE_VOID(nativeGlobal && nativeGlobal->HasJSGlobal()); + + AutoEntryScript aes(nativeGlobal, "JS-engine-initiated execution"); + + MOZ_ASSERT(!JS_IsExceptionPending(aes.cx())); + + DebugOnly ok = closure(aes.cx()); + + MOZ_ASSERT_IF(ok, !JS_IsExceptionPending(aes.cx())); + + // The AutoEntryScript will check for JS_IsExceptionPending on the + // JSContext and report it as needed as it comes off the stack. +} + +/* static */ +CycleCollectedJSRuntime* CycleCollectedJSRuntime::Get() { + auto context = CycleCollectedJSContext::Get(); + if (context) { + return context->Runtime(); + } + return nullptr; +} + +#ifdef MOZ_JS_DEV_ERROR_INTERCEPTOR + +namespace js { +extern void DumpValue(const JS::Value& val); +} + +void CycleCollectedJSRuntime::ErrorInterceptor::Shutdown(JSRuntime* rt) { + JS_SetErrorInterceptorCallback(rt, nullptr); + mThrownError.reset(); +} + +/* virtual */ +void CycleCollectedJSRuntime::ErrorInterceptor::interceptError( + JSContext* cx, JS::HandleValue exn) { + if (mThrownError) { + // We already have an error, we don't need anything more. + return; + } + + if (!nsContentUtils::ThreadsafeIsSystemCaller(cx)) { + // We are only interested in chrome code. + return; + } + + const auto type = JS_GetErrorType(exn); + if (!type) { + // This is not one of the primitive error types. + return; + } + + switch (*type) { + case JSExnType::JSEXN_REFERENCEERR: + case JSExnType::JSEXN_SYNTAXERR: + break; + default: + // Not one of the errors we are interested in. + // Note that we are not interested in instances of `TypeError` + // for the time being, as DOM (ab)uses this constructor to represent + // all sorts of errors that are not even remotely related to type + // errors (e.g. some network errors). + // If we ever have a mechanism to differentiate between DOM-thrown + // and SpiderMonkey-thrown instances of `TypeError`, we should + // consider watching for `TypeError` here. + return; + } + + // Now copy the details of the exception locally. + // While copying the details of an exception could be expensive, in most runs, + // this will be done at most once during the execution of the process, so the + // total cost should be reasonable. + + ErrorDetails details; + details.mType = *type; + // If `exn` isn't an exception object, `ExtractErrorValues` could end up + // calling `toString()`, which could in turn end up throwing an error. While + // this should work, we want to avoid that complex use case. Fortunately, we + // have already checked above that `exn` is an exception object, so nothing + // such should happen. + nsContentUtils::ExtractErrorValues(cx, exn, details.mFilename, &details.mLine, + &details.mColumn, details.mMessage); + + JS::UniqueChars buf = + JS::FormatStackDump(cx, /* showArgs = */ false, /* showLocals = */ false, + /* showThisProps = */ false); + CopyUTF8toUTF16(mozilla::MakeStringSpan(buf.get()), details.mStack); + + mThrownError.emplace(std::move(details)); +} + +void CycleCollectedJSRuntime::ClearRecentDevError() { + mErrorInterceptor.mThrownError.reset(); +} + +bool CycleCollectedJSRuntime::GetRecentDevError( + JSContext* cx, JS::MutableHandle error) { + if (!mErrorInterceptor.mThrownError) { + return true; + } + + // Create a copy of the exception. + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + if (!obj) { + return false; + } + + JS::RootedValue message(cx); + JS::RootedValue filename(cx); + JS::RootedValue stack(cx); + if (!ToJSValue(cx, mErrorInterceptor.mThrownError->mMessage, &message) || + !ToJSValue(cx, mErrorInterceptor.mThrownError->mFilename, &filename) || + !ToJSValue(cx, mErrorInterceptor.mThrownError->mStack, &stack)) { + return false; + } + + // Build the object. + const auto FLAGS = JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT; + if (!JS_DefineProperty(cx, obj, "message", message, FLAGS) || + !JS_DefineProperty(cx, obj, "fileName", filename, FLAGS) || + !JS_DefineProperty(cx, obj, "lineNumber", + mErrorInterceptor.mThrownError->mLine, FLAGS) || + !JS_DefineProperty(cx, obj, "stack", stack, FLAGS)) { + return false; + } + + // Pass the result. + error.setObject(*obj); + return true; +} +#endif // MOZ_JS_DEV_ERROR_INTERCEPTOR + +#undef MOZ_JS_DEV_ERROR_INTERCEPTOR diff --git a/xpcom/base/CycleCollectedJSRuntime.h b/xpcom/base/CycleCollectedJSRuntime.h new file mode 100644 index 0000000000..7008fadb28 --- /dev/null +++ b/xpcom/base/CycleCollectedJSRuntime.h @@ -0,0 +1,528 @@ +/* -*- 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/. */ + +#ifndef mozilla_CycleCollectedJSRuntime_h +#define mozilla_CycleCollectedJSRuntime_h + +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DeferredFinalize.h" +#include "mozilla/HashTable.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "mozilla/SegmentedVector.h" +#include "jsapi.h" +#include "jsfriendapi.h" +#include "js/TypeDecls.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsStringFwd.h" +#include "nsTHashSet.h" + +class nsCycleCollectionNoteRootCallback; +class nsIException; +class nsWrapperCache; + +namespace mozilla { + +class JSGCThingParticipant : public nsCycleCollectionParticipant { + public: + constexpr JSGCThingParticipant() : nsCycleCollectionParticipant(false) {} + + NS_IMETHOD_(void) Root(void*) override { + MOZ_ASSERT(false, "Don't call Root on GC things"); + } + + NS_IMETHOD_(void) Unlink(void*) override { + MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) Unroot(void*) override { + MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr) override { + MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing"); + } + + NS_IMETHOD TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSGCThingParticipant) +}; + +class JSZoneParticipant : public nsCycleCollectionParticipant { + public: + constexpr JSZoneParticipant() : nsCycleCollectionParticipant(false) {} + + NS_IMETHOD_(void) Root(void*) override { + MOZ_ASSERT(false, "Don't call Root on GC things"); + } + + NS_IMETHOD_(void) Unlink(void*) override { + MOZ_ASSERT(false, "Don't call Unlink on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) Unroot(void*) override { + MOZ_ASSERT(false, "Don't call Unroot on GC things, as they may be dead"); + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void*) override { + MOZ_ASSERT(false, "Can't directly delete a cycle collectable GC thing"); + } + + NS_IMETHOD TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) override; + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(JSZoneParticipant) +}; + +class IncrementalFinalizeRunnable; + +// A map from JS holders to tracer objects, where the values are stored in +// SegmentedVector to speed up iteration. +class JSHolderMap { + public: + enum WhichHolders { AllHolders, HoldersRequiredForGrayMarking }; + + class Iter; + + JSHolderMap(); + ~JSHolderMap() { MOZ_RELEASE_ASSERT(!mHasIterator); } + + bool Has(void* aHolder) const; + nsScriptObjectTracer* Get(void* aHolder) const; + nsScriptObjectTracer* Extract(void* aHolder); + void Put(void* aHolder, nsScriptObjectTracer* aTracer, JS::Zone* aZone); + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + + private: + struct Entry { + void* mHolder; + nsScriptObjectTracer* mTracer; +#ifdef DEBUG + JS::Zone* mZone; +#endif + + Entry(); + Entry(void* aHolder, nsScriptObjectTracer* aTracer, JS::Zone* aZone); + }; + + using EntryMap = mozilla::HashMap, + InfallibleAllocPolicy>; + + using EntryVector = SegmentedVector; + + using EntryVectorMap = + mozilla::HashMap, + DefaultHasher, InfallibleAllocPolicy>; + + class EntryVectorIter; + + bool RemoveEntry(EntryVector& aJSHolders, Entry* aEntry); + + // A map from a holder pointer to a pointer to an entry in a vector. + EntryMap mJSHolderMap; + + // A vector of holders not associated with a particular zone or that can + // contain pointers to GC things in more than one zone. + EntryVector mAnyZoneJSHolders; + + // A map from a zone to a vector of holders that only contain pointers to GC + // things in that zone. + // + // Currently this will only contain wrapper cache wrappers since these are the + // only holders to pass a zone parameter through to AddJSHolder. + EntryVectorMap mPerZoneJSHolders; + + // Iterators can mutate the element vectors by removing stale elements. Allow + // at most one to exist at a time. + bool mHasIterator = false; +}; + +// An iterator over an EntryVector that skips over removed entries and removes +// them from the map. +class JSHolderMap::EntryVectorIter { + public: + EntryVectorIter(JSHolderMap& aMap, EntryVector& aVector) + : mHolderMap(aMap), mVector(aVector), mIter(aVector.Iter()) { + Settle(); + } + + const EntryVector& Vector() const { return mVector; } + + bool Done() const { return mIter.Done(); } + const Entry& Get() const { return mIter.Get(); } + void Next() { + mIter.Next(); + Settle(); + } + + operator const Entry*() const { return &Get(); } + const Entry* operator->() const { return &Get(); } + + private: + void Settle(); + friend class JSHolderMap::Iter; + + JSHolderMap& mHolderMap; + EntryVector& mVector; + EntryVector::IterImpl mIter; +}; + +class JSHolderMap::Iter { + public: + explicit Iter(JSHolderMap& aMap, WhichHolders aWhich = AllHolders); + + ~Iter() { + MOZ_RELEASE_ASSERT(mHolderMap.mHasIterator); + mHolderMap.mHasIterator = false; + } + + bool Done() const { return mIter.Done(); } + const Entry& Get() const { return mIter.Get(); } + void Next() { + mIter.Next(); + Settle(); + } + + // If the holders have been removed from the map while the iterator is live, + // then the iterator may point to a removed entry. Update the iterator to make + // sure it points to a valid entry or is done. + void UpdateForRemovals(); + + operator const Entry*() const { return &Get(); } + const Entry* operator->() const { return &Get(); } + + JS::Zone* Zone() const { return mZone; } + + private: + void Settle(); + + JSHolderMap& mHolderMap; + Vector mZones; + JS::Zone* mZone = nullptr; + EntryVectorIter mIter; +}; + +class CycleCollectedJSRuntime { + friend class JSGCThingParticipant; + friend class JSZoneParticipant; + friend class IncrementalFinalizeRunnable; + friend class CycleCollectedJSContext; + + protected: + CycleCollectedJSRuntime(JSContext* aMainContext); + virtual ~CycleCollectedJSRuntime(); + + virtual void Shutdown(JSContext* cx); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + void UnmarkSkippableJSHolders(); + + virtual void TraverseAdditionalNativeRoots( + nsCycleCollectionNoteRootCallback& aCb) {} + virtual void TraceAdditionalNativeGrayRoots(JSTracer* aTracer) {} + + virtual void CustomGCCallback(JSGCStatus aStatus) {} + virtual void CustomOutOfMemoryCallback() {} + + CycleCollectedJSContext* GetContext() { return mContext; } + + private: + void DescribeGCThing(bool aIsMarked, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool DescribeCustomObjects(JSObject* aObject, const JSClass* aClasp, + char (&aName)[72]) const { + return false; // We did nothing. + } + + void NoteGCThingJSChildren(JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb) const; + + void NoteGCThingXPCOMChildren(const JSClass* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const; + + virtual bool NoteCustomGCThingXPCOMChildren( + const JSClass* aClasp, JSObject* aObj, + nsCycleCollectionTraversalCallback& aCb) const { + return false; // We did nothing. + } + + enum TraverseSelect { TRAVERSE_CPP, TRAVERSE_FULL }; + + void TraverseGCThing(TraverseSelect aTs, JS::GCCellPtr aThing, + nsCycleCollectionTraversalCallback& aCb); + + void TraverseZone(JS::Zone* aZone, nsCycleCollectionTraversalCallback& aCb); + + static void TraverseObjectShim(void* aData, JS::GCCellPtr aThing, + const JS::AutoRequireNoGC& nogc); + + void TraverseNativeRoots(nsCycleCollectionNoteRootCallback& aCb); + + static void TraceBlackJS(JSTracer* aTracer, void* aData); + + // Trace gray JS roots until budget is exceeded and return whether we + // finished. + static bool TraceGrayJS(JSTracer* aTracer, js::SliceBudget& budget, + void* aData); + + static void GCCallback(JSContext* aContext, JSGCStatus aStatus, + JS::GCReason aReason, void* aData); + static void GCSliceCallback(JSContext* aContext, JS::GCProgress aProgress, + const JS::GCDescription& aDesc); + static void GCNurseryCollectionCallback(JSContext* aContext, + JS::GCNurseryProgress aProgress, + JS::GCReason aReason); + static void OutOfMemoryCallback(JSContext* aContext, void* aData); + + static bool ContextCallback(JSContext* aCx, unsigned aOperation, void* aData); + + static void* BeforeWaitCallback(uint8_t* aMemory); + static void AfterWaitCallback(void* aCookie); + + virtual void TraceNativeBlackRoots(JSTracer* aTracer){}; + +#ifdef NS_BUILD_REFCNT_LOGGING + void TraceAllNativeGrayRoots(JSTracer* aTracer); +#endif + + bool TraceNativeGrayRoots(JSTracer* aTracer, JSHolderMap::WhichHolders aWhich, + js::SliceBudget& aBudget); + bool TraceJSHolders(JSTracer* aTracer, JSHolderMap::Iter& aIter, + js::SliceBudget& aBudget); + + public: + enum DeferredFinalizeType { + // Never finalize immediately, because it would be unsafe. + FinalizeLater, + // Finalize later if we can, but it is okay to do it immediately. + FinalizeIncrementally, + // Finalize immediately, for shutdown or testing purposes. + FinalizeNow, + }; + + void FinalizeDeferredThings(DeferredFinalizeType aType); + + virtual void PrepareForForgetSkippable() = 0; + virtual void BeginCycleCollectionCallback(mozilla::CCReason aReason) = 0; + virtual void EndCycleCollectionCallback(CycleCollectorResults& aResults) = 0; + virtual void DispatchDeferredDeletion(bool aContinuation, + bool aPurge = false) = 0; + + // Two conditions, JSOutOfMemory and JSLargeAllocationFailure, are noted in + // crash reports. Here are the values that can appear in the reports: + enum class OOMState : uint32_t { + // The condition has never happened. No entry appears in the crash report. + OK, + + // We are currently reporting the given condition. + // + // Suppose a crash report contains "JSLargeAllocationFailure: + // Reporting". This means we crashed while executing memory-pressure + // observers, trying to shake loose some memory. The large allocation in + // question did not return null: it is still on the stack. Had we not + // crashed, it would have been retried. + Reporting, + + // The condition has been reported since the last GC. + // + // If a crash report contains "JSOutOfMemory: Reported", that means a small + // allocation failed, and then we crashed, probably due to buggy + // error-handling code that ran after allocation returned null. + // + // This contrasts with "Reporting" which means that no error-handling code + // had executed yet. + Reported, + + // The condition has happened, but a GC cycle ended since then. + // + // GC is taken as a proxy for "we've been banging on the heap a good bit + // now and haven't crashed; the OOM was probably handled correctly". + Recovered + }; + + const char* OOMStateToString(const OOMState aOomState) const; + + // Returns true if OOM was reported and a new successful GC cycle hasn't + // occurred since. + bool OOMReported(); + + void SetLargeAllocationFailure(OOMState aNewState); + + void AnnotateAndSetOutOfMemory(OOMState* aStatePtr, OOMState aNewState); + void OnGC(JSContext* aContext, JSGCStatus aStatus, JS::GCReason aReason); + void OnOutOfMemory(); + void OnLargeAllocationFailure(); + + JSRuntime* Runtime() { return mJSRuntime; } + const JSRuntime* Runtime() const { return mJSRuntime; } + + bool HasPendingIdleGCTask() const { + // Idle GC task associates with JSRuntime. + MOZ_ASSERT_IF(mHasPendingIdleGCTask, Runtime()); + return mHasPendingIdleGCTask; + } + void SetPendingIdleGCTask() { + // Idle GC task associates with JSRuntime. + MOZ_ASSERT(Runtime()); + mHasPendingIdleGCTask = true; + } + void ClearPendingIdleGCTask() { mHasPendingIdleGCTask = false; } + + void RunIdleTimeGCTask() { + if (HasPendingIdleGCTask()) { + JS::MaybeRunNurseryCollection(Runtime(), + JS::GCReason::EAGER_NURSERY_COLLECTION); + ClearPendingIdleGCTask(); + } + } + + bool IsIdleGCTaskNeeded() { + return !HasPendingIdleGCTask() && Runtime() && + JS::WantEagerMinorGC(Runtime()) != JS::GCReason::NO_REASON; + } + + public: + void AddJSHolder(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone); + void RemoveJSHolder(void* aHolder); +#ifdef DEBUG + void AssertNoObjectsToTrace(void* aPossibleJSHolder); +#endif + + nsCycleCollectionParticipant* GCThingParticipant(); + nsCycleCollectionParticipant* ZoneParticipant(); + + nsresult TraverseRoots(nsCycleCollectionNoteRootCallback& aCb); + virtual bool UsefulToMergeZones() const; + void FixWeakMappingGrayBits() const; + void CheckGrayBits() const; + bool AreGCGrayBitsValid() const; + void GarbageCollect(JS::GCOptions options, JS::GCReason aReason) const; + + // This needs to be an nsWrapperCache, not a JSObject, because we need to know + // when our object gets moved. But we can't trace it (and hence update our + // storage), because we do not want to keep it alive. nsWrapperCache handles + // this for us via its "object moved" handling. + void NurseryWrapperAdded(nsWrapperCache* aCache); + void JSObjectsTenured(); + + void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, void* aThing); + void DeferredFinalize(nsISupports* aSupports); + + void DumpJSHeap(FILE* aFile); + + // Add aZone to the set of zones waiting for a GC. + void AddZoneWaitingForGC(JS::Zone* aZone) { + mZonesWaitingForGC.Insert(aZone); + } + + static void OnZoneDestroyed(JS::GCContext* aGcx, JS::Zone* aZone); + + // Prepare any zones for GC that have been passed to AddZoneWaitingForGC() + // since the last GC or since the last call to PrepareWaitingZonesForGC(), + // whichever was most recent. If there were no such zones, prepare for a + // full GC. + void PrepareWaitingZonesForGC(); + + // Get the current thread's CycleCollectedJSRuntime. Returns null if there + // isn't one. + static CycleCollectedJSRuntime* Get(); + + void SetContext(CycleCollectedJSContext* aContext); + +#ifdef NIGHTLY_BUILD + bool GetRecentDevError(JSContext* aContext, + JS::MutableHandle aError); + void ClearRecentDevError(); +#endif // defined(NIGHTLY_BUILD) + + private: + CycleCollectedJSContext* mContext; + + JSGCThingParticipant mGCThingCycleCollectorGlobal; + + JSZoneParticipant mJSZoneCycleCollectorGlobal; + + JSRuntime* mJSRuntime; + bool mHasPendingIdleGCTask; + + JS::GCSliceCallback mPrevGCSliceCallback; + JS::GCNurseryCollectionCallback mPrevGCNurseryCollectionCallback; + + mozilla::TimeStamp mLatestNurseryCollectionStart; + + JSHolderMap mJSHolders; + Maybe mHolderIter; + + typedef nsTHashMap, void*> + DeferredFinalizerTable; + DeferredFinalizerTable mDeferredFinalizerTable; + + RefPtr mFinalizeRunnable; + + OOMState mOutOfMemoryState; + OOMState mLargeAllocationFailureState; + + static const size_t kSegmentSize = 512; + SegmentedVector + mNurseryObjects; + + nsTHashSet mZonesWaitingForGC; + + struct EnvironmentPreparer : public js::ScriptEnvironmentPreparer { + void invoke(JS::Handle global, Closure& closure) override; + }; + EnvironmentPreparer mEnvironmentPreparer; + +#ifdef DEBUG + bool mShutdownCalled; +#endif + +#ifdef NIGHTLY_BUILD + // Implementation of the error interceptor. + // Built on nightly only to avoid any possible performance impact on release + + struct ErrorInterceptor final : public JSErrorInterceptor { + virtual void interceptError(JSContext* cx, + JS::Handle exn) override; + void Shutdown(JSRuntime* rt); + + // Copy of the details of the exception. + // We store this rather than the exception itself to avoid dealing with + // complicated garbage-collection scenarios, e.g. a JSContext being killed + // while we still hold onto an exception thrown from it. + struct ErrorDetails { + nsString mFilename; + nsString mMessage; + nsString mStack; + JSExnType mType; + uint32_t mLine; + uint32_t mColumn; + }; + + // If we have encountered at least one developer error, + // the first error we have encountered. Otherwise, or + // if we have reset since the latest error, `None`. + Maybe mThrownError; + }; + ErrorInterceptor mErrorInterceptor; + +#endif // defined(NIGHTLY_BUILD) +}; + +void TraceScriptHolder(nsISupports* aHolder, JSTracer* aTracer); + +} // namespace mozilla + +#endif // mozilla_CycleCollectedJSRuntime_h diff --git a/xpcom/base/Debug.cpp b/xpcom/base/Debug.cpp new file mode 100644 index 0000000000..7e2a4c1d10 --- /dev/null +++ b/xpcom/base/Debug.cpp @@ -0,0 +1,21 @@ +/* -*- 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/Debug.h" + +#ifdef XP_WIN +# include +#endif + +#ifdef XP_WIN + +void mozilla::PrintToDebugger(const char* aStr) { + if (::IsDebuggerPresent()) { + ::OutputDebugStringA(aStr); + } +} + +#endif diff --git a/xpcom/base/Debug.h b/xpcom/base/Debug.h new file mode 100644 index 0000000000..1d6eaaf9a4 --- /dev/null +++ b/xpcom/base/Debug.h @@ -0,0 +1,21 @@ +/* -*- 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/. */ + +#ifndef mozilla_Debug_h__ +#define mozilla_Debug_h__ + +namespace mozilla { + +#ifdef XP_WIN + +// Print aStr to a debugger if the debugger is attached. +void PrintToDebugger(const char* aStr); + +#endif + +} // namespace mozilla + +#endif // mozilla_Debug_h__ diff --git a/xpcom/base/DebuggerOnGCRunnable.cpp b/xpcom/base/DebuggerOnGCRunnable.cpp new file mode 100644 index 0000000000..bcb13b3226 --- /dev/null +++ b/xpcom/base/DebuggerOnGCRunnable.cpp @@ -0,0 +1,51 @@ +/* -*- 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/DebuggerOnGCRunnable.h" + +#include + +#include "js/Debug.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/SchedulerGroup.h" + +namespace mozilla { + +/* static */ +nsresult DebuggerOnGCRunnable::Enqueue(JSContext* aCx, + const JS::GCDescription& aDesc) { + auto gcEvent = aDesc.toGCEvent(aCx); + if (!gcEvent) { + return NS_ERROR_OUT_OF_MEMORY; + } + + RefPtr runOnGC = + new DebuggerOnGCRunnable(std::move(gcEvent)); + if (NS_IsMainThread()) { + return SchedulerGroup::Dispatch(TaskCategory::GarbageCollection, + runOnGC.forget()); + } else { + return NS_DispatchToCurrentThread(runOnGC); + } +} + +NS_IMETHODIMP +DebuggerOnGCRunnable::Run() { + dom::AutoJSAPI jsapi; + jsapi.Init(); + if (!JS::dbg::FireOnGarbageCollectionHook(jsapi.cx(), std::move(mGCData))) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +nsresult DebuggerOnGCRunnable::Cancel() { + mGCData = nullptr; + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/base/DebuggerOnGCRunnable.h b/xpcom/base/DebuggerOnGCRunnable.h new file mode 100644 index 0000000000..93ac48b56c --- /dev/null +++ b/xpcom/base/DebuggerOnGCRunnable.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +#ifndef mozilla_DebuggerOnGCRunnable_h +#define mozilla_DebuggerOnGCRunnable_h + +#include + +#include "js/GCAPI.h" +#include "mozilla/UniquePtr.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +// Runnable to fire the SpiderMonkey Debugger API's onGarbageCollection hook. +class DebuggerOnGCRunnable : public CancelableRunnable { + JS::dbg::GarbageCollectionEvent::Ptr mGCData; + + explicit DebuggerOnGCRunnable(JS::dbg::GarbageCollectionEvent::Ptr&& aGCData) + : CancelableRunnable("DebuggerOnGCRunnable"), + mGCData(std::move(aGCData)) {} + + public: + static nsresult Enqueue(JSContext* aCx, const JS::GCDescription& aDesc); + + NS_DECL_NSIRUNNABLE + nsresult Cancel() override; +}; + +} // namespace mozilla + +#endif // ifdef mozilla_dom_DebuggerOnGCRunnable_h diff --git a/xpcom/base/DeferredFinalize.cpp b/xpcom/base/DeferredFinalize.cpp new file mode 100644 index 0000000000..aea034dbf3 --- /dev/null +++ b/xpcom/base/DeferredFinalize.cpp @@ -0,0 +1,23 @@ +/* -*- 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/DeferredFinalize.h" + +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSRuntime.h" + +void mozilla::DeferredFinalize(nsISupports* aSupports) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now"); + rt->DeferredFinalize(aSupports); +} + +void mozilla::DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, void* aThing) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now"); + rt->DeferredFinalize(aAppendFunc, aFunc, aThing); +} diff --git a/xpcom/base/DeferredFinalize.h b/xpcom/base/DeferredFinalize.h new file mode 100644 index 0000000000..8b0e7e28be --- /dev/null +++ b/xpcom/base/DeferredFinalize.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +#ifndef mozilla_DeferredFinalize_h +#define mozilla_DeferredFinalize_h + +#include +#include "mozilla/Attributes.h" + +class nsISupports; + +namespace mozilla { + +// Called back from DeferredFinalize. Should add 'thing' to the array of smart +// pointers in 'pointers', creating the array if 'pointers' is null, and return +// the array. +typedef void* (*DeferredFinalizeAppendFunction)(void* aPointers, void* aThing); + +// Called to finalize a number of objects. Slice is the number of objects to +// finalize. The return value indicates whether it finalized all objects in the +// buffer. If it returns true, the function will not be called again, so the +// function should free aData. +typedef bool (*DeferredFinalizeFunction)(uint32_t aSlice, void* aData); + +void DeferredFinalize(DeferredFinalizeAppendFunction aAppendFunc, + DeferredFinalizeFunction aFunc, void* aThing); + +MOZ_NEVER_INLINE void DeferredFinalize(nsISupports* aSupports); + +} // namespace mozilla + +#endif // mozilla_DeferredFinalize_h diff --git a/xpcom/base/EnumeratedArrayCycleCollection.h b/xpcom/base/EnumeratedArrayCycleCollection.h new file mode 100644 index 0000000000..465d4cf38a --- /dev/null +++ b/xpcom/base/EnumeratedArrayCycleCollection.h @@ -0,0 +1,32 @@ +/* -*- 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/. */ + +#ifndef EnumeratedArrayCycleCollection_h_ +#define EnumeratedArrayCycleCollection_h_ + +#include "mozilla/EnumeratedArray.h" +#include "nsCycleCollectionTraversalCallback.h" + +template +inline void ImplCycleCollectionUnlink( + mozilla::EnumeratedArray& aField) { + for (size_t i = 0; i < size_t(SizeAsEnumValue); ++i) { + aField[IndexType(i)] = nullptr; + } +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + mozilla::EnumeratedArray& aField, + const char* aName, uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + for (size_t i = 0; i < size_t(SizeAsEnumValue); ++i) { + ImplCycleCollectionTraverse(aCallback, aField[IndexType(i)], aName, aFlags); + } +} + +#endif // EnumeratedArrayCycleCollection_h_ diff --git a/xpcom/base/ErrorList.py b/xpcom/base/ErrorList.py new file mode 100755 index 0000000000..68059c8898 --- /dev/null +++ b/xpcom/base/ErrorList.py @@ -0,0 +1,1406 @@ +#!/usr/bin/env python +from collections import OrderedDict + + +class Mod: + """ + A nserror module. When used with a `with` statement, binds the itself to + Mod.active. + """ + + active = None + + def __init__(self, num): + self.num = num + + def __enter__(self): + Mod.active = self + + def __exit__(self, _type, _value, _traceback): + Mod.active = None + + +modules = OrderedDict() + +# To add error code to your module, you need to do the following: +# +# 1) Add a module offset code. Add yours to the bottom of the list +# right below this comment, adding 1. +# +# 2) In your module, define a header file which uses one of the +# NE_ERROR_GENERATExxxxxx macros. Some examples below: +# +# #define NS_ERROR_MYMODULE_MYERROR1 \ +# NS_ERROR_GENERATE(NS_ERROR_SEVERITY_ERROR,NS_ERROR_MODULE_MYMODULE,1) +# #define NS_ERROR_MYMODULE_MYERROR2 NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_MYMODULE,2) +# #define NS_ERROR_MYMODULE_MYERROR3 NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_MYMODULE,3) + +# @name Standard Module Offset Code. Each Module should identify a unique number +# and then all errors associated with that module become offsets from the +# base associated with that module id. There are 16 bits of code bits for +# each module. + +modules["XPCOM"] = Mod(1) +modules["BASE"] = Mod(2) +modules["GFX"] = Mod(3) +modules["WIDGET"] = Mod(4) +modules["CALENDAR"] = Mod(5) +modules["NETWORK"] = Mod(6) +modules["PLUGINS"] = Mod(7) +modules["LAYOUT"] = Mod(8) +modules["HTMLPARSER"] = Mod(9) +modules["RDF"] = Mod(10) +modules["UCONV"] = Mod(11) +modules["REG"] = Mod(12) +modules["FILES"] = Mod(13) +modules["DOM"] = Mod(14) +modules["IMGLIB"] = Mod(15) +modules["MAILNEWS"] = Mod(16) +modules["EDITOR"] = Mod(17) +modules["XPCONNECT"] = Mod(18) +modules["PROFILE"] = Mod(19) +modules["LDAP"] = Mod(20) +modules["SECURITY"] = Mod(21) +modules["DOM_XPATH"] = Mod(22) +# Mod(23) used to be NS_ERROR_MODULE_DOM_RANGE (see bug 711047) +modules["URILOADER"] = Mod(24) +modules["CONTENT"] = Mod(25) +modules["PYXPCOM"] = Mod(26) +modules["XSLT"] = Mod(27) +modules["IPC"] = Mod(28) +modules["SVG"] = Mod(29) +modules["STORAGE"] = Mod(30) +modules["SCHEMA"] = Mod(31) +modules["DOM_FILE"] = Mod(32) +modules["DOM_INDEXEDDB"] = Mod(33) +modules["DOM_FILEHANDLE"] = Mod(34) +modules["SIGNED_JAR"] = Mod(35) +modules["DOM_FILESYSTEM"] = Mod(36) +modules["DOM_BLUETOOTH"] = Mod(37) +modules["SIGNED_APP"] = Mod(38) +modules["DOM_ANIM"] = Mod(39) +modules["DOM_PUSH"] = Mod(40) +modules["DOM_MEDIA"] = Mod(41) +modules["URL_CLASSIFIER"] = Mod(42) +# ErrorResult gets its own module to reduce the chance of someone accidentally +# defining an error code matching one of the ErrorResult ones. +modules["ERRORRESULT"] = Mod(43) +# Win32 system error codes, which are not mapped to a specific other value, +# see Bug 1686041. +modules["WIN32"] = Mod(44) + +# NS_ERROR_MODULE_GENERAL should be used by modules that do not +# care if return code values overlap. Callers of methods that +# return such codes should be aware that they are not +# globally unique. Implementors should be careful about blindly +# returning codes from other modules that might also use +# the generic base. +modules["GENERAL"] = Mod(51) + +MODULE_BASE_OFFSET = 0x45 + +NS_ERROR_SEVERITY_SUCCESS = 0 +NS_ERROR_SEVERITY_ERROR = 1 + + +def SUCCESS_OR_FAILURE(sev, module, code): + return (sev << 31) | ((module + MODULE_BASE_OFFSET) << 16) | code + + +def FAILURE(code): + return SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_ERROR, Mod.active.num, code) + + +def SUCCESS(code): + return SUCCESS_OR_FAILURE(NS_ERROR_SEVERITY_SUCCESS, Mod.active.num, code) + + +# Errors is an ordered dictionary, so that we can recover the order in which +# they were defined. This is important for determining which name is the +# canonical name for an error code. +errors = OrderedDict() + +# Standard "it worked" return value +errors["NS_OK"] = 0 + +# ======================================================================= +# Core errors, not part of any modules +# ======================================================================= +errors["NS_ERROR_BASE"] = 0xC1F30000 +# Returned when an instance is not initialized +errors["NS_ERROR_NOT_INITIALIZED"] = errors["NS_ERROR_BASE"] + 1 +# Returned when an instance is already initialized +errors["NS_ERROR_ALREADY_INITIALIZED"] = errors["NS_ERROR_BASE"] + 2 +# Returned by a not implemented function +errors["NS_ERROR_NOT_IMPLEMENTED"] = 0x80004001 +# Returned when a given interface is not supported. +errors["NS_NOINTERFACE"] = 0x80004002 +errors["NS_ERROR_NO_INTERFACE"] = errors["NS_NOINTERFACE"] +# Returned when a function aborts +errors["NS_ERROR_ABORT"] = 0x80004004 +# Returned when a function fails +errors["NS_ERROR_FAILURE"] = 0x80004005 +# Returned when an unexpected error occurs +errors["NS_ERROR_UNEXPECTED"] = 0x8000FFFF +# Returned when a memory allocation fails +errors["NS_ERROR_OUT_OF_MEMORY"] = 0x8007000E +# Returned when an illegal value is passed +errors["NS_ERROR_ILLEGAL_VALUE"] = 0x80070057 +errors["NS_ERROR_INVALID_ARG"] = errors["NS_ERROR_ILLEGAL_VALUE"] +errors["NS_ERROR_INVALID_POINTER"] = errors["NS_ERROR_INVALID_ARG"] +errors["NS_ERROR_NULL_POINTER"] = errors["NS_ERROR_INVALID_ARG"] +# Returned when an operation can't complete due to an unavailable resource +errors["NS_ERROR_NOT_AVAILABLE"] = 0x80040111 +# Returned when a class is not registered +errors["NS_ERROR_FACTORY_NOT_REGISTERED"] = 0x80040154 +# Returned when a class cannot be registered, but may be tried again later +errors["NS_ERROR_FACTORY_REGISTER_AGAIN"] = 0x80040155 +# Returned when a dynamically loaded factory couldn't be found +errors["NS_ERROR_FACTORY_NOT_LOADED"] = 0x800401F8 +# Returned when a factory doesn't support signatures +errors["NS_ERROR_FACTORY_NO_SIGNATURE_SUPPORT"] = errors["NS_ERROR_BASE"] + 0x101 +# Returned when a factory already is registered +errors["NS_ERROR_FACTORY_EXISTS"] = errors["NS_ERROR_BASE"] + 0x100 + + +# ======================================================================= +# 1: NS_ERROR_MODULE_XPCOM +# ======================================================================= +with modules["XPCOM"]: + # Result codes used by nsIVariant + errors["NS_ERROR_CANNOT_CONVERT_DATA"] = FAILURE(1) + errors["NS_ERROR_OBJECT_IS_IMMUTABLE"] = FAILURE(2) + errors["NS_ERROR_LOSS_OF_SIGNIFICANT_DATA"] = FAILURE(3) + # Result code used by nsIThreadManager + errors["NS_ERROR_NOT_SAME_THREAD"] = FAILURE(4) + # Various operations are not permitted during XPCOM shutdown and will fail + # with this exception. + errors["NS_ERROR_ILLEGAL_DURING_SHUTDOWN"] = FAILURE(30) + errors["NS_ERROR_SERVICE_NOT_AVAILABLE"] = FAILURE(22) + + errors["NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA"] = SUCCESS(1) + # Used by nsCycleCollectionParticipant + errors["NS_SUCCESS_INTERRUPTED_TRAVERSE"] = SUCCESS(2) + +# ======================================================================= +# 2: NS_ERROR_MODULE_BASE +# ======================================================================= +with modules["BASE"]: + # I/O Errors + + # Stream closed + errors["NS_BASE_STREAM_CLOSED"] = FAILURE(2) + # Error from the operating system + errors["NS_BASE_STREAM_OSERROR"] = FAILURE(3) + # Illegal arguments + errors["NS_BASE_STREAM_ILLEGAL_ARGS"] = FAILURE(4) + # For unichar streams + errors["NS_BASE_STREAM_NO_CONVERTER"] = FAILURE(5) + # For unichar streams + errors["NS_BASE_STREAM_BAD_CONVERSION"] = FAILURE(6) + errors["NS_BASE_STREAM_WOULD_BLOCK"] = FAILURE(7) + + +# ======================================================================= +# 3: NS_ERROR_MODULE_GFX +# ======================================================================= +with modules["GFX"]: + # no printer available (e.g. cannot find _any_ printer) + errors["NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE"] = FAILURE(1) + # _specified_ (by name) printer not found + errors["NS_ERROR_GFX_PRINTER_NAME_NOT_FOUND"] = FAILURE(2) + # print-to-file: could not open output file + errors["NS_ERROR_GFX_PRINTER_COULD_NOT_OPEN_FILE"] = FAILURE(3) + # print: starting document + errors["NS_ERROR_GFX_PRINTER_STARTDOC"] = FAILURE(4) + # print: ending document + errors["NS_ERROR_GFX_PRINTER_ENDDOC"] = FAILURE(5) + # print: starting page + errors["NS_ERROR_GFX_PRINTER_STARTPAGE"] = FAILURE(6) + # The document is still being loaded + errors["NS_ERROR_GFX_PRINTER_DOC_IS_BUSY"] = FAILURE(7) + + # Font cmap is strangely structured - avoid this font! + errors["NS_ERROR_GFX_CMAP_MALFORMED"] = FAILURE(51) + + +# ======================================================================= +# 4: NS_ERROR_MODULE_WIDGET +# ======================================================================= +with modules["WIDGET"]: + # Used by: + # - nsIWidget::NotifyIME() + # Returned when the notification or the event is handled and it's consumed + # by somebody. + errors["NS_SUCCESS_EVENT_CONSUMED"] = SUCCESS(1) + + +# ======================================================================= +# 6: NS_ERROR_MODULE_NETWORK +# ======================================================================= +with modules["NETWORK"]: + # General async request error codes: + # + # These error codes are commonly passed through callback methods to indicate + # the status of some requested async request. + # + # For example, see nsIRequestObserver::onStopRequest. + + # The async request completed successfully. + errors["NS_BINDING_SUCCEEDED"] = errors["NS_OK"] + + # The async request failed for some unknown reason. + errors["NS_BINDING_FAILED"] = FAILURE(1) + # The async request failed because it was aborted by some user action. + errors["NS_BINDING_ABORTED"] = FAILURE(2) + # The async request has been "redirected" to a different async request. + # (e.g., an HTTP redirect occurred). + # + # This error code is used with load groups to notify the load group observer + # when a request in the load group is redirected to another request. + errors["NS_BINDING_REDIRECTED"] = FAILURE(3) + # The async request has been "retargeted" to a different "handler." + # + # This error code is used with load groups to notify the load group observer + # when a request in the load group is removed from the load group and added + # to a different load group. + errors["NS_BINDING_RETARGETED"] = FAILURE(4) + + # Miscellaneous error codes: These errors are not typically passed via + # onStopRequest. + + # The URI is malformed. + errors["NS_ERROR_MALFORMED_URI"] = FAILURE(10) + # The requested action could not be completed while the object is busy. + # Implementations of nsIChannel::asyncOpen will commonly return this error + # if the channel has already been opened (and has not yet been closed). + errors["NS_ERROR_IN_PROGRESS"] = FAILURE(15) + # Returned from nsIChannel::asyncOpen to indicate that OnDataAvailable will + # not be called because there is no content available. This is used by + # helper app style protocols (e.g., mailto). XXX perhaps this should be a + # success code. + errors["NS_ERROR_NO_CONTENT"] = FAILURE(17) + # The URI scheme corresponds to an unknown protocol handler. + errors["NS_ERROR_UNKNOWN_PROTOCOL"] = FAILURE(18) + # The content encoding of the source document was incorrect, for example + # returning a plain HTML document advertised as Content-Encoding: gzip + errors["NS_ERROR_INVALID_CONTENT_ENCODING"] = FAILURE(27) + # A transport level corruption was found in the source document. for example + # a document with a calculated checksum that does not match the Content-MD5 + # http header. + errors["NS_ERROR_CORRUPTED_CONTENT"] = FAILURE(29) + # A content signature verification failed for some reason. This can be either + # an actual verification error, or any other error that led to the fact that + # a content signature that was expected couldn't be verified. + errors["NS_ERROR_INVALID_SIGNATURE"] = FAILURE(58) + # While parsing for the first component of a header field using syntax as in + # Content-Disposition or Content-Type, the first component was found to be + # empty, such as in: Content-Disposition: ; filename=foo + errors["NS_ERROR_FIRST_HEADER_FIELD_COMPONENT_EMPTY"] = FAILURE(34) + # Returned from nsIChannel::asyncOpen when trying to open the channel again + # (reopening is not supported). + errors["NS_ERROR_ALREADY_OPENED"] = FAILURE(73) + + # Connectivity error codes: + + # The connection is already established. XXX unused - consider removing. + errors["NS_ERROR_ALREADY_CONNECTED"] = FAILURE(11) + # The connection does not exist. XXX unused - consider removing. + errors["NS_ERROR_NOT_CONNECTED"] = FAILURE(12) + # The connection attempt failed, for example, because no server was + # listening at specified host:port. + errors["NS_ERROR_CONNECTION_REFUSED"] = FAILURE(13) + # The connection was lost due to a timeout error. + errors["NS_ERROR_NET_TIMEOUT"] = FAILURE(14) + # The requested action could not be completed while the networking library + # is in the offline state. + errors["NS_ERROR_OFFLINE"] = FAILURE(16) + # The requested action was prohibited because it would have caused the + # networking library to establish a connection to an unsafe or otherwise + # banned port. + errors["NS_ERROR_PORT_ACCESS_NOT_ALLOWED"] = FAILURE(19) + # The connection was established, but no data was ever received. + errors["NS_ERROR_NET_RESET"] = FAILURE(20) + # The connection was established, but the data transfer was interrupted. + errors["NS_ERROR_NET_INTERRUPT"] = FAILURE(71) + # The connection attempt to a proxy failed. + errors["NS_ERROR_PROXY_CONNECTION_REFUSED"] = FAILURE(72) + # A transfer was only partially done when it completed. + errors["NS_ERROR_NET_PARTIAL_TRANSFER"] = FAILURE(76) + # HTTP/2 detected invalid TLS configuration + errors["NS_ERROR_NET_INADEQUATE_SECURITY"] = FAILURE(82) + # HTTP/2 sent a GOAWAY + errors["NS_ERROR_NET_HTTP2_SENT_GOAWAY"] = FAILURE(83) + # HTTP/3 protocol internal error + errors["NS_ERROR_NET_HTTP3_PROTOCOL_ERROR"] = FAILURE(84) + # A timeout error code that can be used to cancel requests. + errors["NS_ERROR_NET_TIMEOUT_EXTERNAL"] = FAILURE(85) + # An error related to HTTPS-only mode + errors["NS_ERROR_HTTPS_ONLY"] = FAILURE(86) + # A WebSocket connection is failed. + errors["NS_ERROR_WEBSOCKET_CONNECTION_REFUSED"] = FAILURE(87) + # A connection to a non local address is refused because + # xpc::AreNonLocalConnectionsDisabled() returns true. + errors["NS_ERROR_NON_LOCAL_CONNECTION_REFUSED"] = FAILURE(88) + # Connection to a sts host without a hsts header. + errors["NS_ERROR_BAD_HSTS_CERT"] = FAILURE(89) + + # XXX really need to better rationalize these error codes. are consumers of + # necko really expected to know how to discern the meaning of these?? + # This request is not resumable, but it was tried to resume it, or to + # request resume-specific data. + errors["NS_ERROR_NOT_RESUMABLE"] = FAILURE(25) + # The request failed as a result of a detected redirection loop. + errors["NS_ERROR_REDIRECT_LOOP"] = FAILURE(31) + # It was attempted to resume the request, but the entity has changed in the + # meantime. + errors["NS_ERROR_ENTITY_CHANGED"] = FAILURE(32) + # The request failed because the content type returned by the server was not + # a type expected by the channel (for nested channels such as the JAR + # channel). + errors["NS_ERROR_UNSAFE_CONTENT_TYPE"] = FAILURE(74) + # The request resulted in an error page being displayed. + errors["NS_ERROR_LOAD_SHOWED_ERRORPAGE"] = FAILURE(77) + # The request occurred in docshell that lacks a treeowner, so it is + # probably in the process of being torn down. + errors["NS_ERROR_DOCSHELL_DYING"] = FAILURE(78) + + # DNS specific error codes: + + # The lookup of a hostname failed. This generally refers to the hostname + # from the URL being loaded. + errors["NS_ERROR_UNKNOWN_HOST"] = FAILURE(30) + # A low or medium priority DNS lookup failed because the pending queue was + # already full. High priorty (the default) always makes room + errors["NS_ERROR_DNS_LOOKUP_QUEUE_FULL"] = FAILURE(33) + # The lookup of a proxy hostname failed. If a channel is configured to + # speak to a proxy server, then it will generate this error if the proxy + # hostname cannot be resolved. + errors["NS_ERROR_UNKNOWN_PROXY_HOST"] = FAILURE(42) + # This DNS error will occur when the resolver uses the Extended DNS Error + # option to indicate an error code for which we should not fall back to the + # default DNS resolver. This means the DNS failure is definitive. + errors["NS_ERROR_DEFINITIVE_UNKNOWN_HOST"] = FAILURE(43) + + # Socket specific error codes: + + # The specified socket type does not exist. + errors["NS_ERROR_UNKNOWN_SOCKET_TYPE"] = FAILURE(51) + # The specified socket type could not be created. + errors["NS_ERROR_SOCKET_CREATE_FAILED"] = FAILURE(52) + # The operating system doesn't support the given type of address. + errors["NS_ERROR_SOCKET_ADDRESS_NOT_SUPPORTED"] = FAILURE(53) + # The address to which we tried to bind the socket was busy. + errors["NS_ERROR_SOCKET_ADDRESS_IN_USE"] = FAILURE(54) + + # Cache specific error codes: + errors["NS_ERROR_CACHE_KEY_NOT_FOUND"] = FAILURE(61) + errors["NS_ERROR_CACHE_DATA_IS_STREAM"] = FAILURE(62) + errors["NS_ERROR_CACHE_DATA_IS_NOT_STREAM"] = FAILURE(63) + errors["NS_ERROR_CACHE_WAIT_FOR_VALIDATION"] = FAILURE(64) + errors["NS_ERROR_CACHE_ENTRY_DOOMED"] = FAILURE(65) + errors["NS_ERROR_CACHE_READ_ACCESS_DENIED"] = FAILURE(66) + errors["NS_ERROR_CACHE_WRITE_ACCESS_DENIED"] = FAILURE(67) + errors["NS_ERROR_CACHE_IN_USE"] = FAILURE(68) + # Error passed through onStopRequest if the document could not be fetched + # from the cache. + errors["NS_ERROR_DOCUMENT_NOT_CACHED"] = FAILURE(70) + + # Effective TLD Service specific error codes: + + # The requested number of domain levels exceeds those present in the host + # string. + errors["NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS"] = FAILURE(80) + # The host string is an IP address. + errors["NS_ERROR_HOST_IS_IP_ADDRESS"] = FAILURE(81) + + # StreamLoader specific result codes: + + # Result code returned by nsIStreamLoaderObserver to indicate that the + # observer is taking over responsibility for the data buffer, and the loader + # should NOT free it. + errors["NS_SUCCESS_ADOPTED_DATA"] = SUCCESS(90) + + # This success code may be returned by nsIAuthModule::getNextToken to + # indicate that the authentication is finished and thus there's no need + # to call getNextToken again. + errors["NS_SUCCESS_AUTH_FINISHED"] = SUCCESS(40) + + # These are really not "results", they're statuses, used by nsITransport and + # friends. This is abuse of nsresult, but we'll put up with it for now. + # nsITransport + errors["NS_NET_STATUS_READING"] = SUCCESS(8) + errors["NS_NET_STATUS_WRITING"] = SUCCESS(9) + + # nsISocketTransport + errors["NS_NET_STATUS_RESOLVING_HOST"] = SUCCESS(3) + errors["NS_NET_STATUS_RESOLVED_HOST"] = SUCCESS(11) + errors["NS_NET_STATUS_CONNECTING_TO"] = SUCCESS(7) + errors["NS_NET_STATUS_CONNECTED_TO"] = SUCCESS(4) + errors["NS_NET_STATUS_TLS_HANDSHAKE_STARTING"] = SUCCESS(12) + errors["NS_NET_STATUS_TLS_HANDSHAKE_ENDED"] = SUCCESS(13) + errors["NS_NET_STATUS_SENDING_TO"] = SUCCESS(5) + errors["NS_NET_STATUS_WAITING_FOR"] = SUCCESS(10) + errors["NS_NET_STATUS_RECEIVING_FROM"] = SUCCESS(6) + + # nsIInterceptedChannel + # Generic error for non-specific failures during service worker interception + errors["NS_ERROR_INTERCEPTION_FAILED"] = FAILURE(100) + + errors["NS_ERROR_WEBTRANSPORT_CODE_BASE"] = FAILURE(200) + errors["NS_ERROR_WEBTRANSPORT_CODE_END"] = ( + errors["NS_ERROR_WEBTRANSPORT_CODE_BASE"] + 255 + ) + + # All Http proxy CONNECT response codes + errors["NS_ERROR_PROXY_CODE_BASE"] = FAILURE(1000) + # Redirection 3xx + errors["NS_ERROR_PROXY_MULTIPLE_CHOICES"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 300 + errors["NS_ERROR_PROXY_MOVED_PERMANENTLY"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 301 + ) + errors["NS_ERROR_PROXY_FOUND"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 302 + errors["NS_ERROR_PROXY_SEE_OTHER"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 303 + errors["NS_ERROR_PROXY_NOT_MODIFIED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 304 + errors["NS_ERROR_PROXY_TEMPORARY_REDIRECT"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 307 + ) + errors["NS_ERROR_PROXY_PERMANENT_REDIRECT"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 308 + ) + + # Client error 4xx + errors["NS_ERROR_PROXY_BAD_REQUEST"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 400 + errors["NS_ERROR_PROXY_UNAUTHORIZED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 401 + errors["NS_ERROR_PROXY_PAYMENT_REQUIRED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 402 + errors["NS_ERROR_PROXY_FORBIDDEN"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 403 + errors["NS_ERROR_PROXY_NOT_FOUND"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 404 + errors["NS_ERROR_PROXY_METHOD_NOT_ALLOWED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 405 + ) + errors["NS_ERROR_PROXY_NOT_ACCEPTABLE"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 406 + # The proxy requires authentication; used when we can't easily propagate 407s. + errors["NS_ERROR_PROXY_AUTHENTICATION_FAILED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 407 + ) + errors["NS_ERROR_PROXY_REQUEST_TIMEOUT"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 408 + errors["NS_ERROR_PROXY_CONFLICT"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 409 + errors["NS_ERROR_PROXY_GONE"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 410 + errors["NS_ERROR_PROXY_LENGTH_REQUIRED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 411 + errors["NS_ERROR_PROXY_PRECONDITION_FAILED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 412 + ) + errors["NS_ERROR_PROXY_REQUEST_ENTITY_TOO_LARGE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 413 + ) + errors["NS_ERROR_PROXY_REQUEST_URI_TOO_LONG"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 414 + ) + errors["NS_ERROR_PROXY_UNSUPPORTED_MEDIA_TYPE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 415 + ) + errors["NS_ERROR_PROXY_REQUESTED_RANGE_NOT_SATISFIABLE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 416 + ) + errors["NS_ERROR_PROXY_EXPECTATION_FAILED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 417 + ) + errors["NS_ERROR_PROXY_MISDIRECTED_REQUEST"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 421 + ) + errors["NS_ERROR_PROXY_TOO_EARLY"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 425 + errors["NS_ERROR_PROXY_UPGRADE_REQUIRED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 426 + errors["NS_ERROR_PROXY_PRECONDITION_REQUIRED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 428 + ) + # Indicates that we have sent too many requests in a given amount of time. + errors["NS_ERROR_PROXY_TOO_MANY_REQUESTS"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 429 + ) + errors["NS_ERROR_PROXY_REQUEST_HEADER_FIELDS_TOO_LARGE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 431 + ) + errors["NS_ERROR_PROXY_UNAVAILABLE_FOR_LEGAL_REASONS"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 451 + ) + + # Server error 5xx + errors["NS_ERROR_PROXY_INTERNAL_SERVER_ERROR"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 500 + ) + errors["NS_ERROR_PROXY_NOT_IMPLEMENTED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 501 + errors["NS_ERROR_PROXY_BAD_GATEWAY"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 502 + errors["NS_ERROR_PROXY_SERVICE_UNAVAILABLE"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 503 + ) + # The proxy did get any response from the remote server in time. + errors["NS_ERROR_PROXY_GATEWAY_TIMEOUT"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 504 + errors["NS_ERROR_PROXY_VERSION_NOT_SUPPORTED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 505 + ) + errors["NS_ERROR_PROXY_VARIANT_ALSO_NEGOTIATES"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 506 + ) + errors["NS_ERROR_PROXY_NOT_EXTENDED"] = errors["NS_ERROR_PROXY_CODE_BASE"] + 510 + errors["NS_ERROR_PROXY_NETWORK_AUTHENTICATION_REQUIRED"] = ( + errors["NS_ERROR_PROXY_CODE_BASE"] + 511 + ) + +# ======================================================================= +# 7: NS_ERROR_MODULE_PLUGINS +# ======================================================================= +with modules["PLUGINS"]: + errors["NS_ERROR_PLUGINS_PLUGINSNOTCHANGED"] = FAILURE(1000) + errors["NS_ERROR_PLUGIN_DISABLED"] = FAILURE(1001) + errors["NS_ERROR_PLUGIN_BLOCKLISTED"] = FAILURE(1002) + errors["NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED"] = FAILURE(1003) + errors["NS_ERROR_PLUGIN_CLICKTOPLAY"] = FAILURE(1004) + + +# ======================================================================= +# 8: NS_ERROR_MODULE_LAYOUT +# ======================================================================= +with modules["LAYOUT"]: + # Return code for SheetLoadData::VerifySheetReadyToParse + errors["NS_OK_PARSE_SHEET"] = SUCCESS(1) + + +# ======================================================================= +# 9: NS_ERROR_MODULE_HTMLPARSER +# ======================================================================= +with modules["HTMLPARSER"]: + errors["NS_ERROR_HTMLPARSER_CONTINUE"] = errors["NS_OK"] + + errors["NS_ERROR_HTMLPARSER_EOF"] = FAILURE(1000) + errors["NS_ERROR_HTMLPARSER_UNKNOWN"] = FAILURE(1001) + errors["NS_ERROR_HTMLPARSER_CANTPROPAGATE"] = FAILURE(1002) + errors["NS_ERROR_HTMLPARSER_CONTEXTMISMATCH"] = FAILURE(1003) + errors["NS_ERROR_HTMLPARSER_BADFILENAME"] = FAILURE(1004) + errors["NS_ERROR_HTMLPARSER_BADURL"] = FAILURE(1005) + errors["NS_ERROR_HTMLPARSER_INVALIDPARSERCONTEXT"] = FAILURE(1006) + errors["NS_ERROR_HTMLPARSER_INTERRUPTED"] = FAILURE(1007) + errors["NS_ERROR_HTMLPARSER_BLOCK"] = FAILURE(1008) + errors["NS_ERROR_HTMLPARSER_BADTOKENIZER"] = FAILURE(1009) + errors["NS_ERROR_HTMLPARSER_BADATTRIBUTE"] = FAILURE(1010) + errors["NS_ERROR_HTMLPARSER_UNRESOLVEDDTD"] = FAILURE(1011) + errors["NS_ERROR_HTMLPARSER_MISPLACEDTABLECONTENT"] = FAILURE(1012) + errors["NS_ERROR_HTMLPARSER_BADDTD"] = FAILURE(1013) + errors["NS_ERROR_HTMLPARSER_BADCONTEXT"] = FAILURE(1014) + errors["NS_ERROR_HTMLPARSER_STOPPARSING"] = FAILURE(1015) + errors["NS_ERROR_HTMLPARSER_UNTERMINATEDSTRINGLITERAL"] = FAILURE(1016) + errors["NS_ERROR_HTMLPARSER_HIERARCHYTOODEEP"] = FAILURE(1017) + errors["NS_ERROR_HTMLPARSER_FAKE_ENDTAG"] = FAILURE(1018) + errors["NS_ERROR_HTMLPARSER_INVALID_COMMENT"] = FAILURE(1019) + + +# ======================================================================= +# 10: NS_ERROR_MODULE_RDF +# ======================================================================= +with modules["RDF"]: + # Returned from nsIRDFDataSource::Assert() and Unassert() if the assertion + # (or unassertion was accepted by the datasource + errors["NS_RDF_ASSERTION_ACCEPTED"] = errors["NS_OK"] + # Returned from nsIRDFDataSource::GetSource() and GetTarget() if the + # source/target has no value + errors["NS_RDF_NO_VALUE"] = SUCCESS(2) + # Returned from nsIRDFDataSource::Assert() and Unassert() if the assertion + # (or unassertion) was rejected by the datasource; i.e., the datasource was + # not willing to record the statement. + errors["NS_RDF_ASSERTION_REJECTED"] = SUCCESS(3) + # Return this from rdfITripleVisitor to stop cycling + errors["NS_RDF_STOP_VISIT"] = SUCCESS(4) + + +# ======================================================================= +# 11: NS_ERROR_MODULE_UCONV +# ======================================================================= +with modules["UCONV"]: + errors["NS_ERROR_UCONV_NOCONV"] = FAILURE(1) + errors["NS_ERROR_UDEC_ILLEGALINPUT"] = FAILURE(14) + + errors["NS_OK_HAD_REPLACEMENTS"] = SUCCESS(3) + errors["NS_OK_UDEC_MOREINPUT"] = SUCCESS(12) + errors["NS_OK_UDEC_MOREOUTPUT"] = SUCCESS(13) + errors["NS_OK_UENC_MOREOUTPUT"] = SUCCESS(34) + errors["NS_ERROR_UENC_NOMAPPING"] = SUCCESS(35) + + # BEGIN DEPRECATED + errors["NS_ERROR_ILLEGAL_INPUT"] = errors["NS_ERROR_UDEC_ILLEGALINPUT"] + # END DEPRECATED + + +# ======================================================================= +# 13: NS_ERROR_MODULE_FILES +# ======================================================================= +with modules["FILES"]: + errors["NS_ERROR_FILE_UNRECOGNIZED_PATH"] = FAILURE(1) + errors["NS_ERROR_FILE_UNRESOLVABLE_SYMLINK"] = FAILURE(2) + errors["NS_ERROR_FILE_EXECUTION_FAILED"] = FAILURE(3) + errors["NS_ERROR_FILE_UNKNOWN_TYPE"] = FAILURE(4) + errors["NS_ERROR_FILE_DESTINATION_NOT_DIR"] = FAILURE(5) + errors["NS_ERROR_FILE_COPY_OR_MOVE_FAILED"] = FAILURE(7) + errors["NS_ERROR_FILE_ALREADY_EXISTS"] = FAILURE(8) + errors["NS_ERROR_FILE_INVALID_PATH"] = FAILURE(9) + errors["NS_ERROR_FILE_CORRUPTED"] = FAILURE(11) + errors["NS_ERROR_FILE_NOT_DIRECTORY"] = FAILURE(12) + errors["NS_ERROR_FILE_IS_DIRECTORY"] = FAILURE(13) + errors["NS_ERROR_FILE_IS_LOCKED"] = FAILURE(14) + errors["NS_ERROR_FILE_TOO_BIG"] = FAILURE(15) + errors["NS_ERROR_FILE_NO_DEVICE_SPACE"] = FAILURE(16) + errors["NS_ERROR_FILE_NAME_TOO_LONG"] = FAILURE(17) + errors["NS_ERROR_FILE_NOT_FOUND"] = FAILURE(18) + errors["NS_ERROR_FILE_READ_ONLY"] = FAILURE(19) + errors["NS_ERROR_FILE_DIR_NOT_EMPTY"] = FAILURE(20) + errors["NS_ERROR_FILE_ACCESS_DENIED"] = FAILURE(21) + errors["NS_ERROR_FILE_FS_CORRUPTED"] = FAILURE(22) + errors["NS_ERROR_FILE_DEVICE_FAILURE"] = FAILURE(23) + errors["NS_ERROR_FILE_DEVICE_TEMPORARY_FAILURE"] = FAILURE(24) + errors["NS_ERROR_FILE_INVALID_HANDLE"] = FAILURE(25) + + errors["NS_SUCCESS_FILE_DIRECTORY_EMPTY"] = SUCCESS(1) + # Result codes used by nsIDirectoryServiceProvider2 + errors["NS_SUCCESS_AGGREGATE_RESULT"] = SUCCESS(2) + + +# ======================================================================= +# 14: NS_ERROR_MODULE_DOM +# ======================================================================= +with modules["DOM"]: + # XXX If you add a new DOM error code, also add an error string to + # dom/base/domerr.msg + + # Standard DOM error codes: http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html + errors["NS_ERROR_DOM_INDEX_SIZE_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_HIERARCHY_REQUEST_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_WRONG_DOCUMENT_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_INVALID_CHARACTER_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR"] = FAILURE(7) + errors["NS_ERROR_DOM_NOT_FOUND_ERR"] = FAILURE(8) + errors["NS_ERROR_DOM_NOT_SUPPORTED_ERR"] = FAILURE(9) + errors["NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR"] = FAILURE(10) + errors["NS_ERROR_DOM_INVALID_STATE_ERR"] = FAILURE(11) + errors["NS_ERROR_DOM_SYNTAX_ERR"] = FAILURE(12) + errors["NS_ERROR_DOM_INVALID_MODIFICATION_ERR"] = FAILURE(13) + errors["NS_ERROR_DOM_NAMESPACE_ERR"] = FAILURE(14) + errors["NS_ERROR_DOM_INVALID_ACCESS_ERR"] = FAILURE(15) + errors["NS_ERROR_DOM_TYPE_MISMATCH_ERR"] = FAILURE(17) + errors["NS_ERROR_DOM_SECURITY_ERR"] = FAILURE(18) + errors["NS_ERROR_DOM_NETWORK_ERR"] = FAILURE(19) + errors["NS_ERROR_DOM_ABORT_ERR"] = FAILURE(20) + errors["NS_ERROR_DOM_URL_MISMATCH_ERR"] = FAILURE(21) + errors["NS_ERROR_DOM_QUOTA_EXCEEDED_ERR"] = FAILURE(22) + errors["NS_ERROR_DOM_TIMEOUT_ERR"] = FAILURE(23) + errors["NS_ERROR_DOM_INVALID_NODE_TYPE_ERR"] = FAILURE(24) + errors["NS_ERROR_DOM_DATA_CLONE_ERR"] = FAILURE(25) + # StringEncoding API errors from http://wiki.whatwg.org/wiki/StringEncoding + errors["NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR"] = FAILURE(28) + # WebCrypto API errors from http://www.w3.org/TR/WebCryptoAPI/ + errors["NS_ERROR_DOM_UNKNOWN_ERR"] = FAILURE(30) + errors["NS_ERROR_DOM_DATA_ERR"] = FAILURE(31) + errors["NS_ERROR_DOM_OPERATION_ERR"] = FAILURE(32) + # https://heycam.github.io/webidl/#notallowederror + errors["NS_ERROR_DOM_NOT_ALLOWED_ERR"] = FAILURE(33) + # DOM error codes defined by us + errors["NS_ERROR_DOM_WRONG_TYPE_ERR"] = FAILURE(1002) + errors["NS_ERROR_DOM_NOT_NUMBER_ERR"] = FAILURE(1005) + errors["NS_ERROR_DOM_PROP_ACCESS_DENIED"] = FAILURE(1010) + errors["NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED"] = FAILURE(1011) + errors["NS_ERROR_DOM_BAD_URI"] = FAILURE(1012) + errors["NS_ERROR_DOM_RETVAL_UNDEFINED"] = FAILURE(1013) + + # A way to represent uncatchable exceptions + errors["NS_ERROR_UNCATCHABLE_EXCEPTION"] = FAILURE(1015) + + errors["NS_ERROR_DOM_MALFORMED_URI"] = FAILURE(1016) + errors["NS_ERROR_DOM_INVALID_HEADER_NAME"] = FAILURE(1017) + + errors["NS_ERROR_DOM_INVALID_STATE_XHR_HAS_INVALID_CONTEXT"] = FAILURE(1018) + + # When manipulating the bytecode cache with the JS API, some transcoding + # errors, such as a different bytecode format can cause failures of the + # decoding process. + errors["NS_ERROR_DOM_JS_DECODING_ERROR"] = FAILURE(1026) + + # Image decode errors. + errors["NS_ERROR_DOM_IMAGE_INACTIVE_DOCUMENT"] = FAILURE(1027) + errors["NS_ERROR_DOM_IMAGE_INVALID_REQUEST"] = FAILURE(1028) + errors["NS_ERROR_DOM_IMAGE_BROKEN"] = FAILURE(1029) + + # Used to indicate that a resource with the Cross-Origin-Resource-Policy + # response header set failed the origin check. + # https://fetch.spec.whatwg.org/#cross-origin-resource-policy-header + errors["NS_ERROR_DOM_CORP_FAILED"] = FAILURE(1036) + + # Used to indicate that a URI may not be loaded into a cross-origin + # context. + errors["NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI"] = FAILURE(1037) + + # The request failed because there are too many recursive iframes or + # objects being loaded. + errors["NS_ERROR_RECURSIVE_DOCUMENT_LOAD"] = FAILURE(1038) + + # WebExtension content script may not load this URL. + errors["NS_ERROR_DOM_WEBEXT_CONTENT_SCRIPT_URI"] = FAILURE(1039) + + # Used to indicate that a resource load was blocked because of the + # Cross-Origin-Embedder-Policy response header. + # https://html.spec.whatwg.org/multipage/origin.html#coep + errors["NS_ERROR_DOM_COEP_FAILED"] = FAILURE(1040) + + # Used to indicate that a resource load was blocked because of the + # Cross-Origin-Opener-Policy response header. + # https://html.spec.whatwg.org/multipage/origin.html#cross-origin-opener-policies + errors["NS_ERROR_DOM_COOP_FAILED"] = FAILURE(1041) + + # May be used to indicate when e.g. setting a property value didn't + # actually change the value, like for obj.foo = "bar"; obj.foo = "bar"; + # the second assignment throws NS_SUCCESS_DOM_NO_OPERATION. + errors["NS_SUCCESS_DOM_NO_OPERATION"] = SUCCESS(1) + + # A success code that indicates that evaluating a string of JS went + # just fine except it threw an exception. Only for legacy use by + # nsJSUtils. + errors["NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW"] = SUCCESS(2) + + # A success code that indicates that evaluating a string of JS went + # just fine except it was killed by an uncatchable exception. + # Only for legacy use by nsJSUtils. + errors["NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE"] = SUCCESS(3) + + +# ======================================================================= +# 15: NS_ERROR_MODULE_IMGLIB +# ======================================================================= +with modules["IMGLIB"]: + errors["NS_IMAGELIB_ERROR_FAILURE"] = FAILURE(5) + errors["NS_IMAGELIB_ERROR_NO_DECODER"] = FAILURE(6) + errors["NS_IMAGELIB_ERROR_NOT_FINISHED"] = FAILURE(7) + errors["NS_IMAGELIB_ERROR_NO_ENCODER"] = FAILURE(9) + + +# ======================================================================= +# 17: NS_ERROR_MODULE_EDITOR +# ======================================================================= +with modules["EDITOR"]: + errors["NS_ERROR_EDITOR_DESTROYED"] = FAILURE(1) + + # An error code that indicates that the DOM tree has been modified by + # web app or add-on while the editor modifying the tree. However, + # this shouldn't be exposed to the web because the result should've + # been expected by the web app. + errors["NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE"] = FAILURE(2) + + # An error code that indicates that the edit action canceled by + # clipboard event listener or beforeinput event listener. Note that + # don't make this as a success code since it's not check with NS_FAILED() + # and may keep handling the operation unexpectedly. + errors["NS_ERROR_EDITOR_ACTION_CANCELED"] = FAILURE(3) + + # An error code that indicates that there is no editable selection ranges. + # E.g., Selection has no range, caret is in non-editable element, + # non-collapsed range crosses editing host boundaries. + errors["NS_ERROR_EDITOR_NO_EDITABLE_RANGE"] = FAILURE(4) + + errors["NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND"] = SUCCESS(1) + errors["NS_SUCCESS_EDITOR_FOUND_TARGET"] = SUCCESS(2) + + # If most callers ignore error except serious error (like + # NS_ERROR_EDITOR_DESTROYED), this success code is useful. E.g. such + # callers can do: + # nsresult rv = Foo(); + # if (MOZ_UNLIKELY(NS_FAILED(rv))) { + # NS_WARNING("Foo() failed"); + # return rv; + # } + # NS_WARNING_ASSERTION( + # rv != NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR, + # "Foo() failed, but ignored"); + errors["NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR"] = SUCCESS(3) + + +# ======================================================================= +# 18: NS_ERROR_MODULE_XPCONNECT +# ======================================================================= +with modules["XPCONNECT"]: + errors["NS_ERROR_XPC_NOT_ENOUGH_ARGS"] = FAILURE(1) + errors["NS_ERROR_XPC_NEED_OUT_OBJECT"] = FAILURE(2) + errors["NS_ERROR_XPC_CANT_SET_OUT_VAL"] = FAILURE(3) + errors["NS_ERROR_XPC_NATIVE_RETURNED_FAILURE"] = FAILURE(4) + errors["NS_ERROR_XPC_CANT_GET_INTERFACE_INFO"] = FAILURE(5) + errors["NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO"] = FAILURE(6) + errors["NS_ERROR_XPC_CANT_GET_METHOD_INFO"] = FAILURE(7) + errors["NS_ERROR_XPC_UNEXPECTED"] = FAILURE(8) + errors["NS_ERROR_XPC_BAD_CONVERT_JS"] = FAILURE(9) + errors["NS_ERROR_XPC_BAD_CONVERT_NATIVE"] = FAILURE(10) + errors["NS_ERROR_XPC_BAD_CONVERT_JS_NULL_REF"] = FAILURE(11) + errors["NS_ERROR_XPC_BAD_OP_ON_WN_PROTO"] = FAILURE(12) + errors["NS_ERROR_XPC_CANT_CONVERT_WN_TO_FUN"] = FAILURE(13) + errors["NS_ERROR_XPC_CANT_DEFINE_PROP_ON_WN"] = FAILURE(14) + errors["NS_ERROR_XPC_CANT_WATCH_WN_STATIC"] = FAILURE(15) + errors["NS_ERROR_XPC_CANT_EXPORT_WN_STATIC"] = FAILURE(16) + errors["NS_ERROR_XPC_SCRIPTABLE_CALL_FAILED"] = FAILURE(17) + errors["NS_ERROR_XPC_SCRIPTABLE_CTOR_FAILED"] = FAILURE(18) + errors["NS_ERROR_XPC_CANT_CALL_WO_SCRIPTABLE"] = FAILURE(19) + errors["NS_ERROR_XPC_CANT_CTOR_WO_SCRIPTABLE"] = FAILURE(20) + errors["NS_ERROR_XPC_CI_RETURNED_FAILURE"] = FAILURE(21) + errors["NS_ERROR_XPC_GS_RETURNED_FAILURE"] = FAILURE(22) + errors["NS_ERROR_XPC_BAD_CID"] = FAILURE(23) + errors["NS_ERROR_XPC_BAD_IID"] = FAILURE(24) + errors["NS_ERROR_XPC_CANT_CREATE_WN"] = FAILURE(25) + errors["NS_ERROR_XPC_JS_THREW_EXCEPTION"] = FAILURE(26) + errors["NS_ERROR_XPC_JS_THREW_NATIVE_OBJECT"] = FAILURE(27) + errors["NS_ERROR_XPC_JS_THREW_JS_OBJECT"] = FAILURE(28) + errors["NS_ERROR_XPC_JS_THREW_NULL"] = FAILURE(29) + errors["NS_ERROR_XPC_JS_THREW_STRING"] = FAILURE(30) + errors["NS_ERROR_XPC_JS_THREW_NUMBER"] = FAILURE(31) + errors["NS_ERROR_XPC_JAVASCRIPT_ERROR"] = FAILURE(32) + errors["NS_ERROR_XPC_JAVASCRIPT_ERROR_WITH_DETAILS"] = FAILURE(33) + errors["NS_ERROR_XPC_CANT_CONVERT_PRIMITIVE_TO_ARRAY"] = FAILURE(34) + errors["NS_ERROR_XPC_CANT_CONVERT_OBJECT_TO_ARRAY"] = FAILURE(35) + errors["NS_ERROR_XPC_NOT_ENOUGH_ELEMENTS_IN_ARRAY"] = FAILURE(36) + errors["NS_ERROR_XPC_CANT_GET_ARRAY_INFO"] = FAILURE(37) + errors["NS_ERROR_XPC_NOT_ENOUGH_CHARS_IN_STRING"] = FAILURE(38) + errors["NS_ERROR_XPC_SECURITY_MANAGER_VETO"] = FAILURE(39) + errors["NS_ERROR_XPC_INTERFACE_NOT_SCRIPTABLE"] = FAILURE(40) + errors["NS_ERROR_XPC_INTERFACE_NOT_FROM_NSISUPPORTS"] = FAILURE(41) + errors["NS_ERROR_XPC_CANT_SET_READ_ONLY_CONSTANT"] = FAILURE(43) + errors["NS_ERROR_XPC_CANT_SET_READ_ONLY_ATTRIBUTE"] = FAILURE(44) + errors["NS_ERROR_XPC_CANT_SET_READ_ONLY_METHOD"] = FAILURE(45) + errors["NS_ERROR_XPC_CANT_ADD_PROP_TO_WRAPPED_NATIVE"] = FAILURE(46) + errors["NS_ERROR_XPC_CALL_TO_SCRIPTABLE_FAILED"] = FAILURE(47) + errors["NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED"] = FAILURE(48) + errors["NS_ERROR_XPC_BAD_ID_STRING"] = FAILURE(49) + errors["NS_ERROR_XPC_BAD_INITIALIZER_NAME"] = FAILURE(50) + errors["NS_ERROR_XPC_HAS_BEEN_SHUTDOWN"] = FAILURE(51) + errors["NS_ERROR_XPC_CANT_MODIFY_PROP_ON_WN"] = FAILURE(52) + errors["NS_ERROR_XPC_BAD_CONVERT_JS_ZERO_ISNOT_NULL"] = FAILURE(53) + # any new errors here should have an associated entry added in xpc.msg + + +# ======================================================================= +# 19: NS_ERROR_MODULE_PROFILE +# ======================================================================= +with modules["PROFILE"]: + errors["NS_ERROR_LAUNCHED_CHILD_PROCESS"] = FAILURE(200) + errors["NS_ERROR_SHOW_PROFILE_MANAGER"] = FAILURE(201) + errors["NS_ERROR_DATABASE_CHANGED"] = FAILURE(202) + + +# ======================================================================= +# 21: NS_ERROR_MODULE_SECURITY +# ======================================================================= +with modules["SECURITY"]: + # Error code for XFO + errors["NS_ERROR_XFO_VIOLATION"] = FAILURE(96) + + # Error code for CSP + errors["NS_ERROR_CSP_NAVIGATE_TO_VIOLATION"] = FAILURE(97) + errors["NS_ERROR_CSP_FORM_ACTION_VIOLATION"] = FAILURE(98) + errors["NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION"] = FAILURE(99) + + # Error code for Sub-Resource Integrity + errors["NS_ERROR_SRI_CORRUPT"] = FAILURE(200) + errors["NS_ERROR_SRI_NOT_ELIGIBLE"] = FAILURE(201) + errors["NS_ERROR_SRI_UNEXPECTED_HASH_TYPE"] = FAILURE(202) + errors["NS_ERROR_SRI_IMPORT"] = FAILURE(203) + + # CMS specific nsresult error codes. Note: the numbers used here correspond + # to the values in nsICMSMessageErrors.idl. + errors["NS_ERROR_CMS_VERIFY_NOT_SIGNED"] = FAILURE(1024) + errors["NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO"] = FAILURE(1025) + errors["NS_ERROR_CMS_VERIFY_BAD_DIGEST"] = FAILURE(1026) + errors["NS_ERROR_CMS_VERIFY_NOCERT"] = FAILURE(1028) + errors["NS_ERROR_CMS_VERIFY_UNTRUSTED"] = FAILURE(1029) + errors["NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED"] = FAILURE(1031) + errors["NS_ERROR_CMS_VERIFY_ERROR_PROCESSING"] = FAILURE(1032) + errors["NS_ERROR_CMS_VERIFY_BAD_SIGNATURE"] = FAILURE(1033) + errors["NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH"] = FAILURE(1034) + errors["NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO"] = FAILURE(1035) + errors["NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO"] = FAILURE(1036) + errors["NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE"] = FAILURE(1037) + errors["NS_ERROR_CMS_VERIFY_HEADER_MISMATCH"] = FAILURE(1038) + errors["NS_ERROR_CMS_VERIFY_NOT_YET_ATTEMPTED"] = FAILURE(1039) + errors["NS_ERROR_CMS_VERIFY_CERT_WITHOUT_ADDRESS"] = FAILURE(1040) + errors["NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG"] = FAILURE(1056) + errors["NS_ERROR_CMS_ENCRYPT_INCOMPLETE"] = FAILURE(1057) + + +# ======================================================================= +# 24: NS_ERROR_MODULE_URILOADER +# ======================================================================= +with modules["URILOADER"]: + errors["NS_ERROR_WONT_HANDLE_CONTENT"] = FAILURE(1) + # The load has been cancelled because it was found on a malware or phishing + # list. + errors["NS_ERROR_MALWARE_URI"] = FAILURE(30) + errors["NS_ERROR_PHISHING_URI"] = FAILURE(31) + errors["NS_ERROR_TRACKING_URI"] = FAILURE(34) + errors["NS_ERROR_UNWANTED_URI"] = FAILURE(35) + errors["NS_ERROR_BLOCKED_URI"] = FAILURE(37) + errors["NS_ERROR_HARMFUL_URI"] = FAILURE(38) + errors["NS_ERROR_FINGERPRINTING_URI"] = FAILURE(41) + errors["NS_ERROR_CRYPTOMINING_URI"] = FAILURE(42) + errors["NS_ERROR_SOCIALTRACKING_URI"] = FAILURE(43) + errors["NS_ERROR_EMAILTRACKING_URI"] = FAILURE(44) + # Used when "Save Link As..." doesn't see the headers quickly enough to + # choose a filename. See nsContextMenu.js. + errors["NS_ERROR_SAVE_LINK_AS_TIMEOUT"] = FAILURE(32) + # Used when the data from a channel has already been parsed and cached so it + # doesn't need to be reparsed from the original source. + errors["NS_ERROR_PARSED_DATA_CACHED"] = FAILURE(33) + + # When browser.tabs.documentchannel.parent-controlled pref and SHIP + # are enabled and a load gets cancelled due to another one + # starting, the error is NS_BINDING_CANCELLED_OLD_LOAD. + errors["NS_BINDING_CANCELLED_OLD_LOAD"] = FAILURE(39) + + +# ======================================================================= +# 25: NS_ERROR_MODULE_CONTENT +# ======================================================================= +with modules["CONTENT"]: + # Error codes for content policy blocking + errors["NS_ERROR_CONTENT_BLOCKED"] = FAILURE(6) + errors["NS_ERROR_CONTENT_BLOCKED_SHOW_ALT"] = FAILURE(7) + # Success variations of content policy blocking + errors["NS_PROPTABLE_PROP_NOT_THERE"] = FAILURE(10) + # Error code for when the content process crashed + errors["NS_ERROR_CONTENT_CRASHED"] = FAILURE(16) + # Error code for when a subframe process crashed + errors["NS_ERROR_FRAME_CRASHED"] = FAILURE(14) + # Error code for when the content process had a different buildID than the + # parent + errors["NS_ERROR_BUILDID_MISMATCH"] = FAILURE(17) + + errors["NS_PROPTABLE_PROP_OVERWRITTEN"] = SUCCESS(11) + # Error codes for FindBroadcaster in XULBroadcastManager.cpp + errors["NS_FINDBROADCASTER_NOT_FOUND"] = SUCCESS(12) + errors["NS_FINDBROADCASTER_FOUND"] = SUCCESS(13) + + +# ======================================================================= +# 27: NS_ERROR_MODULE_XSLT +# ======================================================================= +with modules["XSLT"]: + errors["NS_ERROR_XPATH_INVALID_ARG"] = errors["NS_ERROR_INVALID_ARG"] + + errors["NS_ERROR_XSLT_PARSE_FAILURE"] = FAILURE(1) + errors["NS_ERROR_XPATH_PARSE_FAILURE"] = FAILURE(2) + errors["NS_ERROR_XSLT_ALREADY_SET"] = FAILURE(3) + errors["NS_ERROR_XSLT_EXECUTION_FAILURE"] = FAILURE(4) + errors["NS_ERROR_XPATH_UNKNOWN_FUNCTION"] = FAILURE(5) + errors["NS_ERROR_XSLT_BAD_RECURSION"] = FAILURE(6) + errors["NS_ERROR_XSLT_BAD_VALUE"] = FAILURE(7) + errors["NS_ERROR_XSLT_NODESET_EXPECTED"] = FAILURE(8) + errors["NS_ERROR_XSLT_ABORTED"] = FAILURE(9) + errors["NS_ERROR_XSLT_NETWORK_ERROR"] = FAILURE(10) + errors["NS_ERROR_XSLT_WRONG_MIME_TYPE"] = FAILURE(11) + errors["NS_ERROR_XSLT_LOAD_RECURSION"] = FAILURE(12) + errors["NS_ERROR_XPATH_BAD_ARGUMENT_COUNT"] = FAILURE(13) + errors["NS_ERROR_XPATH_BAD_EXTENSION_FUNCTION"] = FAILURE(14) + errors["NS_ERROR_XPATH_PAREN_EXPECTED"] = FAILURE(15) + errors["NS_ERROR_XPATH_INVALID_AXIS"] = FAILURE(16) + errors["NS_ERROR_XPATH_NO_NODE_TYPE_TEST"] = FAILURE(17) + errors["NS_ERROR_XPATH_BRACKET_EXPECTED"] = FAILURE(18) + errors["NS_ERROR_XPATH_INVALID_VAR_NAME"] = FAILURE(19) + errors["NS_ERROR_XPATH_UNEXPECTED_END"] = FAILURE(20) + errors["NS_ERROR_XPATH_OPERATOR_EXPECTED"] = FAILURE(21) + errors["NS_ERROR_XPATH_UNCLOSED_LITERAL"] = FAILURE(22) + errors["NS_ERROR_XPATH_BAD_COLON"] = FAILURE(23) + errors["NS_ERROR_XPATH_BAD_BANG"] = FAILURE(24) + errors["NS_ERROR_XPATH_ILLEGAL_CHAR"] = FAILURE(25) + errors["NS_ERROR_XPATH_BINARY_EXPECTED"] = FAILURE(26) + errors["NS_ERROR_XSLT_LOAD_BLOCKED_ERROR"] = FAILURE(27) + errors["NS_ERROR_XPATH_INVALID_EXPRESSION_EVALUATED"] = FAILURE(28) + errors["NS_ERROR_XPATH_UNBALANCED_CURLY_BRACE"] = FAILURE(29) + errors["NS_ERROR_XSLT_BAD_NODE_NAME"] = FAILURE(30) + errors["NS_ERROR_XSLT_VAR_ALREADY_SET"] = FAILURE(31) + errors["NS_ERROR_XSLT_CALL_TO_KEY_NOT_ALLOWED"] = FAILURE(32) + + errors["NS_XSLT_GET_NEW_HANDLER"] = SUCCESS(1) + + +# ======================================================================= +# 28: NS_ERROR_MODULE_IPC +# ======================================================================= +with modules["IPC"]: + # Initial creation of a Transport object failed internally for unknown reasons. + errors["NS_ERROR_TRANSPORT_INIT"] = FAILURE(1) + # Generic error related to duplicating handle failures. + errors["NS_ERROR_DUPLICATE_HANDLE"] = FAILURE(2) + # Bridging: failure trying to open the connection to the parent + errors["NS_ERROR_BRIDGE_OPEN_PARENT"] = FAILURE(3) + # Bridging: failure trying to open the connection to the child + errors["NS_ERROR_BRIDGE_OPEN_CHILD"] = FAILURE(4) + + +# ======================================================================= +# 30: NS_ERROR_MODULE_STORAGE +# ======================================================================= +with modules["STORAGE"]: + # To add additional errors to Storage, please append entries to the bottom + # of the list in the following format: + # NS_ERROR_STORAGE_YOUR_ERR, FAILURE(n) + # where n is the next unique positive integer. You must also add an entry + # to js/xpconnect/src/xpc.msg under the code block beginning with the + # comment 'storage related codes (from mozStorage.h)', in the following + # format: 'XPC_MSG_DEF(NS_ERROR_STORAGE_YOUR_ERR, "brief description of your + # error")' + errors["NS_ERROR_STORAGE_BUSY"] = FAILURE(1) + errors["NS_ERROR_STORAGE_IOERR"] = FAILURE(2) + errors["NS_ERROR_STORAGE_CONSTRAINT"] = FAILURE(3) + + +# ======================================================================= +# 32: NS_ERROR_MODULE_DOM_FILE +# ======================================================================= +with modules["DOM_FILE"]: + errors["NS_ERROR_DOM_FILE_NOT_FOUND_ERR"] = FAILURE(0) + errors["NS_ERROR_DOM_FILE_NOT_READABLE_ERR"] = FAILURE(1) + + +# ======================================================================= +# 33: NS_ERROR_MODULE_DOM_INDEXEDDB +# ======================================================================= +with modules["DOM_INDEXEDDB"]: + # IndexedDB error codes http://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html + errors["NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_INDEXEDDB_NOT_FOUND_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_INDEXEDDB_CONSTRAINT_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_INDEXEDDB_DATA_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR"] = FAILURE(6) + errors["NS_ERROR_DOM_INDEXEDDB_TRANSACTION_INACTIVE_ERR"] = FAILURE(7) + errors["NS_ERROR_DOM_INDEXEDDB_ABORT_ERR"] = FAILURE(8) + errors["NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR"] = FAILURE(9) + errors["NS_ERROR_DOM_INDEXEDDB_QUOTA_ERR"] = FAILURE(11) + errors["NS_ERROR_DOM_INDEXEDDB_VERSION_ERR"] = FAILURE(12) + errors["NS_ERROR_DOM_INDEXEDDB_KEY_ERR"] = FAILURE(1002) + errors["NS_ERROR_DOM_INDEXEDDB_RENAME_OBJECT_STORE_ERR"] = FAILURE(1003) + errors["NS_ERROR_DOM_INDEXEDDB_RENAME_INDEX_ERR"] = FAILURE(1004) + + +# ======================================================================= +# 34: NS_ERROR_MODULE_DOM_FILEHANDLE +# ======================================================================= +with modules["DOM_FILEHANDLE"]: + errors["NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_FILEHANDLE_NOT_ALLOWED_ERR"] = FAILURE(2) + errors["NS_ERROR_DOM_FILEHANDLE_INACTIVE_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_FILEHANDLE_ABORT_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_FILEHANDLE_READ_ONLY_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR"] = FAILURE(6) + + +# ======================================================================= +# 35: NS_ERROR_MODULE_SIGNED_JAR +# ======================================================================= +with modules["SIGNED_JAR"]: + errors["NS_ERROR_SIGNED_JAR_NOT_SIGNED"] = FAILURE(1) + errors["NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY"] = FAILURE(2) + errors["NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY"] = FAILURE(3) + errors["NS_ERROR_SIGNED_JAR_ENTRY_MISSING"] = FAILURE(4) + errors["NS_ERROR_SIGNED_JAR_WRONG_SIGNATURE"] = FAILURE(5) + errors["NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE"] = FAILURE(6) + errors["NS_ERROR_SIGNED_JAR_ENTRY_INVALID"] = FAILURE(7) + errors["NS_ERROR_SIGNED_JAR_MANIFEST_INVALID"] = FAILURE(8) + + +# ======================================================================= +# 36: NS_ERROR_MODULE_DOM_FILESYSTEM +# ======================================================================= +with modules["DOM_FILESYSTEM"]: + errors["NS_ERROR_DOM_FILESYSTEM_INVALID_PATH_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_FILESYSTEM_INVALID_MODIFICATION_ERR"] = FAILURE(2) + errors["NS_ERROR_DOM_FILESYSTEM_NO_MODIFICATION_ALLOWED_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_FILESYSTEM_PATH_EXISTS_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_FILESYSTEM_UNKNOWN_ERR"] = FAILURE(6) + + +# ======================================================================= +# 38: NS_ERROR_MODULE_SIGNED_APP +# ======================================================================= +with modules["SIGNED_APP"]: + errors["NS_ERROR_SIGNED_APP_MANIFEST_INVALID"] = FAILURE(1) + + +# ======================================================================= +# 40: NS_ERROR_MODULE_DOM_PUSH +# ======================================================================= +with modules["DOM_PUSH"]: + errors["NS_ERROR_DOM_PUSH_DENIED_ERR"] = FAILURE(2) + errors["NS_ERROR_DOM_PUSH_ABORT_ERR"] = FAILURE(3) + errors["NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE"] = FAILURE(4) + errors["NS_ERROR_DOM_PUSH_INVALID_KEY_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR"] = FAILURE(6) + + +# ======================================================================= +# 41: NS_ERROR_MODULE_DOM_MEDIA +# ======================================================================= +with modules["DOM_MEDIA"]: + # HTMLMediaElement API errors from + # https://html.spec.whatwg.org/multipage/embedded-content.html#media-elements + errors["NS_ERROR_DOM_MEDIA_ABORT_ERR"] = FAILURE(1) + errors["NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR"] = FAILURE(2) + errors["NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR"] = FAILURE(3) + + # HTMLMediaElement internal decoding error + errors["NS_ERROR_DOM_MEDIA_DECODE_ERR"] = FAILURE(4) + errors["NS_ERROR_DOM_MEDIA_FATAL_ERR"] = FAILURE(5) + errors["NS_ERROR_DOM_MEDIA_METADATA_ERR"] = FAILURE(6) + errors["NS_ERROR_DOM_MEDIA_OVERFLOW_ERR"] = FAILURE(7) + errors["NS_ERROR_DOM_MEDIA_END_OF_STREAM"] = FAILURE(8) + errors["NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA"] = FAILURE(9) + errors["NS_ERROR_DOM_MEDIA_CANCELED"] = FAILURE(10) + errors["NS_ERROR_DOM_MEDIA_MEDIASINK_ERR"] = FAILURE(11) + errors["NS_ERROR_DOM_MEDIA_DEMUXER_ERR"] = FAILURE(12) + errors["NS_ERROR_DOM_MEDIA_CDM_ERR"] = FAILURE(13) + errors["NS_ERROR_DOM_MEDIA_NEED_NEW_DECODER"] = FAILURE(14) + errors["NS_ERROR_DOM_MEDIA_INITIALIZING_DECODER"] = FAILURE(15) + errors["NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_RDD_OR_GPU_ERR"] = FAILURE(16) + errors["NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_UTILITY_ERR"] = FAILURE(17) + errors["NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR"] = FAILURE(18) + + # QuotaExceededError specializations + errors["NS_ERROR_DOM_MEDIA_KEY_QUOTA_EXCEEDED_ERR"] = FAILURE(30) + errors["NS_ERROR_DOM_MEDIA_SOURCE_MAX_BUFFER_QUOTA_EXCEEDED_ERR"] = FAILURE(31) + errors["NS_ERROR_DOM_MEDIA_SOURCE_FULL_BUFFER_QUOTA_EXCEEDED_ERR"] = FAILURE(32) + + # Internal CDM error + errors["NS_ERROR_DOM_MEDIA_CDM_NO_SESSION_ERR"] = FAILURE(50) + errors["NS_ERROR_DOM_MEDIA_CDM_SESSION_OPERATION_ERR"] = FAILURE(51) + + # Internal platform-related errors + errors["NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR"] = FAILURE(101) + errors["NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR"] = FAILURE(102) + errors["NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR"] = FAILURE(103) + +# ======================================================================= +# 42: NS_ERROR_MODULE_URL_CLASSIFIER +# ======================================================================= +with modules["URL_CLASSIFIER"]: + # Errors during list updates + errors["NS_ERROR_UC_UPDATE_UNKNOWN"] = FAILURE(1) + errors["NS_ERROR_UC_UPDATE_DUPLICATE_PREFIX"] = FAILURE(2) + errors["NS_ERROR_UC_UPDATE_INFINITE_LOOP"] = FAILURE(3) + errors["NS_ERROR_UC_UPDATE_WRONG_REMOVAL_INDICES"] = FAILURE(4) + errors["NS_ERROR_UC_UPDATE_CHECKSUM_MISMATCH"] = FAILURE(5) + errors["NS_ERROR_UC_UPDATE_MISSING_CHECKSUM"] = FAILURE(6) + errors["NS_ERROR_UC_UPDATE_SHUTDOWNING"] = FAILURE(7) + errors["NS_ERROR_UC_UPDATE_TABLE_NOT_FOUND"] = FAILURE(8) + errors["NS_ERROR_UC_UPDATE_BUILD_PREFIX_FAILURE"] = FAILURE(9) + errors["NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK"] = FAILURE(10) + errors["NS_ERROR_UC_UPDATE_UNEXPECTED_VERSION"] = FAILURE(11) + + # Specific errors while parsing pver2/pver4 responses + errors["NS_ERROR_UC_PARSER_MISSING_PARAM"] = FAILURE(12) + errors["NS_ERROR_UC_PARSER_DECODE_FAILURE"] = FAILURE(13) + errors["NS_ERROR_UC_PARSER_UNKNOWN_THREAT"] = FAILURE(14) + errors["NS_ERROR_UC_PARSER_MISSING_VALUE"] = FAILURE(15) + + +# ======================================================================= +# 43: NS_ERROR_MODULE_ERRORRESULT +# ======================================================================= +with modules["ERRORRESULT"]: + # Represents a JS Value being thrown as an exception. + errors["NS_ERROR_INTERNAL_ERRORRESULT_JS_EXCEPTION"] = FAILURE(1) + # Used to indicate that we want to throw a DOMException. + errors["NS_ERROR_INTERNAL_ERRORRESULT_DOMEXCEPTION"] = FAILURE(2) + # Used to indicate that an exception is already pending on the JSContext. + errors["NS_ERROR_INTERNAL_ERRORRESULT_EXCEPTION_ON_JSCONTEXT"] = FAILURE(3) + # Used to indicate that we want to throw a TypeError. + errors["NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR"] = FAILURE(4) + # Used to indicate that we want to throw a RangeError. + errors["NS_ERROR_INTERNAL_ERRORRESULT_RANGEERROR"] = FAILURE(5) + + +# ======================================================================= +# 51: NS_ERROR_MODULE_GENERAL +# ======================================================================= +with modules["GENERAL"]: + # Error code used internally by the incremental downloader to cancel the + # network channel when the download is already complete. + errors["NS_ERROR_DOWNLOAD_COMPLETE"] = FAILURE(1) + # Error code used internally by the incremental downloader to cancel the + # network channel when the response to a range request is 200 instead of + # 206. + errors["NS_ERROR_DOWNLOAD_NOT_PARTIAL"] = FAILURE(2) + errors["NS_ERROR_UNORM_MOREOUTPUT"] = FAILURE(33) + + errors["NS_ERROR_DOCSHELL_REQUEST_REJECTED"] = FAILURE(1001) + # This is needed for displaying an error message when navigation is + # attempted on a document when printing The value arbitrary as long as it + # doesn't conflict with any of the other values in the errors in + # DisplayLoadError + errors["NS_ERROR_DOCUMENT_IS_PRINTMODE"] = FAILURE(2001) + + errors["NS_SUCCESS_DONT_FIXUP"] = SUCCESS(1) + # This success code may be returned by nsIAppStartup::Run to indicate that + # the application should be restarted. This condition corresponds to the + # case in which nsIAppStartup::Quit was called with the eRestart flag. + errors["NS_SUCCESS_RESTART_APP"] = SUCCESS(1) + + # a11y + # raised when current pivot's position is needed but it's not in the tree + errors["NS_ERROR_NOT_IN_TREE"] = FAILURE(38) + + # see nsTextEquivUtils + errors["NS_OK_NO_NAME_CLAUSE_HANDLED"] = SUCCESS(34) + + # Error code used to indicate that functionality has been blocked by the + # Policy Manager + errors["NS_ERROR_BLOCKED_BY_POLICY"] = FAILURE(3) + + +# ============================================================================ +# Write out the resulting module declarations to C++ and rust files +# ============================================================================ + + +def error_list_h(output): + output.write( + """ +/* THIS FILE IS GENERATED BY ErrorList.py - DO NOT EDIT */ + +#ifndef ErrorList_h__ +#define ErrorList_h__ + +#include + +""" + ) + + output.write("#define NS_ERROR_MODULE_BASE_OFFSET {}\n".format(MODULE_BASE_OFFSET)) + + for mod, val in modules.items(): + output.write("#define NS_ERROR_MODULE_{} {}\n".format(mod, val.num)) + + items = [] + for error, val in errors.items(): + items.append(" {} = 0x{:X}".format(error, val)) + output.write( + """ +enum class nsresult : uint32_t +{{ +{} +}}; + +""".format( + ",\n".join(items) + ) + ) + + items = [] + for error, val in errors.items(): + items.append(" {0} = nsresult::{0}".format(error)) + + output.write( + """ +const nsresult +{} +; + +#endif // ErrorList_h__ +""".format( + ",\n".join(items) + ) + ) + + +def error_names_internal_h(output): + """Generate ErrorNamesInternal.h, which is a header file declaring one + function, const char* GetErrorNameInternal(nsresult). This method is not + intended to be used by consumer code, which should instead call + GetErrorName in ErrorNames.h.""" + + output.write( + """ +/* THIS FILE IS GENERATED BY ErrorList.py - DO NOT EDIT */ + +#ifndef ErrorNamesInternal_h__ +#define ErrorNamesInternal_h__ + +#include "ErrorNames.h" + +namespace { + +const char* +GetErrorNameInternal(nsresult rv) +{ + switch (rv) { +""" + ) + + # NOTE: Making sure we don't write out duplicate values is important as + # we're using a switch statement to implement this. + seen = set() + for error, val in errors.items(): + if val not in seen: + output.write(' case nsresult::{0}: return "{0}";\n'.format(error)) + seen.add(val) + + output.write( + """ + default: return nullptr; + } +} // GetErrorNameInternal + +} // namespace + +#endif // ErrorNamesInternal_h__ +""" + ) + + +def error_list_rs(output): + output.write( + """ +/* THIS FILE IS GENERATED BY ErrorList.py - DO NOT EDIT */ + +use super::nsresult; + +""" + ) + + output.write( + "pub const NS_ERROR_MODULE_BASE_OFFSET: nsresult = nsresult({});\n".format( + MODULE_BASE_OFFSET + ) + ) + + for mod, val in modules.items(): + output.write( + "pub const NS_ERROR_MODULE_{}: nsresult = nsresult({});\n".format( + mod, val.num + ) + ) + + for error, val in errors.items(): + output.write("pub const {}: nsresult = nsresult(0x{:X});\n".format(error, val)) + + +def gen_jinja(output, input_filename): + # This is used to generate Java code for error lists, and can be expanded to + # other required contexts in the future if desired. + import os + + from jinja2 import Environment, FileSystemLoader, StrictUndefined + + # FileSystemLoader requires the path to the directory containing templates, + # not the file name of the template itself. + (path, leaf) = os.path.split(input_filename) + env = Environment( + loader=FileSystemLoader(path, encoding="utf-8"), + undefined=StrictUndefined, + ) + tpl = env.get_template(leaf) + + context = { + "MODULE_BASE_OFFSET": MODULE_BASE_OFFSET, + "modules": ((mod, val.num) for mod, val in modules.items()), + "errors": errors.items(), + } + + tpl.stream(context).dump(output, encoding="utf-8") diff --git a/xpcom/base/ErrorNames.cpp b/xpcom/base/ErrorNames.cpp new file mode 100644 index 0000000000..42401aa860 --- /dev/null +++ b/xpcom/base/ErrorNames.cpp @@ -0,0 +1,78 @@ +/* -*- 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/ArrayUtils.h" +#include "mozilla/ErrorNames.h" +#include "nsString.h" +#include "prerror.h" +#include "MainThreadUtils.h" + +// Get the GetErrorNameInternal method +#include "ErrorNamesInternal.h" + +namespace mozilla { + +const char* GetStaticErrorName(nsresult rv) { return GetErrorNameInternal(rv); } + +void GetErrorName(nsresult rv, nsACString& name) { + if (const char* errorName = GetErrorNameInternal(rv)) { + name.AssignASCII(errorName); + return; + } + + bool isSecurityError = NS_ERROR_GET_MODULE(rv) == NS_ERROR_MODULE_SECURITY; + + // NS_ERROR_MODULE_SECURITY is the only module that is "allowed" to + // synthesize nsresult error codes that are not listed in ErrorList.h. (The + // NS_ERROR_MODULE_SECURITY error codes are synthesized from NSPR error + // codes.) + MOZ_ASSERT(isSecurityError); + + if (NS_SUCCEEDED(rv)) { + name.AssignLiteral("NS_ERROR_GENERATE_SUCCESS("); + } else { + name.AssignLiteral("NS_ERROR_GENERATE_FAILURE("); + } + + if (isSecurityError) { + name.AppendLiteral("NS_ERROR_MODULE_SECURITY"); + } else { + // This should never happen given the assertion above, so we don't bother + // trying to print a symbolic name for the module here. + name.AppendInt(NS_ERROR_GET_MODULE(rv)); + } + + name.AppendLiteral(", "); + + const char* nsprName = nullptr; + if (isSecurityError && NS_IsMainThread()) { + // Invert the logic from NSSErrorsService::GetXPCOMFromNSSError + PRErrorCode nsprCode = -1 * static_cast(NS_ERROR_GET_CODE(rv)); + nsprName = PR_ErrorToName(nsprCode); + + // All NSPR error codes defined by NSPR or NSS should have a name mapping. + MOZ_ASSERT(nsprName); + } + + if (nsprName) { + name.AppendASCII(nsprName); + } else { + name.AppendInt(NS_ERROR_GET_CODE(rv)); + } + + name.AppendLiteral(")"); +} + +} // namespace mozilla + +extern "C" { + +// This is an extern "C" binding for the GetErrorName method which is used by +// the nsresult rust bindings in xpcom/rust/nserror. +void Gecko_GetErrorName(nsresult aRv, nsACString& aName) { + mozilla::GetErrorName(aRv, aName); +} +} diff --git a/xpcom/base/ErrorNames.h b/xpcom/base/ErrorNames.h new file mode 100644 index 0000000000..d209cc492b --- /dev/null +++ b/xpcom/base/ErrorNames.h @@ -0,0 +1,29 @@ +/* -*- 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/. */ + +#ifndef mozilla_ErrorNames_h +#define mozilla_ErrorNames_h + +#include "nsError.h" +#include "nsStringFwd.h" + +namespace mozilla { + +// Maps the given nsresult to its symbolic name. For example, +// GetErrorName(NS_OK, name) will result in name == "NS_OK". +// When the symbolic name is unknown, name will be of the form +// "NS_ERROR_GENERATE_SUCCESS(, )" or +// "NS_ERROR_GENERATE_FAILURE(, )". +void GetErrorName(nsresult rv, nsACString& name); + +// Same as GetErrorName, except that only nsresult values with statically +// known symbolic names are handled. For all other values, nullptr is +// returned. +const char* GetStaticErrorName(nsresult rv); + +} // namespace mozilla + +#endif // mozilla_ErrorNames_h diff --git a/xpcom/base/GkRustUtils.cpp b/xpcom/base/GkRustUtils.cpp new file mode 100644 index 0000000000..00d32e6f65 --- /dev/null +++ b/xpcom/base/GkRustUtils.cpp @@ -0,0 +1,16 @@ +/* -*- 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 "gk_rust_utils_ffi_generated.h" +#include "GkRustUtils.h" + +using namespace mozilla; + +/* static */ +bool GkRustUtils::ParseSemVer(const nsACString& aVersion, uint64_t& aOutMajor, + uint64_t& aOutMinor, uint64_t& aOutPatch) { + return GkRustUtils_ParseSemVer(&aVersion, &aOutMajor, &aOutMinor, &aOutPatch); +} diff --git a/xpcom/base/GkRustUtils.h b/xpcom/base/GkRustUtils.h new file mode 100644 index 0000000000..b7f8c3b99c --- /dev/null +++ b/xpcom/base/GkRustUtils.h @@ -0,0 +1,18 @@ +/* -*- 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/. */ + +#ifndef __mozilla_GkRustUtils_h +#define __mozilla_GkRustUtils_h + +#include "nsString.h" + +class GkRustUtils { + public: + static bool ParseSemVer(const nsACString& aVersion, uint64_t& aOutMajor, + uint64_t& aOutMinor, uint64_t& aOutPatch); +}; + +#endif diff --git a/xpcom/base/HoldDropJSObjects.cpp b/xpcom/base/HoldDropJSObjects.cpp new file mode 100644 index 0000000000..2b3a810d89 --- /dev/null +++ b/xpcom/base/HoldDropJSObjects.cpp @@ -0,0 +1,53 @@ +/* -*- 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/HoldDropJSObjects.h" + +#include "mozilla/Assertions.h" +#include "mozilla/CycleCollectedJSRuntime.h" + +namespace mozilla { +namespace cyclecollector { + +void HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now"); + rt->AddJSHolder(aHolder, aTracer, aZone); +} + +void HoldJSObjectsImpl(nsISupports* aHolder) { + nsXPCOMCycleCollectionParticipant* participant = nullptr; + CallQueryInterface(aHolder, &participant); + MOZ_ASSERT(participant, "Failed to QI to nsXPCOMCycleCollectionParticipant!"); + MOZ_ASSERT( + participant->CheckForRightISupports(aHolder), + "The result of QIing a JS holder should be the same as ToSupports"); + + HoldJSObjectsImpl(aHolder, participant); +} + +void DropJSObjectsImpl(void* aHolder) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + MOZ_ASSERT(rt, "Should have a CycleCollectedJSRuntime by now"); + rt->RemoveJSHolder(aHolder); +} + +void DropJSObjectsImpl(nsISupports* aHolder) { +#ifdef DEBUG + nsXPCOMCycleCollectionParticipant* participant = nullptr; + CallQueryInterface(aHolder, &participant); + MOZ_ASSERT(participant, "Failed to QI to nsXPCOMCycleCollectionParticipant!"); + MOZ_ASSERT( + participant->CheckForRightISupports(aHolder), + "The result of QIing a JS holder should be the same as ToSupports"); +#endif + DropJSObjectsImpl(static_cast(aHolder)); +} + +} // namespace cyclecollector + +} // namespace mozilla diff --git a/xpcom/base/HoldDropJSObjects.h b/xpcom/base/HoldDropJSObjects.h new file mode 100644 index 0000000000..e01590208f --- /dev/null +++ b/xpcom/base/HoldDropJSObjects.h @@ -0,0 +1,84 @@ +/* -*- 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/. */ + +#ifndef mozilla_HoldDropJSObjects_h +#define mozilla_HoldDropJSObjects_h + +#include +#include "nsCycleCollectionNoteChild.h" + +class nsISupports; +class nsScriptObjectTracer; +class nsCycleCollectionParticipant; + +namespace JS { +class Zone; +} + +// Only HoldJSObjects and DropJSObjects should be called directly. + +namespace mozilla { +namespace cyclecollector { + +void HoldJSObjectsImpl(void* aHolder, nsScriptObjectTracer* aTracer, + JS::Zone* aZone = nullptr); +void HoldJSObjectsImpl(nsISupports* aHolder); +void DropJSObjectsImpl(void* aHolder); +void DropJSObjectsImpl(nsISupports* aHolder); + +} // namespace cyclecollector + +template ::value, + typename P = typename T::NS_CYCLE_COLLECTION_INNERCLASS> +struct HoldDropJSObjectsHelper { + static void Hold(T* aHolder) { + cyclecollector::HoldJSObjectsImpl(aHolder, + NS_CYCLE_COLLECTION_PARTICIPANT(T)); + } + static void Drop(T* aHolder) { cyclecollector::DropJSObjectsImpl(aHolder); } +}; + +template +struct HoldDropJSObjectsHelper { + static void Hold(T* aHolder) { + cyclecollector::HoldJSObjectsImpl(ToSupports(aHolder)); + } + static void Drop(T* aHolder) { + cyclecollector::DropJSObjectsImpl(ToSupports(aHolder)); + } +}; + +/** + Classes that hold strong references to JS GC things such as `JSObjects` and + `JS::Values` (e.g. `JS::Heap mFoo;`) must use these, generally by + calling `HoldJSObjects(this)` and `DropJSObjects(this)` in the ctor and dtor + respectively. + + For classes that are wrapper cached and hold no other strong references to JS + GC things, there's no need to call these; it will be taken care of + automatically by nsWrapperCache. +**/ +template +void HoldJSObjects(T* aHolder) { + static_assert(!std::is_base_of::value, + "Don't call this on the CC participant but on the object that " + "it's for (in an Unlink implementation it's usually stored in " + "a variable named 'tmp')."); + HoldDropJSObjectsHelper::Hold(aHolder); +} + +template +void DropJSObjects(T* aHolder) { + static_assert(!std::is_base_of::value, + "Don't call this on the CC participant but on the object that " + "it's for (in an Unlink implementation it's usually stored in " + "a variable named 'tmp')."); + HoldDropJSObjectsHelper::Drop(aHolder); +} + +} // namespace mozilla + +#endif // mozilla_HoldDropJSObjects_h diff --git a/xpcom/base/IntentionalCrash.h b/xpcom/base/IntentionalCrash.h new file mode 100644 index 0000000000..3ee751f7c7 --- /dev/null +++ b/xpcom/base/IntentionalCrash.h @@ -0,0 +1,71 @@ +/* -*- 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/. */ + +#ifndef mozilla_IntentionalCrash_h +#define mozilla_IntentionalCrash_h + +#include +#include +#include +#include + +#ifdef XP_WIN +# include +# define getpid _getpid +#else +# include +#endif + +namespace mozilla { + +inline void NoteIntentionalCrash(const char* aProcessType, uint32_t aPid = 0) { +// In opt builds we don't actually have the leak checking enabled, and the +// sandbox doesn't allow writing to this path, so we just disable this +// function's behaviour. +#ifdef MOZ_DEBUG + char* f = getenv("XPCOM_MEM_BLOAT_LOG"); + if (!f) { + return; + } + + fprintf(stderr, "XPCOM_MEM_BLOAT_LOG: %s\n", f); + + uint32_t processPid = aPid == 0 ? getpid() : aPid; + + std::ostringstream bloatName; + std::string processType(aProcessType); + if (!processType.compare("default")) { + bloatName << f; + } else { + std::string bloatLog(f); + + bool hasExt = false; + if (bloatLog.size() >= 4 && + bloatLog.compare(bloatLog.size() - 4, 4, ".log", 4) == 0) { + hasExt = true; + bloatLog.erase(bloatLog.size() - 4, 4); + } + + bloatName << bloatLog << "_" << processType << "_pid" << processPid; + if (hasExt) { + bloatName << ".log"; + } + } + + fprintf(stderr, "Writing to log: %s\n", bloatName.str().c_str()); + + FILE* processfd = fopen(bloatName.str().c_str(), "a"); + if (processfd) { + fprintf(processfd, "\n==> process %d will purposefully crash\n", + processPid); + fclose(processfd); + } +#endif +} + +} // namespace mozilla + +#endif // mozilla_IntentionalCrash_h diff --git a/xpcom/base/JSONStringWriteFuncs.h b/xpcom/base/JSONStringWriteFuncs.h new file mode 100644 index 0000000000..65b688788c --- /dev/null +++ b/xpcom/base/JSONStringWriteFuncs.h @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +#ifndef JSONSTRINGWRITEFUNCS_H +#define JSONSTRINGWRITEFUNCS_H + +#include "mozilla/JSONWriter.h" +#include "nsString.h" + +#include + +namespace mozilla { + +// JSONWriteFunc that writes to an owned string. +template +class JSONStringWriteFunc final : public JSONWriteFunc { + static_assert( + !std::is_reference_v, + "Use JSONStringRefWriteFunc instead to write to a referenced string"); + + public: + JSONStringWriteFunc() = default; + + void Write(const Span& aStr) final { mString.Append(aStr); } + + const StringType& StringCRef() const { return mString; } + + StringType&& StringRRef() && { return std::move(mString); } + + private: + StringType mString; +}; + +// JSONWriteFunc that writes to a given nsACString reference. +class JSONStringRefWriteFunc final : public JSONWriteFunc { + public: + MOZ_IMPLICIT JSONStringRefWriteFunc(nsACString& aString) : mString(aString) {} + + void Write(const Span& aStr) final { mString.Append(aStr); } + + const nsACString& StringCRef() const { return mString; } + + private: + nsACString& mString; +}; + +} // namespace mozilla + +#endif // JSONSTRINGWRITEFUNCS_H diff --git a/xpcom/base/JSObjectHolder.cpp b/xpcom/base/JSObjectHolder.cpp new file mode 100644 index 0000000000..5bcc3cabbf --- /dev/null +++ b/xpcom/base/JSObjectHolder.cpp @@ -0,0 +1,9 @@ +/* -*- 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 "JSObjectHolder.h" + +NS_IMPL_ISUPPORTS(mozilla::JSObjectHolder, nsISupports) diff --git a/xpcom/base/JSObjectHolder.h b/xpcom/base/JSObjectHolder.h new file mode 100644 index 0000000000..d64d41e479 --- /dev/null +++ b/xpcom/base/JSObjectHolder.h @@ -0,0 +1,42 @@ +/* -*- 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/. */ + +#ifndef mozilla_JSObjectHolder_h +#define mozilla_JSObjectHolder_h + +#include "js/RootingAPI.h" +#include "nsISupportsImpl.h" + +namespace mozilla { + +// This class is useful when something on one thread needs to keep alive +// a JS Object from another thread. If they are both on the same thread, the +// owning class should instead be made a cycle collected SCRIPT_HOLDER class. +// This object should only be AddRefed and Released on the same thread as +// mJSObject. +// +// Note that this keeps alive the JS object until it goes away, so be sure not +// to create cycles that keep alive the holder. +// +// JSObjectHolder is ISupports to make it usable with +// NS_ReleaseOnMainThread. +class JSObjectHolder final : public nsISupports { + public: + JSObjectHolder(JSContext* aCx, JSObject* aObject) : mJSObject(aCx, aObject) {} + + NS_DECL_ISUPPORTS + + JSObject* GetJSObject() { return mJSObject; } + + private: + ~JSObjectHolder() = default; + + JS::PersistentRooted mJSObject; +}; + +} // namespace mozilla + +#endif // mozilla_JSObjectHolder_h diff --git a/xpcom/base/LogCommandLineHandler.cpp b/xpcom/base/LogCommandLineHandler.cpp new file mode 100644 index 0000000000..26e78670e9 --- /dev/null +++ b/xpcom/base/LogCommandLineHandler.cpp @@ -0,0 +1,90 @@ +/* -*- 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 "LogCommandLineHandler.h" + +#include "mozilla/Tokenizer.h" +#include "nsDebug.h" + +namespace mozilla { + +void LoggingHandleCommandLineArgs( + int argc, char const* const* argv, + std::function const& consumer) { + // Keeps the name of a pending env var (MOZ_LOG or MOZ_LOG_FILE) that + // we expect to get a value for in the next iterated arg. + // Used for the `-MOZ_LOG ` form of argument. + nsAutoCString env; + + auto const names = {"MOZ_LOG"_ns, "MOZ_LOG_FILE"_ns}; + + for (int arg = 1; arg < argc; ++arg) { + Tokenizer p(argv[arg]); + + if (!env.IsEmpty() && p.CheckChar('-')) { + NS_WARNING( + "Expects value after -MOZ_LOG(_FILE) argument, but another argument " + "found"); + + // We only expect values for the pending env var, start over + p.Rollback(); + env.Truncate(); + } + + if (env.IsEmpty()) { + if (!p.CheckChar('-')) { + continue; + } + // We accept `-MOZ_LOG` as well as `--MOZ_LOG`. + Unused << p.CheckChar('-'); + + for (auto const& name : names) { + if (!p.CheckWord(name)) { + continue; + } + + env.Assign(name); + env.Append('='); + break; + } + + if (env.IsEmpty()) { + // An unknonwn argument, ignore. + continue; + } + + // We accept `-MOZ_LOG ` as well as `-MOZ_LOG=`. + + if (p.CheckEOF()) { + // We have a lone `-MOZ_LOG` arg, the next arg is expected to be + // the value, |env| is now prepared as `MOZ_LOG=`. + continue; + } + + if (!p.CheckChar('=')) { + // There is a character after the arg name and it's not '=', + // ignore this argument. + NS_WARNING("-MOZ_LOG(_FILE) argument not in a proper form"); + + env.Truncate(); + continue; + } + } + + // This can be non-empty from previous iteration or in this iteration. + if (!env.IsEmpty()) { + nsDependentCSubstring value; + Unused << p.ReadUntil(Tokenizer::Token::EndOfFile(), value); + env.Append(value); + + consumer(env); + + env.Truncate(); + } + } +} + +} // namespace mozilla diff --git a/xpcom/base/LogCommandLineHandler.h b/xpcom/base/LogCommandLineHandler.h new file mode 100644 index 0000000000..ef945d7980 --- /dev/null +++ b/xpcom/base/LogCommandLineHandler.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef LogCommandLineHandler_h +#define LogCommandLineHandler_h + +#include +#include "nsString.h" + +namespace mozilla { + +/** + * A helper function parsing provided command line arguments and handling two + * specific args: + * + * -MOZ_LOG=modulelist + * -MOZ_LOG_FILE=file/path + * + * both expecting an argument, and corresponding to the same-name environment + * variables we use for logging setup. + * + * When an argument is found in the proper form, the consumer callback is called + * with a string in a follwing form, note that we do this for every occurence, + * and not just at the end of the parsing: + * + * "MOZ_LOG=modulelist" or "MOZ_LOG_FILE=file/path" + * + * All the following forms of arguments of the application are possible: + * + * --MOZ_LOG modulelist + * -MOZ_LOG modulelist + * --MOZ_LOG=modulelist + * -MOZ_LOG=modulelist + * + * The motivation for a separte function and not implementing a command line + * handler interface is that we need to process this very early during the + * application startup. Command line handlers are proccessed way later + * after logging has already been set up. + */ +void LoggingHandleCommandLineArgs( + int argc, char const* const* argv, + std::function const& consumer); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/LogModulePrefWatcher.cpp b/xpcom/base/LogModulePrefWatcher.cpp new file mode 100644 index 0000000000..bd06660533 --- /dev/null +++ b/xpcom/base/LogModulePrefWatcher.cpp @@ -0,0 +1,164 @@ +/* -*- 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 "LogModulePrefWatcher.h" + +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "nsString.h" +#include "nsXULAppAPI.h" +#include "base/process_util.h" + +static const char kLoggingPrefPrefix[] = "logging."; +static const char kLoggingConfigPrefPrefix[] = "logging.config"; +static const int kLoggingConfigPrefixLen = sizeof(kLoggingConfigPrefPrefix) - 1; +static const char kLoggingPrefClearOnStartup[] = + "logging.config.clear_on_startup"; +static const char kLoggingPrefLogFile[] = "logging.config.LOG_FILE"; +static const char kLoggingPrefAddTimestamp[] = "logging.config.add_timestamp"; +static const char kLoggingPrefSync[] = "logging.config.sync"; +static const char kLoggingPrefStacks[] = "logging.config.profilerstacks"; + +namespace mozilla { + +NS_IMPL_ISUPPORTS(LogModulePrefWatcher, nsIObserver) + +/** + * Resets all the preferences in the logging. branch + * This is needed because we may crash while logging, and this would cause us + * to log after restarting as well. + * + * If logging after restart is desired, set the logging.config.clear_on_startup + * pref to false, or use the MOZ_LOG_FILE and MOZ_LOG_MODULES env vars. + */ +static void ResetExistingPrefs() { + nsTArray names; + nsresult rv = + Preferences::GetRootBranch()->GetChildList(kLoggingPrefPrefix, names); + if (NS_SUCCEEDED(rv)) { + for (auto& name : names) { + // Clearing the pref will cause it to reload, thus resetting the log level + Preferences::ClearUser(name.get()); + } + } +} + +/** + * Loads the log level from the given pref and updates the corresponding + * LogModule. + */ +static void LoadPrefValue(const char* aName) { + LogLevel logLevel = LogLevel::Disabled; + + nsresult rv; + int32_t prefLevel = 0; + nsAutoCString prefValue; + + if (strncmp(aName, kLoggingConfigPrefPrefix, kLoggingConfigPrefixLen) == 0) { + nsAutoCString prefName(aName); + + if (prefName.EqualsLiteral(kLoggingPrefLogFile)) { + rv = Preferences::GetCString(aName, prefValue); + // The pref was reset. Clear the user file. + if (NS_FAILED(rv) || prefValue.IsEmpty()) { + LogModule::SetLogFile(nullptr); + return; + } + + // If the pref value doesn't have a PID placeholder, append it to the end. + if (!strstr(prefValue.get(), MOZ_LOG_PID_TOKEN)) { + prefValue.AppendLiteral(MOZ_LOG_PID_TOKEN); + } + + LogModule::SetLogFile(prefValue.BeginReading()); + } else if (prefName.EqualsLiteral(kLoggingPrefAddTimestamp)) { + bool addTimestamp = Preferences::GetBool(aName, false); + LogModule::SetAddTimestamp(addTimestamp); + } else if (prefName.EqualsLiteral(kLoggingPrefSync)) { + bool sync = Preferences::GetBool(aName, false); + LogModule::SetIsSync(sync); + } else if (prefName.EqualsLiteral(kLoggingPrefStacks)) { + bool captureStacks = Preferences::GetBool(aName, false); + LogModule::SetCaptureStacks(captureStacks); + } + return; + } + + if (Preferences::GetInt(aName, &prefLevel) == NS_OK) { + logLevel = ToLogLevel(prefLevel); + } else if (Preferences::GetCString(aName, prefValue) == NS_OK) { + if (prefValue.LowerCaseEqualsLiteral("error")) { + logLevel = LogLevel::Error; + } else if (prefValue.LowerCaseEqualsLiteral("warning")) { + logLevel = LogLevel::Warning; + } else if (prefValue.LowerCaseEqualsLiteral("info")) { + logLevel = LogLevel::Info; + } else if (prefValue.LowerCaseEqualsLiteral("debug")) { + logLevel = LogLevel::Debug; + } else if (prefValue.LowerCaseEqualsLiteral("verbose")) { + logLevel = LogLevel::Verbose; + } + } + + const char* moduleName = aName + strlen(kLoggingPrefPrefix); + LogModule::Get(moduleName)->SetLevel(logLevel); +} + +static void LoadExistingPrefs() { + nsIPrefBranch* root = Preferences::GetRootBranch(); + if (!root) { + return; + } + + nsTArray names; + nsresult rv = root->GetChildList(kLoggingPrefPrefix, names); + if (NS_SUCCEEDED(rv)) { + for (auto& name : names) { + LoadPrefValue(name.get()); + } + } +} + +LogModulePrefWatcher::LogModulePrefWatcher() = default; + +void LogModulePrefWatcher::RegisterPrefWatcher() { + RefPtr prefWatcher = new LogModulePrefWatcher(); + Preferences::AddStrongObserver(prefWatcher, kLoggingPrefPrefix); + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService && XRE_IsParentProcess()) { + observerService->AddObserver(prefWatcher, + "browser-delayed-startup-finished", false); + } + + LoadExistingPrefs(); +} + +NS_IMETHODIMP +LogModulePrefWatcher::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic) == 0) { + NS_LossyConvertUTF16toASCII prefName(aData); + LoadPrefValue(prefName.get()); + } else if (strcmp("browser-delayed-startup-finished", aTopic) == 0) { + bool clear = Preferences::GetBool(kLoggingPrefClearOnStartup, true); + if (clear) { + ResetExistingPrefs(); + } + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->RemoveObserver(this, "browser-delayed-startup-finished"); + } + } + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/base/LogModulePrefWatcher.h b/xpcom/base/LogModulePrefWatcher.h new file mode 100644 index 0000000000..92a2a3d5b7 --- /dev/null +++ b/xpcom/base/LogModulePrefWatcher.h @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +#ifndef LogModulePrefWatcher_h +#define LogModulePrefWatcher_h + +#include "nsIObserver.h" + +namespace mozilla { + +/** + * Watches for changes to "logging.*" prefs and then updates the appropriate + * LogModule's log level. Both the integer and string versions of the LogLevel + * enum are supported. + * + * For example setting the pref "logging.Foo" to "Verbose" will set the + * LogModule for "Foo" to the LogLevel::Verbose level. Setting "logging.Bar" to + * 4 would set the LogModule for "Bar" to the LogLevel::Debug level. + */ +class LogModulePrefWatcher : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + /** + * Starts observing logging pref changes. + */ + static void RegisterPrefWatcher(); + + private: + LogModulePrefWatcher(); + virtual ~LogModulePrefWatcher() = default; +}; +} // namespace mozilla + +#endif // LogModulePrefWatcher_h diff --git a/xpcom/base/Logging.cpp b/xpcom/base/Logging.cpp new file mode 100644 index 0000000000..44f74686a9 --- /dev/null +++ b/xpcom/base/Logging.cpp @@ -0,0 +1,912 @@ +/* -*- 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/Logging.h" + +#include + +#include "base/process_util.h" +#include "GeckoProfiler.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/FileUtils.h" +#include "mozilla/LateWriteChecks.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Printf.h" +#include "mozilla/Atomics.h" +#include "mozilla/Sprintf.h" +#include "mozilla/UniquePtrExtensions.h" +#include "MainThreadUtils.h" +#include "nsClassHashtable.h" +#include "nsDebug.h" +#include "nsDebugImpl.h" +#include "nsPrintfCString.h" +#include "NSPRLogModulesParser.h" +#include "nsXULAppAPI.h" +#include "LogCommandLineHandler.h" + +#include "prenv.h" +#ifdef XP_WIN +# include +# include +#else +# include // for umask() +# include +# include +#endif + +// NB: Amount determined by performing a typical browsing session and finding +// the maximum number of modules instantiated, and padding up to the next +// power of 2. +const uint32_t kInitialModuleCount = 256; +// When rotate option is added to the modules list, this is the hardcoded +// number of files we create and rotate. When there is rotate:40, +// we will keep four files per process, each limited to 10MB. Sum is 40MB, +// the given limit. +// +// (Note: When this is changed to be >= 10, SandboxBroker::LaunchApp must add +// another rule to allow logfile.?? be written by content processes.) +const uint32_t kRotateFilesNumber = 4; + +namespace mozilla { + +namespace detail { + +void log_print(const LogModule* aModule, LogLevel aLevel, const char* aFmt, + ...) { + va_list ap; + va_start(ap, aFmt); + aModule->Printv(aLevel, aFmt, ap); + va_end(ap); +} + +void log_print(const LogModule* aModule, LogLevel aLevel, TimeStamp* aStart, + const char* aFmt, ...) { + va_list ap; + va_start(ap, aFmt); + aModule->Printv(aLevel, aStart, aFmt, ap); + va_end(ap); +} + +} // namespace detail + +LogLevel ToLogLevel(int32_t aLevel) { + aLevel = std::min(aLevel, static_cast(LogLevel::Verbose)); + aLevel = std::max(aLevel, static_cast(LogLevel::Disabled)); + return static_cast(aLevel); +} + +static const char* ToLogStr(LogLevel aLevel) { + switch (aLevel) { + case LogLevel::Error: + return "E"; + case LogLevel::Warning: + return "W"; + case LogLevel::Info: + return "I"; + case LogLevel::Debug: + return "D"; + case LogLevel::Verbose: + return "V"; + case LogLevel::Disabled: + default: + MOZ_CRASH("Invalid log level."); + return ""; + } +} + +namespace detail { + +/** + * A helper class providing reference counting for FILE*. + * It encapsulates the following: + * - the FILE handle + * - the order number it was created for when rotating (actual path) + * - number of active references + */ +class LogFile { + FILE* mFile; + uint32_t mFileNum; + + public: + LogFile(FILE* aFile, uint32_t aFileNum) + : mFile(aFile), mFileNum(aFileNum), mNextToRelease(nullptr) {} + + ~LogFile() { + fclose(mFile); + delete mNextToRelease; + } + + FILE* File() const { return mFile; } + uint32_t Num() const { return mFileNum; } + + LogFile* mNextToRelease; +}; + +static const char* ExpandLogFileName(const char* aFilename, + char (&buffer)[2048]) { + MOZ_ASSERT(aFilename); + static const char kPIDToken[] = MOZ_LOG_PID_TOKEN; + static const char kMOZLOGExt[] = MOZ_LOG_FILE_EXTENSION; + + bool hasMozLogExtension = StringEndsWith(nsDependentCString(aFilename), + nsLiteralCString(kMOZLOGExt)); + + const char* pidTokenPtr = strstr(aFilename, kPIDToken); + if (pidTokenPtr && + SprintfLiteral(buffer, "%.*s%s%" PRIPID "%s%s", + static_cast(pidTokenPtr - aFilename), aFilename, + XRE_IsParentProcess() ? "-main." : "-child.", + base::GetCurrentProcId(), pidTokenPtr + strlen(kPIDToken), + hasMozLogExtension ? "" : kMOZLOGExt) > 0) { + return buffer; + } + + if (!hasMozLogExtension && + SprintfLiteral(buffer, "%s%s", aFilename, kMOZLOGExt) > 0) { + return buffer; + } + + return aFilename; +} + +// Drop initial lines from the given file until it is less than or equal to the +// given size. +// +// For simplicity and to reduce memory consumption, lines longer than the given +// long line size may be broken. +// +// This function uses `mkstemp` and `rename` on POSIX systems and `_mktemp_s` +// and `ReplaceFileA` on Win32 systems. `ReplaceFileA` was introduced in +// Windows 7 so it's available. +bool LimitFileToLessThanSize(const char* aFilename, uint32_t aSize, + uint16_t aLongLineSize = 16384) { + // `tempFilename` will be further updated below. + char tempFilename[2048]; + SprintfLiteral(tempFilename, "%s.tempXXXXXX", aFilename); + + bool failedToWrite = false; + + { // Scope `file` and `temp`, so that they are definitely closed. + ScopedCloseFile file(fopen(aFilename, "rb")); + if (!file) { + return false; + } + + if (fseek(file, 0, SEEK_END)) { + // If we can't seek for some reason, better to just not limit the log at + // all and hope to sort out large logs upon further analysis. + return false; + } + + // `ftell` returns a positive `long`, which might be more than 32 bits. + uint64_t fileSize = static_cast(ftell(file)); + + if (fileSize <= aSize) { + return true; + } + + uint64_t minBytesToDrop = fileSize - aSize; + uint64_t numBytesDropped = 0; + + if (fseek(file, 0, SEEK_SET)) { + // Same as above: if we can't seek, hope for the best. + return false; + } + + ScopedCloseFile temp; + +#if defined(OS_WIN) + // This approach was cribbed from + // https://searchfox.org/mozilla-central/rev/868935867c6241e1302e64cf9be8f56db0fd0d1c/xpcom/build/LateWriteChecks.cpp#158. + HANDLE hFile; + do { + // mkstemp isn't supported so keep trying until we get a file. + _mktemp_s(tempFilename, strlen(tempFilename) + 1); + hFile = CreateFileA(tempFilename, GENERIC_WRITE, 0, nullptr, CREATE_NEW, + FILE_ATTRIBUTE_NORMAL, nullptr); + } while (GetLastError() == ERROR_FILE_EXISTS); + + if (hFile == INVALID_HANDLE_VALUE) { + NS_WARNING("INVALID_HANDLE_VALUE"); + return false; + } + + int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND); + if (fd == -1) { + NS_WARNING("_open_osfhandle failed!"); + return false; + } + + temp.reset(_fdopen(fd, "ab")); +#elif defined(OS_POSIX) + + // Coverity would prefer us to set a secure umask before using `mkstemp`. + // However, the umask is process-wide, so setting it may lead to difficult + // to debug complications; and it is fine for this particular short-lived + // temporary file to be insecure. + // + // coverity[SECURE_TEMP : FALSE] + int fd = mkstemp(tempFilename); + if (fd == -1) { + NS_WARNING("mkstemp failed!"); + return false; + } + temp.reset(fdopen(fd, "ab")); +#else +# error Do not know how to open named temporary file +#endif + + if (!temp) { + NS_WARNING(nsPrintfCString("could not open named temporary file %s", + tempFilename) + .get()); + return false; + } + + // `fgets` always null terminates. If the line is too long, it won't + // include a trailing '\n' but will be null-terminated. + UniquePtr line = MakeUnique(aLongLineSize + 1); + while (fgets(line.get(), aLongLineSize + 1, file)) { + if (numBytesDropped >= minBytesToDrop) { + if (fputs(line.get(), temp) < 0) { + NS_WARNING( + nsPrintfCString("fputs failed: ferror %d\n", ferror(temp)).get()); + failedToWrite = true; + break; + } + } else { + // Binary mode avoids platform-specific wrinkles with text streams. In + // particular, on Windows, `\r\n` gets read as `\n` (and the reverse + // when writing), complicating this calculation. + numBytesDropped += strlen(line.get()); + } + } + } + + // At this point, `file` and `temp` are closed, so we can remove and rename. + if (failedToWrite) { + remove(tempFilename); + return false; + } + +#if defined(OS_WIN) + if (!::ReplaceFileA(aFilename, tempFilename, nullptr, 0, 0, 0)) { + NS_WARNING( + nsPrintfCString("ReplaceFileA failed: %lu\n", GetLastError()).get()); + return false; + } +#elif defined(OS_POSIX) + if (rename(tempFilename, aFilename)) { + NS_WARNING( + nsPrintfCString("rename failed: %s (%d)\n", strerror(errno), errno) + .get()); + return false; + } +#else +# error Do not know how to atomically replace file +#endif + + return true; +} + +} // namespace detail + +namespace { +// Helper method that initializes an empty va_list to be empty. +void empty_va(va_list* va, ...) { + va_start(*va, va); + va_end(*va); +} +} // namespace + +class LogModuleManager { + public: + LogModuleManager() + : mModulesLock("logmodules"), + mModules(kInitialModuleCount), +#ifdef DEBUG + mLoggingModuleRegistered(0), +#endif + mPrintEntryCount(0), + mOutFile(nullptr), + mToReleaseFile(nullptr), + mOutFileNum(0), + mOutFilePath(strdup("")), + mMainThread(PR_GetCurrentThread()), + mSetFromEnv(false), + mAddTimestamp(false), + mCaptureProfilerStack(false), + mIsRaw(false), + mIsSync(false), + mRotate(0), + mInitialized(false) { + } + + ~LogModuleManager() { + detail::LogFile* logFile = mOutFile.exchange(nullptr); + delete logFile; + } + + /** + * Loads config from command line args or env vars if present, in + * this specific order of priority. + * + * Notes: + * + * 1) This function is only intended to be called once per session. + * 2) None of the functions used in Init should rely on logging. + */ + void Init(int argc, char* argv[]) { + MOZ_DIAGNOSTIC_ASSERT(!mInitialized); + mInitialized = true; + + LoggingHandleCommandLineArgs(argc, static_cast(argv), + [](nsACString const& env) { + // We deliberately set/rewrite the + // environment variables so that when child + // processes are spawned w/o passing the + // arguments they still inherit the logging + // settings as well as sandboxing can be + // correctly set. Scripts can pass + // -MOZ_LOG=$MOZ_LOG,modules as an argument + // to merge existing settings, if required. + + // PR_SetEnv takes ownership of the string. + PR_SetEnv(ToNewCString(env)); + }); + + bool shouldAppend = false; + bool addTimestamp = false; + bool isSync = false; + bool isRaw = false; + bool captureStacks = false; + int32_t rotate = 0; + int32_t maxSize = 0; + bool prependHeader = false; + const char* modules = PR_GetEnv("MOZ_LOG"); + if (!modules || !modules[0]) { + modules = PR_GetEnv("MOZ_LOG_MODULES"); + if (modules) { + NS_WARNING( + "MOZ_LOG_MODULES is deprecated." + "\nPlease use MOZ_LOG instead."); + } + } + if (!modules || !modules[0]) { + modules = PR_GetEnv("NSPR_LOG_MODULES"); + if (modules) { + NS_WARNING( + "NSPR_LOG_MODULES is deprecated." + "\nPlease use MOZ_LOG instead."); + } + } + + // Need to capture `this` since `sLogModuleManager` is not set until after + // initialization is complete. + NSPRLogModulesParser( + modules, + [this, &shouldAppend, &addTimestamp, &isSync, &isRaw, &rotate, &maxSize, + &prependHeader, &captureStacks](const char* aName, LogLevel aLevel, + int32_t aValue) mutable { + if (strcmp(aName, "append") == 0) { + shouldAppend = true; + } else if (strcmp(aName, "timestamp") == 0) { + addTimestamp = true; + } else if (strcmp(aName, "sync") == 0) { + isSync = true; + } else if (strcmp(aName, "raw") == 0) { + isRaw = true; + } else if (strcmp(aName, "rotate") == 0) { + rotate = (aValue << 20) / kRotateFilesNumber; + } else if (strcmp(aName, "maxsize") == 0) { + maxSize = aValue << 20; + } else if (strcmp(aName, "prependheader") == 0) { + prependHeader = true; + } else if (strcmp(aName, "profilerstacks") == 0) { + captureStacks = true; + } else { + this->CreateOrGetModule(aName)->SetLevel(aLevel); + } + }); + + // Rotate implies timestamp to make the files readable + mAddTimestamp = addTimestamp || rotate > 0; + mIsSync = isSync; + mIsRaw = isRaw; + mRotate = rotate; + mCaptureProfilerStack = captureStacks; + + if (rotate > 0 && shouldAppend) { + NS_WARNING("MOZ_LOG: when you rotate the log, you cannot use append!"); + } + + if (rotate > 0 && maxSize > 0) { + NS_WARNING( + "MOZ_LOG: when you rotate the log, you cannot use maxsize! (ignoring " + "maxsize)"); + maxSize = 0; + } + + if (maxSize > 0 && !shouldAppend) { + NS_WARNING( + "MOZ_LOG: when you limit the log to maxsize, you must use append! " + "(ignorning maxsize)"); + maxSize = 0; + } + + if (rotate > 0 && prependHeader) { + NS_WARNING( + "MOZ_LOG: when you rotate the log, you cannot use prependheader!"); + prependHeader = false; + } + + const char* logFile = PR_GetEnv("MOZ_LOG_FILE"); + if (!logFile || !logFile[0]) { + logFile = PR_GetEnv("NSPR_LOG_FILE"); + } + + if (logFile && logFile[0]) { + char buf[2048]; + logFile = detail::ExpandLogFileName(logFile, buf); + mOutFilePath.reset(strdup(logFile)); + + if (mRotate > 0) { + // Delete all the previously captured files, including non-rotated + // log files, so that users don't complain our logs eat space even + // after the rotate option has been added and don't happen to send + // us old large logs along with the rotated files. + remove(mOutFilePath.get()); + for (uint32_t i = 0; i < kRotateFilesNumber; ++i) { + RemoveFile(i); + } + } + + mOutFile = OpenFile(shouldAppend, mOutFileNum, maxSize); + mSetFromEnv = true; + } + + if (prependHeader && XRE_IsParentProcess()) { + va_list va; + empty_va(&va); + Print("Logger", LogLevel::Info, nullptr, "\n***\n\n", "Opening log\n", + va); + } + } + + void SetLogFile(const char* aFilename) { + // For now we don't allow you to change the file at runtime. + if (mSetFromEnv) { + NS_WARNING( + "LogModuleManager::SetLogFile - Log file was set from the " + "MOZ_LOG_FILE environment variable."); + return; + } + + const char* filename = aFilename ? aFilename : ""; + char buf[2048]; + filename = detail::ExpandLogFileName(filename, buf); + + // Can't use rotate or maxsize at runtime yet. + MOZ_ASSERT(mRotate == 0, + "We don't allow rotate for runtime logfile changes"); + mOutFilePath.reset(strdup(filename)); + + // Exchange mOutFile and set it to be released once all the writes are done. + detail::LogFile* newFile = OpenFile(false, 0); + detail::LogFile* oldFile = mOutFile.exchange(newFile); + + // Since we don't allow changing the logfile if MOZ_LOG_FILE is already set, + // and we don't allow log rotation when setting it at runtime, + // mToReleaseFile will be null, so we're not leaking. + DebugOnly prevFile = mToReleaseFile.exchange(oldFile); + MOZ_ASSERT(!prevFile, "Should be null because rotation is not allowed"); + + // If we just need to release a file, we must force print, in order to + // trigger the closing and release of mToReleaseFile. + if (oldFile) { + va_list va; + empty_va(&va); + Print("Logger", LogLevel::Info, "Flushing old log files\n", va); + } + } + + uint32_t GetLogFile(char* aBuffer, size_t aLength) { + uint32_t len = strlen(mOutFilePath.get()); + if (len + 1 > aLength) { + return 0; + } + snprintf(aBuffer, aLength, "%s", mOutFilePath.get()); + return len; + } + + void SetIsSync(bool aIsSync) { mIsSync = aIsSync; } + + void SetCaptureStacks(bool aCaptureStacks) { + mCaptureProfilerStack = aCaptureStacks; + } + + void SetAddTimestamp(bool aAddTimestamp) { mAddTimestamp = aAddTimestamp; } + + detail::LogFile* OpenFile(bool aShouldAppend, uint32_t aFileNum, + uint32_t aMaxSize = 0) { + FILE* file; + + if (mRotate > 0) { + char buf[2048]; + SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum); + + // rotate doesn't support append (or maxsize). + file = fopen(buf, "w"); + } else if (aShouldAppend && aMaxSize > 0) { + detail::LimitFileToLessThanSize(mOutFilePath.get(), aMaxSize >> 1); + file = fopen(mOutFilePath.get(), "a"); + } else { + file = fopen(mOutFilePath.get(), aShouldAppend ? "a" : "w"); + } + + if (!file) { + return nullptr; + } + + return new detail::LogFile(file, aFileNum); + } + + void RemoveFile(uint32_t aFileNum) { + char buf[2048]; + SprintfLiteral(buf, "%s.%d", mOutFilePath.get(), aFileNum); + remove(buf); + } + + LogModule* CreateOrGetModule(const char* aName) { + OffTheBooksMutexAutoLock guard(mModulesLock); + return mModules + .LookupOrInsertWith( + aName, + [&] { +#ifdef DEBUG + if (++mLoggingModuleRegistered > kInitialModuleCount) { + NS_WARNING( + "kInitialModuleCount too low, consider increasing its " + "value"); + } +#endif + return UniquePtr( + new LogModule{aName, LogLevel::Disabled}); + }) + .get(); + } + + void Print(const char* aName, LogLevel aLevel, const char* aFmt, + va_list aArgs) MOZ_FORMAT_PRINTF(4, 0) { + Print(aName, aLevel, nullptr, "", aFmt, aArgs); + } + + void Print(const char* aName, LogLevel aLevel, const TimeStamp* aStart, + const char* aPrepend, const char* aFmt, va_list aArgs) + MOZ_FORMAT_PRINTF(6, 0) { + AutoSuspendLateWriteChecks suspendLateWriteChecks; + long pid = static_cast(base::GetCurrentProcId()); + const size_t kBuffSize = 1024; + char buff[kBuffSize]; + + char* buffToWrite = buff; + SmprintfPointer allocatedBuff; + + va_list argsCopy; + va_copy(argsCopy, aArgs); + int charsWritten = VsprintfLiteral(buff, aFmt, argsCopy); + va_end(argsCopy); + + if (charsWritten < 0) { + // Print out at least something. We must copy to the local buff, + // can't just assign aFmt to buffToWrite, since when + // buffToWrite != buff, we try to release it. + MOZ_ASSERT(false, "Probably incorrect format string in LOG?"); + strncpy(buff, aFmt, kBuffSize - 1); + buff[kBuffSize - 1] = '\0'; + charsWritten = strlen(buff); + } else if (static_cast(charsWritten) >= kBuffSize - 1) { + // We may have maxed out, allocate a buffer instead. + allocatedBuff = mozilla::Vsmprintf(aFmt, aArgs); + buffToWrite = allocatedBuff.get(); + charsWritten = strlen(buffToWrite); + } + + if (profiler_thread_is_being_profiled_for_markers()) { + struct LogMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("Log"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter, + const ProfilerString8View& aModule, + const ProfilerString8View& aText) { + aWriter.StringProperty("module", aModule); + aWriter.StringProperty("name", aText); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.SetTableLabel("({marker.data.module}) {marker.data.name}"); + schema.AddKeyLabelFormatSearchable("module", "Module", + MS::Format::String, + MS::Searchable::Searchable); + schema.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String, + MS::Searchable::Searchable); + return schema; + } + }; + + profiler_add_marker( + "LogMessages", geckoprofiler::category::OTHER, + {aStart ? MarkerTiming::IntervalUntilNowFrom(*aStart) + : MarkerTiming::InstantNow(), + MarkerStack::MaybeCapture(mCaptureProfilerStack)}, + LogMarker{}, ProfilerString8View::WrapNullTerminatedString(aName), + ProfilerString8View::WrapNullTerminatedString(buffToWrite)); + } + + // Determine if a newline needs to be appended to the message. + const char* newline = ""; + if (charsWritten == 0 || buffToWrite[charsWritten - 1] != '\n') { + newline = "\n"; + } + + FILE* out = stderr; + + // In case we use rotate, this ensures the FILE is kept alive during + // its use. Increased before we load mOutFile. + ++mPrintEntryCount; + + detail::LogFile* outFile = mOutFile; + if (outFile) { + out = outFile->File(); + } + + // This differs from the NSPR format in that we do not output the + // opaque system specific thread pointer (ie pthread_t) cast + // to a long. The address of the current PR_Thread continues to be + // prefixed. + // + // Additionally we prefix the output with the abbreviated log level + // and the module name. + PRThread* currentThread = PR_GetCurrentThread(); + const char* currentThreadName = (mMainThread == currentThread) + ? "Main Thread" + : PR_GetThreadName(currentThread); + + char noNameThread[40]; + if (!currentThreadName) { + SprintfLiteral(noNameThread, "Unnamed thread %p", currentThread); + currentThreadName = noNameThread; + } + + if (!mAddTimestamp && !aStart) { + if (!mIsRaw) { + fprintf_stderr(out, "%s[%s %ld: %s]: %s/%s %s%s", aPrepend, + nsDebugImpl::GetMultiprocessMode(), pid, + currentThreadName, ToLogStr(aLevel), aName, buffToWrite, + newline); + } else { + fprintf_stderr(out, "%s%s%s", aPrepend, buffToWrite, newline); + } + } else { + if (aStart) { + // XXX is there a reasonable way to convert one to the other? this is + // bad + PRTime prnow = PR_Now(); + TimeStamp tmnow = TimeStamp::Now(); + TimeDuration duration = tmnow - *aStart; + PRTime prstart = prnow - duration.ToMicroseconds(); + + PRExplodedTime now; + PRExplodedTime start; + PR_ExplodeTime(prnow, PR_GMTParameters, &now); + PR_ExplodeTime(prstart, PR_GMTParameters, &start); + // Ignore that the start time might be in a different day + fprintf_stderr( + out, + "%s%04d-%02d-%02d %02d:%02d:%02d.%06d -> %02d:%02d:%02d.%06d UTC " + "(%.1gms)- [%s %ld: %s]: %s/%s %s%s", + aPrepend, now.tm_year, now.tm_month + 1, start.tm_mday, + start.tm_hour, start.tm_min, start.tm_sec, start.tm_usec, + now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec, + duration.ToMilliseconds(), nsDebugImpl::GetMultiprocessMode(), pid, + currentThreadName, ToLogStr(aLevel), aName, buffToWrite, newline); + } else { + PRExplodedTime now; + PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now); + fprintf_stderr(out, + "%s%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - [%s %ld: " + "%s]: %s/%s %s%s", + aPrepend, now.tm_year, now.tm_month + 1, now.tm_mday, + now.tm_hour, now.tm_min, now.tm_sec, now.tm_usec, + nsDebugImpl::GetMultiprocessMode(), pid, + currentThreadName, ToLogStr(aLevel), aName, buffToWrite, + newline); + } + } + + if (mIsSync) { + fflush(out); + } + + if (mRotate > 0 && outFile) { + int32_t fileSize = ftell(out); + if (fileSize > mRotate) { + uint32_t fileNum = outFile->Num(); + + uint32_t nextFileNum = fileNum + 1; + if (nextFileNum >= kRotateFilesNumber) { + nextFileNum = 0; + } + + // And here is the trick. The current out-file remembers its order + // number. When no other thread shifted the global file number yet, + // we are the thread to open the next file. + if (mOutFileNum.compareExchange(fileNum, nextFileNum)) { + // We can work with mToReleaseFile because we are sure the + // mPrintEntryCount can't drop to zero now - the condition + // to actually delete what's stored in that member. + // And also, no other thread can enter this piece of code + // because mOutFile is still holding the current file with + // the non-shifted number. The compareExchange() above is + // a no-op for other threads. + outFile->mNextToRelease = mToReleaseFile; + mToReleaseFile = outFile; + + mOutFile = OpenFile(false, nextFileNum); + } + } + } + + if (--mPrintEntryCount == 0 && mToReleaseFile) { + // We were the last Print() entered, if there is a file to release + // do it now. exchange() is atomic and makes sure we release the file + // only once on one thread. + detail::LogFile* release = mToReleaseFile.exchange(nullptr); + delete release; + } + } + + private: + OffTheBooksMutex mModulesLock; + nsClassHashtable mModules; + +#ifdef DEBUG + Atomic mLoggingModuleRegistered; +#endif + // Print() entry counter, actually reflects concurrent use of the current + // output file. ReleaseAcquire ensures that manipulation with mOutFile + // and mToReleaseFile is synchronized by manipulation with this value. + Atomic mPrintEntryCount; + // File to write to. ReleaseAcquire because we need to sync mToReleaseFile + // with this. + Atomic mOutFile; + // File to be released when reference counter drops to zero. This member + // is assigned mOutFile when the current file has reached the limit. + // It can be Relaxed, since it's synchronized with mPrintEntryCount + // manipulation and we do atomic exchange() on it. + Atomic mToReleaseFile; + // The next file number. This is mostly only for synchronization sake. + // Can have relaxed ordering, since we only do compareExchange on it which + // is atomic regardless ordering. + Atomic mOutFileNum; + // Just keeps the actual file path for further use. + UniqueFreePtr mOutFilePath; + + PRThread* mMainThread; + bool mSetFromEnv; + Atomic mAddTimestamp; + Atomic mCaptureProfilerStack; + Atomic mIsRaw; + Atomic mIsSync; + int32_t mRotate; + bool mInitialized; +}; + +StaticAutoPtr sLogModuleManager; + +LogModule* LogModule::Get(const char* aName) { + // This is just a pass through to the LogModuleManager so + // that the LogModuleManager implementation can be kept internal. + MOZ_ASSERT(sLogModuleManager != nullptr); + return sLogModuleManager->CreateOrGetModule(aName); +} + +void LogModule::SetLogFile(const char* aFilename) { + MOZ_ASSERT(sLogModuleManager); + sLogModuleManager->SetLogFile(aFilename); +} + +uint32_t LogModule::GetLogFile(char* aBuffer, size_t aLength) { + MOZ_ASSERT(sLogModuleManager); + return sLogModuleManager->GetLogFile(aBuffer, aLength); +} + +void LogModule::SetAddTimestamp(bool aAddTimestamp) { + sLogModuleManager->SetAddTimestamp(aAddTimestamp); +} + +void LogModule::SetIsSync(bool aIsSync) { + sLogModuleManager->SetIsSync(aIsSync); +} + +void LogModule::SetCaptureStacks(bool aCaptureStacks) { + sLogModuleManager->SetCaptureStacks(aCaptureStacks); +} + +// This function is defined in gecko_logger/src/lib.rs +// We mirror the level in rust code so we don't get forwarded all of the +// rust logging and have to create an LogModule for each rust component. +extern "C" void set_rust_log_level(const char* name, uint8_t level); + +void LogModule::SetLevel(LogLevel level) { + mLevel = level; + + // If the log module contains `::` it is likely a rust module, so we + // pass the level into the rust code so it will know to forward the logging + // to Gecko. + if (strstr(mName, "::")) { + set_rust_log_level(mName, static_cast(level)); + } +} + +void LogModule::Init(int argc, char* argv[]) { + // NB: This method is not threadsafe; it is expected to be called very early + // in startup prior to any other threads being run. + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + + if (sLogModuleManager) { + // Already initialized. + return; + } + + // NB: We intentionally do not register for ClearOnShutdown as that happens + // before all logging is complete. And, yes, that means we leak, but + // we're doing that intentionally. + + // Don't assign the pointer until after Init is called. This should help us + // detect if any of the functions called by Init somehow rely on logging. + auto mgr = new LogModuleManager(); + mgr->Init(argc, argv); + sLogModuleManager = mgr; +} + +void LogModule::Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const { + MOZ_ASSERT(sLogModuleManager != nullptr); + + // Forward to LogModule manager w/ level and name + sLogModuleManager->Print(Name(), aLevel, aFmt, aArgs); +} + +void LogModule::Printv(LogLevel aLevel, const TimeStamp* aStart, + const char* aFmt, va_list aArgs) const { + MOZ_ASSERT(sLogModuleManager != nullptr); + + // Forward to LogModule manager w/ level and name + sLogModuleManager->Print(Name(), aLevel, aStart, "", aFmt, aArgs); +} + +} // namespace mozilla + +extern "C" { + +// This function is called by external code (rust) to log to one of our +// log modules. +void ExternMozLog(const char* aModule, mozilla::LogLevel aLevel, + const char* aMsg) { + MOZ_ASSERT(mozilla::sLogModuleManager != nullptr); + + mozilla::LogModule* m = + mozilla::sLogModuleManager->CreateOrGetModule(aModule); + if (MOZ_LOG_TEST(m, aLevel)) { + mozilla::detail::log_print(m, aLevel, "%s", aMsg); + } +} + +} // extern "C" diff --git a/xpcom/base/Logging.h b/xpcom/base/Logging.h new file mode 100644 index 0000000000..6ed2752315 --- /dev/null +++ b/xpcom/base/Logging.h @@ -0,0 +1,322 @@ +/* -*- 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/. */ + +#ifndef mozilla_logging_h +#define mozilla_logging_h + +#include +#include +#include +#include + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" + +// We normally have logging enabled everywhere, but measurements showed that +// having logging enabled on Android is quite expensive (hundreds of kilobytes +// for both the format strings for logging and the code to perform all the +// logging calls). Because retrieving logs from a mobile device is +// comparatively more difficult for Android than it is for desktop and because +// desktop machines tend to be less space/bandwidth-constrained than Android +// devices, we've chosen to leave logging enabled on desktop, but disabled on +// Android. Given that logging can still be useful for development purposes, +// however, we leave logging enabled on Android developer builds. +#if !defined(ANDROID) || !defined(RELEASE_OR_BETA) +# define MOZ_LOGGING_ENABLED 1 +#else +# define MOZ_LOGGING_ENABLED 0 +#endif + +// The mandatory extension we add to log files. Note that rotate will append +// the file piece number still at the end. +#define MOZ_LOG_FILE_EXTENSION ".moz_log" + +// Token for Process ID substitution. +#define MOZ_LOG_PID_TOKEN "%PID" + +namespace mozilla { + +class TimeStamp; + +// While not a 100% mapping to PR_LOG's numeric values, mozilla::LogLevel does +// maintain a direct mapping for the Disabled, Debug and Verbose levels. +// +// Mappings of LogLevel to PR_LOG's numeric values: +// +// +---------+------------------+-----------------+ +// | Numeric | NSPR Logging | Mozilla Logging | +// +---------+------------------+-----------------+ +// | 0 | PR_LOG_NONE | Disabled | +// | 1 | PR_LOG_ALWAYS | Error | +// | 2 | PR_LOG_ERROR | Warning | +// | 3 | PR_LOG_WARNING | Info | +// | 4 | PR_LOG_DEBUG | Debug | +// | 5 | PR_LOG_DEBUG + 1 | Verbose | +// +---------+------------------+-----------------+ +// +enum class LogLevel { + Disabled = 0, + Error, + Warning, + Info, + Debug, + Verbose, +}; + +/** + * Safely converts an integer into a valid LogLevel. + */ +LogLevel ToLogLevel(int32_t aLevel); + +class LogModule { + public: + ~LogModule() { ::free(mName); } + + /** + * Retrieves the module with the given name. If it does not already exist + * it will be created. + * + * @param aName The name of the module. + * @return A log module for the given name. This may be shared. + */ + static LogModule* Get(const char* aName); + + /** + * Logging processes -MOZ_LOG and -MOZ_LOG_FILE command line arguments + * to override or set modules and the file as if passed through MOZ_LOG + * and MOZ_LOG_FILE env vars. It's fine to pass (0, nullptr) if args + * are not accessible in the caller's context, it will just do nothing. + * Note that the args take effect (are processed) only when this function + * is called the first time. + */ + static void Init(int argc, char* argv[]); + + /** + * Sets the log file to the given filename. + */ + static void SetLogFile(const char* aFilename); + + /** + * @param aBuffer - pointer to a buffer + * @param aLength - the length of the buffer + * + * @return the actual length of the filepath. + */ + static uint32_t GetLogFile(char* aBuffer, size_t aLength); + + /** + * @param aAddTimestamp If we should log a time stamp with every message. + */ + static void SetAddTimestamp(bool aAddTimestamp); + + /** + * @param aIsSync If we should flush the file after every logged message. + */ + static void SetIsSync(bool aIsSync); + + /** + * @param aCaptureStacks If we should capture stacks for the Firefox + * Profiler markers that are recorded for for each log entry. + */ + static void SetCaptureStacks(bool aCaptureStacks); + + /** + * Indicates whether or not the given log level is enabled. + */ + bool ShouldLog(LogLevel aLevel) const { return mLevel >= aLevel; } + + /** + * Retrieves the log module's current level. + */ + LogLevel Level() const { return mLevel; } + + /** + * Sets the log module's level. + */ + void SetLevel(LogLevel level); + + /** + * Print a log message for this module. + */ + void Printv(LogLevel aLevel, const char* aFmt, va_list aArgs) const + MOZ_FORMAT_PRINTF(3, 0); + + void Printv(LogLevel aLevel, const TimeStamp* aStart, const char* aFmt, + va_list aArgs) const MOZ_FORMAT_PRINTF(4, 0); + + /** + * Retrieves the module name. + */ + const char* Name() const { return mName; } + + private: + friend class LogModuleManager; + + explicit LogModule(const char* aName, LogLevel aLevel) + : mName(strdup(aName)), mLevel(aLevel) {} + + LogModule(LogModule&) = delete; + LogModule& operator=(const LogModule&) = delete; + + char* mName; + + Atomic mLevel; +}; + +/** + * Helper class that lazy loads the given log module. This is safe to use for + * declaring static references to log modules and can be used as a replacement + * for accessing a LogModule directly. + * + * Example usage: + * static LazyLogModule sLayoutLog("layout"); + * + * void Foo() { + * MOZ_LOG(sLayoutLog, LogLevel::Verbose, ("Entering foo")); + * } + */ +class LazyLogModule final { + public: + explicit constexpr LazyLogModule(const char* aLogName) + : mLogName(aLogName), mLog(nullptr) {} + + MOZ_NEVER_INLINE_DEBUG operator LogModule*() { + // NB: The use of an atomic makes the reading and assignment of mLog + // thread-safe. There is a small chance that mLog will be set more + // than once, but that's okay as it will be set to the same LogModule + // instance each time. Also note LogModule::Get is thread-safe. + LogModule* tmp = mLog; + if (MOZ_UNLIKELY(!tmp)) { + tmp = LogModule::Get(mLogName); + mLog = tmp; + } + + return tmp; + } + + private: + const char* const mLogName; + + Atomic mLog; +}; + +namespace detail { + +inline bool log_test(const LogModule* module, LogLevel level) { + MOZ_ASSERT(level != LogLevel::Disabled); + return module && module->ShouldLog(level); +} + +void log_print(const LogModule* aModule, LogLevel aLevel, const char* aFmt, ...) + MOZ_FORMAT_PRINTF(3, 4); + +void log_print(const LogModule* aModule, LogLevel aLevel, TimeStamp* aStart, + const char* aFmt, ...) MOZ_FORMAT_PRINTF(4, 5); +} // namespace detail + +} // namespace mozilla + +// Helper macro used convert MOZ_LOG's third parameter, |_args|, from a +// parenthesized form to a varargs form. For example: +// ("%s", "a message") => "%s", "a message" +#define MOZ_LOG_EXPAND_ARGS(...) __VA_ARGS__ + +#if MOZ_LOGGING_ENABLED +# define MOZ_LOG_TEST(_module, _level) \ + MOZ_UNLIKELY(mozilla::detail::log_test(_module, _level)) +#else +// Define away MOZ_LOG_TEST here so the compiler will fold away entire +// logging blocks via dead code elimination, e.g.: +// +// if (MOZ_LOG_TEST(...)) { +// ...compute things to log and log them... +// } +# define MOZ_LOG_TEST(_module, _level) false +#endif + +// The natural definition of the MOZ_LOG macro would expand to: +// +// do { +// if (MOZ_LOG_TEST(_module, _level)) { +// mozilla::detail::log_print(_module, ...); +// } +// } while (0) +// +// However, since _module is a LazyLogModule, and we need to call +// LazyLogModule::operator() to get a LogModule* for the MOZ_LOG_TEST +// macro and for the logging call, we'll wind up doing *two* calls, one +// for each, rather than a single call. The compiler is not able to +// fold the two calls into one, and the extra call can have a +// significant effect on code size. (Making LazyLogModule::operator() a +// `const` function does not have any effect.) +// +// Therefore, we will have to make a single call ourselves. But again, +// the natural definition: +// +// do { +// ::mozilla::LogModule* real_module = _module; +// if (MOZ_LOG_TEST(real_module, _level)) { +// mozilla::detail::log_print(real_module, ...); +// } +// } while (0) +// +// also has a problem: if logging is disabled, then we will call +// LazyLogModule::operator() unnecessarily, and the compiler will not be +// able to optimize away the call as dead code. We would like to avoid +// such a scenario, as the whole point of disabling logging is for the +// logging statements to not generate any code. +// +// Therefore, we need different definitions of MOZ_LOG, depending on +// whether logging is enabled or not. (We need an actual definition of +// MOZ_LOG even when logging is disabled to ensure the compiler sees that +// variables only used during logging code are actually used, even if the +// code will never be executed.) Hence, the following code. +// +// MOZ_LOG_DURATION takes a start time, and will generate a time range +// in the logs. Also, if the Firefox Profiler is running, +// MOZ_LOG_DURATION will generate a marker with a time duration +// instead of a single point in time. +#if MOZ_LOGGING_ENABLED +# define MOZ_LOG(_module, _level, _args) \ + do { \ + const ::mozilla::LogModule* moz_real_module = _module; \ + if (MOZ_LOG_TEST(moz_real_module, _level)) { \ + mozilla::detail::log_print(moz_real_module, _level, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +# define MOZ_LOG_DURATION(_module, _level, start, _args) \ + do { \ + const ::mozilla::LogModule* moz_real_module = _module; \ + if (MOZ_LOG_TEST(moz_real_module, _level)) { \ + mozilla::detail::log_print(moz_real_module, _level, start, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +#else +# define MOZ_LOG(_module, _level, _args) \ + do { \ + if (MOZ_LOG_TEST(_module, _level)) { \ + mozilla::detail::log_print(_module, _level, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +# define MOZ_LOG_DURATION(_module, _level, start, _args) \ + do { \ + if (MOZ_LOG_TEST(_module, _level)) { \ + mozilla::detail::log_print(_module, _level, start, \ + MOZ_LOG_EXPAND_ARGS _args); \ + } \ + } while (0) +#endif + +// This #define is a Logging.h-only knob! Don't encourage people to get fancy +// with their log definitions by exporting it outside of Logging.h. +#undef MOZ_LOGGING_ENABLED + +#endif // mozilla_logging_h diff --git a/xpcom/base/MacHelpers.h b/xpcom/base/MacHelpers.h new file mode 100644 index 0000000000..baf4321034 --- /dev/null +++ b/xpcom/base/MacHelpers.h @@ -0,0 +1,18 @@ +/* -*- 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/. */ + +#ifndef mozilla_MacHelpers_h +#define mozilla_MacHelpers_h + +#include "nsString.h" + +namespace mozilla { + +nsresult GetSelectedCityInfo(nsAString& aCountryCode); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/MacHelpers.mm b/xpcom/base/MacHelpers.mm new file mode 100644 index 0000000000..22d8ef61a4 --- /dev/null +++ b/xpcom/base/MacHelpers.mm @@ -0,0 +1,32 @@ +/* -*- 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 "nsString.h" +#include "MacHelpers.h" +#include "MacStringHelpers.h" +#include "nsObjCExceptions.h" + +#import + +namespace mozilla { + +nsresult GetSelectedCityInfo(nsAString& aCountryCode) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + // Can be replaced with [[NSLocale currentLocale] countryCode] once we build + // with the 10.12 SDK. + id countryCode = [[NSLocale currentLocale] objectForKey:NSLocaleCountryCode]; + + if (![countryCode isKindOfClass:[NSString class]]) { + return NS_ERROR_FAILURE; + } + + return mozilla::CopyCocoaStringToXPCOMString((NSString*)countryCode, aCountryCode); + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +} // namespace mozilla diff --git a/xpcom/base/MacStringHelpers.h b/xpcom/base/MacStringHelpers.h new file mode 100644 index 0000000000..c2f9ee82dc --- /dev/null +++ b/xpcom/base/MacStringHelpers.h @@ -0,0 +1,20 @@ +/* -*- 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/. */ + +#ifndef mozilla_MacStringHelpers_h +#define mozilla_MacStringHelpers_h + +#include "nsString.h" + +#import + +namespace mozilla { + +nsresult CopyCocoaStringToXPCOMString(NSString* aFrom, nsAString& aTo); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/MacStringHelpers.mm b/xpcom/base/MacStringHelpers.mm new file mode 100644 index 0000000000..9bfbdd912e --- /dev/null +++ b/xpcom/base/MacStringHelpers.mm @@ -0,0 +1,34 @@ +/* -*- 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 "MacStringHelpers.h" +#include "nsObjCExceptions.h" + +#include "mozilla/IntegerTypeTraits.h" +#include + +namespace mozilla { + +nsresult CopyCocoaStringToXPCOMString(NSString* aFrom, nsAString& aTo) { + NS_OBJC_BEGIN_TRY_BLOCK_RETURN; + + NSUInteger len = [aFrom length]; + if (len > std::numeric_limits::max()) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!aTo.SetLength(len, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + [aFrom getCharacters:reinterpret_cast(aTo.BeginWriting()) range:NSMakeRange(0, len)]; + + return NS_OK; + + NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE); +} + +} // namespace mozilla diff --git a/xpcom/base/MemoryInfo.cpp b/xpcom/base/MemoryInfo.cpp new file mode 100644 index 0000000000..6ce7f2b768 --- /dev/null +++ b/xpcom/base/MemoryInfo.cpp @@ -0,0 +1,105 @@ +/* -*- 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/MemoryInfo.h" + +#include "mozilla/DebugOnly.h" + +#include +#include + +namespace mozilla { + +/* static */ +MemoryInfo MemoryInfo::Get(const void* aPtr, size_t aSize) { + MemoryInfo result; + + result.mStart = uintptr_t(aPtr); + const char* ptr = reinterpret_cast(aPtr); + const char* end = ptr + aSize; + DebugOnly base = nullptr; + while (ptr < end) { + MEMORY_BASIC_INFORMATION basicInfo; + if (!VirtualQuery(ptr, &basicInfo, sizeof(basicInfo))) { + break; + } + + MOZ_ASSERT_IF(base, base == basicInfo.AllocationBase); + base = basicInfo.AllocationBase; + + size_t regionSize = + std::min(size_t(basicInfo.RegionSize), size_t(end - ptr)); + + if (basicInfo.State == MEM_COMMIT) { + result.mCommitted += regionSize; + } else if (basicInfo.State == MEM_RESERVE) { + result.mReserved += regionSize; + } else if (basicInfo.State == MEM_FREE) { + result.mFree += regionSize; + } else { + MOZ_ASSERT_UNREACHABLE("Unexpected region state"); + } + result.mSize += regionSize; + ptr += regionSize; + + if (result.mType.isEmpty()) { + if (basicInfo.Type & MEM_IMAGE) { + result.mType += PageType::Image; + } + if (basicInfo.Type & MEM_MAPPED) { + result.mType += PageType::Mapped; + } + if (basicInfo.Type & MEM_PRIVATE) { + result.mType += PageType::Private; + } + + // The first 8 bits of AllocationProtect are an enum. The remaining bits + // are flags. + switch (basicInfo.AllocationProtect & 0xff) { + case PAGE_EXECUTE_WRITECOPY: + result.mPerms += Perm::CopyOnWrite; + [[fallthrough]]; + case PAGE_EXECUTE_READWRITE: + result.mPerms += Perm::Write; + [[fallthrough]]; + case PAGE_EXECUTE_READ: + result.mPerms += Perm::Read; + [[fallthrough]]; + case PAGE_EXECUTE: + result.mPerms += Perm::Execute; + break; + + case PAGE_WRITECOPY: + result.mPerms += Perm::CopyOnWrite; + [[fallthrough]]; + case PAGE_READWRITE: + result.mPerms += Perm::Write; + [[fallthrough]]; + case PAGE_READONLY: + result.mPerms += Perm::Read; + break; + + default: + break; + } + + if (basicInfo.AllocationProtect & PAGE_GUARD) { + result.mPerms += Perm::Guard; + } + if (basicInfo.AllocationProtect & PAGE_NOCACHE) { + result.mPerms += Perm::NoCache; + } + if (basicInfo.AllocationProtect & PAGE_WRITECOMBINE) { + result.mPerms += Perm::WriteCombine; + } + } + } + + result.mEnd = uintptr_t(ptr); + return result; +} + +} // namespace mozilla diff --git a/xpcom/base/MemoryInfo.h b/xpcom/base/MemoryInfo.h new file mode 100644 index 0000000000..d122f6d377 --- /dev/null +++ b/xpcom/base/MemoryInfo.h @@ -0,0 +1,81 @@ +/* -*- 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/. */ + +#ifndef mozilla_MemoryInfo_h +#define mozilla_MemoryInfo_h + +#include +#include +#include "mozilla/Attributes.h" +#include "mozilla/EnumSet.h" +/** + * MemoryInfo is a helper class which describes the attributes and sizes of a + * particular region of VM memory on Windows. It roughtly corresponds to the + * values in a MEMORY_BASIC_INFORMATION struct, summed over an entire region or + * memory. + */ + +namespace mozilla { + +class MemoryInfo final { + public: + enum class Perm : uint8_t { + Read, + Write, + Execute, + CopyOnWrite, + Guard, + NoCache, + WriteCombine, + }; + enum class PageType : uint8_t { + Image, + Mapped, + Private, + }; + + using PermSet = EnumSet; + using PageTypeSet = EnumSet; + + MemoryInfo() = default; + MOZ_IMPLICIT MemoryInfo(const MemoryInfo&) = default; + + uintptr_t Start() const { return mStart; } + uintptr_t End() const { return mEnd; } + + PageTypeSet Type() const { return mType; } + PermSet Perms() const { return mPerms; } + + size_t Reserved() const { return mReserved; } + size_t Committed() const { return mCommitted; } + size_t Free() const { return mFree; } + size_t Size() const { return mSize; } + + // Returns a MemoryInfo object containing the sums of all region sizes, + // divided into Reserved, Committed, and Free, depending on their State + // properties. + // + // The entire range of aSize bytes starting at aPtr must correspond to a + // single allocation. This restriction is enforced in debug builds. + static MemoryInfo Get(const void* aPtr, size_t aSize); + + private: + uintptr_t mStart = 0; + uintptr_t mEnd = 0; + + size_t mReserved = 0; + size_t mCommitted = 0; + size_t mFree = 0; + size_t mSize = 0; + + PageTypeSet mType{}; + + PermSet mPerms{}; +}; + +} // namespace mozilla + +#endif // mozilla_MemoryInfo_h diff --git a/xpcom/base/MemoryMapping.cpp b/xpcom/base/MemoryMapping.cpp new file mode 100644 index 0000000000..1ec1cf1f61 --- /dev/null +++ b/xpcom/base/MemoryMapping.cpp @@ -0,0 +1,208 @@ +/* -*- 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/MemoryMapping.h" + +#include "mozilla/BinarySearch.h" +#include "mozilla/FileUtils.h" + +#include +#include +#include + +namespace mozilla { + +namespace { +struct VMFlagString { + const char* mName; + const char* mPrettyName; + VMFlag mFlag; +}; + +static const VMFlagString sVMFlagStrings[] = { + // clang-format off + {"ac", "Accountable", VMFlag::Accountable}, + {"ar", "ArchSpecific", VMFlag::ArchSpecific}, + {"dc", "NoFork", VMFlag::NoFork}, + {"dd", "NoCore", VMFlag::NoCore}, + {"de", "NoExpand", VMFlag::NoExpand}, + {"dw", "DisabledWrite", VMFlag::DisabledWrite}, + {"ex", "Executable", VMFlag::Executable}, + {"gd", "GrowsDown", VMFlag::GrowsDown}, + {"hg", "HugePage", VMFlag::HugePage}, + {"ht", "HugeTLB", VMFlag::HugeTLB}, + {"io", "IO", VMFlag::IO}, + {"lo", "Locked", VMFlag::Locked}, + {"me", "MayExecute", VMFlag::MayExecute}, + {"mg", "Mergeable", VMFlag::Mergeable}, + {"mm", "MixedMap", VMFlag::MixedMap}, + {"mr", "MayRead", VMFlag::MayRead}, + {"ms", "MayShare", VMFlag::MayShare}, + {"mw", "MayWrite", VMFlag::MayWrite}, + {"nh", "NoHugePage", VMFlag::NoHugePage}, + {"nl", "NonLinear", VMFlag::NonLinear}, + {"nr", "NotReserved", VMFlag::NotReserved}, + {"pf", "PurePFN", VMFlag::PurePFN}, + {"rd", "Readable", VMFlag::Readable}, + {"rr", "Random", VMFlag::Random}, + {"sd", "SoftDirty", VMFlag::SoftDirty}, + {"sh", "Shared", VMFlag::Shared}, + {"sr", "Sequential", VMFlag::Sequential}, + {"wr", "Writable", VMFlag::Writable}, + // clang-format on +}; +} // anonymous namespace + +constexpr size_t kVMFlags = size_t(-1); + +// An array of known field names which may be present in an smaps file, and the +// offsets of the corresponding fields in a MemoryMapping class. +const MemoryMapping::Field MemoryMapping::sFields[] = { + // clang-format off + {"AnonHugePages", offsetof(MemoryMapping, mAnonHugePages)}, + {"Anonymous", offsetof(MemoryMapping, mAnonymous)}, + {"KernelPageSize", offsetof(MemoryMapping, mKernelPageSize)}, + {"LazyFree", offsetof(MemoryMapping, mLazyFree)}, + {"Locked", offsetof(MemoryMapping, mLocked)}, + {"MMUPageSize", offsetof(MemoryMapping, mMMUPageSize)}, + {"Private_Clean", offsetof(MemoryMapping, mPrivate_Clean)}, + {"Private_Dirty", offsetof(MemoryMapping, mPrivate_Dirty)}, + {"Private_Hugetlb", offsetof(MemoryMapping, mPrivate_Hugetlb)}, + {"Pss", offsetof(MemoryMapping, mPss)}, + {"Referenced", offsetof(MemoryMapping, mReferenced)}, + {"Rss", offsetof(MemoryMapping, mRss)}, + {"Shared_Clean", offsetof(MemoryMapping, mShared_Clean)}, + {"Shared_Dirty", offsetof(MemoryMapping, mShared_Dirty)}, + {"Shared_Hugetlb", offsetof(MemoryMapping, mShared_Hugetlb)}, + {"ShmemPmdMapped", offsetof(MemoryMapping, mShmemPmdMapped)}, + {"Size", offsetof(MemoryMapping, mSize)}, + {"Swap", offsetof(MemoryMapping, mSwap)}, + {"SwapPss", offsetof(MemoryMapping, mSwapPss)}, + // VmFlags is a special case. It contains an array of flag strings, which + // describe attributes of the mapping, rather than a mapping size. We include + // it in this array to aid in parsing, but give it a separate sentinel value, + // and treat it specially. + {"VmFlags", kVMFlags}, + // clang-format on +}; + +template +const T* FindEntry(const char* aName, const T (&aEntries)[n]) { + size_t index; + if (BinarySearchIf( + aEntries, 0, n, + [&](const T& aEntry) { return strcmp(aName, aEntry.mName); }, + &index)) { + return &aEntries[index]; + } + return nullptr; +} + +using Perm = MemoryMapping::Perm; +using PermSet = MemoryMapping::PermSet; + +nsresult GetMemoryMappings(nsTArray& aMappings, pid_t aPid) { + std::ifstream stream; + if (aPid == 0) { + stream.open("/proc/self/smaps"); + } else { + std::ostringstream path; + path << "/proc/" << aPid << "/smaps" << std::ends; + stream.open(path.str()); + } + if (stream.fail()) { + return NS_ERROR_FAILURE; + } + + MemoryMapping* current = nullptr; + std::string buffer; + while (std::getline(stream, buffer)) { + size_t start, end, offset; + char flags[4] = "---"; + char name[512]; + + name[0] = 0; + + // clang-format off + // Match the start of an entry. A typical line looks something like: + // + // 1487118a7000-148711a5a000 r-xp 00000000 103:03 54004561 /usr/lib/libc-2.27.so + // clang-format on + if (sscanf(buffer.c_str(), "%zx-%zx %4c %zx %*u:%*u %*u %511s\n", &start, + &end, flags, &offset, name) >= 4) { + PermSet perms; + if (flags[0] == 'r') { + perms += Perm::Read; + } + if (flags[1] == 'w') { + perms += Perm::Write; + } + if (flags[2] == 'x') { + perms += Perm::Execute; + } + if (flags[3] == 'p') { + perms += Perm::Private; + } else if (flags[3] == 's') { + perms += Perm::Shared; + } + + current = aMappings.AppendElement( + MemoryMapping{start, end, perms, offset, name}); + continue; + } + if (!current) { + continue; + } + + nsAutoCStringN<128> line(buffer.c_str()); + char* savePtr; + char* fieldName = strtok_r(line.BeginWriting(), ":", &savePtr); + if (!fieldName) { + continue; + } + auto* field = FindEntry(fieldName, MemoryMapping::sFields); + if (!field) { + continue; + } + + if (field->mOffset == kVMFlags) { + while (char* flagName = strtok_r(nullptr, " \n", &savePtr)) { + if (auto* flag = FindEntry(flagName, sVMFlagStrings)) { + current->mFlags += flag->mFlag; + } + } + continue; + } + + const char* rest = strtok_r(nullptr, "\n", &savePtr); + size_t value; + if (sscanf(rest, "%zd kB", &value) > 0) { + current->ValueForField(*field) = value * 1024; + } + } + + return NS_OK; +} + +void MemoryMapping::Dump(nsACString& aOut) const { + aOut.AppendPrintf("%zx-%zx Size: %zu Offset: %zx %s\n", mStart, mEnd, + mEnd - mStart, mOffset, mName.get()); + + for (auto& field : MemoryMapping::sFields) { + if (field.mOffset < sizeof(*this)) { + aOut.AppendPrintf(" %s: %zd\n", field.mName, ValueForField(field)); + } + } + + aOut.AppendPrintf(" Flags: %x\n", mFlags.serialize()); + for (auto& flag : sVMFlagStrings) { + if (mFlags.contains(flag.mFlag)) { + aOut.AppendPrintf(" : %s %s\n", flag.mName, flag.mPrettyName); + } + } +} + +} // namespace mozilla diff --git a/xpcom/base/MemoryMapping.h b/xpcom/base/MemoryMapping.h new file mode 100644 index 0000000000..51bbb0ab66 --- /dev/null +++ b/xpcom/base/MemoryMapping.h @@ -0,0 +1,183 @@ +/* -*- 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/. */ + +#ifndef mozilla_MemoryMapping_h +#define mozilla_MemoryMapping_h + +#include +#include "mozilla/EnumSet.h" +#include "nsString.h" +#include "nsTArrayForwardDeclare.h" + +/** + * MemoryMapping is a helper class which describes an entry in the Linux + * /proc//smaps file. See procfs(5) for details on the entry format. + * + * The GetMemoryMappings() function returns an array of such entries, sorted by + * start address, one for each entry in the current process's address space. + */ + +namespace mozilla { + +enum class VMFlag : uint8_t { + Readable, // rd - readable + Writable, // wr - writable + Executable, // ex - executable + Shared, // sh - shared + MayRead, // mr - may read + MayWrite, // mw - may write + MayExecute, // me - may execute + MayShare, // ms - may share + GrowsDown, // gd - stack segment grows down + PurePFN, // pf - pure PFN range + DisabledWrite, // dw - disabled write to the mapped file + Locked, // lo - pages are locked in memory + IO, // io - memory mapped I/O area + Sequential, // sr - sequential read advise provided + Random, // rr - random read advise provided + NoFork, // dc - do not copy area on fork + NoExpand, // de - do not expand area on remapping + Accountable, // ac - area is accountable + NotReserved, // nr - swap space is not reserved for the area + HugeTLB, // ht - area uses huge tlb pages + NonLinear, // nl - non-linear mapping + ArchSpecific, // ar - architecture specific flag + NoCore, // dd - do not include area into core dump + SoftDirty, // sd - soft-dirty flag + MixedMap, // mm - mixed map area + HugePage, // hg - huge page advise flag + NoHugePage, // nh - no-huge page advise flag + Mergeable, // mg - mergeable advise flag +}; + +using VMFlagSet = EnumSet; + +class MemoryMapping final { + public: + enum class Perm : uint8_t { + Read, + Write, + Execute, + Shared, + Private, + }; + + using PermSet = EnumSet; + + MemoryMapping(uintptr_t aStart, uintptr_t aEnd, PermSet aPerms, + size_t aOffset, const char* aName) + : mStart(aStart), + mEnd(aEnd), + mOffset(aOffset), + mName(aName), + mPerms(aPerms) {} + + const nsCString& Name() const { return mName; } + + uintptr_t Start() const { return mStart; } + uintptr_t End() const { return mEnd; } + + bool Includes(const void* aPtr) const { + auto ptr = uintptr_t(aPtr); + return ptr >= mStart && ptr < mEnd; + } + + PermSet Perms() const { return mPerms; } + VMFlagSet VMFlags() const { return mFlags; } + + // For file mappings, the offset in the mapped file which corresponds to the + // start of the mapped region. + size_t Offset() const { return mOffset; } + + size_t AnonHugePages() const { return mAnonHugePages; } + size_t Anonymous() const { return mAnonymous; } + size_t KernelPageSize() const { return mKernelPageSize; } + size_t LazyFree() const { return mLazyFree; } + size_t Locked() const { return mLocked; } + size_t MMUPageSize() const { return mMMUPageSize; } + size_t Private_Clean() const { return mPrivate_Clean; } + size_t Private_Dirty() const { return mPrivate_Dirty; } + size_t Private_Hugetlb() const { return mPrivate_Hugetlb; } + size_t Pss() const { return mPss; } + size_t Referenced() const { return mReferenced; } + size_t Rss() const { return mRss; } + size_t Shared_Clean() const { return mShared_Clean; } + size_t Shared_Dirty() const { return mShared_Dirty; } + size_t Shared_Hugetlb() const { return mShared_Hugetlb; } + size_t ShmemPmdMapped() const { return mShmemPmdMapped; } + size_t Size() const { return mSize; } + size_t Swap() const { return mSwap; } + size_t SwapPss() const { return mSwapPss; } + + // Dumps a string representation of the entry, similar to its format in the + // smaps file, to the given string. Mainly useful for debugging. + void Dump(nsACString& aOut) const; + + // These comparison operators are used for binary searching sorted arrays of + // MemoryMapping entries to find the one which contains a given pointer. + bool operator==(const void* aPtr) const { return Includes(aPtr); } + bool operator<(const void* aPtr) const { return mStart < uintptr_t(aPtr); } + + private: + friend nsresult GetMemoryMappings(nsTArray& aMappings, + pid_t aPid); + + uintptr_t mStart = 0; + uintptr_t mEnd = 0; + + size_t mOffset = 0; + + nsCString mName; + + // Members for size fields in the smaps file. Please keep these in sync with + // the sFields array. + size_t mAnonHugePages = 0; + size_t mAnonymous = 0; + size_t mKernelPageSize = 0; + size_t mLazyFree = 0; + size_t mLocked = 0; + size_t mMMUPageSize = 0; + size_t mPrivate_Clean = 0; + size_t mPrivate_Dirty = 0; + size_t mPrivate_Hugetlb = 0; + size_t mPss = 0; + size_t mReferenced = 0; + size_t mRss = 0; + size_t mShared_Clean = 0; + size_t mShared_Dirty = 0; + size_t mShared_Hugetlb = 0; + size_t mShmemPmdMapped = 0; + size_t mSize = 0; + size_t mSwap = 0; + size_t mSwapPss = 0; + + PermSet mPerms{}; + VMFlagSet mFlags{}; + + // Contains the name and offset of one of the above size_t fields, for use in + // parsing in dumping. The below helpers contain a list of the fields, and map + // Field entries to the appropriate member in a class instance. + struct Field { + const char* mName; + size_t mOffset; + }; + + static const Field sFields[20]; + + size_t& ValueForField(const Field& aField) { + char* fieldPtr = reinterpret_cast(this) + aField.mOffset; + return reinterpret_cast(fieldPtr)[0]; + } + size_t ValueForField(const Field& aField) const { + return const_cast(this)->ValueForField(aField); + } +}; + +nsresult GetMemoryMappings(nsTArray& aMappings, pid_t aPid = 0); + +} // namespace mozilla + +#endif // mozilla_MemoryMapping_h diff --git a/xpcom/base/MemoryPressureLevelMac.h b/xpcom/base/MemoryPressureLevelMac.h new file mode 100644 index 0000000000..38cb49a4fd --- /dev/null +++ b/xpcom/base/MemoryPressureLevelMac.h @@ -0,0 +1,77 @@ +/* -*- 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/. */ + +#ifndef mozilla_MemoryPressureLevelMac_h +#define mozilla_MemoryPressureLevelMac_h + +namespace mozilla { + +#if defined(XP_DARWIN) +// An internal representation of the Mac memory-pressure level constants. +class MacMemoryPressureLevel { + public: + // Order enum values so that higher integer values represent higher + // memory pressure levels allowing comparison operators to be used. + enum class Value { + eUnset, + eUnexpected, + eNormal, + eWarning, + eCritical, + }; + + MacMemoryPressureLevel() : mValue(Value::eUnset) {} + MOZ_IMPLICIT MacMemoryPressureLevel(Value aValue) : mValue(aValue) {} + + bool operator==(const Value& aRhsValue) const { return mValue == aRhsValue; } + bool operator==(const MacMemoryPressureLevel& aRhs) const { + return mValue == aRhs.mValue; + } + + // Implement '<' and derive the other comparators from it. + bool operator<(const MacMemoryPressureLevel& aRhs) const { + return mValue < aRhs.mValue; + } + bool operator>(const MacMemoryPressureLevel& aRhs) const { + return aRhs < *this; + } + bool operator<=(const MacMemoryPressureLevel& aRhs) const { + return !(aRhs < *this); + } + bool operator>=(const MacMemoryPressureLevel& aRhs) const { + return !(*this < aRhs); + } + + Value GetValue() { return mValue; } + bool IsNormal() { return mValue == Value::eNormal; } + bool IsUnsetOrNormal() { return IsNormal() || (mValue == Value::eUnset); } + bool IsWarningOrAbove() { + return (mValue == Value::eWarning) || (mValue == Value::eCritical); + } + + const char* ToString() { + switch (mValue) { + case Value::eUnset: + return "Unset"; + case Value::eUnexpected: + return "Unexpected"; + case Value::eNormal: + return "Normal"; + case Value::eWarning: + return "Warning"; + case Value::eCritical: + return "Critical"; + } + } + + private: + Value mValue; +}; +#endif + +} // namespace mozilla + +#endif // mozilla_MemoryPressureLevelMac_h diff --git a/xpcom/base/MemoryReportingProcess.h b/xpcom/base/MemoryReportingProcess.h new file mode 100644 index 0000000000..36410e702b --- /dev/null +++ b/xpcom/base/MemoryReportingProcess.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +#ifndef xpcom_base_MemoryReportingProcess_h +#define xpcom_base_MemoryReportingProcess_h + +#include +#include "nscore.h" + +namespace mozilla { +namespace ipc { +class FileDescriptor; +} // namespace ipc + +template +class Maybe; + +// Top-level process actors should implement this to integrate with +// nsMemoryReportManager. +class MemoryReportingProcess { + public: + NS_IMETHOD_(MozExternalRefCountType) AddRef() = 0; + NS_IMETHOD_(MozExternalRefCountType) Release() = 0; + + virtual ~MemoryReportingProcess() = default; + + // Return true if the process is still alive, false otherwise. + virtual bool IsAlive() const = 0; + + // Initiate a memory report request, returning true if a report was + // successfully initiated and false otherwise. + virtual bool SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe& aDMDFile) = 0; + + virtual int32_t Pid() const = 0; +}; + +} // namespace mozilla + +#endif // xpcom_base_MemoryReportingProcess_h diff --git a/xpcom/base/MemoryTelemetry.cpp b/xpcom/base/MemoryTelemetry.cpp new file mode 100644 index 0000000000..e9b6bff80e --- /dev/null +++ b/xpcom/base/MemoryTelemetry.cpp @@ -0,0 +1,520 @@ +/* -*- 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 "MemoryTelemetry.h" +#include "nsMemoryReporterManager.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/SimpleEnumerator.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsContentUtils.h" +#include "nsIBrowserDOMWindow.h" +#include "nsIDOMChromeWindow.h" +#include "nsIMemoryReporter.h" +#include "nsIWindowMediator.h" +#include "nsImportModule.h" +#include "nsITelemetry.h" +#include "nsNetCID.h" +#include "nsObserverService.h" +#include "nsReadableUtils.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "xpcpublic.h" + +#include + +using namespace mozilla; + +using mozilla::dom::AutoJSAPI; +using mozilla::dom::ContentParent; + +// Do not gather data more than once a minute (ms) +static constexpr uint32_t kTelemetryInterval = 60 * 1000; + +static constexpr const char* kTopicCycleCollectorBegin = + "cycle-collector-begin"; + +namespace { + +enum class PrevValue : uint32_t { +#ifdef XP_WIN + LOW_MEMORY_EVENTS_VIRTUAL, + LOW_MEMORY_EVENTS_COMMIT_SPACE, + LOW_MEMORY_EVENTS_PHYSICAL, +#endif +#if defined(XP_LINUX) && !defined(ANDROID) + PAGE_FAULTS_HARD, +#endif + SIZE_, +}; + +} // anonymous namespace + +constexpr uint32_t kUninitialized = ~0; + +static uint32_t gPrevValues[uint32_t(PrevValue::SIZE_)]; + +static uint32_t PrevValueIndex(Telemetry::HistogramID aId) { + switch (aId) { +#ifdef XP_WIN + case Telemetry::LOW_MEMORY_EVENTS_VIRTUAL: + return uint32_t(PrevValue::LOW_MEMORY_EVENTS_VIRTUAL); + case Telemetry::LOW_MEMORY_EVENTS_COMMIT_SPACE: + return uint32_t(PrevValue::LOW_MEMORY_EVENTS_COMMIT_SPACE); + case Telemetry::LOW_MEMORY_EVENTS_PHYSICAL: + return uint32_t(PrevValue::LOW_MEMORY_EVENTS_PHYSICAL); +#endif +#if defined(XP_LINUX) && !defined(ANDROID) + case Telemetry::PAGE_FAULTS_HARD: + return uint32_t(PrevValue::PAGE_FAULTS_HARD); +#endif + default: + MOZ_ASSERT_UNREACHABLE("Unexpected histogram ID"); + return 0; + } +} + +NS_IMPL_ISUPPORTS(MemoryTelemetry, nsIObserver, nsISupportsWeakReference) + +MemoryTelemetry::MemoryTelemetry() + : mThreadPool(do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID)) {} + +void MemoryTelemetry::Init() { + for (auto& val : gPrevValues) { + val = kUninitialized; + } + + if (XRE_IsContentProcess()) { + nsCOMPtr obs = services::GetObserverService(); + MOZ_RELEASE_ASSERT(obs); + + obs->AddObserver(this, "content-child-shutdown", true); + } +} + +/* static */ MemoryTelemetry& MemoryTelemetry::Get() { + static RefPtr sInstance; + + MOZ_ASSERT(NS_IsMainThread()); + + if (!sInstance) { + sInstance = new MemoryTelemetry(); + sInstance->Init(); + ClearOnShutdown(&sInstance); + } + return *sInstance; +} + +nsresult MemoryTelemetry::DelayedInit() { + if (Telemetry::CanRecordExtended()) { + nsCOMPtr obs = services::GetObserverService(); + MOZ_RELEASE_ASSERT(obs); + + obs->AddObserver(this, kTopicCycleCollectorBegin, true); + } + + GatherReports(); + + return NS_OK; +} + +nsresult MemoryTelemetry::Shutdown() { + nsCOMPtr obs = services::GetObserverService(); + MOZ_RELEASE_ASSERT(obs); + + obs->RemoveObserver(this, kTopicCycleCollectorBegin); + + return NS_OK; +} + +static inline void HandleMemoryReport(Telemetry::HistogramID aId, + int32_t aUnits, uint64_t aAmount, + const nsCString& aKey = VoidCString()) { + uint32_t val; + switch (aUnits) { + case nsIMemoryReporter::UNITS_BYTES: + val = uint32_t(aAmount / 1024); + break; + + case nsIMemoryReporter::UNITS_PERCENTAGE: + // UNITS_PERCENTAGE amounts are 100x greater than their raw value. + val = uint32_t(aAmount / 100); + break; + + case nsIMemoryReporter::UNITS_COUNT: + val = uint32_t(aAmount); + break; + + case nsIMemoryReporter::UNITS_COUNT_CUMULATIVE: { + // If the reporter gives us a cumulative count, we'll report the + // difference in its value between now and our previous ping. + + uint32_t idx = PrevValueIndex(aId); + uint32_t prev = gPrevValues[idx]; + gPrevValues[idx] = aAmount; + + if (prev == kUninitialized) { + // If this is the first time we're reading this reporter, store its + // current value but don't report it in the telemetry ping, so we + // ignore the effect startup had on the reporter. + return; + } + val = aAmount - prev; + break; + } + + default: + MOZ_ASSERT_UNREACHABLE("Unexpected aUnits value"); + return; + } + + // Note: The reference equality check here should allow the compiler to + // optimize this case out at compile time when we weren't given a key, + // while IsEmpty() or IsVoid() most likely will not. + if (&aKey == &VoidCString()) { + Telemetry::Accumulate(aId, val); + } else { + Telemetry::Accumulate(aId, aKey, val); + } +} + +nsresult MemoryTelemetry::GatherReports( + const std::function& aCompletionCallback) { + auto cleanup = MakeScopeExit([&]() { + if (aCompletionCallback) { + aCompletionCallback(); + } + }); + + RefPtr mgr = nsMemoryReporterManager::GetOrCreate(); + MOZ_DIAGNOSTIC_ASSERT(mgr); + NS_ENSURE_TRUE(mgr, NS_ERROR_FAILURE); + +#define RECORD(id, metric, units) \ + do { \ + int64_t amt; \ + nsresult rv = mgr->Get##metric(&amt); \ + if (NS_SUCCEEDED(rv)) { \ + HandleMemoryReport(Telemetry::id, nsIMemoryReporter::units, amt); \ + } else if (rv != NS_ERROR_NOT_AVAILABLE) { \ + NS_WARNING("Failed to retrieve memory telemetry for " #metric); \ + } \ + } while (0) + + // GHOST_WINDOWS is opt-out as of Firefox 55 + RECORD(GHOST_WINDOWS, GhostWindows, UNITS_COUNT); + + // If we're running in the parent process, collect data from all processes for + // the MEMORY_TOTAL histogram. + if (XRE_IsParentProcess() && !mGatheringTotalMemory) { + GatherTotalMemory(); + } + + if (!Telemetry::CanRecordReleaseData()) { + return NS_OK; + } + + // Get memory measurements from distinguished amount attributes. We used + // to measure "explicit" too, but it could cause hangs, and the data was + // always really noisy anyway. See bug 859657. + // + // test_TelemetrySession.js relies on some of these histograms being + // here. If you remove any of the following histograms from here, you'll + // have to modify test_TelemetrySession.js: + // + // * MEMORY_TOTAL, + // * MEMORY_JS_GC_HEAP, and + // * MEMORY_JS_COMPARTMENTS_SYSTEM. + // + // The distinguished amount attribute names don't match the telemetry id + // names in some cases due to a combination of (a) historical reasons, and + // (b) the fact that we can't change telemetry id names without breaking + // data continuity. + + // Collect cheap or main-thread only metrics synchronously, on the main + // thread. + RECORD(MEMORY_JS_GC_HEAP, JSMainRuntimeGCHeap, UNITS_BYTES); + RECORD(MEMORY_JS_COMPARTMENTS_SYSTEM, JSMainRuntimeCompartmentsSystem, + UNITS_COUNT); + RECORD(MEMORY_JS_COMPARTMENTS_USER, JSMainRuntimeCompartmentsUser, + UNITS_COUNT); + RECORD(MEMORY_JS_REALMS_SYSTEM, JSMainRuntimeRealmsSystem, UNITS_COUNT); + RECORD(MEMORY_JS_REALMS_USER, JSMainRuntimeRealmsUser, UNITS_COUNT); + RECORD(MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED, ImagesContentUsedUncompressed, + UNITS_BYTES); + RECORD(MEMORY_STORAGE_SQLITE, StorageSQLite, UNITS_BYTES); +#ifdef XP_WIN + RECORD(LOW_MEMORY_EVENTS_PHYSICAL, LowMemoryEventsPhysical, + UNITS_COUNT_CUMULATIVE); +#endif +#if defined(XP_LINUX) && !defined(ANDROID) + RECORD(PAGE_FAULTS_HARD, PageFaultsHard, UNITS_COUNT_CUMULATIVE); +#endif + + RefPtr completionRunnable; + if (aCompletionCallback) { + completionRunnable = NS_NewRunnableFunction(__func__, aCompletionCallback); + } + + // Collect expensive metrics that can be calculated off-main-thread + // asynchronously, on a background thread. + RefPtr runnable = NS_NewRunnableFunction( + "MemoryTelemetry::GatherReports", [mgr, completionRunnable]() mutable { + Telemetry::AutoTimer autoTimer; + RECORD(MEMORY_VSIZE, Vsize, UNITS_BYTES); +#if !defined(HAVE_64BIT_BUILD) || !defined(XP_WIN) + RECORD(MEMORY_VSIZE_MAX_CONTIGUOUS, VsizeMaxContiguous, UNITS_BYTES); +#endif + RECORD(MEMORY_RESIDENT_FAST, ResidentFast, UNITS_BYTES); + RECORD(MEMORY_RESIDENT_PEAK, ResidentPeak, UNITS_BYTES); +// Although we can measure unique memory on MacOS we choose not to, because +// doing so is too slow for telemetry. +#ifndef XP_MACOSX + RECORD(MEMORY_UNIQUE, ResidentUnique, UNITS_BYTES); +#endif + +#ifdef HAVE_JEMALLOC_STATS + jemalloc_stats_t stats; + jemalloc_stats(&stats); + HandleMemoryReport(Telemetry::MEMORY_HEAP_ALLOCATED, + nsIMemoryReporter::UNITS_BYTES, + mgr->HeapAllocated(stats)); + HandleMemoryReport(Telemetry::MEMORY_HEAP_OVERHEAD_FRACTION, + nsIMemoryReporter::UNITS_PERCENTAGE, + mgr->HeapOverheadFraction(stats)); +#endif + + if (completionRunnable) { + NS_DispatchToMainThread(completionRunnable.forget(), + NS_DISPATCH_NORMAL); + } + }); + +#undef RECORD + + nsresult rv = mThreadPool->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); + if (!NS_WARN_IF(NS_FAILED(rv))) { + cleanup.release(); + } + + return NS_OK; +} + +namespace { +struct ChildProcessInfo { + GeckoProcessType mType; +#if defined(XP_WIN) + HANDLE mHandle; +#elif defined(XP_MACOSX) + task_t mHandle; +#else + pid_t mHandle; +#endif +}; +} // namespace + +/** + * Runs a task on the background thread pool to fetch the memory usage of all + * processes. + */ +void MemoryTelemetry::GatherTotalMemory() { + MOZ_ASSERT(!mGatheringTotalMemory); + mGatheringTotalMemory = true; + + nsTArray infos; + mozilla::ipc::GeckoChildProcessHost::GetAll( + [&](mozilla::ipc::GeckoChildProcessHost* aGeckoProcess) { + if (!aGeckoProcess->GetChildProcessHandle()) { + return; + } + + ChildProcessInfo info{}; + info.mType = aGeckoProcess->GetProcessType(); + + // NOTE: For now we ignore non-content processes here for compatibility + // with the existing probe. We may want to introduce a new probe in the + // future which also collects data for non-content processes. + if (info.mType != GeckoProcessType_Content) { + return; + } + +#if defined(XP_WIN) + if (!::DuplicateHandle(::GetCurrentProcess(), + aGeckoProcess->GetChildProcessHandle(), + ::GetCurrentProcess(), &info.mHandle, 0, false, + DUPLICATE_SAME_ACCESS)) { + return; + } +#elif defined(XP_MACOSX) + info.mHandle = aGeckoProcess->GetChildTask(); + if (mach_port_mod_refs(mach_task_self(), info.mHandle, + MACH_PORT_RIGHT_SEND, 1) != KERN_SUCCESS) { + return; + } +#else + info.mHandle = aGeckoProcess->GetChildProcessId(); +#endif + + infos.AppendElement(info); + }); + + mThreadPool->Dispatch(NS_NewRunnableFunction( + "MemoryTelemetry::GatherTotalMemory", [infos = std::move(infos)] { + RefPtr mgr = + nsMemoryReporterManager::GetOrCreate(); + MOZ_RELEASE_ASSERT(mgr); + + int64_t totalMemory = mgr->ResidentFast(); + nsTArray childSizes(infos.Length()); + + // Use our handle for the remote process to collect resident unique set + // size information for that process. + for (const auto& info : infos) { +#ifdef XP_MACOSX + int64_t memory = + nsMemoryReporterManager::PhysicalFootprint(info.mHandle); +#else + int64_t memory = + nsMemoryReporterManager::ResidentUnique(info.mHandle); +#endif + if (memory > 0) { + childSizes.AppendElement(memory); + totalMemory += memory; + } + +#if defined(XP_WIN) + ::CloseHandle(info.mHandle); +#elif defined(XP_MACOSX) + mach_port_deallocate(mach_task_self(), info.mHandle); +#endif + } + + NS_DispatchToMainThread(NS_NewRunnableFunction( + "MemoryTelemetry::FinishGatheringTotalMemory", + [totalMemory, childSizes = std::move(childSizes)] { + MemoryTelemetry::Get().FinishGatheringTotalMemory(totalMemory, + childSizes); + })); + })); +} + +nsresult MemoryTelemetry::FinishGatheringTotalMemory( + int64_t aTotalMemory, const nsTArray& aChildSizes) { + mGatheringTotalMemory = false; + + // Total memory usage can be difficult to measure both accurately and fast + // enough for telemetry (iterating memory maps can jank whole processes on + // MacOS). Therefore this shouldn't be relied on as an absolute measurement + // especially on MacOS where it double-counts shared memory. For a more + // detailed explaination see: + // https://groups.google.com/a/mozilla.org/g/dev-platform/c/WGNOtjHdsdA + HandleMemoryReport(Telemetry::MEMORY_TOTAL, nsIMemoryReporter::UNITS_BYTES, + aTotalMemory); + + if (aChildSizes.Length() > 1) { + int32_t tabsCount; + MOZ_TRY_VAR(tabsCount, GetOpenTabsCount()); + + nsCString key; + if (tabsCount <= 10) { + key = "0 - 10 tabs"; + } else if (tabsCount <= 500) { + key = "11 - 500 tabs"; + } else { + key = "more tabs"; + } + + // Mean of the USS of all the content processes. + int64_t mean = 0; + for (auto size : aChildSizes) { + mean += size; + } + mean /= aChildSizes.Length(); + + // For some users, for unknown reasons (though most likely because they're + // in a sandbox without procfs mounted), we wind up with 0 here, which + // triggers a floating point exception if we try to calculate values using + // it. + if (!mean) { + return NS_ERROR_UNEXPECTED; + } + + // Absolute error of USS for each content process, normalized by the mean + // (*100 to get it in percentage). 20% means for a content process that it + // is using 20% more or 20% less than the mean. + for (auto size : aChildSizes) { + int64_t diff = llabs(size - mean) * 100 / mean; + + HandleMemoryReport(Telemetry::MEMORY_DISTRIBUTION_AMONG_CONTENT, + nsIMemoryReporter::UNITS_COUNT, diff, key); + } + } + + // This notification is for testing only. + if (nsCOMPtr obs = services::GetObserverService()) { + obs->NotifyObservers(nullptr, "gather-memory-telemetry-finished", nullptr); + } + + return NS_OK; +} + +/* static */ Result MemoryTelemetry::GetOpenTabsCount() { + nsresult rv; + + nsCOMPtr windowMediator( + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv)); + MOZ_TRY(rv); + + nsCOMPtr enumerator; + MOZ_TRY(windowMediator->GetEnumerator(u"navigator:browser", + getter_AddRefs(enumerator))); + + uint32_t total = 0; + for (auto& window : SimpleEnumerator(enumerator)) { + nsCOMPtr browserWin; + MOZ_TRY(window->GetBrowserDOMWindow(getter_AddRefs(browserWin))); + + NS_ENSURE_TRUE(browserWin, Err(NS_ERROR_UNEXPECTED)); + + uint32_t tabCount; + MOZ_TRY(browserWin->GetTabCount(&tabCount)); + total += tabCount; + } + + return total; +} + +nsresult MemoryTelemetry::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (strcmp(aTopic, kTopicCycleCollectorBegin) == 0) { + auto now = TimeStamp::Now(); + if (!mLastPoll.IsNull() && + (now - mLastPoll).ToMilliseconds() < kTelemetryInterval) { + return NS_OK; + } + + mLastPoll = now; + + NS_DispatchToCurrentThreadQueue( + NewRunnableMethod>( + "MemoryTelemetry::GatherReports", this, + &MemoryTelemetry::GatherReports, nullptr), + EventQueuePriority::Idle); + } else if (strcmp(aTopic, "content-child-shutdown") == 0) { + if (nsCOMPtr telemetry = + do_GetService("@mozilla.org/base/telemetry;1")) { + telemetry->FlushBatchedChildTelemetry(); + } + } + return NS_OK; +} diff --git a/xpcom/base/MemoryTelemetry.h b/xpcom/base/MemoryTelemetry.h new file mode 100644 index 0000000000..b7c7fe8ad6 --- /dev/null +++ b/xpcom/base/MemoryTelemetry.h @@ -0,0 +1,72 @@ +/* -*- 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/. */ + +#ifndef mozilla_MemoryTelemetry_h +#define mozilla_MemoryTelemetry_h + +#include "mozilla/TimeStamp.h" +#include "mozilla/Result.h" +#include "nsIObserver.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "nsWeakReference.h" + +#include + +class nsIEventTarget; + +namespace mozilla { + +namespace ipc { +enum class ResponseRejectReason; +} + +/** + * Periodically gathers memory usage metrics after cycle collection, and + * populates telemetry histograms with their values. + */ +class MemoryTelemetry final : public nsIObserver, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + static MemoryTelemetry& Get(); + + nsresult GatherReports( + const std::function& aCompletionCallback = nullptr); + + /** + * Does expensive initialization, which should happen only after startup has + * completed, and the event loop is idle. + */ + nsresult DelayedInit(); + + nsresult Shutdown(); + + private: + MemoryTelemetry(); + + ~MemoryTelemetry() = default; + + void Init(); + + static Result GetOpenTabsCount(); + + void GatherTotalMemory(); + nsresult FinishGatheringTotalMemory(int64_t aTotalMemory, + const nsTArray& aChildSizes); + + nsCOMPtr mThreadPool; + + bool mGatheringTotalMemory = false; + + TimeStamp mLastPoll{}; +}; + +} // namespace mozilla + +#endif // defined mozilla_MemoryTelemetry_h diff --git a/xpcom/base/NSPRLogModulesParser.cpp b/xpcom/base/NSPRLogModulesParser.cpp new file mode 100644 index 0000000000..44fb50dbc7 --- /dev/null +++ b/xpcom/base/NSPRLogModulesParser.cpp @@ -0,0 +1,66 @@ +/* -*- 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 "NSPRLogModulesParser.h" + +#include "mozilla/Tokenizer.h" + +const char kDelimiters[] = ", "; +const char kAdditionalWordChars[] = "_-.*"; + +namespace mozilla { + +void NSPRLogModulesParser( + const char* aLogModules, + const std::function& aCallback) { + if (!aLogModules) { + return; + } + + Tokenizer parser(aLogModules, kDelimiters, kAdditionalWordChars); + nsAutoCString moduleName; + + Tokenizer::Token rustModSep = + parser.AddCustomToken("::", Tokenizer::CASE_SENSITIVE); + + auto readModuleName = [&](nsAutoCString& moduleName) -> bool { + moduleName.Truncate(); + nsDependentCSubstring sub; + parser.Record(); + // If the name doesn't include at least one word, we've reached the end. + if (!parser.ReadWord(sub)) { + return false; + } + // We will exit this loop if when any of the condition fails + while (parser.Check(rustModSep) && parser.ReadWord(sub)) { + } + // Claim will include characters of the last sucessfully read item + parser.Claim(moduleName, Tokenizer::INCLUDE_LAST); + return true; + }; + + // Format: LOG_MODULES="Foo:2,Bar, Baz:5,rust_crate::mod::file:5" + while (readModuleName(moduleName)) { + // Next should be :, default to Error if not provided. + LogLevel logLevel = LogLevel::Error; + int32_t levelValue = 0; + if (parser.CheckChar(':')) { + // NB: If a level isn't provided after the ':' we assume the default + // Error level is desired. This differs from NSPR which will stop + // processing the log module string in this case. + if (parser.ReadSignedInteger(&levelValue)) { + logLevel = ToLogLevel(levelValue); + } + } + + aCallback(moduleName.get(), logLevel, levelValue); + + // Skip ahead to the next token. + parser.SkipWhites(); + } +} + +} // namespace mozilla diff --git a/xpcom/base/NSPRLogModulesParser.h b/xpcom/base/NSPRLogModulesParser.h new file mode 100644 index 0000000000..2aa6571e65 --- /dev/null +++ b/xpcom/base/NSPRLogModulesParser.h @@ -0,0 +1,24 @@ +/* -*- 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/Logging.h" + +#include + +namespace mozilla { + +/** + * Helper function that parses the legacy NSPR_LOG_MODULES env var format + * for specifying log levels and logging options. + * + * @param aLogModules The log modules configuration string. + * @param aCallback The callback to invoke for each log module config entry. + */ +void NSPRLogModulesParser( + const char* aLogModules, + const std::function& aCallback); + +} // namespace mozilla diff --git a/xpcom/base/OwningNonNull.h b/xpcom/base/OwningNonNull.h new file mode 100644 index 0000000000..24c22f8f60 --- /dev/null +++ b/xpcom/base/OwningNonNull.h @@ -0,0 +1,213 @@ +/* -*- 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/. */ + +/* A class for non-null strong pointers to reference-counted objects. */ + +#ifndef mozilla_OwningNonNull_h +#define mozilla_OwningNonNull_h + +#include "nsCOMPtr.h" +#include "nsCycleCollectionNoteChild.h" + +namespace mozilla { + +// OwningNonNull is similar to a RefPtr, which is not null after initial +// initialization. It has a restricted interface compared to RefPtr, with some +// additional operations defined. The main use is in DOM bindings. Use it +// outside DOM bindings only if you can ensure it never escapes without being +// properly initialized, and you don't need to move it. Otherwise, use a +// RefPtr instead. +// +// Compared to a plain RefPtr, in particular +// - it is copyable but not movable +// - it can be constructed and assigned from T& and is convertible to T& +// implicitly +// - it cannot be cleared by the user once initialized, though it can be +// re-assigned a new (non-null) value +// - it is not convertible to bool, but there is an explicit isInitialized +// member function +// +// Beware that there are two cases where an OwningNonNull actually is nullptr +// - it was default-constructed and not yet initialized +// - it was cleared during CC unlinking. +// All attempts to use it in an invalid state will trigger an assertion in debug +// builds. +// +// The original intent of OwningNonNull was to implement a class with the +// same auto-conversion and annotation semantics as mozilla::dom::NonNull +// (i.e. never null once you have properly initialized it, auto-converts to T&), +// but that holds a strong reference to the object involved. This was designed +// for use in DOM bindings and in particular for storing what WebIDL represents +// as InterfaceName (as opposed to `InterfaceName?`) in various containers +// (dictionaries, sequences). DOM bindings never allow a default-constructed +// uninitialized OwningNonNull to escape. RefPtr could have been used for this +// use case, just like we could have used T* instead of NonNull, but it +// seemed desirable to explicitly annotate the non-null nature of the things +// involved to eliminate pointless null-checks, which otherwise tend to +// proliferate. +template +class MOZ_IS_SMARTPTR_TO_REFCOUNTED OwningNonNull { + public: + using element_type = T; + + OwningNonNull() = default; + + MOZ_IMPLICIT OwningNonNull(T& aValue) { init(&aValue); } + + template + MOZ_IMPLICIT OwningNonNull(already_AddRefed&& aValue) { + init(aValue); + } + + template + MOZ_IMPLICIT OwningNonNull(RefPtr&& aValue) { + init(std::move(aValue)); + } + + template + MOZ_IMPLICIT OwningNonNull(const OwningNonNull& aValue) { + init(aValue); + } + + // This is no worse than get() in terms of const handling. + operator T&() const { return ref(); } + + operator T*() const { return get(); } + + // Conversion to bool is always true, so delete to catch errors + explicit operator bool() const = delete; + + T* operator->() const { return get(); } + + T& operator*() const { return ref(); } + + OwningNonNull& operator=(T* aValue) { + init(aValue); + return *this; + } + + OwningNonNull& operator=(T& aValue) { + init(&aValue); + return *this; + } + + template + OwningNonNull& operator=(already_AddRefed&& aValue) { + init(aValue); + return *this; + } + + template + OwningNonNull& operator=(RefPtr&& aValue) { + init(std::move(aValue)); + return *this; + } + + template + OwningNonNull& operator=(const OwningNonNull& aValue) { + init(aValue); + return *this; + } + + // Don't allow assigning nullptr, it makes no sense + void operator=(decltype(nullptr)) = delete; + + T& ref() const { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull was set to null"); + return *mPtr; + } + + // Make us work with smart pointer helpers that expect a get(). + T* get() const { + MOZ_ASSERT(mInited); + MOZ_ASSERT(mPtr, "OwningNonNull was set to null"); + return mPtr; + } + + template + void swap(U& aOther) { + mPtr.swap(aOther); +#ifdef DEBUG + mInited = mPtr; +#endif + } + + // We have some consumers who want to check whether we're inited in non-debug + // builds as well. Luckily, we have the invariant that we're inited precisely + // when mPtr is non-null. + bool isInitialized() const { + MOZ_ASSERT(!!mPtr == mInited, "mInited out of sync with mPtr?"); + return mPtr; + } + + private: + void unlinkForCC() { +#ifdef DEBUG + mInited = false; +#endif + mPtr = nullptr; + } + + // Allow ImplCycleCollectionUnlink to call unlinkForCC(). + template + friend void ImplCycleCollectionUnlink(OwningNonNull& aField); + + protected: + template + void init(U&& aValue) { + mPtr = std::move(aValue); + MOZ_ASSERT(mPtr); +#ifdef DEBUG + mInited = true; +#endif + } + + RefPtr mPtr; +#ifdef DEBUG + bool mInited = false; +#endif +}; + +template +inline void ImplCycleCollectionUnlink(OwningNonNull& aField) { + aField.unlinkForCC(); +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, OwningNonNull& aField, + const char* aName, uint32_t aFlags = 0) { + CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags); +} + +} // namespace mozilla + +// Declared in nsCOMPtr.h +template +template +nsCOMPtr::nsCOMPtr(const mozilla::OwningNonNull& aOther) + : nsCOMPtr(aOther.get()) {} + +template +template +nsCOMPtr& nsCOMPtr::operator=(const mozilla::OwningNonNull& aOther) { + return operator=(aOther.get()); +} + +// Declared in mozilla/RefPtr.h +template +template +RefPtr::RefPtr(const mozilla::OwningNonNull& aOther) + : RefPtr(aOther.get()) {} + +template +template +RefPtr& RefPtr::operator=(const mozilla::OwningNonNull& aOther) { + return operator=(aOther.get()); +} + +#endif // mozilla_OwningNonNull_h diff --git a/xpcom/base/RLBoxSandboxPool.cpp b/xpcom/base/RLBoxSandboxPool.cpp new file mode 100644 index 0000000000..c3b86f1698 --- /dev/null +++ b/xpcom/base/RLBoxSandboxPool.cpp @@ -0,0 +1,111 @@ +/* -*- 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 "nsThreadUtils.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/RLBoxSandboxPool.h" +#ifdef MOZ_USING_WASM_SANDBOXING +# include "wasm2c_rt_mem.h" +#endif + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(RLBoxSandboxPool, nsITimerCallback, nsINamed) + +void RLBoxSandboxPool::StartTimer() { + mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!mTimer, "timer already initialized"); + if (NS_IsMainThread() && + PastShutdownPhase(ShutdownPhase::AppShutdownConfirmed)) { + // If we're shutting down, setting the time might fail, and we don't need it + // (since all the memory will be cleaned up soon anyway). Note that + // PastShutdownPhase() can only be called on the main thread, but that's + // fine, because other threads will have joined already by the point timers + // start failing to register. + mPool.Clear(); + return; + } + DebugOnly rv = NS_NewTimerWithCallback( + getter_AddRefs(mTimer), this, mDelaySeconds * 1000, + nsITimer::TYPE_ONE_SHOT, GetMainThreadSerialEventTarget()); + MOZ_ASSERT(NS_SUCCEEDED(rv), "failed to create timer"); +} + +void RLBoxSandboxPool::CancelTimer() { + mMutex.AssertCurrentThreadOwns(); + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } +} + +NS_IMETHODIMP RLBoxSandboxPool::Notify(nsITimer* aTimer) { + MutexAutoLock lock(mMutex); + + mPool.Clear(); + mTimer = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP RLBoxSandboxPool::GetName(nsACString& aName) { + aName.AssignLiteral("RLBoxSandboxPool"); + return NS_OK; +} + +void RLBoxSandboxPool::Push(UniquePtr sbxData) { + MutexAutoLock lock(mMutex); + + mPool.AppendElement(std::move(sbxData)); + if (!mTimer) { + StartTimer(); + } +} + +UniquePtr RLBoxSandboxPool::PopOrCreate( + uint64_t aMinSize) { + MutexAutoLock lock(mMutex); + + UniquePtr sbxData; + + if (!mPool.IsEmpty()) { + const int64_t lastIndex = ReleaseAssertedCast(mPool.Length()) - 1; + for (int64_t i = lastIndex; i >= 0; i--) { + if (mPool[i]->mSize >= aMinSize) { + sbxData = std::move(mPool[i]); + mPool.RemoveElementAt(i); + + // If we reuse a sandbox from the pool, reset the timer to clear the + // pool + CancelTimer(); + if (!mPool.IsEmpty()) { + StartTimer(); + } + break; + } + } + } + + if (!sbxData) { +#ifdef MOZ_USING_WASM_SANDBOXING + // RLBox's wasm sandboxes have a limited platform dependent capacity. We + // track this capacity in this pool. + const w2c_mem_capacity w2c_capacity = get_valid_wasm2c_memory_capacity( + aMinSize, true /* 32-bit wasm memory*/); + const uint64_t chosenCapacity = w2c_capacity.max_size; +#else + // Note the noop sandboxes have no capacity limit. In this case we simply + // specify a value of 4gb. This is not actually enforced by the noop + // sandbox. + const uint64_t chosenCapacity = static_cast(1) << 32; +#endif + sbxData = CreateSandboxData(chosenCapacity); + NS_ENSURE_TRUE(sbxData, nullptr); + } + + return MakeUnique(std::move(sbxData), this); +} diff --git a/xpcom/base/RLBoxSandboxPool.h b/xpcom/base/RLBoxSandboxPool.h new file mode 100644 index 0000000000..47f3893548 --- /dev/null +++ b/xpcom/base/RLBoxSandboxPool.h @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 20; 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/. */ + +#ifndef SECURITY_RLBOX_SANDBOX_POOL_H_ +#define SECURITY_RLBOX_SANDBOX_POOL_H_ + +#include "nsCOMPtr.h" +#include "nsITimer.h" +#include "nsTArray.h" +#include "nsINamed.h" + +#include "mozilla/Mutex.h" +#include "mozilla/rlbox/rlbox_types.hpp" + +namespace mozilla { + +class RLBoxSandboxDataBase; +class RLBoxSandboxPoolData; + +// The RLBoxSandboxPool class is used to manage a pool of sandboxes that are +// reused -- to save sandbox creation time and memory -- and automatically +// destroyed when no longer in used. The sandbox pool is threadsafe and can be +// used to share unused sandboxes across a thread pool. +// +// Each sandbox pool manages a particular kind of sandbox (e.g., expat +// sandboxes, woff2 sandboxes, etc.); this is largely because different +// sandboxes might have different callbacks and attacker assumptions. Hence, +// RLBoxSandboxPool is intended to be subclassed for the different kinds of +// sandbox pools. Each sandbox pool class needs to implement the +// CreateSandboxData() method, which returns a pointer to a RLBoxSandboxDataBase +// object. RLBoxSandboxDataBase itself should be subclassed to implement +// sandbox-specific details. +class RLBoxSandboxPool : public nsITimerCallback, public nsINamed { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + RLBoxSandboxPool(size_t aDelaySeconds = 10) + : mPool(), + mDelaySeconds(aDelaySeconds), + mMutex("RLBoxSandboxPool::mMutex"){}; + + void Push(UniquePtr sbx); + // PopOrCreate returns a sandbox from the pool if the pool is not empty and + // tries to mint a new one otherwise. If creating a new sandbox fails, the + // function returns a nullptr. The parameter aMinSize is the minimum size of + // the sandbox memory. + UniquePtr PopOrCreate(uint64_t aMinSize = 0); + + protected: + // CreateSandboxData takes a parameter which is the size of the sandbox memory + virtual UniquePtr CreateSandboxData(uint64_t aSize) = 0; + virtual ~RLBoxSandboxPool() = default; + + private: + void StartTimer() MOZ_REQUIRES(mMutex); + void CancelTimer() MOZ_REQUIRES(mMutex); + + nsTArray> mPool MOZ_GUARDED_BY(mMutex); + const size_t mDelaySeconds MOZ_GUARDED_BY(mMutex); + nsCOMPtr mTimer MOZ_GUARDED_BY(mMutex); + mozilla::Mutex mMutex; +}; + +// The RLBoxSandboxDataBase class serves as the subclass for all sandbox data +// classes, which keep track of the RLBox sandbox and any relevant sandbox data +// (e.g., callbacks). +class RLBoxSandboxDataBase { + public: + const uint64_t mSize; + explicit RLBoxSandboxDataBase(uint64_t aSize) : mSize(aSize) {} + virtual ~RLBoxSandboxDataBase() = default; +}; + +// This class is used wrap sandbox data objects (RLBoxSandboxDataBase) when they +// are popped from sandbox pools. The wrapper destructor pushes the sandbox back +// into the pool. +class RLBoxSandboxPoolData { + public: + RLBoxSandboxPoolData(UniquePtr aSbxData, + RefPtr aPool) { + mSbxData = std::move(aSbxData); + mPool = aPool; + MOZ_COUNT_CTOR(RLBoxSandboxPoolData); + } + + RLBoxSandboxDataBase* SandboxData() const { return mSbxData.get(); }; + + ~RLBoxSandboxPoolData() { + mPool->Push(std::move(mSbxData)); + MOZ_COUNT_DTOR(RLBoxSandboxPoolData); + }; + + private: + UniquePtr mSbxData; + RefPtr mPool; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/RLBoxUtils.h b/xpcom/base/RLBoxUtils.h new file mode 100644 index 0000000000..4a73affb63 --- /dev/null +++ b/xpcom/base/RLBoxUtils.h @@ -0,0 +1,70 @@ +/* -*- Mode: C++; tab-width: 20; 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/. */ + +#ifndef SECURITY_RLBOX_UTILS_H_ +#define SECURITY_RLBOX_UTILS_H_ + +#include "mozilla/rlbox/rlbox_types.hpp" + +namespace mozilla { + +/* The RLBoxTransferBufferToSandbox class is used to copy (or directly expose in + * the noop-sandbox case) buffers into the sandbox that are automatically freed + * when the RLBoxTransferBufferToSandbox is out of scope. NOTE: The sandbox + * lifetime must outlive all of its RLBoxTransferBufferToSandbox. + */ +template +class MOZ_STACK_CLASS RLBoxTransferBufferToSandbox { + public: + RLBoxTransferBufferToSandbox() = delete; + RLBoxTransferBufferToSandbox(rlbox::rlbox_sandbox* aSandbox, const T* aBuf, + const size_t aLen) + : mSandbox(aSandbox), mCopied(false), mBuf(nullptr) { + if (aBuf) { + mBuf = rlbox::copy_memory_or_grant_access(*mSandbox, aBuf, aLen, false, + mCopied); + } + }; + ~RLBoxTransferBufferToSandbox() { + if (mCopied) { + mSandbox->free_in_sandbox(mBuf); + } + }; + rlbox::tainted operator*() const { return mBuf; }; + + private: + rlbox::rlbox_sandbox* mSandbox; + bool mCopied; + rlbox::tainted mBuf; +}; + +/* The RLBoxAllocateInSandbox class is used to allocate data int sandbox that is + * automatically freed when the RLBoxAllocateInSandbox is out of scope. NOTE: + * The sandbox lifetime must outlive all of its RLBoxAllocateInSandbox'ations. + */ +template +class MOZ_STACK_CLASS RLBoxAllocateInSandbox { + public: + RLBoxAllocateInSandbox() = delete; + explicit RLBoxAllocateInSandbox(rlbox::rlbox_sandbox* aSandbox) + : mSandbox(aSandbox) { + mPtr = mSandbox->template malloc_in_sandbox(); + }; + ~RLBoxAllocateInSandbox() { + if (mPtr) { + mSandbox->free_in_sandbox(mPtr); + } + }; + rlbox::tainted get() const { return mPtr; }; + + private: + rlbox::rlbox_sandbox* mSandbox; + rlbox::tainted mPtr; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/ShutdownPhase.h b/xpcom/base/ShutdownPhase.h new file mode 100644 index 0000000000..ebe5ebd37e --- /dev/null +++ b/xpcom/base/ShutdownPhase.h @@ -0,0 +1,32 @@ +/* -*- 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/. */ + +#ifndef ShutdownPhase_h +#define ShutdownPhase_h + +namespace mozilla { + +// Must be contiguous starting at 0 +enum class ShutdownPhase { + NotInShutdown = 0, + AppShutdownConfirmed, + AppShutdownNetTeardown, + AppShutdownTeardown, + AppShutdown, + AppShutdownQM, + AppShutdownTelemetry, + XPCOMWillShutdown, + XPCOMShutdown, + XPCOMShutdownThreads, + XPCOMShutdownFinal, + CCPostLastCycleCollection, + ShutdownPhase_Length, // never pass this value + First = AppShutdownConfirmed // for iteration +}; + +} // namespace mozilla + +#endif // ShutdownPhase_h diff --git a/xpcom/base/SizeOfState.h b/xpcom/base/SizeOfState.h new file mode 100644 index 0000000000..2df0b24e93 --- /dev/null +++ b/xpcom/base/SizeOfState.h @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +#ifndef SizeOfState_h +#define SizeOfState_h + +#include "mozilla/fallible.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Unused.h" +#include "nsTHashtable.h" +#include "nsHashKeys.h" + +// This file includes types that are useful during memory reporting, but which +// cannot be put into mfbt/MemoryReporting.h because they depend on things that +// are not in MFBT. + +namespace mozilla { + +// A table of seen pointers. Useful when measuring structures that contain +// nodes that may be pointed to from multiple places, e.g. via RefPtr (in C++ +// code) or Arc (in Rust code). +class SeenPtrs : public nsTHashtable> { + public: + // Returns true if we have seen this pointer before, false otherwise. Also + // remembers this pointer for later queries. + bool HaveSeenPtr(const void* aPtr) { + uint32_t oldCount = Count(); + + mozilla::Unused << PutEntry(aPtr, fallible); + + // If the counts match, there are two possibilities. + // + // - Lookup succeeded: we've seen the pointer before, and didn't need to + // add a new entry. + // + // - PutEntry() tried to add the entry and failed due to lack of memory. In + // this case we can't tell if this pointer has been seen before (because + // the table is in an unreliable state and may have dropped previous + // insertions). When doing memory reporting it's better to err on the + // side of under-reporting rather than over-reporting, so we assume we've + // seen the pointer before. + // + return oldCount == Count(); + } +}; + +// Memory reporting state. Some memory measuring functions +// (SizeOfIncludingThis(), etc.) just need a MallocSizeOf parameter, but some +// also need a record of pointers that have been seen and should not be +// re-measured. This class encapsulates both of those things. +class SizeOfState { + public: + explicit SizeOfState(MallocSizeOf aMallocSizeOf) + : mMallocSizeOf(aMallocSizeOf) {} + + bool HaveSeenPtr(const void* aPtr) { return mSeenPtrs.HaveSeenPtr(aPtr); } + + MallocSizeOf mMallocSizeOf; + SeenPtrs mSeenPtrs; +}; + +} // namespace mozilla + +#endif // SizeOfState_h diff --git a/xpcom/base/StaticLocalPtr.h b/xpcom/base/StaticLocalPtr.h new file mode 100644 index 0000000000..2e2fc035bd --- /dev/null +++ b/xpcom/base/StaticLocalPtr.h @@ -0,0 +1,253 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_StaticLocalPtr_h +#define mozilla_StaticLocalPtr_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +/** + * StaticLocalAutoPtr and StaticLocalRefPtr are like UniquePtr and RefPtr, + * except they are suitable for use as "magic static" local variables -- that + * is, they are able to take advantage of C++11's guarantee of thread safety + * during initialization by atomically constructing both the smart pointer + * itself as well as the object being pointed to. + * + * A static local instance of StaticLocal{Auto,Ref}Ptr does not cause the + * compiler to emit any atexit calls. In order to accomplish this, + * StaticLocal{Auto,Ref}Ptr must have a trivial destructor. As a consequence, + * it does not delete/release its raw pointer upon destruction. + * + * The clang plugin, run as part of our "static analysis" builds, makes it a + * compile-time error to use StaticLocal{Auto,Ref}Ptr as anything except a + * static local variable. + * + * StaticLocal{Auto,Ref}Ptr have a limited interface as compared to + * ns{Auto,Ref}Ptr; this is intentional, since their range of acceptable uses is + * smaller. + */ + +template +class MOZ_STATIC_LOCAL_CLASS StaticLocalAutoPtr final { + public: + explicit StaticLocalAutoPtr(T* aRawPtr) : mRawPtr(aRawPtr) {} + + StaticLocalAutoPtr(StaticLocalAutoPtr&& aOther) : mRawPtr(aOther.mRawPtr) { + aOther.mRawPtr = nullptr; + } + + StaticLocalAutoPtr& operator=(T* aRhs) { + Assign(aRhs); + return *this; + } + + T* get() const { return mRawPtr; } + + operator T*() const { return get(); } + + T* operator->() const { + MOZ_ASSERT(mRawPtr); + return get(); + } + + T& operator*() const { return *get(); } + + T* forget() { + T* temp = mRawPtr; + mRawPtr = nullptr; + return temp; + } + + private: + StaticLocalAutoPtr(const StaticLocalAutoPtr& aOther) = delete; + + // We do not allow assignment as the intention of this class is to only + // assign to mRawPtr during construction. + StaticLocalAutoPtr& operator=(const StaticLocalAutoPtr& aOther) = delete; + StaticLocalAutoPtr& operator=(StaticLocalAutoPtr&&) = delete; + + void Assign(T* aNewPtr) { + MOZ_ASSERT(!aNewPtr || mRawPtr != aNewPtr); + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + delete oldPtr; + } + + T* mRawPtr; +}; + +template +class MOZ_STATIC_LOCAL_CLASS StaticLocalRefPtr final { + public: + explicit StaticLocalRefPtr(T* aRawPtr) : mRawPtr(nullptr) { + AssignWithAddref(aRawPtr); + } + + explicit StaticLocalRefPtr(already_AddRefed& aPtr) : mRawPtr(nullptr) { + AssignAssumingAddRef(aPtr.take()); + } + + explicit StaticLocalRefPtr(already_AddRefed&& aPtr) : mRawPtr(nullptr) { + AssignAssumingAddRef(aPtr.take()); + } + + StaticLocalRefPtr(const StaticLocalRefPtr& aPtr) + : StaticLocalRefPtr(aPtr.mRawPtr) {} + + StaticLocalRefPtr(StaticLocalRefPtr&& aPtr) : mRawPtr(aPtr.mRawPtr) { + aPtr.mRawPtr = nullptr; + } + + StaticLocalRefPtr& operator=(T* aRhs) { + AssignWithAddref(aRhs); + return *this; + } + + already_AddRefed forget() { + T* temp = mRawPtr; + mRawPtr = nullptr; + return already_AddRefed(temp); + } + + T* get() const { return mRawPtr; } + + operator T*() const { return get(); } + + T* operator->() const { + MOZ_ASSERT(mRawPtr); + return get(); + } + + T& operator*() const { return *get(); } + + private: + // We do not allow assignment as the intention of this class is to only + // assign to mRawPtr during construction. + StaticLocalRefPtr& operator=(const StaticLocalRefPtr& aRhs) = delete; + StaticLocalRefPtr& operator=(StaticLocalRefPtr&& aRhs) = delete; + + void AssignWithAddref(T* aNewPtr) { + if (aNewPtr) { + aNewPtr->AddRef(); + } + AssignAssumingAddRef(aNewPtr); + } + + void AssignAssumingAddRef(T* aNewPtr) { + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + if (oldPtr) { + oldPtr->Release(); + } + } + + T* MOZ_OWNING_REF mRawPtr; +}; + +namespace StaticLocalPtr_internal { +class Zero; +} // namespace StaticLocalPtr_internal + +#define REFLEXIVE_EQUALITY_OPERATORS(type1, type2, eq_fn, ...) \ + template <__VA_ARGS__> \ + inline bool operator==(type1 lhs, type2 rhs) { \ + return eq_fn; \ + } \ + \ + template <__VA_ARGS__> \ + inline bool operator==(type2 lhs, type1 rhs) { \ + return rhs == lhs; \ + } \ + \ + template <__VA_ARGS__> \ + inline bool operator!=(type1 lhs, type2 rhs) { \ + return !(lhs == rhs); \ + } \ + \ + template <__VA_ARGS__> \ + inline bool operator!=(type2 lhs, type1 rhs) { \ + return !(lhs == rhs); \ + } + +// StaticLocalAutoPtr (in)equality operators + +template +inline bool operator==(const StaticLocalAutoPtr& aLhs, + const StaticLocalAutoPtr& aRhs) { + return aLhs.get() == aRhs.get(); +} + +template +inline bool operator!=(const StaticLocalAutoPtr& aLhs, + const StaticLocalAutoPtr& aRhs) { + return !(aLhs == aRhs); +} + +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalAutoPtr&, const U*, + lhs.get() == rhs, class T, class U) + +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalAutoPtr&, U*, lhs.get() == rhs, + class T, class U) + +// Let us compare StaticLocalAutoPtr to 0. +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalAutoPtr&, + StaticLocalPtr_internal::Zero*, + lhs.get() == nullptr, class T) + +// StaticLocalRefPtr (in)equality operators + +template +inline bool operator==(const StaticLocalRefPtr& aLhs, + const StaticLocalRefPtr& aRhs) { + return aLhs.get() == aRhs.get(); +} + +template +inline bool operator!=(const StaticLocalRefPtr& aLhs, + const StaticLocalRefPtr& aRhs) { + return !(aLhs == aRhs); +} + +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalRefPtr&, const U*, + lhs.get() == rhs, class T, class U) + +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalRefPtr&, U*, lhs.get() == rhs, + class T, class U) + +// Let us compare StaticLocalRefPtr to 0. +REFLEXIVE_EQUALITY_OPERATORS(const StaticLocalRefPtr&, + StaticLocalPtr_internal::Zero*, + lhs.get() == nullptr, class T) + +#undef REFLEXIVE_EQUALITY_OPERATORS + +} // namespace mozilla + +// Declared in mozilla/RefPtr.h +template +template +RefPtr::RefPtr(const mozilla::StaticLocalRefPtr& aOther) + : RefPtr(aOther.get()) {} + +template +template +RefPtr& RefPtr::operator=(const mozilla::StaticLocalRefPtr& aOther) { + return operator=(aOther.get()); +} + +template +inline already_AddRefed do_AddRef( + const mozilla::StaticLocalRefPtr& aObj) { + RefPtr ref(aObj); + return ref.forget(); +} + +#endif // mozilla_StaticLocalPtr_h diff --git a/xpcom/base/StaticMonitor.h b/xpcom/base/StaticMonitor.h new file mode 100644 index 0000000000..b35d3871d2 --- /dev/null +++ b/xpcom/base/StaticMonitor.h @@ -0,0 +1,117 @@ +/* -*- 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/. */ + +#ifndef mozilla_StaticMonitor_h +#define mozilla_StaticMonitor_h + +#include "mozilla/Atomics.h" +#include "mozilla/CondVar.h" +#include "mozilla/ThreadSafety.h" + +namespace mozilla { + +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS MOZ_CAPABILITY("monitor") + StaticMonitor { + public: + // In debug builds, check that mMutex is initialized for us as we expect by + // the compiler. In non-debug builds, don't declare a constructor so that + // the compiler can see that the constructor is trivial. +#ifdef DEBUG + StaticMonitor() { MOZ_ASSERT(!mMutex); } +#endif + + void Lock() MOZ_CAPABILITY_ACQUIRE() { Mutex()->Lock(); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { Mutex()->Unlock(); } + + void Wait() { CondVar()->Wait(); } + CVStatus Wait(TimeDuration aDuration) { + AssertCurrentThreadOwns(); + return CondVar()->Wait(aDuration); + } + + void Notify() { CondVar()->Notify(); } + void NotifyAll() { CondVar()->NotifyAll(); } + + void AssertCurrentThreadOwns() MOZ_ASSERT_CAPABILITY(this) { +#ifdef DEBUG + Mutex()->AssertCurrentThreadOwns(); +#endif + } + + private: + OffTheBooksMutex* Mutex() { + if (mMutex) { + return mMutex; + } + + OffTheBooksMutex* mutex = new OffTheBooksMutex("StaticMutex"); + if (!mMutex.compareExchange(nullptr, mutex)) { + delete mutex; + } + + return mMutex; + } + + OffTheBooksCondVar* CondVar() { + if (mCondVar) { + return mCondVar; + } + + OffTheBooksCondVar* condvar = + new OffTheBooksCondVar(*Mutex(), "StaticCondVar"); + if (!mCondVar.compareExchange(nullptr, condvar)) { + delete condvar; + } + + return mCondVar; + } + + Atomic mMutex; + Atomic mCondVar; + + // Disallow copy constructor, but only in debug mode. We only define + // a default constructor in debug mode (see above); if we declared + // this constructor always, the compiler wouldn't generate a trivial + // default constructor for us in non-debug mode. +#ifdef DEBUG + StaticMonitor(const StaticMonitor& aOther); +#endif + + // Disallow these operators. + StaticMonitor& operator=(const StaticMonitor& aRhs); + static void* operator new(size_t) noexcept(true); + static void operator delete(void*); +}; + +class MOZ_STACK_CLASS MOZ_SCOPED_CAPABILITY StaticMonitorAutoLock { + public: + explicit StaticMonitorAutoLock(StaticMonitor& aMonitor) + MOZ_CAPABILITY_ACQUIRE(aMonitor) + : mMonitor(&aMonitor) { + mMonitor->Lock(); + } + + ~StaticMonitorAutoLock() MOZ_CAPABILITY_RELEASE() { mMonitor->Unlock(); } + + void Wait() { mMonitor->Wait(); } + CVStatus Wait(TimeDuration aDuration) { return mMonitor->Wait(aDuration); } + + void Notify() { mMonitor->Notify(); } + void NotifyAll() { mMonitor->NotifyAll(); } + + private: + StaticMonitorAutoLock(); + StaticMonitorAutoLock(const StaticMonitorAutoLock&); + StaticMonitorAutoLock& operator=(const StaticMonitorAutoLock&); + static void* operator new(size_t) noexcept(true); + + StaticMonitor* mMonitor; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/StaticMutex.h b/xpcom/base/StaticMutex.h new file mode 100644 index 0000000000..08c0288486 --- /dev/null +++ b/xpcom/base/StaticMutex.h @@ -0,0 +1,84 @@ +/* -*- 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/. */ + +#ifndef mozilla_StaticMutex_h +#define mozilla_StaticMutex_h + +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" + +namespace mozilla { + +/** + * StaticMutex is a Mutex that can (and in fact, must) be used as a + * global/static variable. + * + * The main reason to use StaticMutex as opposed to + * StaticAutoPtr is that we instantiate the StaticMutex in a + * thread-safe manner the first time it's used. + * + * The same caveats that apply to StaticAutoPtr apply to StaticMutex. In + * particular, do not use StaticMutex as a stack variable or a class instance + * variable, because this class relies on the fact that global variablies are + * initialized to 0 in order to initialize mMutex. It is only safe to use + * StaticMutex as a global or static variable. + */ +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS MOZ_CAPABILITY("mutex") + StaticMutex { + public: + // In debug builds, check that mMutex is initialized for us as we expect by + // the compiler. In non-debug builds, don't declare a constructor so that + // the compiler can see that the constructor is trivial. +#ifdef DEBUG + StaticMutex() { MOZ_ASSERT(!mMutex); } +#endif + + void Lock() MOZ_CAPABILITY_ACQUIRE() { Mutex()->Lock(); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { Mutex()->Unlock(); } + + void AssertCurrentThreadOwns() MOZ_ASSERT_CAPABILITY(this) { +#ifdef DEBUG + Mutex()->AssertCurrentThreadOwns(); +#endif + } + + private: + OffTheBooksMutex* Mutex() { + if (mMutex) { + return mMutex; + } + + OffTheBooksMutex* mutex = new OffTheBooksMutex("StaticMutex"); + if (!mMutex.compareExchange(nullptr, mutex)) { + delete mutex; + } + + return mMutex; + } + + Atomic mMutex; + + // Disallow copy constructor, but only in debug mode. We only define + // a default constructor in debug mode (see above); if we declared + // this constructor always, the compiler wouldn't generate a trivial + // default constructor for us in non-debug mode. +#ifdef DEBUG + StaticMutex(StaticMutex& aOther); +#endif + + // Disallow these operators. + StaticMutex& operator=(StaticMutex* aRhs); + static void* operator new(size_t) noexcept(true); + static void operator delete(void*); +}; + +typedef detail::BaseAutoLock StaticMutexAutoLock; +typedef detail::BaseAutoUnlock StaticMutexAutoUnlock; + +} // namespace mozilla + +#endif diff --git a/xpcom/base/StaticPtr.h b/xpcom/base/StaticPtr.h new file mode 100644 index 0000000000..38fcd82f97 --- /dev/null +++ b/xpcom/base/StaticPtr.h @@ -0,0 +1,235 @@ +/* -*- 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/. */ + +#ifndef mozilla_StaticPtr_h +#define mozilla_StaticPtr_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +/** + * StaticAutoPtr and StaticRefPtr are like UniquePtr and RefPtr, except they + * are suitable for use as global variables. + * + * In particular, a global instance of Static{Auto,Ref}Ptr doesn't cause the + * compiler to emit a static initializer. + * + * Since the compiler guarantees that all global variables are initialized to + * 0, the default constexpr constructors will result in no actual code being + * generated. Since we rely on this, the clang plugin, run as part of our + * "static analysis" builds, makes it a compile-time error to use + * Static{Auto,Ref}Ptr as anything except a global variable. + * + * Static{Auto,Ref}Ptr have a limited interface as compared to ns{Auto,Ref}Ptr; + * this is intentional, since their range of acceptable uses is smaller. + */ + +template +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticAutoPtr { + public: + constexpr StaticAutoPtr() = default; + StaticAutoPtr(const StaticAutoPtr&) = delete; + + StaticAutoPtr& operator=(T* aRhs) { + Assign(aRhs); + return *this; + } + + T* get() const { return mRawPtr; } + + operator T*() const { return get(); } + + T* operator->() const { + MOZ_ASSERT(mRawPtr); + return get(); + } + + T& operator*() const { return *get(); } + + T* forget() { + T* temp = mRawPtr; + mRawPtr = nullptr; + return temp; + } + + private: + void Assign(T* aNewPtr) { + MOZ_ASSERT(!aNewPtr || mRawPtr != aNewPtr); + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + delete oldPtr; + } + + T* mRawPtr = nullptr; +}; + +template +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS StaticRefPtr { + public: + constexpr StaticRefPtr() = default; + StaticRefPtr(const StaticRefPtr&) = delete; + + StaticRefPtr& operator=(T* aRhs) { + AssignWithAddref(aRhs); + return *this; + } + + StaticRefPtr& operator=(const StaticRefPtr& aRhs) { + return (this = aRhs.mRawPtr); + } + + StaticRefPtr& operator=(already_AddRefed& aRhs) { + AssignAssumingAddRef(aRhs.take()); + return *this; + } + + template + StaticRefPtr& operator=(RefPtr&& aRhs) { + AssignAssumingAddRef(aRhs.forget().take()); + return *this; + } + + StaticRefPtr& operator=(already_AddRefed&& aRhs) { + AssignAssumingAddRef(aRhs.take()); + return *this; + } + + already_AddRefed forget() { + T* temp = mRawPtr; + mRawPtr = nullptr; + return already_AddRefed(temp); + } + + T* get() const { return mRawPtr; } + + operator T*() const { return get(); } + + T* operator->() const { + MOZ_ASSERT(mRawPtr); + return get(); + } + + T& operator*() const { return *get(); } + + private: + void AssignWithAddref(T* aNewPtr) { + if (aNewPtr) { + RefPtrTraits::AddRef(aNewPtr); + } + AssignAssumingAddRef(aNewPtr); + } + + void AssignAssumingAddRef(T* aNewPtr) { + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + if (oldPtr) { + RefPtrTraits::Release(oldPtr); + } + } + + T* MOZ_OWNING_REF mRawPtr = nullptr; +}; + +namespace StaticPtr_internal { +class Zero; +} // namespace StaticPtr_internal + +#define REFLEXIVE_EQUALITY_OPERATORS(type1, type2, eq_fn, ...) \ + template <__VA_ARGS__> \ + inline bool operator==(type1 lhs, type2 rhs) { \ + return eq_fn; \ + } \ + \ + template <__VA_ARGS__> \ + inline bool operator==(type2 lhs, type1 rhs) { \ + return rhs == lhs; \ + } \ + \ + template <__VA_ARGS__> \ + inline bool operator!=(type1 lhs, type2 rhs) { \ + return !(lhs == rhs); \ + } \ + \ + template <__VA_ARGS__> \ + inline bool operator!=(type2 lhs, type1 rhs) { \ + return !(lhs == rhs); \ + } + +// StaticAutoPtr (in)equality operators + +template +inline bool operator==(const StaticAutoPtr& aLhs, + const StaticAutoPtr& aRhs) { + return aLhs.get() == aRhs.get(); +} + +template +inline bool operator!=(const StaticAutoPtr& aLhs, + const StaticAutoPtr& aRhs) { + return !(aLhs == aRhs); +} + +REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr&, const U*, + lhs.get() == rhs, class T, class U) + +REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr&, U*, lhs.get() == rhs, + class T, class U) + +// Let us compare StaticAutoPtr to 0. +REFLEXIVE_EQUALITY_OPERATORS(const StaticAutoPtr&, StaticPtr_internal::Zero*, + lhs.get() == nullptr, class T) + +// StaticRefPtr (in)equality operators + +template +inline bool operator==(const StaticRefPtr& aLhs, + const StaticRefPtr& aRhs) { + return aLhs.get() == aRhs.get(); +} + +template +inline bool operator!=(const StaticRefPtr& aLhs, + const StaticRefPtr& aRhs) { + return !(aLhs == aRhs); +} + +REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr&, const U*, lhs.get() == rhs, + class T, class U) + +REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr&, U*, lhs.get() == rhs, + class T, class U) + +// Let us compare StaticRefPtr to 0. +REFLEXIVE_EQUALITY_OPERATORS(const StaticRefPtr&, StaticPtr_internal::Zero*, + lhs.get() == nullptr, class T) + +#undef REFLEXIVE_EQUALITY_OPERATORS + +} // namespace mozilla + +// Declared in mozilla/RefPtr.h +template +template +RefPtr::RefPtr(const mozilla::StaticRefPtr& aOther) + : RefPtr(aOther.get()) {} + +template +template +RefPtr& RefPtr::operator=(const mozilla::StaticRefPtr& aOther) { + return operator=(aOther.get()); +} + +template +inline already_AddRefed do_AddRef(const mozilla::StaticRefPtr& aObj) { + RefPtr ref(aObj); + return ref.forget(); +} + +#endif diff --git a/xpcom/base/components.conf b/xpcom/base/components.conf new file mode 100644 index 0000000000..40c78b033c --- /dev/null +++ b/xpcom/base/components.conf @@ -0,0 +1,30 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{cb6cdb94-e417-4601-b4a5-f991bf41453d}', + 'contract_ids': ['@mozilla.org/xpcom/debug;1'], + 'legacy_constructor': 'nsDebugImpl::Create', + 'headers': ['nsDebugImpl.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{67b3ac0c-d806-4d48-939e-6a819e6c248f}', + 'contract_ids': ['@mozilla.org/message-loop;1'], + 'legacy_constructor': 'nsMessageLoopConstructor', + 'headers': ['/xpcom/base/nsMessageLoop.h'], + }, + { + 'cid': '{68bf4793-5204-45cf-9ee2-69adffbc2e38}', + 'contract_ids': ['@mozilla.org/xpcom/memory-watcher;1'], + 'singleton': True, + 'type': 'mozilla::nsAvailableMemoryWatcherBase', + 'headers': ['/xpcom/base/AvailableMemoryWatcher.h'], + 'constructor': 'mozilla::nsAvailableMemoryWatcherBase::GetSingleton', + 'processes': ProcessSelector.MAIN_PROCESS_ONLY, + }, +] diff --git a/xpcom/base/moz.build b/xpcom/base/moz.build new file mode 100644 index 0000000000..f685758b6f --- /dev/null +++ b/xpcom/base/moz.build @@ -0,0 +1,261 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + "nsIAvailableMemoryWatcherBase.idl", + "nsIConsoleListener.idl", + "nsIConsoleMessage.idl", + "nsIConsoleService.idl", + "nsICycleCollectorListener.idl", + "nsIDebug2.idl", + "nsIException.idl", + "nsIInterfaceRequestor.idl", + "nsIMemoryInfoDumper.idl", + "nsIMemoryReporter.idl", + "nsIMessageLoop.idl", + "nsISecurityConsoleMessage.idl", + "nsISupports.idl", + "nsIUUIDGenerator.idl", + "nsIVersionComparator.idl", + "nsIWeakReference.idl", + "nsrootidl.idl", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + XPIDL_SOURCES += [ + "nsIMacPreferencesReader.idl", + ] + EXPORTS += [ + "nsObjCExceptions.h", + ] + EXPORTS.mozilla += [ + "MacHelpers.h", + "MacStringHelpers.h", + "nsMacPreferencesReader.h", + ] + UNIFIED_SOURCES += [ + "MacHelpers.mm", + "MacStringHelpers.mm", + "nsMacPreferencesReader.mm", + "nsObjCExceptions.mm", + ] + +XPIDL_MODULE = "xpcom_base" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "!ErrorList.h", + "!ErrorNamesInternal.h", + "CodeAddressService.h", + "nsAlgorithm.h", + "nsAutoRef.h", + "nsCom.h", + "nsCOMPtr.h", + "nscore.h", + "nsCRTGlue.h", + "nsCycleCollectionNoteChild.h", + "nsCycleCollectionNoteRootCallback.h", + "nsCycleCollectionParticipant.h", + "nsCycleCollectionTraversalCallback.h", + "nsCycleCollector.h", + "nsDebug.h", + "nsDebugImpl.h", + "nsDumpUtils.h", + "nsError.h", + "nsGZFileWriter.h", + "nsIClassInfoImpl.h", + "nsID.h", + "nsIDUtils.h", + "nsIInterfaceRequestorUtils.h", + "nsINIParser.h", + "nsInterfaceRequestorAgg.h", + "nsISizeOf.h", + "nsISupportsImpl.h", + "nsISupportsUtils.h", + "nsIWeakReferenceUtils.h", + "nsMaybeWeakPtr.h", + "nsMemory.h", + "nsMemoryReporterManager.h", + "nsQueryObject.h", + "nsSystemInfo.h", + "nsTraceRefcnt.h", + "nsVersionComparator.h", + "nsWeakReference.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS += [ + "nsWindowsHelpers.h", + ] + if CONFIG["CC_TYPE"] not in ("gcc", "clang"): + OS_LIBS += [ + "wscapi", + ] + +EXPORTS.mozilla += [ + "AppShutdown.h", + "AutoRestore.h", + "AvailableMemoryTracker.h", + "AvailableMemoryWatcher.h", + "ClearOnShutdown.h", + "CountingAllocatorBase.h", + "CycleCollectedJSContext.h", + "CycleCollectedJSRuntime.h", + "Debug.h", + "DebuggerOnGCRunnable.h", + "DeferredFinalize.h", + "EnumeratedArrayCycleCollection.h", + "ErrorNames.h", + "GkRustUtils.h", + "HoldDropJSObjects.h", + "IntentionalCrash.h", + "JSObjectHolder.h", + "JSONStringWriteFuncs.h", + "Logging.h", + "MemoryInfo.h", + "MemoryMapping.h", + "MemoryReportingProcess.h", + "MemoryTelemetry.h", + "nsMemoryInfoDumper.h", + "NSPRLogModulesParser.h", + "OwningNonNull.h", + "RLBoxSandboxPool.h", + "RLBoxUtils.h", + "ShutdownPhase.h", + "SizeOfState.h", + "StaticLocalPtr.h", + "StaticMonitor.h", + "StaticMutex.h", + "StaticPtr.h", +] + +SOURCES += [ + # nsDebugImpl isn't unified because we disable PGO so that NS_ABORT_OOM isn't + # optimized away oddly. + "nsDebugImpl.cpp", + # nsDumpUtils.cpp includes SpecialSystemDirectory.h which includes + # nsLocalFileMac.h which upsets other files in this dir that have a different + # idea about what `TextRange` means. + "nsDumpUtils.cpp", +] +SOURCES["nsDebugImpl.cpp"].no_pgo = True + +UNIFIED_SOURCES += [ + "AppShutdown.cpp", + "AvailableMemoryTracker.cpp", + "AvailableMemoryWatcher.cpp", + "ClearOnShutdown.cpp", + "CycleCollectedJSContext.cpp", + "CycleCollectedJSRuntime.cpp", + "Debug.cpp", + "DebuggerOnGCRunnable.cpp", + "DeferredFinalize.cpp", + "ErrorNames.cpp", + "GkRustUtils.cpp", + "HoldDropJSObjects.cpp", + "JSObjectHolder.cpp", + "LogCommandLineHandler.cpp", + "Logging.cpp", + "LogModulePrefWatcher.cpp", + "MemoryTelemetry.cpp", + "nsClassInfoImpl.cpp", + "nsCOMPtr.cpp", + "nsConsoleMessage.cpp", + "nsConsoleService.cpp", + "nsCRTGlue.cpp", + "nsCycleCollectionParticipant.cpp", + "nsCycleCollector.cpp", + "nsCycleCollectorTraceJSHelpers.cpp", + "nsGZFileWriter.cpp", + "nsID.cpp", + "nsIInterfaceRequestorUtils.cpp", + "nsINIParser.cpp", + "nsInterfaceRequestorAgg.cpp", + "nsISupportsImpl.cpp", + "nsMemoryImpl.cpp", + "nsMemoryInfoDumper.cpp", + "nsMemoryReporterManager.cpp", + "nsMessageLoop.cpp", + "NSPRLogModulesParser.cpp", + "nsSecurityConsoleMessage.cpp", + "nsSystemInfo.cpp", + "nsTraceRefcnt.cpp", + "nsUUIDGenerator.cpp", + "nsVersionComparator.cpp", + "nsVersionComparatorImpl.cpp", + "nsWeakReference.cpp", + "RLBoxSandboxPool.cpp", +] + +if CONFIG["OS_TARGET"] in ("Linux", "Android"): + UNIFIED_SOURCES += [ + "MemoryMapping.cpp", + ] + +if CONFIG["OS_TARGET"] == "WINNT": + UNIFIED_SOURCES += [ + "AvailableMemoryWatcherWin.cpp", + "MemoryInfo.cpp", + ] + +if CONFIG["OS_TARGET"] == "Darwin": + UNIFIED_SOURCES += [ + "AvailableMemoryWatcherMac.cpp", + ] + EXPORTS.mozilla += [ + "MemoryPressureLevelMac.h", + ] + +if CONFIG["OS_TARGET"] == "Linux": + UNIFIED_SOURCES += [ + "AvailableMemoryWatcherLinux.cpp", + ] + EXPORTS.mozilla += [ + "AvailableMemoryWatcherUtils.h", + ] + +if CONFIG["MOZ_PHC"]: + DEFINES["MOZ_PHC"] = True + +GeneratedFile("ErrorList.h", script="ErrorList.py", entry_point="error_list_h") +GeneratedFile( + "ErrorNamesInternal.h", script="ErrorList.py", entry_point="error_names_internal_h" +) +GeneratedFile("error_list.rs", script="ErrorList.py", entry_point="error_list_rs") + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + SOURCES += [ + "nsMacUtilsImpl.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + SOURCES += [ + "nsCrashOnException.cpp", + ] + +if CONFIG["COMPILE_ENVIRONMENT"]: + EXPORTS.mozilla += [ + "!gk_rust_utils_ffi_generated.h", + ] + + CbindgenHeader("gk_rust_utils_ffi_generated.h", inputs=["/xpcom/rust/gkrust_utils"]) + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "../build", + "/dom/base", + "/mfbt", + "/netwerk/base", + "/xpcom/ds", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] diff --git a/xpcom/base/nsAlgorithm.h b/xpcom/base/nsAlgorithm.h new file mode 100644 index 0000000000..1c4bb0e9dc --- /dev/null +++ b/xpcom/base/nsAlgorithm.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ + +#ifndef nsAlgorithm_h___ +#define nsAlgorithm_h___ + +#include +#include "mozilla/Assertions.h" + +template +inline T NS_ROUNDUP(const T& aA, const T& aB) { + return ((aA + (aB - 1)) / aB) * aB; +} + +// We use these instead of std::min/max because we can't include the algorithm +// header in all of XPCOM because the stl wrappers will error out when included +// in parts of XPCOM. These functions should never be used outside of XPCOM. +template +inline const T& XPCOM_MIN(const T& aA, const T& aB) { + return aB < aA ? aB : aA; +} + +// Must return b when a == b in case a is -0 +template +inline const T& XPCOM_MAX(const T& aA, const T& aB) { + return aA > aB ? aA : aB; +} + +namespace mozilla { + +template +inline const T& clamped(const T& aA, const T& aMin, const T& aMax) { + MOZ_ASSERT(aMax >= aMin, + "clamped(): aMax must be greater than or equal to aMin"); + return XPCOM_MIN(XPCOM_MAX(aA, aMin), aMax); +} + +} // namespace mozilla + +template +inline uint32_t NS_COUNT(InputIterator& aFirst, const InputIterator& aLast, + const T& aValue) { + uint32_t result = 0; + for (; aFirst != aLast; ++aFirst) + if (*aFirst == aValue) { + ++result; + } + return result; +} + +#endif // !defined(nsAlgorithm_h___) diff --git a/xpcom/base/nsAutoRef.h b/xpcom/base/nsAutoRef.h new file mode 100644 index 0000000000..9a1f3ddd93 --- /dev/null +++ b/xpcom/base/nsAutoRef.h @@ -0,0 +1,489 @@ +/* -*- 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/. */ + +// NB: This code may be used from non-XPCOM code, in particular, the +// Windows Default Browser Agent. + +#ifndef nsAutoRef_h_ +#define nsAutoRef_h_ + +#include "mozilla/Attributes.h" + +template +class nsSimpleRef; +template +class nsAutoRefBase; +template +class nsReturnRef; +template +class nsReturningRef; + +/** + * template class nsAutoRef + * + * A class that holds a handle to a resource that must be released. + * No reference is added on construction. + * + * No copy constructor nor copy assignment operators are available, so the + * resource will be held until released on destruction or explicitly + * |reset()| or transferred through provided methods. + * + * The publicly available methods are the public methods on this class and its + * public base classes |nsAutoRefBase| and |nsSimpleRef|. + * + * For function return values see |nsReturnRef|. + * + * For each class |T|, |nsAutoRefTraits| or |nsSimpleRef| must be + * specialized to use |nsAutoRef|. + * + * @param T A class identifying the type of reference held by the + * |nsAutoRef| and the unique set methods for managing references + * to the resource (defined by |nsAutoRefTraits| or + * |nsSimpleRef|). + * + * Often this is the class representing the resource. Sometimes a + * new possibly-incomplete class may need to be declared. + * + * + * Example: An Automatically closing file descriptor + * + * // References that are simple integral types (as file-descriptors are) + * // usually need a new class to represent the resource and how to handle its + * // references. + * class nsRawFD; + * + * // Specializing nsAutoRefTraits describes how to manage file + * // descriptors, so that nsAutoRef provides automatic closing of + * // its file descriptor on destruction. + * template <> + * class nsAutoRefTraits { + * public: + * // The file descriptor is held in an int. + * typedef int RawRef; + * // -1 means that there is no file associated with the handle. + * static int Void() { return -1; } + * // The file associated with a file descriptor is released with close(). + * static void Release(RawRef aFD) { close(aFD); } + * }; + * + * // A function returning a file descriptor that must be closed. + * nsReturnRef get_file(const char *filename) { + * // Constructing from a raw file descriptor assumes ownership. + * nsAutoRef fd(open(filename, O_RDONLY)); + * fcntl(fd, F_SETFD, FD_CLOEXEC); + * return fd.out(); + * } + * + * void f() { + * unsigned char buf[1024]; + * + * // Hold a file descriptor for /etc/hosts in fd1. + * nsAutoRef fd1(get_file("/etc/hosts")); + * + * nsAutoRef fd2; + * fd2.steal(fd1); // fd2 takes the file descriptor from fd1 + * ssize_t count = read(fd1, buf, 1024); // error fd1 has no file + * count = read(fd2, buf, 1024); // reads from /etc/hosts + * + * // If the file descriptor is not stored then it is closed. + * get_file("/etc/login.defs"); // login.defs is closed + * + * // Now use fd1 to hold a file descriptor for /etc/passwd. + * fd1 = get_file("/etc/passwd"); + * + * // The nsAutoRef can give up the file descriptor if explicitly + * // instructed, but the caller must then ensure that the file is closed. + * int rawfd = fd1.disown(); + * + * // Assume ownership of another file descriptor. + * fd1.own(open("/proc/1/maps"); + * + * // On destruction, fd1 closes /proc/1/maps and fd2 closes /etc/hosts, + * // but /etc/passwd is not closed. + * } + * + */ + +template +class nsAutoRef : public nsAutoRefBase { + protected: + typedef nsAutoRef ThisClass; + typedef nsAutoRefBase BaseClass; + typedef nsSimpleRef SimpleRef; + typedef typename BaseClass::RawRefOnly RawRefOnly; + typedef typename BaseClass::LocalSimpleRef LocalSimpleRef; + + public: + nsAutoRef() = default; + + // Explicit construction is required so as not to risk unintentionally + // releasing the resource associated with a raw ref. + explicit nsAutoRef(RawRefOnly aRefToRelease) : BaseClass(aRefToRelease) {} + + // Construction from a nsReturnRef function return value, which expects + // to give up ownership, transfers ownership. + // (nsReturnRef is converted to const nsReturningRef.) + explicit nsAutoRef(const nsReturningRef& aReturning) + : BaseClass(aReturning) {} + + // The only assignment operator provided is for transferring from an + // nsReturnRef smart reference, which expects to pass its ownership to + // another object. + // + // With raw references and other smart references, the type of the lhs and + // its taking and releasing nature is often not obvious from an assignment + // statement. Assignment from a raw ptr especially is not normally + // expected to release the reference. + // + // Use |steal| for taking ownership from other smart refs. + // + // For raw references, use |own| to indicate intention to have the + // resource released. + + ThisClass& operator=(const nsReturningRef& aReturning) { + BaseClass::steal(aReturning.mReturnRef); + return *this; + } + + // Conversion to a raw reference allow the nsAutoRef to often be used + // like a raw reference. + operator typename SimpleRef::RawRef() const { return this->get(); } + + explicit operator bool() const { return this->HaveResource(); } + + // Transfer ownership from another smart reference. + void steal(ThisClass& aOtherRef) { BaseClass::steal(aOtherRef); } + + // Assume ownership of a raw ref. + // + // |own| has similar function to |steal|, and is useful for receiving + // ownership from a return value of a function. It is named differently + // because |own| requires more care to ensure that the function intends to + // give away ownership, and so that |steal| can be safely used, knowing + // that it won't steal ownership from any methods returning raw ptrs to + // data owned by a foreign object. + void own(RawRefOnly aRefToRelease) { BaseClass::own(aRefToRelease); } + + // Exchange ownership with |aOther| + void swap(ThisClass& aOther) { + LocalSimpleRef temp; + temp.SimpleRef::operator=(*this); + SimpleRef::operator=(aOther); + aOther.SimpleRef::operator=(temp); + } + + // Release the reference now. + void reset() { + this->SafeRelease(); + LocalSimpleRef empty; + SimpleRef::operator=(empty); + } + + // Pass out the reference for a function return values. + nsReturnRef out() { return nsReturnRef(this->disown()); } + + // operator->() and disown() are provided by nsAutoRefBase. + // The default nsSimpleRef provides get(). + + // No copy constructor + explicit nsAutoRef(const ThisClass& aRefToSteal) = delete; +}; + +/** + * template class nsReturnRef + * + * A type for function return values that hold a reference to a resource that + * must be released. See also |nsAutoRef::out()|. + */ + +template +class nsReturnRef : public nsAutoRefBase { + protected: + typedef nsAutoRefBase BaseClass; + typedef typename BaseClass::RawRefOnly RawRefOnly; + + public: + // For constructing a return value with no resource + nsReturnRef() = default; + + // For returning a smart reference from a raw reference that must be + // released. Explicit construction is required so as not to risk + // unintentionally releasing the resource associated with a raw ref. + MOZ_IMPLICIT nsReturnRef(RawRefOnly aRefToRelease) + : BaseClass(aRefToRelease) {} + + // Move construction transfers ownership + nsReturnRef(nsReturnRef&& aRefToSteal) = default; + + MOZ_IMPLICIT nsReturnRef(const nsReturningRef& aReturning) + : BaseClass(aReturning) {} + + // Conversion to a temporary (const) object referring to this object so + // that the reference may be passed from a function return value + // (temporary) to another smart reference. There is no need to use this + // explicitly. Simply assign a nsReturnRef function return value to a + // smart reference. + operator nsReturningRef() { return nsReturningRef(*this); } + + // No conversion to RawRef operator is provided on nsReturnRef, to ensure + // that the return value is not carelessly assigned to a raw ptr (and the + // resource then released). If passing to a function that takes a raw + // ptr, use get or disown as appropriate. +}; + +/** + * template class nsReturningRef + * + * A class to allow ownership to be transferred from nsReturnRef function + * return values. + * + * It should not be necessary for clients to reference this + * class directly. Simply pass an nsReturnRef to a parameter taking an + * |nsReturningRef|. + * + * The conversion operator on nsReturnRef constructs a temporary wrapper of + * class nsReturningRef around a non-const reference to the nsReturnRef. + * The wrapper can then be passed as an rvalue parameter. + */ + +template +class nsReturningRef { + private: + friend class nsReturnRef; + + explicit nsReturningRef(nsReturnRef& aReturnRef) + : mReturnRef(aReturnRef) {} + + public: + nsReturnRef& mReturnRef; +}; + +/** + * template class nsAutoRefTraits + * + * A class describing traits of references managed by the default + * |nsSimpleRef| implementation and thus |nsAutoRef|. + * The default |nsSimpleRef is suitable for resources with handles that + * have a void value. (If there is no such void value for a handle, + * specialize |nsSimpleRef|.) + * + * Specializations must be provided for each class |T| according to the + * following pattern: + * + * // The template parameter |T| should be a class such that the set of fields + * // in class nsAutoRefTraits is unique for class |T|. Usually the + * // resource object class is sufficient. For handles that are simple + * // integral typedefs, a new unique possibly-incomplete class may need to be + * // declared. + * + * template <> + * class nsAutoRefTraits + * { + * // Specializations must provide a typedef for RawRef, describing the + * // type of the handle to the resource. + * typedef RawRef; + * + * // Specializations should define Void(), a function returning a value + * // suitable for a handle that does not have an associated resource. + * // + * // The return type must be a suitable as the parameter to a RawRef + * // constructor and operator==. + * // + * // If this method is not accessible then some limited nsAutoRef + * // functionality will still be available, but the default constructor, + * // |reset|, and most transfer of ownership methods will not be available. + * static Void(); + * + * // Specializations must define Release() to properly finalize the + * // handle to a non-void custom-deleted or reference-counted resource. + * static void Release(RawRef aRawRef); + * }; + * + * See nsPointerRefTraits for example specializations for simple pointer + * references. See nsAutoRef for an example specialization for a non-pointer + * reference. + */ + +template +class nsAutoRefTraits; + +/** + * template class nsPointerRefTraits + * + * A convenience class useful as a base class for specializations of + * |nsAutoRefTraits| where the handle to the resource is a pointer to |T|. + * By inheriting from this class, definitions of only Release(RawRef) and + * possibly AddRef(RawRef) need to be added. + * + * Examples of use: + * + * template <> + * class nsAutoRefTraits : public nsPointerRefTraits + * { + * public: + * static void Release(PRFileDesc *ptr) { PR_Close(ptr); } + * }; + * + * template <> + * class nsAutoRefTraits : public nsPointerRefTraits + * { + * public: + * static void Release(FcPattern *ptr) { FcPatternDestroy(ptr); } + * static void AddRef(FcPattern *ptr) { FcPatternReference(ptr); } + * }; + */ + +template +class nsPointerRefTraits { + public: + // The handle is a pointer to T. + typedef T* RawRef; + // A nullptr does not have a resource. + static RawRef Void() { return nullptr; } +}; + +/** + * template class nsSimpleRef + * + * Constructs a non-smart reference, and provides methods to test whether + * there is an associated resource and (if so) get its raw handle. + * + * A default implementation is suitable for resources with handles that have a + * void value. This is not intended for direct use but used by |nsAutoRef|. + * + * Specialize this class if there is no particular void value for the resource + * handle. A specialized implementation must also provide Release(RawRef), + */ + +template +class nsSimpleRef : protected nsAutoRefTraits { + protected: + // The default implementation uses nsAutoRefTrait. + // Specializations need not define this typedef. + typedef nsAutoRefTraits Traits; + // The type of the handle to the resource. + // A specialization must provide a typedef for RawRef. + typedef typename Traits::RawRef RawRef; + + // Construct with no resource. + // + // If this constructor is not accessible then some limited nsAutoRef + // functionality will still be available, but the default constructor, + // |reset|, and most transfer of ownership methods will not be available. + nsSimpleRef() : mRawRef(Traits::Void()) {} + // Construct with a handle to a resource. + // A specialization must provide this. + explicit nsSimpleRef(RawRef aRawRef) : mRawRef(aRawRef) {} + + // Test whether there is an associated resource. A specialization must + // provide this. The function is permitted to always return true if the + // default constructor is not accessible, or if Release (and AddRef) can + // deal with void handles. + bool HaveResource() const { return mRawRef != Traits::Void(); } + + public: + // A specialization must provide get() or loose some functionality. This + // is inherited by derived classes and the specialization may choose + // whether it is public or protected. + RawRef get() const { return mRawRef; } + + private: + RawRef mRawRef; +}; + +/** + * template class nsAutoRefBase + * + * Internal base class for |nsAutoRef| and |nsReturnRef|. + * Adds release on destruction to a |nsSimpleRef|. + */ + +template +class nsAutoRefBase : public nsSimpleRef { + protected: + typedef nsAutoRefBase ThisClass; + typedef nsSimpleRef SimpleRef; + typedef typename SimpleRef::RawRef RawRef; + + nsAutoRefBase() = default; + + // A type for parameters that should be passed a raw ref but should not + // accept implicit conversions (from another smart ref). (The only + // conversion to this type is from a raw ref so only raw refs will be + // accepted.) + class RawRefOnly { + public: + MOZ_IMPLICIT RawRefOnly(RawRef aRawRef) : mRawRef(aRawRef) {} + operator RawRef() const { return mRawRef; } + + private: + RawRef mRawRef; + }; + + // Construction from a raw ref assumes ownership + explicit nsAutoRefBase(RawRefOnly aRefToRelease) : SimpleRef(aRefToRelease) {} + + // Constructors that steal ownership + nsAutoRefBase(ThisClass&& aRefToSteal) : SimpleRef(aRefToSteal.disown()) {} + explicit nsAutoRefBase(const nsReturningRef& aReturning) + : SimpleRef(aReturning.mReturnRef.disown()) {} + + ~nsAutoRefBase() { SafeRelease(); } + + // An internal class providing access to protected nsSimpleRef + // constructors for construction of temporary simple references (that are + // not ThisClass). + class LocalSimpleRef : public SimpleRef { + public: + LocalSimpleRef() = default; + explicit LocalSimpleRef(RawRef aRawRef) : SimpleRef(aRawRef) {} + }; + + public: + ThisClass& operator=(const ThisClass& aSmartRef) = delete; + + RawRef operator->() const { return this->get(); } + + // Transfer ownership to a raw reference. + // + // THE CALLER MUST ENSURE THAT THE REFERENCE IS EXPLICITLY RELEASED. + // + // Is this really what you want to use? Using this removes any guarantee + // of release. Use nsAutoRef::out() for return values, or an + // nsAutoRef modifiable lvalue for an out parameter. Use disown() when + // the reference must be stored in a POD type object, such as may be + // preferred for a namespace-scope object with static storage duration, + // for example. + RawRef disown() { + RawRef temp = this->get(); + LocalSimpleRef empty; + SimpleRef::operator=(empty); + return temp; + } + + protected: + // steal and own are protected because they make no sense on nsReturnRef, + // but steal is implemented on this class for access to aOtherRef.disown() + // when aOtherRef is an nsReturnRef; + + // Transfer ownership from another smart reference. + void steal(ThisClass& aOtherRef) { own(aOtherRef.disown()); } + // Assume ownership of a raw ref. + void own(RawRefOnly aRefToRelease) { + SafeRelease(); + LocalSimpleRef ref(aRefToRelease); + SimpleRef::operator=(ref); + } + + // Release a resource if there is one. + void SafeRelease() { + if (this->HaveResource()) { + this->Release(this->get()); + } + } +}; + +#endif // !defined(nsAutoRef_h_) diff --git a/xpcom/base/nsCOMPtr.cpp b/xpcom/base/nsCOMPtr.cpp new file mode 100644 index 0000000000..ffe0cb1acd --- /dev/null +++ b/xpcom/base/nsCOMPtr.cpp @@ -0,0 +1,34 @@ +/* -*- 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 "nsCOMPtr.h" + +nsresult nsQueryInterfaceISupports::operator()(const nsIID& aIID, + void** aAnswer) const { + nsresult status; + if (mRawPtr) { + status = mRawPtr->QueryInterface(aIID, aAnswer); + } else { + status = NS_ERROR_NULL_POINTER; + } + + return status; +} + +nsresult nsQueryInterfaceISupportsWithError::operator()(const nsIID& aIID, + void** aAnswer) const { + nsresult status; + if (mRawPtr) { + status = mRawPtr->QueryInterface(aIID, aAnswer); + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} diff --git a/xpcom/base/nsCOMPtr.h b/xpcom/base/nsCOMPtr.h new file mode 100644 index 0000000000..3ee56e9800 --- /dev/null +++ b/xpcom/base/nsCOMPtr.h @@ -0,0 +1,1170 @@ +/* -*- 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/. */ + +#ifndef nsCOMPtr_h___ +#define nsCOMPtr_h___ + +/* + * Having problems? + * + * See the documentation at: + * https://firefox-source-docs.mozilla.org/xpcom/refptr.html + * + * + * nsCOMPtr + * better than a raw pointer + * for owning objects + * -- scc + */ + +#include + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "nsCycleCollectionNoteChild.h" +#include "nsDebug.h" // for |NS_ASSERTION| +#include "nsISupportsUtils.h" // for |nsresult|, |NS_ADDREF|, |NS_GET_TEMPLATE_IID| et al + +/* + * WARNING: This file defines several macros for internal use only. These + * macros begin with the prefix |NSCAP_|. Do not use these macros in your own + * code. They are for internal use only for cross-platform compatibility, and + * are subject to change without notice. + */ + +#ifdef _MSC_VER +// Under VC++, we win by inlining StartAssignment. +# define NSCAP_FEATURE_INLINE_STARTASSIGNMENT + +// Also under VC++, at the highest warning level, we are overwhelmed with +// warnings about (unused) inline functions being removed. This is to be +// expected with templates, so we disable the warning. +# pragma warning(disable : 4514) +#endif + +#ifdef DEBUG +# define NSCAP_FEATURE_TEST_DONTQUERY_CASES +#endif + +#ifdef __GNUC__ +// Our use of nsCOMPtr_base::mRawPtr violates the C++ standard's aliasing +// rules. Mark it with the may_alias attribute so that gcc 3.3 and higher +// don't reorder instructions based on aliasing assumptions for +// this variable. Fortunately, gcc versions < 3.3 do not do any +// optimizations that break nsCOMPtr. + +# define NS_MAY_ALIAS_PTR(t) t* __attribute__((__may_alias__)) +#else +# define NS_MAY_ALIAS_PTR(t) t* +#endif + +/* + * The following three macros (NSCAP_ADDREF, NSCAP_RELEASE, and + * NSCAP_LOG_ASSIGNMENT) allow external clients the ability to add logging or + * other interesting debug facilities. In fact, if you want |nsCOMPtr| to + * participate in the standard logging facility, you provide + * (e.g., in "nsISupportsImpl.h") suitable definitions + * + * #define NSCAP_ADDREF(this, ptr) NS_ADDREF(ptr) + * #define NSCAP_RELEASE(this, ptr) NS_RELEASE(ptr) + */ + +#ifndef NSCAP_ADDREF +# define NSCAP_ADDREF(this, ptr) (ptr)->AddRef() +#endif + +#ifndef NSCAP_RELEASE +# define NSCAP_RELEASE(this, ptr) (ptr)->Release() +#endif + +// Clients can define |NSCAP_LOG_ASSIGNMENT| to perform logging. +#ifdef NSCAP_LOG_ASSIGNMENT +// Remember that |NSCAP_LOG_ASSIGNMENT| was defined by some client so that we +// know to instantiate |~nsGetterAddRefs| in turn to note the external +// assignment into the |nsCOMPtr|. +# define NSCAP_LOG_EXTERNAL_ASSIGNMENT +#else +// ...otherwise, just strip it out of the code +# define NSCAP_LOG_ASSIGNMENT(this, ptr) +#endif + +#ifndef NSCAP_LOG_RELEASE +# define NSCAP_LOG_RELEASE(this, ptr) +#endif + +namespace mozilla { +template +class OwningNonNull; +} // namespace mozilla + +template +inline already_AddRefed dont_AddRef(T* aRawPtr) { + return already_AddRefed(aRawPtr); +} + +template +inline already_AddRefed&& dont_AddRef( + already_AddRefed&& aAlreadyAddRefedPtr) { + return std::move(aAlreadyAddRefedPtr); +} + +/* + * An nsCOMPtr_helper transforms commonly called getters into typesafe forms + * that are more convenient to call, and more efficient to use with |nsCOMPtr|s. + * Good candidates for helpers are |QueryInterface()|, |CreateInstance()|, etc. + * + * Here are the rules for a helper: + * - it implements |operator()| to produce an interface pointer + * - (except for its name) |operator()| is a valid [XP]COM `getter' + * - the interface pointer that it returns is already |AddRef()|ed (as from + * any good getter) + * - it matches the type requested with the supplied |nsIID| argument + * - its constructor provides an optional |nsresult*| that |operator()| can + * fill in with an error when it is executed + * + * See |class nsGetInterface| for an example. + */ +class MOZ_STACK_CLASS nsCOMPtr_helper { + public: + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const = 0; +}; + +/* + * nsQueryInterface could have been implemented as an nsCOMPtr_helper to avoid + * adding specialized machinery in nsCOMPtr, but do_QueryInterface is called + * often enough that the codesize savings are big enough to warrant the + * specialcasing. + */ +class MOZ_STACK_CLASS nsQueryInterfaceISupports { + public: + explicit nsQueryInterfaceISupports(nsISupports* aRawPtr) : mRawPtr(aRawPtr) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const; + + private: + nsISupports* MOZ_OWNING_REF mRawPtr; +}; + +template +class MOZ_STACK_CLASS nsQueryInterface final + : public nsQueryInterfaceISupports { + public: + explicit nsQueryInterface(T* aRawPtr) + : nsQueryInterfaceISupports(ToSupports(aRawPtr)) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void** aAnswer) const { + return nsQueryInterfaceISupports::operator()(aIID, aAnswer); + } +}; + +class MOZ_STACK_CLASS nsQueryInterfaceISupportsWithError { + public: + nsQueryInterfaceISupportsWithError(nsISupports* aRawPtr, nsresult* aError) + : mRawPtr(aRawPtr), mErrorPtr(aError) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const; + + private: + nsISupports* MOZ_OWNING_REF mRawPtr; + nsresult* mErrorPtr; +}; + +template +class MOZ_STACK_CLASS nsQueryInterfaceWithError final + : public nsQueryInterfaceISupportsWithError { + public: + explicit nsQueryInterfaceWithError(T* aRawPtr, nsresult* aError) + : nsQueryInterfaceISupportsWithError(ToSupports(aRawPtr), aError) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void** aAnswer) const { + return nsQueryInterfaceISupportsWithError::operator()(aIID, aAnswer); + } +}; + +namespace mozilla { +// PointedToType<> is needed so that do_QueryInterface() will work with a +// variety of smart pointer types in addition to raw pointers. These types +// include RefPtr<>, nsCOMPtr<>, and OwningNonNull<>. +template +using PointedToType = std::remove_pointer_t())>; +} // namespace mozilla + +template +inline nsQueryInterface> do_QueryInterface(T aPtr) { + return nsQueryInterface>(aPtr); +} + +template +inline nsQueryInterfaceWithError> do_QueryInterface( + T aRawPtr, nsresult* aError) { + return nsQueryInterfaceWithError>(aRawPtr, aError); +} + +template +inline void do_QueryInterface(already_AddRefed&) { + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_QueryInterface()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See bug 8221. +} + +template +inline void do_QueryInterface(already_AddRefed&, nsresult*) { + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_QueryInterface()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See bug 8221. +} + +//////////////////////////////////////////////////////////////////////////// +// Using servicemanager with COMPtrs +class nsGetServiceByCID final { + public: + explicit nsGetServiceByCID(const nsCID& aCID) : mCID(aCID) {} + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + + private: + const nsCID& mCID; +}; + +class nsGetServiceByCIDWithError final { + public: + nsGetServiceByCIDWithError(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID), mErrorPtr(aErrorPtr) {} + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + + private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class nsGetServiceByContractID final { + public: + explicit nsGetServiceByContractID(const char* aContractID) + : mContractID(aContractID) {} + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + + private: + const char* mContractID; +}; + +class nsGetServiceByContractIDWithError final { + public: + nsGetServiceByContractIDWithError(const char* aContractID, + nsresult* aErrorPtr) + : mContractID(aContractID), mErrorPtr(aErrorPtr) {} + + nsresult NS_FASTCALL operator()(const nsIID&, void**) const; + + private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +class nsIWeakReference; + +// Weak references +class MOZ_STACK_CLASS nsQueryReferent final { + public: + nsQueryReferent(nsIWeakReference* aWeakPtr, nsresult* aError) + : mWeakPtr(aWeakPtr), mErrorPtr(aError) {} + + nsresult NS_FASTCALL operator()(const nsIID& aIID, void**) const; + + private: + nsIWeakReference* MOZ_NON_OWNING_REF mWeakPtr; + nsresult* mErrorPtr; +}; + +// template class nsGetterAddRefs; + +// Helper for assert_validity method +template +char (&TestForIID(decltype(&NS_GET_TEMPLATE_IID(T))))[2]; +template +char TestForIID(...); + +template +class MOZ_IS_REFPTR nsCOMPtr final { + private: + void assign_with_AddRef(nsISupports*); + template + void assign_from_qi(const nsQueryInterface, const nsIID&); + template + void assign_from_qi_with_error(const nsQueryInterfaceWithError&, + const nsIID&); + void assign_from_gs_cid(const nsGetServiceByCID, const nsIID&); + void assign_from_gs_cid_with_error(const nsGetServiceByCIDWithError&, + const nsIID&); + void assign_from_gs_contractid(const nsGetServiceByContractID, const nsIID&); + void assign_from_gs_contractid_with_error( + const nsGetServiceByContractIDWithError&, const nsIID&); + void assign_from_query_referent(const nsQueryReferent&, const nsIID&); + void assign_from_helper(const nsCOMPtr_helper&, const nsIID&); + void** begin_assignment(); + + void assign_assuming_AddRef(T* aNewPtr) { + T* oldPtr = mRawPtr; + mRawPtr = aNewPtr; + NSCAP_LOG_ASSIGNMENT(this, aNewPtr); + NSCAP_LOG_RELEASE(this, oldPtr); + if (oldPtr) { + NSCAP_RELEASE(this, oldPtr); + } + } + + private: + T* MOZ_OWNING_REF mRawPtr; + + void assert_validity() { + static_assert(1 < sizeof(TestForIID(nullptr)), + "nsCOMPtr only works " + "for types with IIDs. Either use RefPtr; add an IID to " + "your type with NS_DECLARE_STATIC_IID_ACCESSOR/" + "NS_DEFINE_STATIC_IID_ACCESSOR; or make the nsCOMPtr point " + "to a base class with an IID."); + } + + public: + typedef T element_type; + + ~nsCOMPtr() { + NSCAP_LOG_RELEASE(this, mRawPtr); + if (mRawPtr) { + NSCAP_RELEASE(this, mRawPtr); + } + } + +#ifdef NSCAP_FEATURE_TEST_DONTQUERY_CASES + void Assert_NoQueryNeeded() { + if (!mRawPtr) { + return; + } + if constexpr (std::is_same_v) { + // FIXME: nsCOMPtr never asserted this, and it currently + // fails... + return; + } + // This can't be defined in terms of do_QueryInterface because + // that bans casts from a class to itself. + void* out = nullptr; + mRawPtr->QueryInterface(NS_GET_TEMPLATE_IID(T), &out); + T* query_result = static_cast(out); + MOZ_ASSERT(query_result == mRawPtr, "QueryInterface needed"); + NS_RELEASE(query_result); + } + +# define NSCAP_ASSERT_NO_QUERY_NEEDED() Assert_NoQueryNeeded(); +#else +# define NSCAP_ASSERT_NO_QUERY_NEEDED() +#endif + + // Constructors + + nsCOMPtr() : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + MOZ_IMPLICIT nsCOMPtr(decltype(nullptr)) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + } + + nsCOMPtr(const nsCOMPtr& aSmartPtr) : mRawPtr(aSmartPtr.mRawPtr) { + assert_validity(); + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.mRawPtr); + } + + template + MOZ_IMPLICIT nsCOMPtr(const nsCOMPtr& aSmartPtr) + : mRawPtr(aSmartPtr.get()) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of::value, "U should be a subclass of T"); + assert_validity(); + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aSmartPtr.get()); + } + + nsCOMPtr(nsCOMPtr&& aSmartPtr) : mRawPtr(aSmartPtr.mRawPtr) { + assert_validity(); + aSmartPtr.mRawPtr = nullptr; + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + } + + template + MOZ_IMPLICIT nsCOMPtr(nsCOMPtr&& aSmartPtr) + : mRawPtr(aSmartPtr.forget().template downcast().take()) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of::value, "U should be a subclass of T"); + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + MOZ_IMPLICIT nsCOMPtr(T* aRawPtr) : mRawPtr(aRawPtr) { + assert_validity(); + if (mRawPtr) { + NSCAP_ADDREF(this, mRawPtr); + } + NSCAP_LOG_ASSIGNMENT(this, aRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + MOZ_IMPLICIT nsCOMPtr(already_AddRefed& aSmartPtr) + : mRawPtr(aSmartPtr.take()) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |otherComPtr.forget()|. + MOZ_IMPLICIT nsCOMPtr(already_AddRefed&& aSmartPtr) + : mRawPtr(aSmartPtr.take()) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |std::move(otherRefPtr)|. + template + MOZ_IMPLICIT nsCOMPtr(RefPtr&& aSmartPtr) + : mRawPtr(static_cast>(aSmartPtr.forget()).take()) { + assert_validity(); + // Make sure that U actually inherits from T + static_assert(std::is_base_of::value, "U is not a subclass of T"); + NSCAP_LOG_ASSIGNMENT(this, mRawPtr); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |already_AddRefed|. + template + MOZ_IMPLICIT nsCOMPtr(already_AddRefed& aSmartPtr) + : mRawPtr(static_cast(aSmartPtr.take())) { + assert_validity(); + // But make sure that U actually inherits from T. + static_assert(std::is_base_of::value, "U is not a subclass of T"); + NSCAP_LOG_ASSIGNMENT(this, static_cast(mRawPtr)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |otherComPtr.forget()|. + template + MOZ_IMPLICIT nsCOMPtr(already_AddRefed&& aSmartPtr) + : mRawPtr(static_cast(aSmartPtr.take())) { + assert_validity(); + // But make sure that U actually inherits from T. + static_assert(std::is_base_of::value, "U is not a subclass of T"); + NSCAP_LOG_ASSIGNMENT(this, static_cast(mRawPtr)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Construct from |do_QueryInterface(expr)|. + template + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterface aQI) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi(aQI, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_QueryInterface(expr, &rv)|. + template + MOZ_IMPLICIT nsCOMPtr(const nsQueryInterfaceWithError& aQI) + : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_qi_with_error(aQI, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(cid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCID aGS) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(cid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByCIDWithError& aGS) + : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_cid_with_error(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(contractid_expr)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractID aGS) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_GetService(contractid_expr, &rv)|. + MOZ_IMPLICIT nsCOMPtr(const nsGetServiceByContractIDWithError& aGS) + : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_gs_contractid_with_error(aGS, NS_GET_TEMPLATE_IID(T)); + } + + // Construct from |do_QueryReferent(ptr)| + MOZ_IMPLICIT nsCOMPtr(const nsQueryReferent& aQueryReferent) + : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_query_referent(aQueryReferent, NS_GET_TEMPLATE_IID(T)); + } + + // And finally, anything else we might need to construct from can exploit the + // nsCOMPtr_helper facility. + MOZ_IMPLICIT nsCOMPtr(const nsCOMPtr_helper& aHelper) : mRawPtr(nullptr) { + assert_validity(); + NSCAP_LOG_ASSIGNMENT(this, nullptr); + assign_from_helper(aHelper, NS_GET_TEMPLATE_IID(T)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // construct from |mozilla::NotNull|. + template > && + std::is_convertible_v>>> + MOZ_IMPLICIT nsCOMPtr(const mozilla::NotNull& aSmartPtr) + : mRawPtr(nsCOMPtr(aSmartPtr.get()).forget().take()) {} + + // construct from |mozilla::MovingNotNull|. + template > && + std::is_convertible_v>>> + MOZ_IMPLICIT nsCOMPtr(mozilla::MovingNotNull&& aSmartPtr) + : mRawPtr( + nsCOMPtr(std::move(aSmartPtr).unwrapBasePtr()).forget().take()) { + } + + // Defined in OwningNonNull.h + template + MOZ_IMPLICIT nsCOMPtr(const mozilla::OwningNonNull& aOther); + + // Assignment operators + + nsCOMPtr& operator=(const nsCOMPtr& aRhs) { + assign_with_AddRef(ToSupports(aRhs.mRawPtr)); + return *this; + } + + template + nsCOMPtr& operator=(const nsCOMPtr& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of::value, "U should be a subclass of T"); + assign_with_AddRef(ToSupports(static_cast(aRhs.get()))); + return *this; + } + + nsCOMPtr& operator=(nsCOMPtr&& aRhs) { + assign_assuming_AddRef(aRhs.forget().take()); + return *this; + } + + template + nsCOMPtr& operator=(nsCOMPtr&& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of::value, "U should be a subclass of T"); + assign_assuming_AddRef(aRhs.forget().template downcast().take()); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + nsCOMPtr& operator=(T* aRhs) { + assign_with_AddRef(ToSupports(aRhs)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + nsCOMPtr& operator=(decltype(nullptr)) { + assign_assuming_AddRef(nullptr); + return *this; + } + + // Assign from |already_AddRefed|. + template + nsCOMPtr& operator=(already_AddRefed& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of::value, "U is not a subclass of T"); + assign_assuming_AddRef(static_cast(aRhs.take())); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |otherComPtr.forget()|. + template + nsCOMPtr& operator=(already_AddRefed&& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of::value, "U is not a subclass of T"); + assign_assuming_AddRef(static_cast(aRhs.take())); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |std::move(otherRefPtr)|. + template + nsCOMPtr& operator=(RefPtr&& aRhs) { + // Make sure that U actually inherits from T + static_assert(std::is_base_of::value, "U is not a subclass of T"); + assign_assuming_AddRef(static_cast(aRhs.forget().take())); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |do_QueryInterface(expr)|. + template + nsCOMPtr& operator=(const nsQueryInterface aRhs) { + assign_from_qi(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_QueryInterface(expr, &rv)|. + template + nsCOMPtr& operator=(const nsQueryInterfaceWithError& aRhs) { + assign_from_qi_with_error(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(cid_expr)|. + nsCOMPtr& operator=(const nsGetServiceByCID aRhs) { + assign_from_gs_cid(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(cid_expr, &rv)|. + nsCOMPtr& operator=(const nsGetServiceByCIDWithError& aRhs) { + assign_from_gs_cid_with_error(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(contractid_expr)|. + nsCOMPtr& operator=(const nsGetServiceByContractID aRhs) { + assign_from_gs_contractid(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_GetService(contractid_expr, &rv)|. + nsCOMPtr& operator=(const nsGetServiceByContractIDWithError& aRhs) { + assign_from_gs_contractid_with_error(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // Assign from |do_QueryReferent(ptr)|. + nsCOMPtr& operator=(const nsQueryReferent& aRhs) { + assign_from_query_referent(aRhs, NS_GET_TEMPLATE_IID(T)); + return *this; + } + + // And finally, anything else we might need to assign from can exploit the + // nsCOMPtr_helper facility. + nsCOMPtr& operator=(const nsCOMPtr_helper& aRhs) { + assign_from_helper(aRhs, NS_GET_TEMPLATE_IID(T)); + NSCAP_ASSERT_NO_QUERY_NEEDED(); + return *this; + } + + // Assign from |mozilla::NotNull|. + template >>> + nsCOMPtr& operator=(const mozilla::NotNull& aSmartPtr) { + assign_assuming_AddRef(nsCOMPtr(aSmartPtr.get()).forget().take()); + return *this; + } + + // Assign from |mozilla::MovingNotNull|. + template >>> + nsCOMPtr& operator=(mozilla::MovingNotNull&& aSmartPtr) { + assign_assuming_AddRef( + nsCOMPtr(std::move(aSmartPtr).unwrapBasePtr()).forget().take()); + return *this; + } + + // Defined in OwningNonNull.h + template + nsCOMPtr& operator=(const mozilla::OwningNonNull& aOther); + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(nsCOMPtr& aRhs) { + T* temp = aRhs.mRawPtr; + NSCAP_LOG_ASSIGNMENT(&aRhs, mRawPtr); + NSCAP_LOG_ASSIGNMENT(this, temp); + NSCAP_LOG_RELEASE(this, mRawPtr); + NSCAP_LOG_RELEASE(&aRhs, temp); + aRhs.mRawPtr = mRawPtr; + mRawPtr = temp; + // |aRhs| maintains the same invariants, so we don't need to + // |NSCAP_ASSERT_NO_QUERY_NEEDED| + } + + // Exchange ownership with |aRhs|; can save a pair of refcount operations. + void swap(T*& aRhs) { + T* temp = aRhs; + NSCAP_LOG_ASSIGNMENT(this, temp); + NSCAP_LOG_RELEASE(this, mRawPtr); + aRhs = reinterpret_cast(mRawPtr); + mRawPtr = temp; + NSCAP_ASSERT_NO_QUERY_NEEDED(); + } + + // Other pointer operators + + // Return the value of mRawPtr and null out mRawPtr. Useful for + // already_AddRefed return values. + already_AddRefed MOZ_MAY_CALL_AFTER_MUST_RETURN forget() { + T* temp = nullptr; + swap(temp); + return already_AddRefed(temp); + } + + // Set the target of aRhs to the value of mRawPtr and null out mRawPtr. + // Useful to avoid unnecessary AddRef/Release pairs with "out" parameters + // where aRhs bay be a T** or an I** where I is a base class of T. + template + void forget(I** aRhs) { + NS_ASSERTION(aRhs, "Null pointer passed to forget!"); + NSCAP_LOG_RELEASE(this, mRawPtr); + *aRhs = get(); + mRawPtr = nullptr; + } + + // Prefer the implicit conversion provided automatically by + // |operator T*() const|. Use |get()| to resolve ambiguity or to get a + // castable pointer. + T* get() const { return reinterpret_cast(mRawPtr); } + + // Makes an nsCOMPtr act like its underlying raw pointer type whenever it is + // used in a context where a raw pointer is expected. It is this operator + // that makes an nsCOMPtr substitutable for a raw pointer. + // + // Prefer the implicit use of this operator to calling |get()|, except where + // necessary to resolve ambiguity. + operator T*() const& { return get(); } + + // Don't allow implicit conversion of temporary nsCOMPtr to raw pointer, + // because the refcount might be one and the pointer will immediately become + // invalid. + operator T*() const&& = delete; + + // Needed to avoid the deleted operator above + explicit operator bool() const { return !!mRawPtr; } + + T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { + MOZ_ASSERT(mRawPtr != nullptr, + "You can't dereference a NULL nsCOMPtr with operator->()."); + return get(); + } + + // These are not intended to be used by clients. See |address_of| below. + nsCOMPtr* get_address() { return this; } + const nsCOMPtr* get_address() const { return this; } + + public: + T& operator*() const { + MOZ_ASSERT(mRawPtr != nullptr, + "You can't dereference a NULL nsCOMPtr with operator*()."); + return *get(); + } + + T** StartAssignment() { +#ifndef NSCAP_FEATURE_INLINE_STARTASSIGNMENT + return reinterpret_cast(begin_assignment()); +#else + assign_assuming_AddRef(nullptr); + return reinterpret_cast(&mRawPtr); +#endif + } +}; + +template +inline void ImplCycleCollectionUnlink(nsCOMPtr& aField) { + aField = nullptr; +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, nsCOMPtr& aField, + const char* aName, uint32_t aFlags = 0) { + CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags); +} + +template +void nsCOMPtr::assign_with_AddRef(nsISupports* aRawPtr) { + if (aRawPtr) { + NSCAP_ADDREF(this, aRawPtr); + } + assign_assuming_AddRef(reinterpret_cast(aRawPtr)); +} + +template +template +void nsCOMPtr::assign_from_qi(const nsQueryInterface aQI, + const nsIID& aIID) { + // Allow QIing to nsISupports from nsISupports as a special-case, since + // SameCOMIdentity uses it. + static_assert( + std::is_same_v || + !(std::is_same_v || std::is_base_of::value), + "don't use do_QueryInterface for compile-time-determinable casts"); + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +template +template +void nsCOMPtr::assign_from_qi_with_error( + const nsQueryInterfaceWithError& aQI, const nsIID& aIID) { + static_assert( + !(std::is_same_v || std::is_base_of::value), + "don't use do_QueryInterface for compile-time-determinable casts"); + void* newRawPtr; + if (NS_FAILED(aQI(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +template +void nsCOMPtr::assign_from_gs_cid(const nsGetServiceByCID aGS, + const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +template +void nsCOMPtr::assign_from_gs_cid_with_error( + const nsGetServiceByCIDWithError& aGS, const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +template +void nsCOMPtr::assign_from_gs_contractid(const nsGetServiceByContractID aGS, + const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +template +void nsCOMPtr::assign_from_gs_contractid_with_error( + const nsGetServiceByContractIDWithError& aGS, const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aGS(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +template +void nsCOMPtr::assign_from_query_referent( + const nsQueryReferent& aQueryReferent, const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(aQueryReferent(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +template +void nsCOMPtr::assign_from_helper(const nsCOMPtr_helper& helper, + const nsIID& aIID) { + void* newRawPtr; + if (NS_FAILED(helper(aIID, &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); +} + +template +void** nsCOMPtr::begin_assignment() { + assign_assuming_AddRef(nullptr); + union { + T** mT; + void** mVoid; + } result; + result.mT = &mRawPtr; + return result.mVoid; +} + +template +inline nsCOMPtr* address_of(nsCOMPtr& aPtr) { + return aPtr.get_address(); +} + +template +inline const nsCOMPtr* address_of(const nsCOMPtr& aPtr) { + return aPtr.get_address(); +} + +/** + * This class is designed to be used for anonymous temporary objects in the + * argument list of calls that return COM interface pointers, e.g., + * + * nsCOMPtr fooP; + * ...->QueryInterface(iid, getter_AddRefs(fooP)) + * + * DO NOT USE THIS TYPE DIRECTLY IN YOUR CODE. Use |getter_AddRefs()| instead. + * + * When initialized with a |nsCOMPtr|, as in the example above, it returns + * a |void**|, a |T**|, or an |nsISupports**| as needed, that the outer call + * (|QueryInterface| in this case) can fill in. + * + * This type should be a nested class inside |nsCOMPtr|. + */ +template +class nsGetterAddRefs { + public: + explicit nsGetterAddRefs(nsCOMPtr& aSmartPtr) + : mTargetSmartPtr(aSmartPtr) {} + +#if defined(NSCAP_FEATURE_TEST_DONTQUERY_CASES) || \ + defined(NSCAP_LOG_EXTERNAL_ASSIGNMENT) + ~nsGetterAddRefs() { +# ifdef NSCAP_LOG_EXTERNAL_ASSIGNMENT + NSCAP_LOG_ASSIGNMENT(reinterpret_cast(address_of(mTargetSmartPtr)), + mTargetSmartPtr.get()); +# endif + +# ifdef NSCAP_FEATURE_TEST_DONTQUERY_CASES + mTargetSmartPtr.Assert_NoQueryNeeded(); +# endif + } +#endif + + operator void**() { + return reinterpret_cast(mTargetSmartPtr.StartAssignment()); + } + + operator T**() { return mTargetSmartPtr.StartAssignment(); } + T*& operator*() { return *(mTargetSmartPtr.StartAssignment()); } + + private: + nsCOMPtr& mTargetSmartPtr; +}; + +template <> +class nsGetterAddRefs { + public: + explicit nsGetterAddRefs(nsCOMPtr& aSmartPtr) + : mTargetSmartPtr(aSmartPtr) {} + +#ifdef NSCAP_LOG_EXTERNAL_ASSIGNMENT + ~nsGetterAddRefs() { + NSCAP_LOG_ASSIGNMENT(reinterpret_cast(address_of(mTargetSmartPtr)), + mTargetSmartPtr.get()); + } +#endif + + operator void**() { + return reinterpret_cast(mTargetSmartPtr.StartAssignment()); + } + + operator nsISupports**() { return mTargetSmartPtr.StartAssignment(); } + nsISupports*& operator*() { return *(mTargetSmartPtr.StartAssignment()); } + + private: + nsCOMPtr& mTargetSmartPtr; +}; + +template +inline nsGetterAddRefs getter_AddRefs(nsCOMPtr& aSmartPtr) { + return nsGetterAddRefs(aSmartPtr); +} + +template +inline nsresult CallQueryInterface( + T* aSource, nsGetterAddRefs aDestination) { + return CallQueryInterface(aSource, + static_cast(aDestination)); +} + +// Comparing two |nsCOMPtr|s + +template +inline bool operator==(const nsCOMPtr& aLhs, const nsCOMPtr& aRhs) { + return static_cast(aLhs.get()) == static_cast(aRhs.get()); +} + +template +inline bool operator!=(const nsCOMPtr& aLhs, const nsCOMPtr& aRhs) { + return static_cast(aLhs.get()) != static_cast(aRhs.get()); +} + +// Comparing an |nsCOMPtr| to a raw pointer + +template +inline bool operator==(const nsCOMPtr& aLhs, const U* aRhs) { + return static_cast(aLhs.get()) == aRhs; +} + +template +inline bool operator==(const U* aLhs, const nsCOMPtr& aRhs) { + return aLhs == static_cast(aRhs.get()); +} + +template +inline bool operator!=(const nsCOMPtr& aLhs, const U* aRhs) { + return static_cast(aLhs.get()) != aRhs; +} + +template +inline bool operator!=(const U* aLhs, const nsCOMPtr& aRhs) { + return aLhs != static_cast(aRhs.get()); +} + +template +inline bool operator==(const nsCOMPtr& aLhs, U* aRhs) { + return static_cast(aLhs.get()) == const_cast(aRhs); +} + +template +inline bool operator==(U* aLhs, const nsCOMPtr& aRhs) { + return const_cast(aLhs) == static_cast(aRhs.get()); +} + +template +inline bool operator!=(const nsCOMPtr& aLhs, U* aRhs) { + return static_cast(aLhs.get()) != const_cast(aRhs); +} + +template +inline bool operator!=(U* aLhs, const nsCOMPtr& aRhs) { + return const_cast(aLhs) != static_cast(aRhs.get()); +} + +// Comparing an |nsCOMPtr| to |nullptr| + +template +inline bool operator==(const nsCOMPtr& aLhs, decltype(nullptr)) { + return aLhs.get() == nullptr; +} + +template +inline bool operator==(decltype(nullptr), const nsCOMPtr& aRhs) { + return nullptr == aRhs.get(); +} + +template +inline bool operator!=(const nsCOMPtr& aLhs, decltype(nullptr)) { + return aLhs.get() != nullptr; +} + +template +inline bool operator!=(decltype(nullptr), const nsCOMPtr& aRhs) { + return nullptr != aRhs.get(); +} + +// Comparing any two [XP]COM objects for identity + +inline bool SameCOMIdentity(nsISupports* aLhs, nsISupports* aRhs) { + return nsCOMPtr(do_QueryInterface(aLhs)) == + nsCOMPtr(do_QueryInterface(aRhs)); +} + +template +inline nsresult CallQueryInterface(nsCOMPtr& aSourcePtr, + DestinationType** aDestPtr) { + return CallQueryInterface(aSourcePtr.get(), aDestPtr); +} + +template +RefPtr::RefPtr(const nsQueryReferent& aQueryReferent) { + void* newRawPtr; + if (NS_FAILED(aQueryReferent(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + mRawPtr = static_cast(newRawPtr); +} + +template +RefPtr::RefPtr(const nsCOMPtr_helper& aHelper) { + void* newRawPtr; + if (NS_FAILED(aHelper(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + mRawPtr = static_cast(newRawPtr); +} + +template +RefPtr& RefPtr::operator=(const nsQueryReferent& aQueryReferent) { + void* newRawPtr; + if (NS_FAILED(aQueryReferent(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); + return *this; +} + +template +RefPtr& RefPtr::operator=(const nsCOMPtr_helper& aHelper) { + void* newRawPtr; + if (NS_FAILED(aHelper(NS_GET_TEMPLATE_IID(T), &newRawPtr))) { + newRawPtr = nullptr; + } + assign_assuming_AddRef(static_cast(newRawPtr)); + return *this; +} + +template +inline already_AddRefed do_AddRef(const nsCOMPtr& aObj) { + nsCOMPtr ref(aObj); + return ref.forget(); +} + +// MOZ_DBG support + +template +std::ostream& operator<<(std::ostream& aOut, const nsCOMPtr& aObj) { + return mozilla::DebugValue(aOut, aObj.get()); +} + +// ToRefPtr allows to move an nsCOMPtr into a RefPtr. Be mindful when +// using this, because usually RefPtr should only be used with concrete T and +// nsCOMPtr should only be used with XPCOM interface T. +template +RefPtr ToRefPtr(nsCOMPtr&& aObj) { + return aObj.forget(); +} + +// Integration with ResultExtensions.h +template +auto ResultRefAsParam(nsCOMPtr& aResult) { + return getter_AddRefs(aResult); +} + +namespace mozilla::detail { +template +struct outparam_as_pointer; + +template +struct outparam_as_pointer> { + using type = T**; +}; +} // namespace mozilla::detail + +#endif // !defined(nsCOMPtr_h___) diff --git a/xpcom/base/nsCRTGlue.cpp b/xpcom/base/nsCRTGlue.cpp new file mode 100644 index 0000000000..925379f716 --- /dev/null +++ b/xpcom/base/nsCRTGlue.cpp @@ -0,0 +1,328 @@ +/* -*- 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 "nsCRTGlue.h" +#include "nsXPCOM.h" +#include "nsDebug.h" +#include "prtime.h" + +#include +#include +#include +#include + +#include "mozilla/Sprintf.h" + +#ifdef XP_WIN +# include +# include +# include "mozilla/LateWriteChecks.h" +# include "mozilla/UniquePtr.h" +#endif + +#ifdef ANDROID +# include +#endif + +#ifdef FUZZING_SNAPSHOT +# include +#endif + +using namespace mozilla; + +const char* NS_strspnp(const char* aDelims, const char* aStr) { + const char* d; + do { + for (d = aDelims; *d != '\0'; ++d) { + if (*aStr == *d) { + ++aStr; + break; + } + } + } while (*d); + + return aStr; +} + +char* NS_strtok(const char* aDelims, char** aStr) { + if (!*aStr) { + return nullptr; + } + + char* ret = (char*)NS_strspnp(aDelims, *aStr); + + if (!*ret) { + *aStr = ret; + return nullptr; + } + + char* i = ret; + do { + for (const char* d = aDelims; *d != '\0'; ++d) { + if (*i == *d) { + *i = '\0'; + *aStr = ++i; + return ret; + } + } + ++i; + } while (*i); + + *aStr = nullptr; + return ret; +} + +uint32_t NS_strlen(const char16_t* aString) { + MOZ_ASSERT(aString); + const char16_t* end; + + for (end = aString; *end; ++end) { + // empty loop + } + + return end - aString; +} + +int NS_strcmp(const char16_t* aStrA, const char16_t* aStrB) { + while (*aStrB) { + int r = *aStrA - *aStrB; + if (r) { + return r; + } + + ++aStrA; + ++aStrB; + } + + return *aStrA != '\0'; +} + +int NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen) { + while (aLen && *aStrB) { + int r = *aStrA - *aStrB; + if (r) { + return r; + } + + ++aStrA; + ++aStrB; + --aLen; + } + + return aLen ? *aStrA != '\0' : 0; +} + +char16_t* NS_xstrdup(const char16_t* aString) { + uint32_t len = NS_strlen(aString); + return NS_xstrndup(aString, len); +} + +template +CharT* NS_xstrndup(const CharT* aString, uint32_t aLen) { + auto newBuf = (CharT*)moz_xmalloc((aLen + 1) * sizeof(CharT)); + memcpy(newBuf, aString, aLen * sizeof(CharT)); + newBuf[aLen] = '\0'; + return newBuf; +} + +template char16_t* NS_xstrndup(const char16_t* aString, + uint32_t aLen); +template char* NS_xstrndup(const char* aString, uint32_t aLen); + +char* NS_xstrdup(const char* aString) { + uint32_t len = strlen(aString); + char* str = (char*)moz_xmalloc(len + 1); + memcpy(str, aString, len); + str[len] = '\0'; + return str; +} + +// clang-format off + +// This table maps uppercase characters to lower case characters; +// characters that are neither upper nor lower case are unaffected. +const unsigned char nsLowerUpperUtils::kUpper2Lower[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, + + // upper band mapped to lower [A-Z] => [a-z] + 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122, + + 91, 92, 93, 94, 95, + 96, 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111, + 112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 +}; + +const unsigned char nsLowerUpperUtils::kLower2Upper[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, + 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, + 96, + + // lower band mapped to upper [a-z] => [A-Z] + 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, + + 123,124,125,126,127, + 128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143, + 144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159, + 160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175, + 176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191, + 192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207, + 208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223, + 224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239, + 240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255 +}; + +// clang-format on + +bool NS_IsUpper(char aChar) { + return aChar != (char)nsLowerUpperUtils::kUpper2Lower[(unsigned char)aChar]; +} + +bool NS_IsLower(char aChar) { + return aChar != (char)nsLowerUpperUtils::kLower2Upper[(unsigned char)aChar]; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR + +void NS_MakeRandomString(char* aBuf, int32_t aBufLen) { +# define TABLE_SIZE 36 + static const char table[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', + 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', + '1', '2', '3', '4', '5', '6', '7', '8', '9'}; + + // turn PR_Now() into milliseconds since epoch + // and salt rand with that. + static unsigned int seed = 0; + if (seed == 0) { + double fpTime = double(PR_Now()); + seed = + (unsigned int)(fpTime * 1e-6 + 0.5); // use 1e-6, granularity of + // PR_Now() on the mac is seconds + srand(seed); + } + + int32_t i; + for (i = 0; i < aBufLen; ++i) { + *aBuf++ = table[rand() % TABLE_SIZE]; + } + *aBuf = 0; +} + +#endif + +#if defined(XP_WIN) +void vprintf_stderr(const char* aFmt, va_list aArgs) { + if (IsDebuggerPresent()) { + int lengthNeeded = _vscprintf(aFmt, aArgs); + if (lengthNeeded) { + lengthNeeded++; + auto buf = MakeUnique(lengthNeeded); + if (buf) { + va_list argsCpy; + va_copy(argsCpy, aArgs); + vsnprintf(buf.get(), lengthNeeded, aFmt, argsCpy); + buf[lengthNeeded - 1] = '\0'; + va_end(argsCpy); + OutputDebugStringA(buf.get()); + } + } + } + + // stderr is unbuffered by default so we open a new FILE (which is buffered) + // so that calls to printf_stderr are not as likely to get mixed together. + int fd = _fileno(stderr); + if (fd == -2) { + return; + } + + FILE* fp = _fdopen(_dup(fd), "a"); + if (!fp) { + return; + } + + vfprintf(fp, aFmt, aArgs); + + AutoSuspendLateWriteChecks suspend; + fclose(fp); +} + +#elif defined(ANDROID) +void vprintf_stderr(const char* aFmt, va_list aArgs) { + __android_log_vprint(ANDROID_LOG_INFO, "Gecko", aFmt, aArgs); +} +#elif defined(FUZZING_SNAPSHOT) +void vprintf_stderr(const char* aFmt, va_list aArgs) { + if (nyx_puts) { + auto msgbuf = mozilla::Vsmprintf(aFmt, aArgs); + nyx_puts(msgbuf.get()); + } else { + vfprintf(stderr, aFmt, aArgs); + } +} +#else +void vprintf_stderr(const char* aFmt, va_list aArgs) { + vfprintf(stderr, aFmt, aArgs); +} +#endif + +void printf_stderr(const char* aFmt, ...) { + va_list args; + va_start(args, aFmt); + vprintf_stderr(aFmt, args); + va_end(args); +} + +void fprintf_stderr(FILE* aFile, const char* aFmt, ...) { + va_list args; + va_start(args, aFmt); + if (aFile == stderr) { + vprintf_stderr(aFmt, args); + } else { + vfprintf(aFile, aFmt, args); + } + va_end(args); +} + +void print_stderr(std::stringstream& aStr) { +#if defined(ANDROID) + // On Android logcat output is truncated to 1024 chars per line, and + // we usually use std::stringstream to build up giant multi-line gobs + // of output. So to avoid the truncation we find the newlines and + // print the lines individually. + std::string line; + while (std::getline(aStr, line)) { + printf_stderr("%s\n", line.c_str()); + } +#else + printf_stderr("%s", aStr.str().c_str()); +#endif +} + +void fprint_stderr(FILE* aFile, std::stringstream& aStr) { + if (aFile == stderr) { + print_stderr(aStr); + } else { + fprintf_stderr(aFile, "%s", aStr.str().c_str()); + } +} diff --git a/xpcom/base/nsCRTGlue.h b/xpcom/base/nsCRTGlue.h new file mode 100644 index 0000000000..2bfe47b2a8 --- /dev/null +++ b/xpcom/base/nsCRTGlue.h @@ -0,0 +1,172 @@ +/* -*- 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/. */ + +#ifndef nsCRTGlue_h__ +#define nsCRTGlue_h__ + +#include "nscore.h" + +/** + * Scan a string for the first character that is *not* in a set of + * delimiters. If the string is only delimiter characters, the end of the + * string is returned. + * + * @param aDelims The set of delimiters (null-terminated) + * @param aStr The string to search (null-terminated) + */ +const char* NS_strspnp(const char* aDelims, const char* aStr); + +/** + * Tokenize a string. This function is similar to the strtok function in the + * C standard library, but it does not use static variables to maintain state + * and is therefore thread and reentrancy-safe. + * + * Any leading delimiters in str are skipped. Then the string is scanned + * until an additional delimiter or end-of-string is found. The final + * delimiter is set to '\0'. + * + * @param aDelims The set of delimiters. + * @param aStr The string to search. This is an in-out parameter; it is + * reset to the end of the found token + 1, or to the + * end-of-string if there are no more tokens. + * @return The token. If no token is found (the string is only + * delimiter characters), nullptr is returned. + */ +char* NS_strtok(const char* aDelims, char** aStr); + +/** + * "strlen" for char16_t strings + */ +uint32_t NS_strlen(const char16_t* aString); + +/** + * "strcmp" for char16_t strings + */ +int NS_strcmp(const char16_t* aStrA, const char16_t* aStrB); + +/** + * "strncmp" for char16_t strings + */ +int NS_strncmp(const char16_t* aStrA, const char16_t* aStrB, size_t aLen); + +/** + * "strdup" for char16_t strings, uses the infallible moz_xmalloc allocator. + */ +char16_t* NS_xstrdup(const char16_t* aString); + +/** + * "strdup", but using the infallible moz_xmalloc allocator. + */ +char* NS_xstrdup(const char* aString); + +/** + * strndup for char16_t or char strings (normal strndup is not available on + * windows). This function will ensure that the new string is + * null-terminated. Uses the infallible moz_xmalloc allocator. + * + * CharT may be either char16_t or char. + */ +template +CharT* NS_xstrndup(const CharT* aString, uint32_t aLen); + +// The following case-conversion methods only deal in the ascii repertoire +// A-Z and a-z + +// semi-private data declarations... don't use these directly. +class nsLowerUpperUtils { + public: + static const unsigned char kLower2Upper[256]; + static const unsigned char kUpper2Lower[256]; +}; + +inline char NS_ToUpper(char aChar) { + return (char)nsLowerUpperUtils::kLower2Upper[(unsigned char)aChar]; +} + +inline char NS_ToLower(char aChar) { + return (char)nsLowerUpperUtils::kUpper2Lower[(unsigned char)aChar]; +} + +bool NS_IsUpper(char aChar); +bool NS_IsLower(char aChar); + +constexpr bool NS_IsAscii(const char* aString) { + while (*aString) { + if (0x80 & *aString) { + return false; + } + aString++; + } + return true; +} + +constexpr bool NS_IsAscii(const char* aString, uint32_t aLength) { + const char* end = aString + aLength; + while (aString < end) { + if (0x80 & *aString) { + return false; + } + aString++; + } + return true; +} + +constexpr bool NS_IsAsciiWhitespace(char16_t aChar) { + return aChar == ' ' || aChar == '\r' || aChar == '\n' || aChar == '\t'; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +void NS_MakeRandomString(char* aBuf, int32_t aBufLen); +#endif + +#define FF '\f' +#define TAB '\t' + +#define CRSTR "\015" +#define LFSTR "\012" +#define CRLF "\015\012" /* A CR LF equivalent string */ + +#if defined(ANDROID) +// On mobile devices, the file system may be very limited in what it +// considers valid characters. To avoid errors, sanitize conservatively. +# define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|;,+=[]" +#else +// Otherwise, we use the most restrictive filesystem as our default set of +// illegal filename characters. This is currently Windows. +# define OS_FILE_ILLEGAL_CHARACTERS "/:*?\"<>|" +#endif + +// We also provide a list of all known file path separators for all filesystems. +// This can be used in replacement of FILE_PATH_SEPARATOR when you need to +// identify or replace all known path separators. +#define KNOWN_PATH_SEPARATORS "\\/" + +#if defined(XP_MACOSX) +# define FILE_PATH_SEPARATOR "/" +#elif defined(XP_WIN) +# define FILE_PATH_SEPARATOR "\\" +#elif defined(XP_UNIX) +# define FILE_PATH_SEPARATOR "/" +#else +# error need_to_define_your_file_path_separator_and_maybe_illegal_characters +#endif + +// Not all these control characters are illegal in all OSs, but we don't really +// want them appearing in filenames +#define CONTROL_CHARACTERS \ + "\001\002\003\004\005\006\007" \ + "\010\011\012\013\014\015\016\017" \ + "\020\021\022\023\024\025\026\027" \ + "\030\031\032\033\034\035\036\037" \ + "\177" \ + "\200\201\202\203\204\205\206\207" \ + "\210\211\212\213\214\215\216\217" \ + "\220\221\222\223\224\225\226\227" \ + "\230\231\232\233\234\235\236\237" + +#define FILE_ILLEGAL_CHARACTERS CONTROL_CHARACTERS OS_FILE_ILLEGAL_CHARACTERS + +#endif // nsCRTGlue_h__ diff --git a/xpcom/base/nsClassInfoImpl.cpp b/xpcom/base/nsClassInfoImpl.cpp new file mode 100644 index 0000000000..2189863a98 --- /dev/null +++ b/xpcom/base/nsClassInfoImpl.cpp @@ -0,0 +1,61 @@ +/* -*- 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 "nsIClassInfoImpl.h" +#include "nsString.h" + +NS_IMETHODIMP_(MozExternalRefCountType) +GenericClassInfo::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +GenericClassInfo::Release() { return 1; } + +NS_IMPL_QUERY_INTERFACE(GenericClassInfo, nsIClassInfo) + +NS_IMETHODIMP +GenericClassInfo::GetInterfaces(nsTArray& aArray) { + return mData->getinterfaces(aArray); +} + +NS_IMETHODIMP +GenericClassInfo::GetScriptableHelper(nsIXPCScriptable** aHelper) { + if (mData->getscriptablehelper) { + return mData->getscriptablehelper(aHelper); + } + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetContractID(nsACString& aContractID) { + NS_ERROR("GetContractID not implemented"); + aContractID.SetIsVoid(true); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassDescription(nsACString& aDescription) { + aDescription.SetIsVoid(true); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassID(nsCID** aClassID) { + NS_ERROR("GetClassID not implemented"); + *aClassID = nullptr; + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +GenericClassInfo::GetFlags(uint32_t* aFlags) { + *aFlags = mData->flags; + return NS_OK; +} + +NS_IMETHODIMP +GenericClassInfo::GetClassIDNoAlloc(nsCID* aClassIDNoAlloc) { + *aClassIDNoAlloc = mData->cid; + return NS_OK; +} diff --git a/xpcom/base/nsCom.h b/xpcom/base/nsCom.h new file mode 100644 index 0000000000..b6c3ed0296 --- /dev/null +++ b/xpcom/base/nsCom.h @@ -0,0 +1,10 @@ +/* -*- 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/. */ + +#ifndef nsCom_h__ +#define nsCom_h__ +#include "nscore.h" +#endif diff --git a/xpcom/base/nsConsoleMessage.cpp b/xpcom/base/nsConsoleMessage.cpp new file mode 100644 index 0000000000..db6ca8f216 --- /dev/null +++ b/xpcom/base/nsConsoleMessage.cpp @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +/* + * Base implementation for console messages. + */ + +#include "nsConsoleMessage.h" +#include "jsapi.h" + +NS_IMPL_ISUPPORTS(nsConsoleMessage, nsIConsoleMessage) + +nsConsoleMessage::nsConsoleMessage() : mMicroSecondTimeStamp(0), mMessage() {} + +nsConsoleMessage::nsConsoleMessage(const nsAString& aMessage) { + mMicroSecondTimeStamp = JS_Now(); + mMessage.Assign(aMessage); + mIsForwardedFromContentProcess = false; +} + +NS_IMETHODIMP +nsConsoleMessage::GetMessageMoz(nsAString& aMessage) { + aMessage = mMessage; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::GetLogLevel(uint32_t* aLogLevel) { + *aLogLevel = nsConsoleMessage::info; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::GetTimeStamp(int64_t* aTimeStamp) { + *aTimeStamp = mMicroSecondTimeStamp / 1000; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::GetMicroSecondTimeStamp(int64_t* aTimeStamp) { + *aTimeStamp = mMicroSecondTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::ToString(nsACString& /*UTF8*/ aResult) { + CopyUTF16toUTF8(mMessage, aResult); + + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::GetIsForwardedFromContentProcess( + bool* aIsForwardedFromContentProcess) { + *aIsForwardedFromContentProcess = mIsForwardedFromContentProcess; + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleMessage::SetIsForwardedFromContentProcess( + bool aIsForwardedFromContentProcess) { + mIsForwardedFromContentProcess = aIsForwardedFromContentProcess; + return NS_OK; +} diff --git a/xpcom/base/nsConsoleMessage.h b/xpcom/base/nsConsoleMessage.h new file mode 100644 index 0000000000..42509707ba --- /dev/null +++ b/xpcom/base/nsConsoleMessage.h @@ -0,0 +1,31 @@ +/* -*- 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/. */ + +#ifndef __nsconsolemessage_h__ +#define __nsconsolemessage_h__ + +#include "mozilla/Attributes.h" + +#include "nsIConsoleMessage.h" +#include "nsString.h" + +class nsConsoleMessage final : public nsIConsoleMessage { + public: + nsConsoleMessage(); + explicit nsConsoleMessage(const nsAString& aMessage); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICONSOLEMESSAGE + + private: + ~nsConsoleMessage() = default; + + int64_t mMicroSecondTimeStamp; + nsString mMessage; + bool mIsForwardedFromContentProcess; +}; + +#endif /* __nsconsolemessage_h__ */ diff --git a/xpcom/base/nsConsoleService.cpp b/xpcom/base/nsConsoleService.cpp new file mode 100644 index 0000000000..1888e1f567 --- /dev/null +++ b/xpcom/base/nsConsoleService.cpp @@ -0,0 +1,569 @@ +/* -*- 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/. */ + +/* + * Maintains a circular buffer of recent messages, and notifies + * listeners when new messages are logged. + */ + +/* Threadsafe. */ + +#include "nsCOMArray.h" +#include "nsThreadUtils.h" + +#include "nsConsoleService.h" +#include "nsConsoleMessage.h" +#include "nsIClassInfoImpl.h" +#include "nsIConsoleListener.h" +#include "nsIObserverService.h" +#include "nsPrintfCString.h" +#include "nsProxyRelease.h" +#include "nsIScriptError.h" +#include "nsISupportsPrimitives.h" +#include "nsGlobalWindowInner.h" +#include "js/friend/ErrorMessages.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/BrowserParent.h" +#include "mozilla/dom/ScriptSettings.h" + +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" + +#if defined(ANDROID) +# include +# include "mozilla/dom/ContentChild.h" +# include "mozilla/StaticPrefs_consoleservice.h" +#endif +#ifdef XP_WIN +# include +#endif + +using namespace mozilla; + +NS_IMPL_ADDREF(nsConsoleService) +NS_IMPL_RELEASE(nsConsoleService) +NS_IMPL_CLASSINFO(nsConsoleService, nullptr, + nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, + NS_CONSOLESERVICE_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsConsoleService, nsIConsoleService, nsIObserver) +NS_IMPL_CI_INTERFACE_GETTER(nsConsoleService, nsIConsoleService, nsIObserver) + +static const bool gLoggingEnabled = true; +static const bool gLoggingBuffered = true; +#ifdef XP_WIN +static bool gLoggingToDebugger = true; +#endif // XP_WIN + +nsConsoleService::MessageElement::~MessageElement() = default; + +nsConsoleService::nsConsoleService() + : mCurrentSize(0), + // XXX grab this from a pref! + // hm, but worry about circularity, bc we want to be able to report + // prefs errs... + mMaximumSize(250), + mDeliveringMessage(false), + mLock("nsConsoleService.mLock") { +#ifdef XP_WIN + // This environment variable controls whether the console service + // should be prevented from putting output to the attached debugger. + // It only affects the Windows platform. + // + // To disable OutputDebugString, set: + // MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT=1 + // + const char* disableDebugLoggingVar = + getenv("MOZ_CONSOLESERVICE_DISABLE_DEBUGGER_OUTPUT"); + gLoggingToDebugger = + !disableDebugLoggingVar || (disableDebugLoggingVar[0] == '0'); +#endif // XP_WIN +} + +void nsConsoleService::ClearMessagesForWindowID(const uint64_t innerID) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mLock); + + for (MessageElement* e = mMessages.getFirst(); e != nullptr;) { + // Only messages implementing nsIScriptError interface expose the + // inner window ID. + nsCOMPtr scriptError = do_QueryInterface(e->Get()); + if (!scriptError) { + e = e->getNext(); + continue; + } + uint64_t innerWindowID; + nsresult rv = scriptError->GetInnerWindowID(&innerWindowID); + if (NS_FAILED(rv) || innerWindowID != innerID) { + e = e->getNext(); + continue; + } + + MessageElement* next = e->getNext(); + e->remove(); + delete e; + mCurrentSize--; + MOZ_ASSERT(mCurrentSize < mMaximumSize); + + e = next; + } +} + +void nsConsoleService::ClearMessages() { + // NB: A lock is not required here as it's only called from |Reset| which + // locks for us and from the dtor. + while (!mMessages.isEmpty()) { + MessageElement* e = mMessages.popFirst(); + delete e; + } + mCurrentSize = 0; +} + +nsConsoleService::~nsConsoleService() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + ClearMessages(); +} + +class AddConsolePrefWatchers : public Runnable { + public: + explicit AddConsolePrefWatchers(nsConsoleService* aConsole) + : mozilla::Runnable("AddConsolePrefWatchers"), mConsole(aConsole) {} + + NS_IMETHOD Run() override { + nsCOMPtr obs = mozilla::services::GetObserverService(); + MOZ_ASSERT(obs); + obs->AddObserver(mConsole, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + obs->AddObserver(mConsole, "inner-window-destroyed", false); + + if (!gLoggingBuffered) { + mConsole->Reset(); + } + return NS_OK; + } + + private: + RefPtr mConsole; +}; + +nsresult nsConsoleService::Init() { + NS_DispatchToMainThread(new AddConsolePrefWatchers(this)); + + return NS_OK; +} + +nsresult nsConsoleService::MaybeForwardScriptError(nsIConsoleMessage* aMessage, + bool* sent) { + *sent = false; + + nsCOMPtr scriptError = do_QueryInterface(aMessage); + if (!scriptError) { + // Not an nsIScriptError + return NS_OK; + } + + uint64_t windowID; + nsresult rv; + rv = scriptError->GetInnerWindowID(&windowID); + NS_ENSURE_SUCCESS(rv, rv); + if (!windowID) { + // Does not set window id + return NS_OK; + } + + RefPtr windowGlobalParent = + mozilla::dom::WindowGlobalParent::GetByInnerWindowId(windowID); + if (!windowGlobalParent) { + // Could not find parent window by id + return NS_OK; + } + + RefPtr browserParent = + windowGlobalParent->GetBrowserParent(); + if (!browserParent) { + return NS_OK; + } + + mozilla::dom::ContentParent* contentParent = browserParent->Manager(); + if (!contentParent) { + return NS_ERROR_FAILURE; + } + + nsAutoString msg, sourceName, sourceLine; + nsCString category; + uint32_t lineNum, colNum, flags; + uint64_t innerWindowId; + bool fromPrivateWindow, fromChromeContext; + + rv = scriptError->GetErrorMessage(msg); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetSourceName(sourceName); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetSourceLine(sourceLine); + NS_ENSURE_SUCCESS(rv, rv); + + rv = scriptError->GetCategory(getter_Copies(category)); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetLineNumber(&lineNum); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetColumnNumber(&colNum); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetFlags(&flags); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetIsFromPrivateWindow(&fromPrivateWindow); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetIsFromChromeContext(&fromChromeContext); + NS_ENSURE_SUCCESS(rv, rv); + rv = scriptError->GetInnerWindowID(&innerWindowId); + NS_ENSURE_SUCCESS(rv, rv); + + *sent = contentParent->SendScriptError( + msg, sourceName, sourceLine, lineNum, colNum, flags, category, + fromPrivateWindow, innerWindowId, fromChromeContext); + return NS_OK; +} + +namespace { + +class LogMessageRunnable : public Runnable { + public: + LogMessageRunnable(nsIConsoleMessage* aMessage, nsConsoleService* aService) + : mozilla::Runnable("LogMessageRunnable"), + mMessage(aMessage), + mService(aService) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr mMessage; + RefPtr mService; +}; + +NS_IMETHODIMP +LogMessageRunnable::Run() { + // Snapshot of listeners so that we don't reenter this hash during + // enumeration. + nsCOMArray listeners; + mService->CollectCurrentListeners(listeners); + + mService->SetIsDelivering(); + + for (int32_t i = 0; i < listeners.Count(); ++i) { + listeners[i]->Observe(mMessage); + } + + mService->SetDoneDelivering(); + + return NS_OK; +} + +} // namespace + +// nsIConsoleService methods +NS_IMETHODIMP +nsConsoleService::LogMessage(nsIConsoleMessage* aMessage) { + return LogMessageWithMode(aMessage, nsIConsoleService::OutputToLog); +} + +// This can be called off the main thread. +nsresult nsConsoleService::LogMessageWithMode( + nsIConsoleMessage* aMessage, nsIConsoleService::OutputMode aOutputMode) { + if (!aMessage) { + return NS_ERROR_INVALID_ARG; + } + + if (!gLoggingEnabled) { + return NS_OK; + } + + if (NS_IsMainThread() && mDeliveringMessage) { + nsCString msg; + aMessage->ToString(msg); + NS_WARNING( + nsPrintfCString( + "Reentrancy error: some client attempted to display a message to " + "the console while in a console listener. The following message " + "was discarded: \"%s\"", + msg.get()) + .get()); + return NS_ERROR_FAILURE; + } + + if (XRE_IsParentProcess() && NS_IsMainThread()) { + // If mMessage is a scriptError with an innerWindowId set, + // forward it to the matching ContentParent + // This enables logging from parent to content process + bool sent; + nsresult rv = MaybeForwardScriptError(aMessage, &sent); + NS_ENSURE_SUCCESS(rv, rv); + if (sent) { + return NS_OK; + } + } + + RefPtr r; + nsCOMPtr retiredMessage; + + /* + * Lock while updating buffer, and while taking snapshot of + * listeners array. + */ + { + MutexAutoLock lock(mLock); + +#if defined(ANDROID) + if (StaticPrefs::consoleservice_logcat() && aOutputMode == OutputToLog) { + nsCString msg; + aMessage->ToString(msg); + + /** Attempt to use the process name as the log tag. */ + mozilla::dom::ContentChild* child = + mozilla::dom::ContentChild::GetSingleton(); + nsCString appName; + if (child) { + child->GetProcessName(appName); + } else { + appName = "GeckoConsole"; + } + + uint32_t logLevel = 0; + aMessage->GetLogLevel(&logLevel); + + android_LogPriority logPriority = ANDROID_LOG_INFO; + switch (logLevel) { + case nsIConsoleMessage::debug: + logPriority = ANDROID_LOG_DEBUG; + break; + case nsIConsoleMessage::info: + logPriority = ANDROID_LOG_INFO; + break; + case nsIConsoleMessage::warn: + logPriority = ANDROID_LOG_WARN; + break; + case nsIConsoleMessage::error: + logPriority = ANDROID_LOG_ERROR; + break; + } + + __android_log_print(logPriority, appName.get(), "%s", msg.get()); + } +#endif +#ifdef XP_WIN + if (gLoggingToDebugger && IsDebuggerPresent()) { + nsString msg; + aMessage->GetMessageMoz(msg); + msg.Append('\n'); + OutputDebugStringW(msg.get()); + } +#endif + + if (gLoggingBuffered) { + MessageElement* e = new MessageElement(aMessage); + mMessages.insertBack(e); + if (mCurrentSize != mMaximumSize) { + mCurrentSize++; + } else { + MessageElement* p = mMessages.popFirst(); + MOZ_ASSERT(p); + p->swapMessage(retiredMessage); + delete p; + } + } + + if (mListeners.Count() > 0) { + r = new LogMessageRunnable(aMessage, this); + } + } + + if (retiredMessage) { + // Release |retiredMessage| on the main thread in case it is an instance of + // a mainthread-only class like nsScriptErrorWithStack and we're off the + // main thread. + NS_ReleaseOnMainThread("nsConsoleService::retiredMessage", + retiredMessage.forget()); + } + + if (r) { + // avoid failing in XPCShell tests + nsCOMPtr mainThread = do_GetMainThread(); + if (mainThread) { + SchedulerGroup::Dispatch(TaskCategory::Other, r.forget()); + } + } + + return NS_OK; +} + +// See nsIConsoleService.idl for more info about this method +NS_IMETHODIMP +nsConsoleService::CallFunctionAndLogException( + JS::Handle targetGlobal, JS::HandleValue function, JSContext* cx, + JS::MutableHandleValue retval) { + if (!targetGlobal.isObject() || !function.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted contextRealm(cx, JS::GetCurrentRealmOrNull(cx)); + if (!contextRealm) { + return NS_ERROR_INVALID_ARG; + } + + JS::Rooted global( + cx, js::CheckedUnwrapDynamic(&targetGlobal.toObject(), cx)); + if (!global) { + return NS_ERROR_INVALID_ARG; + } + + // Use AutoJSAPI in order to trigger AutoJSAPI::ReportException + // which will do most of the work required for this function. + // + // We only have to pick the right global for which we want to flag + // the exception against. + dom::AutoJSAPI jsapi; + if (!jsapi.Init(global)) { + return NS_ERROR_UNEXPECTED; + } + JSContext* ccx = jsapi.cx(); + + // AutoJSAPI picks `targetGlobal` as execution compartment + // whereas we expect to run `function` from the callsites compartment. + JSAutoRealm ar(ccx, JS::GetRealmGlobalOrNull(contextRealm)); + + JS::RootedValue funVal(ccx, function); + if (!JS_WrapValue(ccx, &funVal)) { + return NS_ERROR_FAILURE; + } + if (!JS_CallFunctionValue(ccx, nullptr, funVal, JS::HandleValueArray::empty(), + retval)) { + return NS_ERROR_XPC_JAVASCRIPT_ERROR; + } + + return NS_OK; +} + +void nsConsoleService::CollectCurrentListeners( + nsCOMArray& aListeners) { + MutexAutoLock lock(mLock); + // XXX When MakeBackInserter(nsCOMArray&) is added, we can do: + // AppendToArray(aListeners, mListeners.Values()); + for (const auto& listener : mListeners.Values()) { + aListeners.AppendObject(listener); + } +} + +NS_IMETHODIMP +nsConsoleService::LogStringMessage(const char16_t* aMessage) { + if (!gLoggingEnabled) { + return NS_OK; + } + + RefPtr msg(new nsConsoleMessage( + aMessage ? nsDependentString(aMessage) : EmptyString())); + return LogMessage(msg); +} + +NS_IMETHODIMP +nsConsoleService::GetMessageArray( + nsTArray>& aMessages) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + MutexAutoLock lock(mLock); + + if (mMessages.isEmpty()) { + return NS_OK; + } + + MOZ_ASSERT(mCurrentSize <= mMaximumSize); + aMessages.SetCapacity(mCurrentSize); + + for (MessageElement* e = mMessages.getFirst(); e != nullptr; + e = e->getNext()) { + aMessages.AppendElement(e->Get()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::RegisterListener(nsIConsoleListener* aListener) { + if (!NS_IsMainThread()) { + NS_ERROR("nsConsoleService::RegisterListener is main thread only."); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsCOMPtr canonical = do_QueryInterface(aListener); + MOZ_ASSERT(canonical); + + MutexAutoLock lock(mLock); + return mListeners.WithEntryHandle(canonical, [&](auto&& entry) { + if (entry) { + // Reregistering a listener isn't good + return NS_ERROR_FAILURE; + } + entry.Insert(aListener); + return NS_OK; + }); +} + +NS_IMETHODIMP +nsConsoleService::UnregisterListener(nsIConsoleListener* aListener) { + if (!NS_IsMainThread()) { + NS_ERROR("nsConsoleService::UnregisterListener is main thread only."); + return NS_ERROR_NOT_SAME_THREAD; + } + + nsCOMPtr canonical = do_QueryInterface(aListener); + + MutexAutoLock lock(mLock); + + return mListeners.Remove(canonical) + ? NS_OK + // Unregistering a listener that was never registered? + : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsConsoleService::Reset() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + /* + * Make sure nobody trips into the buffer while it's being reset + */ + MutexAutoLock lock(mLock); + + ClearMessages(); + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::ResetWindow(uint64_t windowInnerId) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + ClearMessagesForWindowID(windowInnerId); + return NS_OK; +} + +NS_IMETHODIMP +nsConsoleService::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + // Dump all our messages, in case any are cycle collected. + Reset(); + // We could remove ourselves from the observer service, but it is about to + // drop all observers anyways, so why bother. + } else if (!strcmp(aTopic, "inner-window-destroyed")) { + nsCOMPtr supportsInt = do_QueryInterface(aSubject); + MOZ_ASSERT(supportsInt); + + uint64_t windowId; + MOZ_ALWAYS_SUCCEEDS(supportsInt->GetData(&windowId)); + + ClearMessagesForWindowID(windowId); + } else { + MOZ_CRASH(); + } + return NS_OK; +} diff --git a/xpcom/base/nsConsoleService.h b/xpcom/base/nsConsoleService.h new file mode 100644 index 0000000000..fe86f4c0fe --- /dev/null +++ b/xpcom/base/nsConsoleService.h @@ -0,0 +1,113 @@ +/* -*- 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/. */ + +/* + * nsConsoleService class declaration. + */ + +#ifndef __nsconsoleservice_h__ +#define __nsconsoleservice_h__ + +#include +#include + +#include "mozilla/Assertions.h" +#include "mozilla/LinkedList.h" +#include "mozilla/Mutex.h" + +#include "MainThreadUtils.h" +#include "nsCOMPtr.h" +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" + +#include "nsIConsoleListener.h" +#include "nsIConsoleMessage.h" +#include "nsIConsoleService.h" +#include "nsIObserver.h" +#include "nsISupports.h" + +template +class nsCOMArray; + +class nsConsoleService final : public nsIConsoleService, public nsIObserver { + public: + nsConsoleService(); + nsresult Init(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICONSOLESERVICE + NS_DECL_NSIOBSERVER + + void SetIsDelivering() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mDeliveringMessage); + mDeliveringMessage = true; + } + + void SetDoneDelivering() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mDeliveringMessage); + mDeliveringMessage = false; + } + + typedef nsInterfaceHashtable + ListenerHash; + void CollectCurrentListeners(nsCOMArray& aListeners); + + private: + class MessageElement : public mozilla::LinkedListElement { + public: + explicit MessageElement(nsIConsoleMessage* aMessage) : mMessage(aMessage) {} + + nsIConsoleMessage* Get() { return mMessage.get(); } + + // Swap directly into an nsCOMPtr to avoid spurious refcount + // traffic off the main thread in debug builds from + // NSCAP_ASSERT_NO_QUERY_NEEDED(). + void swapMessage(nsCOMPtr& aRetVal) { + mMessage.swap(aRetVal); + } + + ~MessageElement(); + + private: + nsCOMPtr mMessage; + + MessageElement(const MessageElement&) = delete; + MessageElement& operator=(const MessageElement&) = delete; + MessageElement(MessageElement&&) = delete; + MessageElement& operator=(MessageElement&&) = delete; + }; + + ~nsConsoleService(); + + nsresult MaybeForwardScriptError(nsIConsoleMessage* aMessage, bool* sent); + + void ClearMessagesForWindowID(const uint64_t innerID); + void ClearMessages() MOZ_REQUIRES(mLock); + + mozilla::LinkedList mMessages MOZ_GUARDED_BY(mLock); + + // The current size of mMessages. + uint32_t mCurrentSize MOZ_GUARDED_BY(mLock); + + // The maximum size of mMessages. + const uint32_t mMaximumSize; + + // Are we currently delivering a console message on the main thread? If + // so, we suppress incoming messages on the main thread only, to avoid + // infinite repitition. + // Only touched on MainThread + bool mDeliveringMessage; + + // Listeners to notify whenever a new message is logged. + ListenerHash mListeners MOZ_GUARDED_BY(mLock); + + // To serialize interesting methods. + mozilla::Mutex mLock; +}; + +#endif /* __nsconsoleservice_h__ */ diff --git a/xpcom/base/nsCrashOnException.cpp b/xpcom/base/nsCrashOnException.cpp new file mode 100644 index 0000000000..67436c15c4 --- /dev/null +++ b/xpcom/base/nsCrashOnException.cpp @@ -0,0 +1,34 @@ +/* -*- 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 "nsCrashOnException.h" +#include "nsCOMPtr.h" +#include "nsICrashReporter.h" +#include "nsServiceManagerUtils.h" + +namespace mozilla { + +static int ReportException(EXCEPTION_POINTERS* aExceptionInfo) { + nsCOMPtr cr = + do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (cr) { + cr->WriteMinidumpForException(aExceptionInfo); + } + + return EXCEPTION_EXECUTE_HANDLER; +} + +XPCOM_API(LRESULT) +CallWindowProcCrashProtected(WNDPROC aWndProc, HWND aHWnd, UINT aMsg, + WPARAM aWParam, LPARAM aLParam) { + MOZ_SEH_TRY { return aWndProc(aHWnd, aMsg, aWParam, aLParam); } + MOZ_SEH_EXCEPT(ReportException(GetExceptionInformation())) { + ::TerminateProcess(::GetCurrentProcess(), 253); + } + return 0; // not reached +} + +} // namespace mozilla diff --git a/xpcom/base/nsCrashOnException.h b/xpcom/base/nsCrashOnException.h new file mode 100644 index 0000000000..1291cd4961 --- /dev/null +++ b/xpcom/base/nsCrashOnException.h @@ -0,0 +1,23 @@ +/* -*- 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/. */ + +#ifndef nsCrashOnException_h +#define nsCrashOnException_h + +#include +#include + +namespace mozilla { + +// Call a given window procedure, and catch any Win32 exceptions raised from it, +// and report them as crashes. +XPCOM_API(LRESULT) +CallWindowProcCrashProtected(WNDPROC aWndProc, HWND aHWnd, UINT aMsg, + WPARAM aWParam, LPARAM aLParam); + +} // namespace mozilla + +#endif diff --git a/xpcom/base/nsCycleCollectionNoteChild.h b/xpcom/base/nsCycleCollectionNoteChild.h new file mode 100644 index 0000000000..3e1d345aa3 --- /dev/null +++ b/xpcom/base/nsCycleCollectionNoteChild.h @@ -0,0 +1,85 @@ +/* -*- 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/. */ + +// This header will be included by headers that define refpointer and array +// classes in order to specialize CC helpers such as ImplCycleCollectionTraverse +// for them. + +#ifndef nsCycleCollectionNoteChild_h__ +#define nsCycleCollectionNoteChild_h__ + +#include "nsCycleCollectionTraversalCallback.h" +#include "mozilla/Likely.h" + +enum { CycleCollectionEdgeNameArrayFlag = 1 }; + +// Just a helper for appending "[i]". Didn't want to pull in string headers +// here. +void CycleCollectionNoteEdgeNameImpl( + nsCycleCollectionTraversalCallback& aCallback, const char* aName, + uint32_t aFlags = 0); + +// Should be inlined so that in the no-debug-info case this is just a simple +// if(). +MOZ_ALWAYS_INLINE void CycleCollectionNoteEdgeName( + nsCycleCollectionTraversalCallback& aCallback, const char* aName, + uint32_t aFlags = 0) { + if (MOZ_UNLIKELY(aCallback.WantDebugInfo())) { + CycleCollectionNoteEdgeNameImpl(aCallback, aName, aFlags); + } +} + +#define NS_CYCLE_COLLECTION_INNERCLASS cycleCollection + +#define NS_CYCLE_COLLECTION_INNERNAME _cycleCollectorGlobal + +#define NS_CYCLE_COLLECTION_PARTICIPANT(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant() + +template +nsISupports* ToSupports( + T* aPtr, typename T::NS_CYCLE_COLLECTION_INNERCLASS* aDummy = 0) { + return T::NS_CYCLE_COLLECTION_INNERCLASS::Upcast(aPtr); +} + +// The default implementation of this class template is empty, because it +// should never be used: see the partial specializations below. +template ::value> +struct CycleCollectionNoteChildImpl {}; + +template +struct CycleCollectionNoteChildImpl { + static void Run(nsCycleCollectionTraversalCallback& aCallback, T* aChild) { + aCallback.NoteXPCOMChild(ToSupports(aChild)); + } +}; + +template +struct CycleCollectionNoteChildImpl { + static void Run(nsCycleCollectionTraversalCallback& aCallback, T* aChild) { + aCallback.NoteNativeChild(aChild, NS_CYCLE_COLLECTION_PARTICIPANT(T)); + } +}; + +// We declare CycleCollectionNoteChild in 3-argument and 4-argument variants, +// rather than using default arguments, so that forward declarations work +// regardless of header inclusion order. +template +inline void CycleCollectionNoteChild( + nsCycleCollectionTraversalCallback& aCallback, T* aChild, const char* aName, + uint32_t aFlags) { + CycleCollectionNoteEdgeName(aCallback, aName, aFlags); + CycleCollectionNoteChildImpl::Run(aCallback, aChild); +} + +template +inline void CycleCollectionNoteChild( + nsCycleCollectionTraversalCallback& aCallback, T* aChild, + const char* aName) { + CycleCollectionNoteChild(aCallback, aChild, aName, 0); +} + +#endif // nsCycleCollectionNoteChild_h__ diff --git a/xpcom/base/nsCycleCollectionNoteRootCallback.h b/xpcom/base/nsCycleCollectionNoteRootCallback.h new file mode 100644 index 0000000000..21dc89394a --- /dev/null +++ b/xpcom/base/nsCycleCollectionNoteRootCallback.h @@ -0,0 +1,44 @@ +/* -*- 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/. */ + +#ifndef nsCycleCollectionNoteRootCallback_h__ +#define nsCycleCollectionNoteRootCallback_h__ + +#include "nscore.h" + +class nsCycleCollectionParticipant; +class nsISupports; +class JSObject; + +namespace JS { +class GCCellPtr; +} + +class nsCycleCollectionNoteRootCallback { + public: + // aRoot must be canonical (ie the result of QIing to + // nsCycleCollectionISupports). + NS_IMETHOD_(void) + NoteXPCOMRoot(nsISupports* aRoot, + nsCycleCollectionParticipant* aParticipant) = 0; + + NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot) = 0; + NS_IMETHOD_(void) + NoteNativeRoot(void* aRoot, nsCycleCollectionParticipant* aParticipant) = 0; + + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, JSObject* aKeyDelegate, + JS::GCCellPtr aVal) = 0; + + bool WantAllTraces() const { return mWantAllTraces; } + + protected: + nsCycleCollectionNoteRootCallback() : mWantAllTraces(false) {} + + bool mWantAllTraces; +}; + +#endif // nsCycleCollectionNoteRootCallback_h__ diff --git a/xpcom/base/nsCycleCollectionParticipant.cpp b/xpcom/base/nsCycleCollectionParticipant.cpp new file mode 100644 index 0000000000..eaca656e23 --- /dev/null +++ b/xpcom/base/nsCycleCollectionParticipant.cpp @@ -0,0 +1,34 @@ +/* -*- 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 "nsCycleCollectionParticipant.h" +#include "nsCOMPtr.h" + +NS_IMETHODIMP_(void) +nsXPCOMCycleCollectionParticipant::Root(void* aPtr) { + nsISupports* s = static_cast(aPtr); + NS_ADDREF(s); +} + +NS_IMETHODIMP_(void) +nsXPCOMCycleCollectionParticipant::Unroot(void* aPtr) { + nsISupports* s = static_cast(aPtr); + NS_RELEASE(s); +} + +// We define a default trace function because some participants don't need +// to trace anything, so it is okay for them not to define one. +NS_IMETHODIMP_(void) +nsXPCOMCycleCollectionParticipant::Trace(void* aPtr, const TraceCallbacks& aCb, + void* aClosure) {} + +bool nsXPCOMCycleCollectionParticipant::CheckForRightISupports( + nsISupports* aSupports) { + nsISupports* foo; + aSupports->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast(&foo)); + return aSupports == foo; +} diff --git a/xpcom/base/nsCycleCollectionParticipant.h b/xpcom/base/nsCycleCollectionParticipant.h new file mode 100644 index 0000000000..e4d293f42e --- /dev/null +++ b/xpcom/base/nsCycleCollectionParticipant.h @@ -0,0 +1,1107 @@ +/* -*- 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/. */ + +#ifndef nsCycleCollectionParticipant_h__ +#define nsCycleCollectionParticipant_h__ + +#include +#include "js/HeapAPI.h" +#include "js/TypeDecls.h" +#include "mozilla/MacroForEach.h" +#include "nsCycleCollectionNoteChild.h" +#include "nsDebug.h" +#include "nsID.h" +#include "nscore.h" + +/** + * Note: the following two IIDs only differ in one bit in the last byte. This + * is a hack and is intentional in order to speed up the comparison inside + * NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED. + */ +#define NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID \ + { \ + 0xc61eac14, 0x5f7a, 0x4481, { \ + 0x96, 0x5e, 0x7e, 0xaa, 0x6e, 0xff, 0xa8, 0x5e \ + } \ + } + +/** + * Special IID to get at the base nsISupports for a class. Usually this is the + * canonical nsISupports pointer, but in the case of tearoffs for example it is + * the base nsISupports pointer of the tearoff. This allow the cycle collector + * to have separate nsCycleCollectionParticipant's for tearoffs or aggregated + * classes. + */ +#define NS_CYCLECOLLECTIONISUPPORTS_IID \ + { \ + 0xc61eac14, 0x5f7a, 0x4481, { \ + 0x96, 0x5e, 0x7e, 0xaa, 0x6e, 0xff, 0xa8, 0x5f \ + } \ + } + +namespace mozilla { +enum class CCReason : uint8_t { + NO_REASON, + + // Purple buffer "overflow": enough objects are suspected to be cycle + // collectable to trigger an immediate CC. + MANY_SUSPECTED, + + // The previous collection was kCCForced ago, and there are at least + // kCCForcedPurpleLimit objects suspected of being cycle collectable. + TIMED, + + // Run a CC after a GC has completed. + GC_FINISHED, + + // Run a slice of a collection. + SLICE, + + // Manual reasons are explicitly triggered with a custom listener (as opposed + // to exceeding some internal threshold.) If a CC is already in progress, + // continue it. Otherwise, start a new one. + FIRST_MANUAL_REASON = 128, + + // We want to GC, but must finish any ongoing cycle collection first. + GC_WAITING = FIRST_MANUAL_REASON, + + // CC requested via an API. Used by tests. + API, + + // Collecting in order to dump the heap. + DUMP_HEAP, + + // Low memory situation detected. + MEM_PRESSURE, + + // IPC message to a content process to trigger a CC. The original reason is + // not tracked. + IPC_MESSAGE, + + // Cycle collection on a worker. The triggering reason is not tracked. + WORKER, + + // Used for finding leaks. + SHUTDOWN +}; + +#define FOR_EACH_CCREASON(MACRO) \ + MACRO(NO_REASON) \ + MACRO(MANY_SUSPECTED) \ + MACRO(TIMED) \ + MACRO(GC_FINISHED) \ + MACRO(SLICE) \ + MACRO(GC_WAITING) \ + MACRO(API) \ + MACRO(DUMP_HEAP) \ + MACRO(MEM_PRESSURE) \ + MACRO(IPC_MESSAGE) \ + MACRO(WORKER) \ + MACRO(SHUTDOWN) + +static inline bool IsManualCCReason(CCReason reason) { + return reason >= CCReason::FIRST_MANUAL_REASON; +} + +static inline const char* CCReasonToString(CCReason aReason) { + switch (aReason) { +#define SET_REASON_STR(name) \ + case CCReason::name: \ + return #name; \ + break; + FOR_EACH_CCREASON(SET_REASON_STR) +#undef SET_REASON_STR + default: + return ""; + } +} + +} // namespace mozilla + +/** + * Just holds the IID so NS_GET_IID works. + */ +class nsCycleCollectionISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_CYCLECOLLECTIONISUPPORTS_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsCycleCollectionISupports, + NS_CYCLECOLLECTIONISUPPORTS_IID) + +class nsCycleCollectionTraversalCallback; +class nsISupports; +class nsWrapperCache; + +namespace JS { +template +class Heap; +template +class TenuredHeap; +} /* namespace JS */ + +/* + * A struct defining pure virtual methods which are called when tracing cycle + * collection paticipants. The appropriate method is called depending on the + * type of JS GC thing. + */ +struct TraceCallbacks { + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::TenuredHeap* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const = 0; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const = 0; +}; + +/* + * An implementation of TraceCallbacks that calls a single function for all JS + * GC thing types encountered. Implemented in + * nsCycleCollectorTraceJSHelpers.cpp. + */ +struct TraceCallbackFunc : public TraceCallbacks { + typedef void (*Func)(JS::GCCellPtr aPtr, const char* aName, void* aClosure); + + explicit TraceCallbackFunc(Func aCb) : mCallback(aCb) {} + + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::TenuredHeap* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override; + virtual void Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const override; + + private: + Func mCallback; +}; + +/** + * Participant implementation classes + */ +class NS_NO_VTABLE nsCycleCollectionParticipant { + public: + using Flags = uint8_t; + static constexpr Flags FlagMightSkip = 1u << 0; + static constexpr Flags FlagTraverseShouldTrace = 1u << 1; + // The object is a single zone js holder if FlagMaybeSingleZoneJSHolder is set + // and FlagMultiZoneJSHolder isn't set. This setup is needed so that + // inheriting classes can unset single zone behavior. + static constexpr Flags FlagMaybeSingleZoneJSHolder = 1u << 2; + static constexpr Flags FlagMultiZoneJSHolder = 1u << 3; + static constexpr Flags AllFlags = FlagMightSkip | FlagTraverseShouldTrace | + FlagMaybeSingleZoneJSHolder | + FlagMultiZoneJSHolder; + + constexpr explicit nsCycleCollectionParticipant(Flags aFlags) + : mFlags(aFlags) { + MOZ_ASSERT((aFlags & ~AllFlags) == 0); + } + + NS_IMETHOD TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) = 0; + + nsresult TraverseNativeAndJS(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) { + nsresult rv = TraverseNative(aPtr, aCb); + if (TraverseShouldTrace()) { + // Note, we always call Trace, even if Traverse returned + // NS_SUCCESS_INTERRUPTED_TRAVERSE. + TraceCallbackFunc noteJsChild(&nsCycleCollectionParticipant::NoteJSChild); + Trace(aPtr, noteJsChild, &aCb); + } + return rv; + } + + // Implemented in nsCycleCollectorTraceJSHelpers.cpp. + static void NoteJSChild(JS::GCCellPtr aGCThing, const char* aName, + void* aClosure); + + NS_IMETHOD_(void) Root(void* aPtr) = 0; + NS_IMETHOD_(void) Unlink(void* aPtr) = 0; + NS_IMETHOD_(void) Unroot(void* aPtr) = 0; + NS_IMETHOD_(const char*) ClassName() = 0; + + NS_IMETHOD_(void) + Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) {} + + // CanSkip is called during nsCycleCollector_forgetSkippable. If it returns + // true, aPtr is removed from the purple buffer and therefore might be left + // out from the cycle collector graph the next time that's constructed (unless + // it's reachable in some other way). + // + // CanSkip is allowed to expand the set of certainly-alive objects by removing + // other objects from the purple buffer, marking JS things black (in the GC + // sense), and so forth. Furthermore, if aRemovingAllowed is true, this call + // is allowed to remove aPtr itself from the purple buffer. + // + // Things can return true from CanSkip if either they know they have no + // outgoing edges at all in the cycle collection graph (because then they + // can't be parts of a cycle) or they know for sure they're alive. + bool CanSkip(void* aPtr, bool aRemovingAllowed) { + return MightSkip() ? CanSkipReal(aPtr, aRemovingAllowed) : false; + } + + // CanSkipInCC is called during construction of the initial set of roots for + // the cycle collector graph. If it returns true, aPtr is left out of that + // set of roots. Note that the set of roots includes whatever is in the + // purple buffer (after earlier CanSkip calls) plus various other sources of + // roots, so an object can end up having CanSkipInCC called on it even if it + // returned true from CanSkip. One example of this would be an object that + // can potentially trace JS things. + // + // CanSkipInCC is allowed to remove other objects from the purple buffer but + // should not remove aPtr and should not mark JS things black. It should also + // not modify any reference counts. + // + // Things can return true from CanSkipInCC if either they know they have no + // outgoing edges at all in the cycle collection graph or they know for sure + // they're alive _and_ none of their outgoing edges are to gray (in the GC + // sense) gcthings. See also nsWrapperCache::HasNothingToTrace and + // nsWrapperCache::HasKnownLiveWrapperAndDoesNotNeedTracing. The restriction + // on not having outgoing edges to gray gcthings is because if we _do_ have + // them that means we have a "strong" edge to a JS thing and since we're alive + // we need to trace through it and mark keep them alive. Outgoing edges to + // C++ things don't matter here, because the criteria for when a CC + // participant is considered alive are slightly different for JS and C++ + // things: JS things are only considered alive when reachable via an edge from + // a live thing, while C++ things are also considered alive when their + // refcount exceeds the number of edges via which they are reachable. + bool CanSkipInCC(void* aPtr) { + return MightSkip() ? CanSkipInCCReal(aPtr) : false; + } + + // CanSkipThis is called during construction of the cycle collector graph, + // when we traverse an edge to aPtr and consider adding it to the graph. If + // it returns true, aPtr is not added to the graph. + // + // CanSkipThis is not allowed to change the liveness or reference count of any + // objects. + // + // Things can return true from CanSkipThis if either they know they have no + // outgoing edges at all in the cycle collection graph or they know for sure + // they're alive. + // + // Note that CanSkipThis doesn't have to worry about outgoing edges to gray GC + // things, because if this object could have those it already got added to the + // graph during root set construction. An object should never have + // CanSkipThis called on it if it has outgoing strong references to JS things. + bool CanSkipThis(void* aPtr) { + return MightSkip() ? CanSkipThisReal(aPtr) : false; + } + + NS_IMETHOD_(void) DeleteCycleCollectable(void* aPtr) = 0; + + bool IsSingleZoneJSHolder() const { + return (mFlags & FlagMaybeSingleZoneJSHolder) && + !(mFlags & FlagMultiZoneJSHolder); + } + + protected: + NS_IMETHOD_(bool) CanSkipReal(void* aPtr, bool aRemovingAllowed) { + NS_ASSERTION(false, "Forgot to implement CanSkipReal?"); + return false; + } + NS_IMETHOD_(bool) CanSkipInCCReal(void* aPtr) { + NS_ASSERTION(false, "Forgot to implement CanSkipInCCReal?"); + return false; + } + NS_IMETHOD_(bool) CanSkipThisReal(void* aPtr) { + NS_ASSERTION(false, "Forgot to implement CanSkipThisReal?"); + return false; + } + + private: + bool MightSkip() const { return mFlags & FlagMightSkip; } + bool TraverseShouldTrace() const { return mFlags & FlagTraverseShouldTrace; } + + const Flags mFlags; +}; + +class NS_NO_VTABLE nsScriptObjectTracer : public nsCycleCollectionParticipant { + public: + constexpr explicit nsScriptObjectTracer(Flags aFlags) + : nsCycleCollectionParticipant(aFlags | FlagTraverseShouldTrace) {} + + NS_IMETHOD_(void) + Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override = 0; +}; + +class NS_NO_VTABLE nsXPCOMCycleCollectionParticipant + : public nsScriptObjectTracer { + public: + constexpr explicit nsXPCOMCycleCollectionParticipant(Flags aFlags) + : nsScriptObjectTracer(aFlags) {} + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID) + + NS_IMETHOD_(void) Root(void* aPtr) override; + NS_IMETHOD_(void) Unroot(void* aPtr) override; + + NS_IMETHOD_(void) + Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override; + + static bool CheckForRightISupports(nsISupports* aSupports); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsXPCOMCycleCollectionParticipant, + NS_XPCOMCYCLECOLLECTIONPARTICIPANT_IID) + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing a QI to nsXPCOMCycleCollectionParticipant +/////////////////////////////////////////////////////////////////////////////// + +#define NS_CYCLE_COLLECTION_CLASSNAME(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS + +// The IIDs for nsXPCOMCycleCollectionParticipant and nsCycleCollectionISupports +// are special in that they only differ in their last byte. This allows for the +// optimization below where we first check the first three words of the IID and +// if we find a match we check the last word to decide which case we have to +// deal with. +#define NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) \ + if (TopThreeWordsEquals( \ + aIID, NS_GET_IID(nsXPCOMCycleCollectionParticipant), \ + NS_GET_IID( \ + nsCycleCollectionISupports)) && /* The calls to LowWordEquals \ + here are repeated inside the \ + if branch. This is due to the \ + fact that we need to maintain \ + the if/else chain for these \ + macros, so that the control \ + flow never enters the if \ + branch unless if we're \ + certain one of the \ + LowWordEquals() branches will \ + get executed. */ \ + (LowWordEquals(aIID, NS_GET_IID(nsXPCOMCycleCollectionParticipant)) || \ + LowWordEquals(aIID, NS_GET_IID(nsCycleCollectionISupports)))) { \ + if (LowWordEquals(aIID, NS_GET_IID(nsXPCOMCycleCollectionParticipant))) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + return NS_OK; \ + } \ + if (LowWordEquals(aIID, NS_GET_IID(nsCycleCollectionISupports))) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + return NS_OK; \ + } \ + /* Avoid warnings about foundInterface being left uninitialized. */ \ + foundInterface = nullptr; \ + } else + +#define NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(_class) \ + NS_INTERFACE_MAP_BEGIN(_class) \ + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) + +#define NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(_class) \ + if (rv == NS_OK) return rv; \ + nsISupports* foundInterface; \ + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(_class) + +// The IIDs for nsXPCOMCycleCollectionParticipant and nsCycleCollectionISupports +// are special in that they only differ in their last byte. This allows for the +// optimization below where we first check the first three words of the IID and +// if we find a match we check the last word to decide which case we have to +// deal with. +#define NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(_class) \ + NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) { \ + MOZ_ASSERT(aInstancePtr, "null out param"); \ + \ + if (TopThreeWordsEquals(aIID, \ + NS_GET_IID(nsXPCOMCycleCollectionParticipant), \ + NS_GET_IID(nsCycleCollectionISupports))) { \ + if (LowWordEquals(aIID, \ + NS_GET_IID(nsXPCOMCycleCollectionParticipant))) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_PARTICIPANT(_class); \ + return NS_OK; \ + } \ + if (LowWordEquals(aIID, NS_GET_IID(nsCycleCollectionISupports))) { \ + *aInstancePtr = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + return NS_OK; \ + } \ + } \ + nsresult rv = NS_ERROR_FAILURE; + +#define NS_CYCLE_COLLECTION_UPCAST(obj, clazz) \ + NS_CYCLE_COLLECTION_CLASSNAME(clazz)::Upcast(obj) + +#ifdef DEBUG +# define NS_CHECK_FOR_RIGHT_PARTICIPANT(_ptr) _ptr->CheckForRightParticipant() +#else +# define NS_CHECK_FOR_RIGHT_PARTICIPANT(_ptr) +#endif + +// The default implementation of this class template is empty, because it +// should never be used: see the partial specializations below. +template ::value> +struct DowncastCCParticipantImpl {}; + +// Specialization for XPCOM CC participants +template +struct DowncastCCParticipantImpl { + static T* Run(void* aPtr) { + nsISupports* s = static_cast(aPtr); + MOZ_ASSERT(NS_CYCLE_COLLECTION_CLASSNAME(T)::CheckForRightISupports(s), + "not the nsISupports pointer we expect"); + T* rval = NS_CYCLE_COLLECTION_CLASSNAME(T)::Downcast(s); + NS_CHECK_FOR_RIGHT_PARTICIPANT(rval); + return rval; + } +}; + +// Specialization for native CC participants +template +struct DowncastCCParticipantImpl { + static T* Run(void* aPtr) { return static_cast(aPtr); } +}; + +template +T* DowncastCCParticipant(void* aPtr) { + return DowncastCCParticipantImpl::Run(aPtr); +} + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing CanSkip methods +/////////////////////////////////////////////////////////////////////////////// + +// See documentation for nsCycleCollectionParticipant::CanSkip for documentation +// about this method. +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipReal(void* p, \ + bool aRemovingAllowed) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END \ + (void)tmp; \ + return false; \ + } + +// See documentation for nsCycleCollectionParticipant::CanSkipInCC for +// documentation about this method. +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipInCCReal(void* p) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END \ + (void)tmp; \ + return false; \ + } + +// See documentation for nsCycleCollectionParticipant::CanSkipThis for +// documentation about this method. +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(_class) \ + NS_IMETHODIMP_(bool) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::CanSkipThisReal(void* p) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END \ + (void)tmp; \ + return false; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing nsCycleCollectionParticipant::Unlink +// +// You need to use NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED if you want +// the base class Unlink version to be called before your own implementation. +// You can use NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED if you want the +// base class Unlink to get called after your own implementation. You should +// never use them together. +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMETHODIMP_(void) \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::Unlink(void* p) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + nsISupports* s = static_cast(p); \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Unlink(s); + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_HELPER(_field) \ + ImplCycleCollectionUnlink(tmp->_field); + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK(...) \ + MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_UNLINK_HELPER, (), (__VA_ARGS__)) + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + (void)tmp; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(_base_class) \ + nsISupports* s = static_cast(p); \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Unlink(s); \ + (void)tmp; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_0(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing nsCycleCollectionParticipant::Traverse +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, _refcnt) \ + cb.DescribeRefCountedNode(_refcnt, #_class); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \ + NS_IMETHODIMP \ + NS_CYCLE_COLLECTION_CLASSNAME(_class)::TraverseNative( \ + void* p, nsCycleCollectionTraversalCallback& cb) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \ + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(_class, tmp->mRefCnt.get()) + +// Base class' CC participant should return NS_SUCCESS_INTERRUPTED_TRAVERSE +// from Traverse if it wants derived classes to not traverse anything from +// their CC participant. + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(_class) \ + nsISupports* s = static_cast(p); \ + if (NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::TraverseNative(s, cb) == \ + NS_SUCCESS_INTERRUPTED_TRAVERSE) { \ + return NS_SUCCESS_INTERRUPTED_TRAVERSE; \ + } + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_HELPER(_field) \ + ImplCycleCollectionTraverse(cb, tmp->_field, #_field, 0); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE(...) \ + MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_TRAVERSE_HELPER, (), (__VA_ARGS__)) + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(_field) \ + CycleCollectionNoteChild(cb, tmp->_field, #_field); + +#define NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + (void)tmp; \ + return NS_OK; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing nsScriptObjectTracer::Trace +/////////////////////////////////////////////////////////////////////////////// + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \ + void NS_CYCLE_COLLECTION_CLASSNAME(_class)::Trace( \ + void* p, const TraceCallbacks& aCallbacks, void* aClosure) { \ + _class* tmp = DowncastCCParticipant<_class>(p); + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(_class, _base_class) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(_class) \ + nsISupports* s = static_cast(p); \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Trace(s, aCallbacks, aClosure); + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(_field) \ + aCallbacks.Trace(&tmp->_field, #_field, aClosure); + +// NB: The (void)tmp; hack in the TRACE_END macro exists to support +// implementations that don't need to do anything in their Trace method. +// Without this hack, some compilers warn about the unused tmp local. +#define NS_IMPL_CYCLE_COLLECTION_TRACE_END \ + (void)tmp; \ + } + +/////////////////////////////////////////////////////////////////////////////// +// Helpers for implementing a concrete nsCycleCollectionParticipant +/////////////////////////////////////////////////////////////////////////////// + +// If a class defines a participant, then QIing an instance of that class to +// nsXPCOMCycleCollectionParticipant should produce that participant. +#ifdef DEBUG +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_BASE \ + virtual void CheckForRightParticipant() +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_DERIVED \ + virtual void CheckForRightParticipant() override +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) \ + { \ + nsXPCOMCycleCollectionParticipant* p; \ + CallQueryInterface(this, &p); \ + MOZ_ASSERT(p == &NS_CYCLE_COLLECTION_INNERNAME, \ + #_class " should QI to its own CC participant"); \ + } +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_BASE \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_DERIVED \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_BODY(_class) +#else +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) +# define NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) +#endif + +#define NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(const char*) ClassName() override { return #_class; }; + +#define NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + public: \ + NS_IMETHOD TraverseNative(void* p, nsCycleCollectionTraversalCallback& cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void* p) override { \ + DowncastCCParticipant<_class>(p)->DeleteCycleCollectable(); \ + } \ + static _class* Downcast(nsISupports* s) { \ + return static_cast<_class*>(static_cast<_base*>(s)); \ + } \ + static nsISupports* Upcast(_class* p) { \ + return NS_ISUPPORTS_CAST(_base*, p); \ + } \ + template \ + friend nsISupports* ToSupports(T* p, NS_CYCLE_COLLECTION_INNERCLASS* dummy); \ + NS_IMETHOD_(void) Unlink(void* p) override; + +#define NS_PARTICIPANT_AS(type, participant) \ + const_cast(reinterpret_cast(participant)) + +#define NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + static constexpr nsXPCOMCycleCollectionParticipant* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } + +/** + * We use this macro to force that classes that inherit from a ccable class and + * declare their own participant declare themselves as inherited cc classes. + * To avoid possibly unnecessary vtables we only do this checking in debug + * builds. + */ +#ifdef DEBUG +# define NOT_INHERITED_CANT_OVERRIDE \ + virtual void BaseCycleCollectable() final {} +#else +# define NOT_INHERITED_CANT_OVERRIDE +#endif + +#define NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(_class, _base) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : nsXPCOMCycleCollectionParticipant(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ + NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(_class, _class) + +// Cycle collector helper for ambiguous classes that can sometimes be skipped. +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(_class, _base) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \ + Flags aFlags = FlagMightSkip) /* We always want skippability. */ \ + : nsXPCOMCycleCollectionParticipant(aFlags | FlagMightSkip) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ + NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_CLASS_AMBIGUOUS(_class, _class) + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _base) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : nsXPCOMCycleCollectionParticipant(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ + NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS( \ + _class, _base) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public nsXPCOMCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \ + Flags aFlags = FlagMightSkip) /* We always want skippability. */ \ + : nsXPCOMCycleCollectionParticipant(aFlags | FlagMightSkip) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_BODY(_class, _base) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; \ + NOT_INHERITED_CANT_OVERRIDE + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, \ + _class) + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_SCRIPT_HOLDER_CLASS_INHERITED( \ + _class, _base_class) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \ + Flags aFlags = FlagMightSkip | /* We always want skippability. */ \ + FlagMultiZoneJSHolder) \ + : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)(aFlags | FlagMightSkip) { \ + } \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(_class) \ + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(_class, _class) + +#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + public: \ + NS_IMETHOD TraverseNative(void* p, nsCycleCollectionTraversalCallback& cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + static _class* Downcast(nsISupports* s) { \ + return static_cast<_class*>(static_cast<_base_class*>( \ + NS_CYCLE_COLLECTION_CLASSNAME(_base_class)::Downcast(s))); \ + } \ + NS_IMETHOD_(void) Unlink(void* p) override; + +#define NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(_class, _base_class) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(_class, \ + _base_class) \ + class NS_CYCLE_COLLECTION_INNERCLASS \ + : public NS_CYCLE_COLLECTION_CLASSNAME(_base_class) { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : NS_CYCLE_COLLECTION_CLASSNAME(_base_class)(aFlags | \ + FlagMultiZoneJSHolder) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_BODY(_class, _base_class) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + NS_IMPL_GET_XPCOM_CYCLE_COLLECTION_PARTICIPANT(_class) \ + }; \ + NS_CHECK_FOR_RIGHT_PARTICIPANT_IMPL_INHERITED(_class) \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +// Cycle collector participant declarations. + +#define NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + public: \ + NS_IMETHOD_(void) Root(void* p) override { \ + static_cast<_class*>(p)->AddRef(); \ + } \ + NS_IMETHOD_(void) Unlink(void* n) override; \ + NS_IMETHOD_(void) Unroot(void* p) override { \ + static_cast<_class*>(p)->Release(); \ + } \ + NS_IMETHOD TraverseNative(void* n, nsCycleCollectionTraversalCallback& cb) \ + override; \ + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(_class) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void* n) override { \ + DowncastCCParticipant<_class>(n)->DeleteCycleCollectable(); \ + } \ + static _class* Downcast(void* s) { \ + return DowncastCCParticipant<_class>(s); \ + } \ + static void* Upcast(_class* p) { return static_cast(p); } + +#define NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(_class) \ + void DeleteCycleCollectable(void) { delete this; } \ + class NS_CYCLE_COLLECTION_INNERCLASS : public nsCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : nsCycleCollectionParticipant(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + static constexpr nsCycleCollectionParticipant* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS(_class) \ + void DeleteCycleCollectable(void) { delete this; } \ + class NS_CYCLE_COLLECTION_INNERCLASS : public nsCycleCollectionParticipant { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS( \ + Flags aFlags = FlagMightSkip) /* We always want skippability. */ \ + : nsCycleCollectionParticipant(aFlags | FlagMightSkip) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + static nsCycleCollectionParticipant* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS_WITH_CUSTOM_DELETE( \ + _class) \ + class NS_CYCLE_COLLECTION_INNERCLASS : public nsCycleCollectionParticipant { \ + public: \ + constexpr NS_CYCLE_COLLECTION_INNERCLASS() \ + : nsCycleCollectionParticipant(true) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + NS_IMETHOD_(bool) CanSkipReal(void* p, bool aRemovingAllowed) override; \ + NS_IMETHOD_(bool) CanSkipInCCReal(void* p) override; \ + NS_IMETHOD_(bool) CanSkipThisReal(void* p) override; \ + static nsCycleCollectionParticipant* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(_class) \ + void DeleteCycleCollectable(void) { delete this; } \ + class NS_CYCLE_COLLECTION_INNERCLASS : public nsScriptObjectTracer { \ + public: \ + constexpr explicit NS_CYCLE_COLLECTION_INNERCLASS(Flags aFlags = 0) \ + : nsScriptObjectTracer(aFlags) {} \ + \ + private: \ + NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS_BODY(_class) \ + NS_IMETHOD_(void) \ + Trace(void* p, const TraceCallbacks& cb, void* closure) override; \ + static constexpr nsScriptObjectTracer* GetParticipant() { \ + return &_class::NS_CYCLE_COLLECTION_INNERNAME; \ + } \ + }; \ + static NS_CYCLE_COLLECTION_INNERCLASS NS_CYCLE_COLLECTION_INNERNAME; + +#define NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS _class::NS_CYCLE_COLLECTION_INNERNAME; + +// By default JS holders are treated as if they may store pointers to multiple +// zones, but one may optimize GC handling by using single zone holders. +#define NS_IMPL_CYCLE_COLLECTION_SINGLE_ZONE_SCRIPT_HOLDER_CLASS(_class) \ + _class::NS_CYCLE_COLLECTION_INNERCLASS \ + _class::NS_CYCLE_COLLECTION_INNERNAME( \ + nsCycleCollectionParticipant::FlagMaybeSingleZoneJSHolder); + +// NB: This is not something you usually want to use. It is here to allow +// adding things to the CC graph to help debugging via CC logs, but it does not +// traverse or unlink anything, so it is useless for anything else. +#define NS_IMPL_CYCLE_COLLECTION_0(_class) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_IMPL_CYCLE_COLLECTION(_class, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(_class) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +// If you are looking for NS_IMPL_CYCLE_COLLECTION_INHERITED_0(_class, _base) +// you should instead not declare any cycle collected stuff in _class, so it +// will just inherit the CC declarations from _base. + +#define NS_IMPL_CYCLE_COLLECTION_INHERITED(_class, _base, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(_class) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(_class, _base) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(_class, _base) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_CYCLE_COLLECTION_NOTE_EDGE_NAME CycleCollectionNoteEdgeName + +/** + * Convenience macros for defining nISupports methods in a cycle collected + * class. + */ + +#define NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(aClass, aSuper, \ + ...) \ + NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(aClass) \ + NS_INTERFACE_TABLE_INHERITED(aClass, __VA_ARGS__) \ + NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) + +#define NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(aClass, aSuper, ...) \ + NS_IMPL_QUERY_INTERFACE_CYCLE_COLLECTION_INHERITED(aClass, aSuper, \ + __VA_ARGS__) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) + +#define NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(aClass, aSuper) \ + NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(aClass) \ + NS_INTERFACE_MAP_END_INHERITING(aSuper) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) + +/** + * Equivalency of the high three words where two IIDs have the same + * top three words but not the same low word. + */ +inline bool TopThreeWordsEquals(const nsID& aID, const nsID& aOther1, + const nsID& aOther2) { + MOZ_ASSERT((((uint32_t*)&aOther1.m0)[0] == ((uint32_t*)&aOther2.m0)[0]) && + (((uint32_t*)&aOther1.m0)[1] == ((uint32_t*)&aOther2.m0)[1]) && + (((uint32_t*)&aOther1.m0)[2] == ((uint32_t*)&aOther2.m0)[2]) && + (((uint32_t*)&aOther1.m0)[3] != ((uint32_t*)&aOther2.m0)[3])); + + return ((((uint32_t*)&aID.m0)[0] == ((uint32_t*)&aOther1.m0)[0]) && + (((uint32_t*)&aID.m0)[1] == ((uint32_t*)&aOther1.m0)[1]) && + (((uint32_t*)&aID.m0)[2] == ((uint32_t*)&aOther1.m0)[2])); +} + +/** + * Equivalency of the fourth word where the two IIDs have the same + * top three words but not the same low word. + */ +inline bool LowWordEquals(const nsID& aID, const nsID& aOther) { + return (((uint32_t*)&aID.m0)[3] == ((uint32_t*)&aOther.m0)[3]); +} + +// Template magic to modify JS::Heap without including relevant headers, to +// prevent excessive header dependency. +template +inline void ImplCycleCollectionUnlink(JS::Heap& aField) { + aField.setNull(); +} +template +inline void ImplCycleCollectionUnlink(JS::Heap& aField) { + aField = nullptr; +} + +#define NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS(...) \ + MOZ_ASSERT(!IsSingleZoneJSHolder()); \ + MOZ_FOR_EACH(NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK, (), \ + (__VA_ARGS__)) + +#define NS_IMPL_CYCLE_COLLECTION_WITH_JS_MEMBERS(class_, native_members_, \ + js_members_) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(class_) \ + using ::ImplCycleCollectionUnlink; \ + NS_IMPL_CYCLE_COLLECTION_UNLINK( \ + MOZ_FOR_EACH_EXPAND_HELPER native_members_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(MOZ_FOR_EACH_EXPAND_HELPER js_members_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(class_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE( \ + MOZ_FOR_EACH_EXPAND_HELPER native_members_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(class_) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS( \ + MOZ_FOR_EACH_EXPAND_HELPER js_members_) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_END + +#define NS_IMPL_CYCLE_COLLECTION_INHERITED_WITH_JS_MEMBERS( \ + class_, _base, native_members_, js_members_) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(class_, _base) \ + using ::ImplCycleCollectionUnlink; \ + NS_IMPL_CYCLE_COLLECTION_UNLINK( \ + MOZ_FOR_EACH_EXPAND_HELPER native_members_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(MOZ_FOR_EACH_EXPAND_HELPER js_members_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(class_, _base) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE( \ + MOZ_FOR_EACH_EXPAND_HELPER native_members_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END \ + NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(class_, _base) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBERS( \ + MOZ_FOR_EACH_EXPAND_HELPER js_members_) \ + NS_IMPL_CYCLE_COLLECTION_TRACE_END + +template +inline void ImplCycleCollectionUnlink(std::tuple& aField) { + std::apply([](auto&&... aArgs) { (ImplCycleCollectionUnlink(aArgs), ...); }, + aField); +} +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + std::tuple& aField, const char* aName, uint32_t aFlags) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + std::apply( + [&](auto&&... aArgs) { + (ImplCycleCollectionTraverse(aCallback, aArgs, aName, aFlags), ...); + }, + aField); +} + +#endif // nsCycleCollectionParticipant_h__ diff --git a/xpcom/base/nsCycleCollectionTraversalCallback.h b/xpcom/base/nsCycleCollectionTraversalCallback.h new file mode 100644 index 0000000000..9317a5e23a --- /dev/null +++ b/xpcom/base/nsCycleCollectionTraversalCallback.h @@ -0,0 +1,70 @@ +/* -*- 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/. */ + +#ifndef nsCycleCollectionTraversalCallback_h__ +#define nsCycleCollectionTraversalCallback_h__ + +#include +#include "nscore.h" + +class nsCycleCollectionParticipant; +class nsISupports; +class JSObject; + +namespace JS { +class GCCellPtr; +} + +class NS_NO_VTABLE nsCycleCollectionTraversalCallback { + public: + // You must call DescribeRefCountedNode() with an accurate + // refcount, otherwise cycle collection will fail, and probably crash. + // If the callback cares about objname, it should put + // WANT_DEBUG_INFO in mFlags. + NS_IMETHOD_(void) + DescribeRefCountedNode(nsrefcnt aRefcount, const char* aObjName) = 0; + // Note, aCompartmentAddress is 0 if it is unknown. + NS_IMETHOD_(void) + DescribeGCedNode(bool aIsMarked, const char* aObjName, + uint64_t aCompartmentAddress = 0) = 0; + + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) = 0; + NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aThing) = 0; + NS_IMETHOD_(void) + NoteNativeChild(void* aChild, nsCycleCollectionParticipant* aHelper) = 0; + + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aKey, nsISupports* aVal, + nsCycleCollectionParticipant* aValParticipant) = 0; + + // Give a name to the edge associated with the next call to + // NoteXPCOMChild, NoteJSObject, NoteJSScript, or NoteNativeChild. + // Callbacks who care about this should set WANT_DEBUG_INFO in the + // flags. + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) = 0; + + enum { + // Values for flags: + + // Caller should call NoteNextEdgeName and pass useful objName + // to DescribeRefCountedNode and DescribeGCedNode. + WANT_DEBUG_INFO = (1 << 0), + + // Caller should not skip objects that we know will be + // uncollectable. + WANT_ALL_TRACES = (1 << 1) + }; + uint32_t Flags() const { return mFlags; } + bool WantDebugInfo() const { return (mFlags & WANT_DEBUG_INFO) != 0; } + bool WantAllTraces() const { return (mFlags & WANT_ALL_TRACES) != 0; } + + protected: + nsCycleCollectionTraversalCallback() : mFlags(0) {} + + uint32_t mFlags; +}; + +#endif // nsCycleCollectionTraversalCallback_h__ diff --git a/xpcom/base/nsCycleCollector.cpp b/xpcom/base/nsCycleCollector.cpp new file mode 100644 index 0000000000..7bf0154448 --- /dev/null +++ b/xpcom/base/nsCycleCollector.cpp @@ -0,0 +1,4050 @@ +/* -*- 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/. */ + +// +// This file implements a garbage-cycle collector based on the paper +// +// Concurrent Cycle Collection in Reference Counted Systems +// Bacon & Rajan (2001), ECOOP 2001 / Springer LNCS vol 2072 +// +// We are not using the concurrent or acyclic cases of that paper; so +// the green, red and orange colors are not used. +// +// The collector is based on tracking pointers of four colors: +// +// Black nodes are definitely live. If we ever determine a node is +// black, it's ok to forget about, drop from our records. +// +// White nodes are definitely garbage cycles. Once we finish with our +// scanning, we unlink all the white nodes and expect that by +// unlinking them they will self-destruct (since a garbage cycle is +// only keeping itself alive with internal links, by definition). +// +// Snow-white is an addition to the original algorithm. A snow-white node +// has reference count zero and is just waiting for deletion. +// +// Grey nodes are being scanned. Nodes that turn grey will turn +// either black if we determine that they're live, or white if we +// determine that they're a garbage cycle. After the main collection +// algorithm there should be no grey nodes. +// +// Purple nodes are *candidates* for being scanned. They are nodes we +// haven't begun scanning yet because they're not old enough, or we're +// still partway through the algorithm. +// +// XPCOM objects participating in garbage-cycle collection are obliged +// to inform us when they ought to turn purple; that is, when their +// refcount transitions from N+1 -> N, for nonzero N. Furthermore we +// require that *after* an XPCOM object has informed us of turning +// purple, they will tell us when they either transition back to being +// black (incremented refcount) or are ultimately deleted. + +// Incremental cycle collection +// +// Beyond the simple state machine required to implement incremental +// collection, the CC needs to be able to compensate for things the browser +// is doing during the collection. There are two kinds of problems. For each +// of these, there are two cases to deal with: purple-buffered C++ objects +// and JS objects. + +// The first problem is that an object in the CC's graph can become garbage. +// This is bad because the CC touches the objects in its graph at every +// stage of its operation. +// +// All cycle collected C++ objects that die during a cycle collection +// will end up actually getting deleted by the SnowWhiteKiller. Before +// the SWK deletes an object, it checks if an ICC is running, and if so, +// if the object is in the graph. If it is, the CC clears mPointer and +// mParticipant so it does not point to the raw object any more. Because +// objects could die any time the CC returns to the mutator, any time the CC +// accesses a PtrInfo it must perform a null check on mParticipant to +// ensure the object has not gone away. +// +// JS objects don't always run finalizers, so the CC can't remove them from +// the graph when they die. Fortunately, JS objects can only die during a GC, +// so if a GC is begun during an ICC, the browser synchronously finishes off +// the ICC, which clears the entire CC graph. If the GC and CC are scheduled +// properly, this should be rare. +// +// The second problem is that objects in the graph can be changed, say by +// being addrefed or released, or by having a field updated, after the object +// has been added to the graph. The problem is that ICC can miss a newly +// created reference to an object, and end up unlinking an object that is +// actually alive. +// +// The basic idea of the solution, from "An on-the-fly Reference Counting +// Garbage Collector for Java" by Levanoni and Petrank, is to notice if an +// object has had an additional reference to it created during the collection, +// and if so, don't collect it during the current collection. This avoids having +// to rerun the scan as in Bacon & Rajan 2001. +// +// For cycle collected C++ objects, we modify AddRef to place the object in +// the purple buffer, in addition to Release. Then, in the CC, we treat any +// objects in the purple buffer as being alive, after graph building has +// completed. Because they are in the purple buffer, they will be suspected +// in the next CC, so there's no danger of leaks. This is imprecise, because +// we will treat as live an object that has been Released but not AddRefed +// during graph building, but that's probably rare enough that the additional +// bookkeeping overhead is not worthwhile. +// +// For JS objects, the cycle collector is only looking at gray objects. If a +// gray object is touched during ICC, it will be made black by UnmarkGray. +// Thus, if a JS object has become black during the ICC, we treat it as live. +// Merged JS zones have to be handled specially: we scan all zone globals. +// If any are black, we treat the zone as being black. + +// Safety +// +// An XPCOM object is either scan-safe or scan-unsafe, purple-safe or +// purple-unsafe. +// +// An nsISupports object is scan-safe if: +// +// - It can be QI'ed to |nsXPCOMCycleCollectionParticipant|, though +// this operation loses ISupports identity (like nsIClassInfo). +// - Additionally, the operation |traverse| on the resulting +// nsXPCOMCycleCollectionParticipant does not cause *any* refcount +// adjustment to occur (no AddRef / Release calls). +// +// A non-nsISupports ("native") object is scan-safe by explicitly +// providing its nsCycleCollectionParticipant. +// +// An object is purple-safe if it satisfies the following properties: +// +// - The object is scan-safe. +// +// When we receive a pointer |ptr| via +// |nsCycleCollector::suspect(ptr)|, we assume it is purple-safe. We +// can check the scan-safety, but have no way to ensure the +// purple-safety; objects must obey, or else the entire system falls +// apart. Don't involve an object in this scheme if you can't +// guarantee its purple-safety. The easiest way to ensure that an +// object is purple-safe is to use nsCycleCollectingAutoRefCnt. +// +// When we have a scannable set of purple nodes ready, we begin +// our walks. During the walks, the nodes we |traverse| should only +// feed us more scan-safe nodes, and should not adjust the refcounts +// of those nodes. +// +// We do not |AddRef| or |Release| any objects during scanning. We +// rely on the purple-safety of the roots that call |suspect| to +// hold, such that we will clear the pointer from the purple buffer +// entry to the object before it is destroyed. The pointers that are +// merely scan-safe we hold only for the duration of scanning, and +// there should be no objects released from the scan-safe set during +// the scan. +// +// We *do* call |Root| and |Unroot| on every white object, on +// either side of the calls to |Unlink|. This keeps the set of white +// objects alive during the unlinking. +// + +#if !defined(__MINGW32__) +# ifdef WIN32 +# include +# include +# endif +#endif + +#include "base/process_util.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/CycleCollectedJSRuntime.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HashTable.h" +#include "mozilla/HoldDropJSObjects.h" +/* This must occur *after* base/process_util.h to avoid typedefs conflicts. */ +#include +#include + +#include + +#include "js/SliceBudget.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoGlobalTimelineMarker.h" +#include "mozilla/Likely.h" +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/MruCache.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/SegmentedVector.h" +#include "mozilla/Telemetry.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/UniquePtr.h" +#include "nsCycleCollectionNoteRootCallback.h" +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" +#include "nsDeque.h" +#include "nsDumpUtils.h" +#include "nsExceptionHandler.h" +#include "nsIConsoleService.h" +#include "nsICycleCollectorListener.h" +#include "nsIFile.h" +#include "nsIMemoryReporter.h" +#include "nsISerialEventTarget.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "prenv.h" +#include "xpcpublic.h" + +using namespace mozilla; + +struct NurseryPurpleBufferEntry { + void* mPtr; + nsCycleCollectionParticipant* mParticipant; + nsCycleCollectingAutoRefCnt* mRefCnt; +}; + +#define NURSERY_PURPLE_BUFFER_SIZE 2048 +bool gNurseryPurpleBufferEnabled = true; +NurseryPurpleBufferEntry gNurseryPurpleBufferEntry[NURSERY_PURPLE_BUFFER_SIZE]; +uint32_t gNurseryPurpleBufferEntryCount = 0; + +void ClearNurseryPurpleBuffer(); + +static void SuspectUsingNurseryPurpleBuffer( + void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(gNurseryPurpleBufferEnabled); + if (gNurseryPurpleBufferEntryCount == NURSERY_PURPLE_BUFFER_SIZE) { + ClearNurseryPurpleBuffer(); + } + + gNurseryPurpleBufferEntry[gNurseryPurpleBufferEntryCount] = {aPtr, aCp, + aRefCnt}; + ++gNurseryPurpleBufferEntryCount; +} + +// #define COLLECT_TIME_DEBUG + +// Enable assertions that are useful for diagnosing errors in graph +// construction. +// #define DEBUG_CC_GRAPH + +#define DEFAULT_SHUTDOWN_COLLECTIONS 5 + +// One to do the freeing, then another to detect there is no more work to do. +#define NORMAL_SHUTDOWN_COLLECTIONS 2 + +// Cycle collector environment variables +// +// MOZ_CC_LOG_ALL: If defined, always log cycle collector heaps. +// +// MOZ_CC_LOG_SHUTDOWN: If defined, log cycle collector heaps at shutdown. +// +// MOZ_CC_LOG_SHUTDOWN_SKIP: If set to a non-negative integer value n, then +// skip logging for the first n shutdown CCs. This implies MOZ_CC_LOG_SHUTDOWN. +// The first log or two are much larger than the rest, so it can be useful to +// reduce the total size of logs if you know already that the initial logs +// aren't interesting. +// +// MOZ_CC_LOG_THREAD: If set to "main", only automatically log main thread +// CCs. If set to "worker", only automatically log worker CCs. If set to "all", +// log either. The default value is "all". This must be used with either +// MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything. +// +// MOZ_CC_LOG_PROCESS: If set to "main", only automatically log main process +// CCs. If set to "content", only automatically log tab CCs. If set to "all", +// log everything. The default value is "all". This must be used with either +// MOZ_CC_LOG_ALL or MOZ_CC_LOG_SHUTDOWN for it to do anything. +// +// MOZ_CC_ALL_TRACES: If set to "all", any cycle collector +// logging done will be WantAllTraces, which disables +// various cycle collector optimizations to give a fuller picture of +// the heap. If set to "shutdown", only shutdown logging will be WantAllTraces. +// The default is none. +// +// MOZ_CC_RUN_DURING_SHUTDOWN: In non-DEBUG or builds, if this is set, +// run cycle collections at shutdown. +// +// MOZ_CC_LOG_DIRECTORY: The directory in which logs are placed (such as +// logs from MOZ_CC_LOG_ALL and MOZ_CC_LOG_SHUTDOWN, or other uses +// of nsICycleCollectorListener) + +// Various parameters of this collector can be tuned using environment +// variables. + +struct nsCycleCollectorParams { + bool mLogAll; + bool mLogShutdown; + bool mAllTracesAll; + bool mAllTracesShutdown; + bool mLogThisThread; + int32_t mLogShutdownSkip = 0; + + nsCycleCollectorParams() + : mLogAll(PR_GetEnv("MOZ_CC_LOG_ALL") != nullptr), + mLogShutdown(PR_GetEnv("MOZ_CC_LOG_SHUTDOWN") != nullptr), + mAllTracesAll(false), + mAllTracesShutdown(false) { + if (const char* lssEnv = PR_GetEnv("MOZ_CC_LOG_SHUTDOWN_SKIP")) { + mLogShutdown = true; + nsDependentCString lssString(lssEnv); + nsresult rv; + int32_t lss = lssString.ToInteger(&rv); + if (NS_SUCCEEDED(rv) && lss >= 0) { + mLogShutdownSkip = lss; + } + } + + const char* logThreadEnv = PR_GetEnv("MOZ_CC_LOG_THREAD"); + bool threadLogging = true; + if (logThreadEnv && !!strcmp(logThreadEnv, "all")) { + if (NS_IsMainThread()) { + threadLogging = !strcmp(logThreadEnv, "main"); + } else { + threadLogging = !strcmp(logThreadEnv, "worker"); + } + } + + const char* logProcessEnv = PR_GetEnv("MOZ_CC_LOG_PROCESS"); + bool processLogging = true; + if (logProcessEnv && !!strcmp(logProcessEnv, "all")) { + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + processLogging = !strcmp(logProcessEnv, "main"); + break; + case GeckoProcessType_Content: + processLogging = !strcmp(logProcessEnv, "content"); + break; + default: + processLogging = false; + break; + } + } + mLogThisThread = threadLogging && processLogging; + + const char* allTracesEnv = PR_GetEnv("MOZ_CC_ALL_TRACES"); + if (allTracesEnv) { + if (!strcmp(allTracesEnv, "all")) { + mAllTracesAll = true; + } else if (!strcmp(allTracesEnv, "shutdown")) { + mAllTracesShutdown = true; + } + } + } + + // aShutdownCount is how many shutdown CCs we've started. + // For non-shutdown CCs, we'll pass in 0. + // For the first shutdown CC, we'll pass in 1. + bool LogThisCC(int32_t aShutdownCount) { + if (mLogAll) { + return mLogThisThread; + } + if (aShutdownCount == 0 || !mLogShutdown) { + return false; + } + if (aShutdownCount <= mLogShutdownSkip) { + return false; + } + return mLogThisThread; + } + + bool AllTracesThisCC(bool aIsShutdown) { + return mAllTracesAll || (aIsShutdown && mAllTracesShutdown); + } +}; + +#ifdef COLLECT_TIME_DEBUG +class TimeLog { + public: + TimeLog() : mLastCheckpoint(TimeStamp::Now()) {} + + void Checkpoint(const char* aEvent) { + TimeStamp now = TimeStamp::Now(); + double dur = (now - mLastCheckpoint).ToMilliseconds(); + if (dur >= 0.5) { + printf("cc: %s took %.1fms\n", aEvent, dur); + } + mLastCheckpoint = now; + } + + private: + TimeStamp mLastCheckpoint; +}; +#else +class TimeLog { + public: + TimeLog() = default; + void Checkpoint(const char* aEvent) {} +}; +#endif + +//////////////////////////////////////////////////////////////////////// +// Base types +//////////////////////////////////////////////////////////////////////// + +class PtrInfo; + +class EdgePool { + public: + // EdgePool allocates arrays of void*, primarily to hold PtrInfo*. + // However, at the end of a block, the last two pointers are a null + // and then a void** pointing to the next block. This allows + // EdgePool::Iterators to be a single word but still capable of crossing + // block boundaries. + + EdgePool() { + mSentinelAndBlocks[0].block = nullptr; + mSentinelAndBlocks[1].block = nullptr; + } + + ~EdgePool() { + MOZ_ASSERT(!mSentinelAndBlocks[0].block && !mSentinelAndBlocks[1].block, + "Didn't call Clear()?"); + } + + void Clear() { + EdgeBlock* b = EdgeBlocks(); + while (b) { + EdgeBlock* next = b->Next(); + delete b; + b = next; + } + + mSentinelAndBlocks[0].block = nullptr; + mSentinelAndBlocks[1].block = nullptr; + } + +#ifdef DEBUG + bool IsEmpty() { + return !mSentinelAndBlocks[0].block && !mSentinelAndBlocks[1].block; + } +#endif + + private: + struct EdgeBlock; + union PtrInfoOrBlock { + // Use a union to avoid reinterpret_cast and the ensuing + // potential aliasing bugs. + PtrInfo* ptrInfo; + EdgeBlock* block; + }; + struct EdgeBlock { + enum { EdgeBlockSize = 16 * 1024 }; + + PtrInfoOrBlock mPointers[EdgeBlockSize]; + EdgeBlock() { + mPointers[EdgeBlockSize - 2].block = nullptr; // sentinel + mPointers[EdgeBlockSize - 1].block = nullptr; // next block pointer + } + EdgeBlock*& Next() { return mPointers[EdgeBlockSize - 1].block; } + PtrInfoOrBlock* Start() { return &mPointers[0]; } + PtrInfoOrBlock* End() { return &mPointers[EdgeBlockSize - 2]; } + }; + + // Store the null sentinel so that we can have valid iterators + // before adding any edges and without adding any blocks. + PtrInfoOrBlock mSentinelAndBlocks[2]; + + EdgeBlock*& EdgeBlocks() { return mSentinelAndBlocks[1].block; } + EdgeBlock* EdgeBlocks() const { return mSentinelAndBlocks[1].block; } + + public: + class Iterator { + public: + Iterator() : mPointer(nullptr) {} + explicit Iterator(PtrInfoOrBlock* aPointer) : mPointer(aPointer) {} + Iterator(const Iterator& aOther) = default; + + Iterator& operator++() { + if (!mPointer->ptrInfo) { + // Null pointer is a sentinel for link to the next block. + mPointer = (mPointer + 1)->block->mPointers; + } + ++mPointer; + return *this; + } + + PtrInfo* operator*() const { + if (!mPointer->ptrInfo) { + // Null pointer is a sentinel for link to the next block. + return (mPointer + 1)->block->mPointers->ptrInfo; + } + return mPointer->ptrInfo; + } + bool operator==(const Iterator& aOther) const { + return mPointer == aOther.mPointer; + } + bool operator!=(const Iterator& aOther) const { + return mPointer != aOther.mPointer; + } + +#ifdef DEBUG_CC_GRAPH + bool Initialized() const { return mPointer != nullptr; } +#endif + + private: + PtrInfoOrBlock* mPointer; + }; + + class Builder; + friend class Builder; + class Builder { + public: + explicit Builder(EdgePool& aPool) + : mCurrent(&aPool.mSentinelAndBlocks[0]), + mBlockEnd(&aPool.mSentinelAndBlocks[0]), + mNextBlockPtr(&aPool.EdgeBlocks()) {} + + Iterator Mark() { return Iterator(mCurrent); } + + void Add(PtrInfo* aEdge) { + if (mCurrent == mBlockEnd) { + EdgeBlock* b = new EdgeBlock(); + *mNextBlockPtr = b; + mCurrent = b->Start(); + mBlockEnd = b->End(); + mNextBlockPtr = &b->Next(); + } + (mCurrent++)->ptrInfo = aEdge; + } + + private: + // mBlockEnd points to space for null sentinel + PtrInfoOrBlock* mCurrent; + PtrInfoOrBlock* mBlockEnd; + EdgeBlock** mNextBlockPtr; + }; + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + EdgeBlock* b = EdgeBlocks(); + while (b) { + n += aMallocSizeOf(b); + b = b->Next(); + } + return n; + } +}; + +#ifdef DEBUG_CC_GRAPH +# define CC_GRAPH_ASSERT(b) MOZ_ASSERT(b) +#else +# define CC_GRAPH_ASSERT(b) +#endif + +#define CC_TELEMETRY(_name, _value) \ + do { \ + if (NS_IsMainThread()) { \ + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR##_name, _value); \ + } else { \ + Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_WORKER##_name, _value); \ + } \ + } while (0) + +enum NodeColor { black, white, grey }; + +// This structure should be kept as small as possible; we may expect +// hundreds of thousands of them to be allocated and touched +// repeatedly during each cycle collection. +class PtrInfo final { + public: + // mParticipant knows a more concrete type. + void* mPointer; + nsCycleCollectionParticipant* mParticipant; + uint32_t mColor : 2; + uint32_t mInternalRefs : 30; + uint32_t mRefCount; + + private: + EdgePool::Iterator mFirstChild; + + static const uint32_t kInitialRefCount = UINT32_MAX - 1; + + public: + PtrInfo(void* aPointer, nsCycleCollectionParticipant* aParticipant) + : mPointer(aPointer), + mParticipant(aParticipant), + mColor(grey), + mInternalRefs(0), + mRefCount(kInitialRefCount), + mFirstChild() { + MOZ_ASSERT(aParticipant); + + // We initialize mRefCount to a large non-zero value so + // that it doesn't look like a JS object to the cycle collector + // in the case where the object dies before being traversed. + MOZ_ASSERT(!IsGrayJS() && !IsBlackJS()); + } + + // Allow NodePool::NodeBlock's constructor to compile. + PtrInfo() + : mPointer{nullptr}, + mParticipant{nullptr}, + mColor{0}, + mInternalRefs{0}, + mRefCount{0} { + MOZ_ASSERT_UNREACHABLE("should never be called"); + } + + bool IsGrayJS() const { return mRefCount == 0; } + + bool IsBlackJS() const { return mRefCount == UINT32_MAX; } + + bool WasTraversed() const { return mRefCount != kInitialRefCount; } + + EdgePool::Iterator FirstChild() const { + CC_GRAPH_ASSERT(mFirstChild.Initialized()); + return mFirstChild; + } + + // this PtrInfo must be part of a NodePool + EdgePool::Iterator LastChild() const { + CC_GRAPH_ASSERT((this + 1)->mFirstChild.Initialized()); + return (this + 1)->mFirstChild; + } + + void SetFirstChild(EdgePool::Iterator aFirstChild) { + CC_GRAPH_ASSERT(aFirstChild.Initialized()); + mFirstChild = aFirstChild; + } + + // this PtrInfo must be part of a NodePool + void SetLastChild(EdgePool::Iterator aLastChild) { + CC_GRAPH_ASSERT(aLastChild.Initialized()); + (this + 1)->mFirstChild = aLastChild; + } + + void AnnotatedReleaseAssert(bool aCondition, const char* aMessage); +}; + +void PtrInfo::AnnotatedReleaseAssert(bool aCondition, const char* aMessage) { + if (aCondition) { + return; + } + + const char* piName = "Unknown"; + if (mParticipant) { + piName = mParticipant->ClassName(); + } + nsPrintfCString msg("%s, for class %s", aMessage, piName); + NS_WARNING(msg.get()); + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::CycleCollector, + msg); + + MOZ_CRASH(); +} + +/** + * A structure designed to be used like a linked list of PtrInfo, except + * it allocates many PtrInfos at a time. + */ +class NodePool { + private: + // The -2 allows us to use |NodeBlockSize + 1| for |mEntries|, and fit + // |mNext|, all without causing slop. + enum { NodeBlockSize = 4 * 1024 - 2 }; + + struct NodeBlock { + // We create and destroy NodeBlock using moz_xmalloc/free rather than new + // and delete to avoid calling its constructor and destructor. + NodeBlock() : mNext{nullptr} { + MOZ_ASSERT_UNREACHABLE("should never be called"); + + // Ensure NodeBlock is the right size (see the comment on NodeBlockSize + // above). + static_assert( + sizeof(NodeBlock) == 81904 || // 32-bit; equals 19.996 x 4 KiB pages + sizeof(NodeBlock) == + 131048, // 64-bit; equals 31.994 x 4 KiB pages + "ill-sized NodeBlock"); + } + ~NodeBlock() { MOZ_ASSERT_UNREACHABLE("should never be called"); } + + NodeBlock* mNext; + PtrInfo mEntries[NodeBlockSize + 1]; // +1 to store last child of last node + }; + + public: + NodePool() : mBlocks(nullptr), mLast(nullptr) {} + + ~NodePool() { MOZ_ASSERT(!mBlocks, "Didn't call Clear()?"); } + + void Clear() { + NodeBlock* b = mBlocks; + while (b) { + NodeBlock* n = b->mNext; + free(b); + b = n; + } + + mBlocks = nullptr; + mLast = nullptr; + } + +#ifdef DEBUG + bool IsEmpty() { return !mBlocks && !mLast; } +#endif + + class Builder; + friend class Builder; + class Builder { + public: + explicit Builder(NodePool& aPool) + : mNextBlock(&aPool.mBlocks), mNext(aPool.mLast), mBlockEnd(nullptr) { + MOZ_ASSERT(!aPool.mBlocks && !aPool.mLast, "pool not empty"); + } + PtrInfo* Add(void* aPointer, nsCycleCollectionParticipant* aParticipant) { + if (mNext == mBlockEnd) { + NodeBlock* block = static_cast(malloc(sizeof(NodeBlock))); + if (!block) { + return nullptr; + } + + *mNextBlock = block; + mNext = block->mEntries; + mBlockEnd = block->mEntries + NodeBlockSize; + block->mNext = nullptr; + mNextBlock = &block->mNext; + } + return new (mozilla::KnownNotNull, mNext++) + PtrInfo(aPointer, aParticipant); + } + + private: + NodeBlock** mNextBlock; + PtrInfo*& mNext; + PtrInfo* mBlockEnd; + }; + + class Enumerator; + friend class Enumerator; + class Enumerator { + public: + explicit Enumerator(NodePool& aPool) + : mFirstBlock(aPool.mBlocks), + mCurBlock(nullptr), + mNext(nullptr), + mBlockEnd(nullptr), + mLast(aPool.mLast) {} + + bool IsDone() const { return mNext == mLast; } + + bool AtBlockEnd() const { return mNext == mBlockEnd; } + + PtrInfo* GetNext() { + MOZ_ASSERT(!IsDone(), "calling GetNext when done"); + if (mNext == mBlockEnd) { + NodeBlock* nextBlock = mCurBlock ? mCurBlock->mNext : mFirstBlock; + mNext = nextBlock->mEntries; + mBlockEnd = mNext + NodeBlockSize; + mCurBlock = nextBlock; + } + return mNext++; + } + + private: + // mFirstBlock is a reference to allow an Enumerator to be constructed + // for an empty graph. + NodeBlock*& mFirstBlock; + NodeBlock* mCurBlock; + // mNext is the next value we want to return, unless mNext == mBlockEnd + // NB: mLast is a reference to allow enumerating while building! + PtrInfo* mNext; + PtrInfo* mBlockEnd; + PtrInfo*& mLast; + }; + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + // We don't measure the things pointed to by mEntries[] because those + // pointers are non-owning. + size_t n = 0; + NodeBlock* b = mBlocks; + while (b) { + n += aMallocSizeOf(b); + b = b->mNext; + } + return n; + } + + private: + NodeBlock* mBlocks; + PtrInfo* mLast; +}; + +struct PtrToNodeHashPolicy { + using Key = PtrInfo*; + using Lookup = void*; + + static js::HashNumber hash(const Lookup& aLookup) { + return mozilla::HashGeneric(aLookup); + } + + static bool match(const Key& aKey, const Lookup& aLookup) { + return aKey->mPointer == aLookup; + } +}; + +struct WeakMapping { + // map and key will be null if the corresponding objects are GC marked + PtrInfo* mMap; + PtrInfo* mKey; + PtrInfo* mKeyDelegate; + PtrInfo* mVal; +}; + +class CCGraphBuilder; + +struct CCGraph { + NodePool mNodes; + EdgePool mEdges; + nsTArray mWeakMaps; + uint32_t mRootCount; + + private: + friend CCGraphBuilder; + + mozilla::HashSet mPtrInfoMap; + + bool mOutOfMemory; + + static const uint32_t kInitialMapLength = 16384; + + public: + CCGraph() + : mRootCount(0), mPtrInfoMap(kInitialMapLength), mOutOfMemory(false) {} + + ~CCGraph() = default; + + void Init() { MOZ_ASSERT(IsEmpty(), "Failed to call CCGraph::Clear"); } + + void Clear() { + mNodes.Clear(); + mEdges.Clear(); + mWeakMaps.Clear(); + mRootCount = 0; + mPtrInfoMap.clearAndCompact(); + mOutOfMemory = false; + } + +#ifdef DEBUG + bool IsEmpty() { + return mNodes.IsEmpty() && mEdges.IsEmpty() && mWeakMaps.IsEmpty() && + mRootCount == 0 && mPtrInfoMap.empty(); + } +#endif + + PtrInfo* FindNode(void* aPtr); + void RemoveObjectFromMap(void* aObject); + + uint32_t MapCount() const { return mPtrInfoMap.count(); } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + + n += mNodes.SizeOfExcludingThis(aMallocSizeOf); + n += mEdges.SizeOfExcludingThis(aMallocSizeOf); + + // We don't measure what the WeakMappings point to, because the + // pointers are non-owning. + n += mWeakMaps.ShallowSizeOfExcludingThis(aMallocSizeOf); + + n += mPtrInfoMap.shallowSizeOfExcludingThis(aMallocSizeOf); + + return n; + } +}; + +PtrInfo* CCGraph::FindNode(void* aPtr) { + auto p = mPtrInfoMap.lookup(aPtr); + return p ? *p : nullptr; +} + +void CCGraph::RemoveObjectFromMap(void* aObj) { + auto p = mPtrInfoMap.lookup(aObj); + if (p) { + PtrInfo* pinfo = *p; + pinfo->mPointer = nullptr; + pinfo->mParticipant = nullptr; + mPtrInfoMap.remove(p); + } +} + +static nsISupports* CanonicalizeXPCOMParticipant(nsISupports* aIn) { + nsISupports* out = nullptr; + aIn->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), + reinterpret_cast(&out)); + return out; +} + +struct nsPurpleBufferEntry { + nsPurpleBufferEntry(void* aObject, nsCycleCollectingAutoRefCnt* aRefCnt, + nsCycleCollectionParticipant* aParticipant) + : mObject(aObject), mRefCnt(aRefCnt), mParticipant(aParticipant) {} + + nsPurpleBufferEntry(nsPurpleBufferEntry&& aOther) + : mObject(nullptr), mRefCnt(nullptr), mParticipant(nullptr) { + Swap(aOther); + } + + void Swap(nsPurpleBufferEntry& aOther) { + std::swap(mObject, aOther.mObject); + std::swap(mRefCnt, aOther.mRefCnt); + std::swap(mParticipant, aOther.mParticipant); + } + + void Clear() { + mRefCnt->RemoveFromPurpleBuffer(); + mRefCnt = nullptr; + mObject = nullptr; + mParticipant = nullptr; + } + + ~nsPurpleBufferEntry() { + if (mRefCnt) { + mRefCnt->RemoveFromPurpleBuffer(); + } + } + + void* mObject; + nsCycleCollectingAutoRefCnt* mRefCnt; + nsCycleCollectionParticipant* mParticipant; // nullptr for nsISupports +}; + +class nsCycleCollector; + +struct nsPurpleBuffer { + private: + uint32_t mCount; + + // Try to match the size of a jemalloc bucket, to minimize slop bytes. + // - On 32-bit platforms sizeof(nsPurpleBufferEntry) is 12, so mEntries' + // Segment is 16,372 bytes. + // - On 64-bit platforms sizeof(nsPurpleBufferEntry) is 24, so mEntries' + // Segment is 32,760 bytes. + static const uint32_t kEntriesPerSegment = 1365; + static const size_t kSegmentSize = + sizeof(nsPurpleBufferEntry) * kEntriesPerSegment; + typedef SegmentedVector + PurpleBufferVector; + PurpleBufferVector mEntries; + + public: + nsPurpleBuffer() : mCount(0) { + static_assert( + sizeof(PurpleBufferVector::Segment) == 16372 || // 32-bit + sizeof(PurpleBufferVector::Segment) == 32760 || // 64-bit + sizeof(PurpleBufferVector::Segment) == 32744, // 64-bit Windows + "ill-sized nsPurpleBuffer::mEntries"); + } + + ~nsPurpleBuffer() = default; + + // This method compacts mEntries. + template + void VisitEntries(PurpleVisitor& aVisitor) { + Maybe> ar; + if (NS_IsMainThread()) { + ar.emplace(gNurseryPurpleBufferEnabled); + gNurseryPurpleBufferEnabled = false; + ClearNurseryPurpleBuffer(); + } + + if (mEntries.IsEmpty()) { + return; + } + + uint32_t oldLength = mEntries.Length(); + uint32_t keptLength = 0; + auto revIter = mEntries.IterFromLast(); + auto iter = mEntries.Iter(); + // After iteration this points to the first empty entry. + auto firstEmptyIter = mEntries.Iter(); + auto iterFromLastEntry = mEntries.IterFromLast(); + for (; !iter.Done(); iter.Next()) { + nsPurpleBufferEntry& e = iter.Get(); + if (e.mObject) { + if (!aVisitor.Visit(*this, &e)) { + return; + } + } + + // Visit call above may have cleared the entry, or the entry was empty + // already. + if (!e.mObject) { + // Try to find a non-empty entry from the end of the vector. + for (; !revIter.Done(); revIter.Prev()) { + nsPurpleBufferEntry& otherEntry = revIter.Get(); + if (&e == &otherEntry) { + break; + } + if (otherEntry.mObject) { + if (!aVisitor.Visit(*this, &otherEntry)) { + return; + } + // Visit may have cleared otherEntry. + if (otherEntry.mObject) { + e.Swap(otherEntry); + revIter.Prev(); // We've swapped this now empty entry. + break; + } + } + } + } + + // Entry is non-empty even after the Visit call, ensure it is kept + // in mEntries. + if (e.mObject) { + firstEmptyIter.Next(); + ++keptLength; + } + + if (&e == &revIter.Get()) { + break; + } + } + + // There were some empty entries. + if (oldLength != keptLength) { + // While visiting entries, some new ones were possibly added. This can + // happen during CanSkip. Move all such new entries to be after other + // entries. Note, we don't call Visit on newly added entries! + if (&iterFromLastEntry.Get() != &mEntries.GetLast()) { + iterFromLastEntry.Next(); // Now pointing to the first added entry. + auto& iterForNewEntries = iterFromLastEntry; + while (!iterForNewEntries.Done()) { + MOZ_ASSERT(!firstEmptyIter.Done()); + MOZ_ASSERT(!firstEmptyIter.Get().mObject); + firstEmptyIter.Get().Swap(iterForNewEntries.Get()); + firstEmptyIter.Next(); + iterForNewEntries.Next(); + } + } + + mEntries.PopLastN(oldLength - keptLength); + } + } + + void FreeBlocks() { + mCount = 0; + mEntries.Clear(); + } + + void SelectPointers(CCGraphBuilder& aBuilder); + + // RemoveSkippable removes entries from the purple buffer synchronously + // (1) if !aAsyncSnowWhiteFreeing and nsPurpleBufferEntry::mRefCnt is 0 or + // (2) if nsXPCOMCycleCollectionParticipant::CanSkip() for the obj or + // (3) if nsPurpleBufferEntry::mRefCnt->IsPurple() is false. + // (4) If aRemoveChildlessNodes is true, then any nodes in the purple buffer + // that will have no children in the cycle collector graph will also be + // removed. CanSkip() may be run on these children. + void RemoveSkippable(nsCycleCollector* aCollector, js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb); + + MOZ_ALWAYS_INLINE void Put(void* aObject, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt) { + nsPurpleBufferEntry entry(aObject, aRefCnt, aCp); + Unused << mEntries.Append(std::move(entry)); + MOZ_ASSERT(!entry.mRefCnt, "Move didn't work!"); + ++mCount; + } + + void Remove(nsPurpleBufferEntry* aEntry) { + MOZ_ASSERT(mCount != 0, "must have entries"); + --mCount; + aEntry->Clear(); + } + + uint32_t Count() const { return mCount; } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + return mEntries.SizeOfExcludingThis(aMallocSizeOf); + } +}; + +static bool AddPurpleRoot(CCGraphBuilder& aBuilder, void* aRoot, + nsCycleCollectionParticipant* aParti); + +struct SelectPointersVisitor { + explicit SelectPointersVisitor(CCGraphBuilder& aBuilder) + : mBuilder(aBuilder) {} + + bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) { + MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer"); + MOZ_ASSERT(aEntry->mRefCnt->get() != 0, + "SelectPointersVisitor: snow-white object in the purple buffer"); + if (!aEntry->mRefCnt->IsPurple() || + AddPurpleRoot(mBuilder, aEntry->mObject, aEntry->mParticipant)) { + aBuffer.Remove(aEntry); + } + return true; + } + + private: + CCGraphBuilder& mBuilder; +}; + +void nsPurpleBuffer::SelectPointers(CCGraphBuilder& aBuilder) { + SelectPointersVisitor visitor(aBuilder); + VisitEntries(visitor); + + MOZ_ASSERT(mCount == 0, "AddPurpleRoot failed"); + if (mCount == 0) { + FreeBlocks(); + } +} + +enum ccPhase { + IdlePhase, + GraphBuildingPhase, + ScanAndCollectWhitePhase, + CleanupPhase +}; + +enum ccIsManual { CCIsNotManual = false, CCIsManual = true }; + +//////////////////////////////////////////////////////////////////////// +// Top level structure for the cycle collector. +//////////////////////////////////////////////////////////////////////// + +using js::SliceBudget; + +class JSPurpleBuffer; + +class nsCycleCollector : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + + private: + bool mActivelyCollecting; + bool mFreeingSnowWhite; + // mScanInProgress should be false when we're collecting white objects. + bool mScanInProgress; + CycleCollectorResults mResults; + TimeStamp mCollectionStart; + + CycleCollectedJSRuntime* mCCJSRuntime; + + ccPhase mIncrementalPhase; + int32_t mShutdownCount = 0; + CCGraph mGraph; + UniquePtr mBuilder; + RefPtr mLogger; + +#ifdef DEBUG + nsISerialEventTarget* mEventTarget; +#endif + + nsCycleCollectorParams mParams; + + uint32_t mWhiteNodeCount; + + CC_BeforeUnlinkCallback mBeforeUnlinkCB; + CC_ForgetSkippableCallback mForgetSkippableCB; + + nsPurpleBuffer mPurpleBuf; + + uint32_t mUnmergedNeeded; + uint32_t mMergedInARow; + + RefPtr mJSPurpleBuffer; + + private: + virtual ~nsCycleCollector(); + + public: + nsCycleCollector(); + + void SetCCJSRuntime(CycleCollectedJSRuntime* aCCRuntime); + void ClearCCJSRuntime(); + + void SetBeforeUnlinkCallback(CC_BeforeUnlinkCallback aBeforeUnlinkCB) { + CheckThreadSafety(); + mBeforeUnlinkCB = aBeforeUnlinkCB; + } + + void SetForgetSkippableCallback( + CC_ForgetSkippableCallback aForgetSkippableCB) { + CheckThreadSafety(); + mForgetSkippableCB = aForgetSkippableCB; + } + + void Suspect(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt); + void SuspectNurseryEntries(); + uint32_t SuspectedCount(); + void ForgetSkippable(js::SliceBudget& aBudget, bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing); + bool FreeSnowWhite(bool aUntilNoSWInPurpleBuffer); + bool FreeSnowWhiteWithBudget(js::SliceBudget& aBudget); + + // This method assumes its argument is already canonicalized. + void RemoveObjectFromGraph(void* aPtr); + + void PrepareForGarbageCollection(); + void FinishAnyCurrentCollection(CCReason aReason); + + bool Collect(CCReason aReason, ccIsManual aIsManual, SliceBudget& aBudget, + nsICycleCollectorListener* aManualListener, + bool aPreferShorterSlices = false); + MOZ_CAN_RUN_SCRIPT + void Shutdown(bool aDoCollect); + + bool IsIdle() const { return mIncrementalPhase == IdlePhase; } + + void SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + size_t* aObjectSize, size_t* aGraphSize, + size_t* aPurpleBufferSize) const; + + JSPurpleBuffer* GetJSPurpleBuffer(); + + CycleCollectedJSRuntime* Runtime() { return mCCJSRuntime; } + + private: + void CheckThreadSafety(); + MOZ_CAN_RUN_SCRIPT + void ShutdownCollect(); + + void FixGrayBits(bool aIsShutdown, TimeLog& aTimeLog); + bool IsIncrementalGCInProgress(); + void FinishAnyIncrementalGCInProgress(); + bool ShouldMergeZones(ccIsManual aIsManual); + + void BeginCollection(CCReason aReason, ccIsManual aIsManual, + nsICycleCollectorListener* aManualListener); + void MarkRoots(SliceBudget& aBudget); + void ScanRoots(bool aFullySynchGraphBuild); + void ScanIncrementalRoots(); + void ScanWhiteNodes(bool aFullySynchGraphBuild); + void ScanBlackNodes(); + void ScanWeakMaps(); + + // returns whether anything was collected + bool CollectWhite(); + + void CleanupAfterCollection(); +}; + +NS_IMPL_ISUPPORTS(nsCycleCollector, nsIMemoryReporter) + +/** + * GraphWalker is templatized over a Visitor class that must provide + * the following two methods: + * + * bool ShouldVisitNode(PtrInfo const *pi); + * void VisitNode(PtrInfo *pi); + */ +template +class GraphWalker { + private: + Visitor mVisitor; + + void DoWalk(nsDeque& aQueue); + + void CheckedPush(nsDeque& aQueue, PtrInfo* aPi) { + if (!aPi) { + MOZ_CRASH(); + } + if (!aQueue.Push(aPi, fallible)) { + mVisitor.Failed(); + } + } + + public: + void Walk(PtrInfo* aPi); + void WalkFromRoots(CCGraph& aGraph); + // copy-constructing the visitor should be cheap, and less + // indirection than using a reference + explicit GraphWalker(const Visitor aVisitor) : mVisitor(aVisitor) {} +}; + +//////////////////////////////////////////////////////////////////////// +// The static collector struct +//////////////////////////////////////////////////////////////////////// + +struct CollectorData { + RefPtr mCollector; + CycleCollectedJSContext* mContext; +}; + +static MOZ_THREAD_LOCAL(CollectorData*) sCollectorData; + +//////////////////////////////////////////////////////////////////////// +// Utility functions +//////////////////////////////////////////////////////////////////////// + +static inline void ToParticipant(nsISupports* aPtr, + nsXPCOMCycleCollectionParticipant** aCp) { + // We use QI to move from an nsISupports to an + // nsXPCOMCycleCollectionParticipant, which is a per-class singleton helper + // object that implements traversal and unlinking logic for the nsISupports + // in question. + *aCp = nullptr; + CallQueryInterface(aPtr, aCp); +} + +static void ToParticipant(void* aParti, nsCycleCollectionParticipant** aCp) { + // If the participant is null, this is an nsISupports participant, + // so we must QI to get the real participant. + + if (!*aCp) { + nsISupports* nsparti = static_cast(aParti); + MOZ_ASSERT(CanonicalizeXPCOMParticipant(nsparti) == nsparti); + nsXPCOMCycleCollectionParticipant* xcp; + ToParticipant(nsparti, &xcp); + *aCp = xcp; + } +} + +template +MOZ_NEVER_INLINE void GraphWalker::Walk(PtrInfo* aPi) { + nsDeque queue; + CheckedPush(queue, aPi); + DoWalk(queue); +} + +template +MOZ_NEVER_INLINE void GraphWalker::WalkFromRoots(CCGraph& aGraph) { + nsDeque queue; + NodePool::Enumerator etor(aGraph.mNodes); + for (uint32_t i = 0; i < aGraph.mRootCount; ++i) { + CheckedPush(queue, etor.GetNext()); + } + DoWalk(queue); +} + +template +MOZ_NEVER_INLINE void GraphWalker::DoWalk(nsDeque& aQueue) { + // Use a aQueue to match the breadth-first traversal used when we + // built the graph, for hopefully-better locality. + while (aQueue.GetSize() > 0) { + PtrInfo* pi = aQueue.PopFront(); + + if (pi->WasTraversed() && mVisitor.ShouldVisitNode(pi)) { + mVisitor.VisitNode(pi); + for (EdgePool::Iterator child = pi->FirstChild(), + child_end = pi->LastChild(); + child != child_end; ++child) { + CheckedPush(aQueue, *child); + } + } + } +} + +struct CCGraphDescriber : public LinkedListElement { + CCGraphDescriber() : mAddress("0x"), mCnt(0), mType(eUnknown) {} + + enum Type { + eRefCountedObject, + eGCedObject, + eGCMarkedObject, + eEdge, + eRoot, + eGarbage, + eUnknown + }; + + nsCString mAddress; + nsCString mName; + nsCString mCompartmentOrToAddress; + uint32_t mCnt; + Type mType; +}; + +class LogStringMessageAsync : public DiscardableRunnable { + public: + explicit LogStringMessageAsync(const nsAString& aMsg) + : mozilla::DiscardableRunnable("LogStringMessageAsync"), mMsg(aMsg) {} + + NS_IMETHOD Run() override { + nsCOMPtr cs = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (cs) { + cs->LogStringMessage(mMsg.get()); + } + return NS_OK; + } + + private: + nsString mMsg; +}; + +class nsCycleCollectorLogSinkToFile final : public nsICycleCollectorLogSink { + public: + NS_DECL_ISUPPORTS + + nsCycleCollectorLogSinkToFile() + : mProcessIdentifier(base::GetCurrentProcId()), + mGCLog("gc-edges"), + mCCLog("cc-edges") {} + + NS_IMETHOD GetFilenameIdentifier(nsAString& aIdentifier) override { + aIdentifier = mFilenameIdentifier; + return NS_OK; + } + + NS_IMETHOD SetFilenameIdentifier(const nsAString& aIdentifier) override { + mFilenameIdentifier = aIdentifier; + return NS_OK; + } + + NS_IMETHOD GetProcessIdentifier(int32_t* aIdentifier) override { + *aIdentifier = mProcessIdentifier; + return NS_OK; + } + + NS_IMETHOD SetProcessIdentifier(int32_t aIdentifier) override { + mProcessIdentifier = aIdentifier; + return NS_OK; + } + + NS_IMETHOD GetGcLog(nsIFile** aPath) override { + NS_IF_ADDREF(*aPath = mGCLog.mFile); + return NS_OK; + } + + NS_IMETHOD GetCcLog(nsIFile** aPath) override { + NS_IF_ADDREF(*aPath = mCCLog.mFile); + return NS_OK; + } + + NS_IMETHOD Open(FILE** aGCLog, FILE** aCCLog) override { + nsresult rv; + + if (mGCLog.mStream || mCCLog.mStream) { + return NS_ERROR_UNEXPECTED; + } + + rv = OpenLog(&mGCLog); + NS_ENSURE_SUCCESS(rv, rv); + *aGCLog = mGCLog.mStream; + + rv = OpenLog(&mCCLog); + NS_ENSURE_SUCCESS(rv, rv); + *aCCLog = mCCLog.mStream; + + return NS_OK; + } + + NS_IMETHOD CloseGCLog() override { + if (!mGCLog.mStream) { + return NS_ERROR_UNEXPECTED; + } + CloseLog(&mGCLog, u"Garbage"_ns); + return NS_OK; + } + + NS_IMETHOD CloseCCLog() override { + if (!mCCLog.mStream) { + return NS_ERROR_UNEXPECTED; + } + CloseLog(&mCCLog, u"Cycle"_ns); + return NS_OK; + } + + private: + ~nsCycleCollectorLogSinkToFile() { + if (mGCLog.mStream) { + MozillaUnRegisterDebugFILE(mGCLog.mStream); + fclose(mGCLog.mStream); + } + if (mCCLog.mStream) { + MozillaUnRegisterDebugFILE(mCCLog.mStream); + fclose(mCCLog.mStream); + } + } + + struct FileInfo { + const char* const mPrefix; + nsCOMPtr mFile; + FILE* mStream; + + explicit FileInfo(const char* aPrefix) + : mPrefix(aPrefix), mStream(nullptr) {} + }; + + /** + * Create a new file named something like aPrefix.$PID.$IDENTIFIER.log in + * $MOZ_CC_LOG_DIRECTORY or in the system's temp directory. No existing + * file will be overwritten; if aPrefix.$PID.$IDENTIFIER.log exists, we'll + * try a file named something like aPrefix.$PID.$IDENTIFIER-1.log, and so + * on. + */ + already_AddRefed CreateTempFile(const char* aPrefix) { + nsPrintfCString filename("%s.%d%s%s.log", aPrefix, mProcessIdentifier, + mFilenameIdentifier.IsEmpty() ? "" : ".", + NS_ConvertUTF16toUTF8(mFilenameIdentifier).get()); + + // Get the log directory either from $MOZ_CC_LOG_DIRECTORY or from + // the fallback directories in OpenTempFile. We don't use an nsCOMPtr + // here because OpenTempFile uses an in/out param and getter_AddRefs + // wouldn't work. + nsIFile* logFile = nullptr; + if (char* env = PR_GetEnv("MOZ_CC_LOG_DIRECTORY")) { + NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, &logFile); + } + + // On Android or B2G, this function will open a file named + // aFilename under a memory-reporting-specific folder + // (/data/local/tmp/memory-reports). Otherwise, it will open a + // file named aFilename under "NS_OS_TEMP_DIR". + nsresult rv = + nsDumpUtils::OpenTempFile(filename, &logFile, "memory-reports"_ns); + if (NS_FAILED(rv)) { + NS_IF_RELEASE(logFile); + return nullptr; + } + + return dont_AddRef(logFile); + } + + nsresult OpenLog(FileInfo* aLog) { + // Initially create the log in a file starting with "incomplete-". + // We'll move the file and strip off the "incomplete-" once the dump + // completes. (We do this because we don't want scripts which poll + // the filesystem looking for GC/CC dumps to grab a file before we're + // finished writing to it.) + nsAutoCString incomplete; + incomplete += "incomplete-"; + incomplete += aLog->mPrefix; + MOZ_ASSERT(!aLog->mFile); + aLog->mFile = CreateTempFile(incomplete.get()); + if (NS_WARN_IF(!aLog->mFile)) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(!aLog->mStream); + nsresult rv = aLog->mFile->OpenANSIFileDesc("w", &aLog->mStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_UNEXPECTED; + } + MozillaRegisterDebugFILE(aLog->mStream); + return NS_OK; + } + + nsresult CloseLog(FileInfo* aLog, const nsAString& aCollectorKind) { + MOZ_ASSERT(aLog->mStream); + MOZ_ASSERT(aLog->mFile); + + MozillaUnRegisterDebugFILE(aLog->mStream); + fclose(aLog->mStream); + aLog->mStream = nullptr; + + // Strip off "incomplete-". + nsCOMPtr logFileFinalDestination = CreateTempFile(aLog->mPrefix); + if (NS_WARN_IF(!logFileFinalDestination)) { + return NS_ERROR_UNEXPECTED; + } + + nsAutoString logFileFinalDestinationName; + logFileFinalDestination->GetLeafName(logFileFinalDestinationName); + if (NS_WARN_IF(logFileFinalDestinationName.IsEmpty())) { + return NS_ERROR_UNEXPECTED; + } + + aLog->mFile->MoveTo(/* directory */ nullptr, logFileFinalDestinationName); + + // Save the file path. + aLog->mFile = logFileFinalDestination; + + // Log to the error console. + nsAutoString logPath; + logFileFinalDestination->GetPath(logPath); + nsAutoString msg = + aCollectorKind + u" Collector log dumped to "_ns + logPath; + + // We don't want any JS to run between ScanRoots and CollectWhite calls, + // and since ScanRoots calls this method, better to log the message + // asynchronously. + RefPtr log = new LogStringMessageAsync(msg); + NS_DispatchToCurrentThread(log); + return NS_OK; + } + + int32_t mProcessIdentifier; + nsString mFilenameIdentifier; + FileInfo mGCLog; + FileInfo mCCLog; +}; + +NS_IMPL_ISUPPORTS(nsCycleCollectorLogSinkToFile, nsICycleCollectorLogSink) + +class nsCycleCollectorLogger final : public nsICycleCollectorListener { + ~nsCycleCollectorLogger() { ClearDescribers(); } + + public: + nsCycleCollectorLogger() + : mLogSink(nsCycleCollector_createLogSink()), + mWantAllTraces(false), + mDisableLog(false), + mWantAfterProcessing(false), + mCCLog(nullptr) {} + + NS_DECL_ISUPPORTS + + void SetAllTraces() { mWantAllTraces = true; } + + bool IsAllTraces() { return mWantAllTraces; } + + NS_IMETHOD AllTraces(nsICycleCollectorListener** aListener) override { + SetAllTraces(); + NS_ADDREF(*aListener = this); + return NS_OK; + } + + NS_IMETHOD GetWantAllTraces(bool* aAllTraces) override { + *aAllTraces = mWantAllTraces; + return NS_OK; + } + + NS_IMETHOD GetDisableLog(bool* aDisableLog) override { + *aDisableLog = mDisableLog; + return NS_OK; + } + + NS_IMETHOD SetDisableLog(bool aDisableLog) override { + mDisableLog = aDisableLog; + return NS_OK; + } + + NS_IMETHOD GetWantAfterProcessing(bool* aWantAfterProcessing) override { + *aWantAfterProcessing = mWantAfterProcessing; + return NS_OK; + } + + NS_IMETHOD SetWantAfterProcessing(bool aWantAfterProcessing) override { + mWantAfterProcessing = aWantAfterProcessing; + return NS_OK; + } + + NS_IMETHOD GetLogSink(nsICycleCollectorLogSink** aLogSink) override { + NS_ADDREF(*aLogSink = mLogSink); + return NS_OK; + } + + NS_IMETHOD SetLogSink(nsICycleCollectorLogSink* aLogSink) override { + if (!aLogSink) { + return NS_ERROR_INVALID_ARG; + } + mLogSink = aLogSink; + return NS_OK; + } + + nsresult Begin() { + nsresult rv; + + mCurrentAddress.AssignLiteral("0x"); + ClearDescribers(); + if (mDisableLog) { + return NS_OK; + } + + FILE* gcLog; + rv = mLogSink->Open(&gcLog, &mCCLog); + NS_ENSURE_SUCCESS(rv, rv); + // Dump the JS heap. + CollectorData* data = sCollectorData.get(); + if (data && data->mContext) { + data->mContext->Runtime()->DumpJSHeap(gcLog); + } + rv = mLogSink->CloseGCLog(); + NS_ENSURE_SUCCESS(rv, rv); + + fprintf(mCCLog, "# WantAllTraces=%s\n", mWantAllTraces ? "true" : "false"); + return NS_OK; + } + void NoteRefCountedObject(uint64_t aAddress, uint32_t aRefCount, + const char* aObjectDescription) { + if (!mDisableLog) { + fprintf(mCCLog, "%p [rc=%u] %s\n", (void*)aAddress, aRefCount, + aObjectDescription); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + mCurrentAddress.AssignLiteral("0x"); + mCurrentAddress.AppendInt(aAddress, 16); + d->mType = CCGraphDescriber::eRefCountedObject; + d->mAddress = mCurrentAddress; + d->mCnt = aRefCount; + d->mName.Append(aObjectDescription); + } + } + void NoteGCedObject(uint64_t aAddress, bool aMarked, + const char* aObjectDescription, + uint64_t aCompartmentAddress) { + if (!mDisableLog) { + fprintf(mCCLog, "%p [gc%s] %s\n", (void*)aAddress, + aMarked ? ".marked" : "", aObjectDescription); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + mCurrentAddress.AssignLiteral("0x"); + mCurrentAddress.AppendInt(aAddress, 16); + d->mType = aMarked ? CCGraphDescriber::eGCMarkedObject + : CCGraphDescriber::eGCedObject; + d->mAddress = mCurrentAddress; + d->mName.Append(aObjectDescription); + if (aCompartmentAddress) { + d->mCompartmentOrToAddress.AssignLiteral("0x"); + d->mCompartmentOrToAddress.AppendInt(aCompartmentAddress, 16); + } else { + d->mCompartmentOrToAddress.SetIsVoid(true); + } + } + } + void NoteEdge(uint64_t aToAddress, const char* aEdgeName) { + if (!mDisableLog) { + fprintf(mCCLog, "> %p %s\n", (void*)aToAddress, aEdgeName); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + d->mType = CCGraphDescriber::eEdge; + d->mAddress = mCurrentAddress; + d->mCompartmentOrToAddress.AssignLiteral("0x"); + d->mCompartmentOrToAddress.AppendInt(aToAddress, 16); + d->mName.Append(aEdgeName); + } + } + void NoteWeakMapEntry(uint64_t aMap, uint64_t aKey, uint64_t aKeyDelegate, + uint64_t aValue) { + if (!mDisableLog) { + fprintf(mCCLog, "WeakMapEntry map=%p key=%p keyDelegate=%p value=%p\n", + (void*)aMap, (void*)aKey, (void*)aKeyDelegate, (void*)aValue); + } + // We don't support after-processing for weak map entries. + } + void NoteIncrementalRoot(uint64_t aAddress) { + if (!mDisableLog) { + fprintf(mCCLog, "IncrementalRoot %p\n", (void*)aAddress); + } + // We don't support after-processing for incremental roots. + } + void BeginResults() { + if (!mDisableLog) { + fputs("==========\n", mCCLog); + } + } + void DescribeRoot(uint64_t aAddress, uint32_t aKnownEdges) { + if (!mDisableLog) { + fprintf(mCCLog, "%p [known=%u]\n", (void*)aAddress, aKnownEdges); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + d->mType = CCGraphDescriber::eRoot; + d->mAddress.AppendInt(aAddress, 16); + d->mCnt = aKnownEdges; + } + } + void DescribeGarbage(uint64_t aAddress) { + if (!mDisableLog) { + fprintf(mCCLog, "%p [garbage]\n", (void*)aAddress); + } + if (mWantAfterProcessing) { + CCGraphDescriber* d = new CCGraphDescriber(); + mDescribers.insertBack(d); + d->mType = CCGraphDescriber::eGarbage; + d->mAddress.AppendInt(aAddress, 16); + } + } + void End() { + if (!mDisableLog) { + mCCLog = nullptr; + Unused << NS_WARN_IF(NS_FAILED(mLogSink->CloseCCLog())); + } + } + NS_IMETHOD ProcessNext(nsICycleCollectorHandler* aHandler, + bool* aCanContinue) override { + if (NS_WARN_IF(!aHandler) || NS_WARN_IF(!mWantAfterProcessing)) { + return NS_ERROR_UNEXPECTED; + } + CCGraphDescriber* d = mDescribers.popFirst(); + if (d) { + switch (d->mType) { + case CCGraphDescriber::eRefCountedObject: + aHandler->NoteRefCountedObject(d->mAddress, d->mCnt, d->mName); + break; + case CCGraphDescriber::eGCedObject: + case CCGraphDescriber::eGCMarkedObject: + aHandler->NoteGCedObject( + d->mAddress, d->mType == CCGraphDescriber::eGCMarkedObject, + d->mName, d->mCompartmentOrToAddress); + break; + case CCGraphDescriber::eEdge: + aHandler->NoteEdge(d->mAddress, d->mCompartmentOrToAddress, d->mName); + break; + case CCGraphDescriber::eRoot: + aHandler->DescribeRoot(d->mAddress, d->mCnt); + break; + case CCGraphDescriber::eGarbage: + aHandler->DescribeGarbage(d->mAddress); + break; + case CCGraphDescriber::eUnknown: + MOZ_ASSERT_UNREACHABLE("CCGraphDescriber::eUnknown"); + break; + } + delete d; + } + if (!(*aCanContinue = !mDescribers.isEmpty())) { + mCurrentAddress.AssignLiteral("0x"); + } + return NS_OK; + } + NS_IMETHOD AsLogger(nsCycleCollectorLogger** aRetVal) override { + RefPtr rval = this; + rval.forget(aRetVal); + return NS_OK; + } + + private: + void ClearDescribers() { + CCGraphDescriber* d; + while ((d = mDescribers.popFirst())) { + delete d; + } + } + + nsCOMPtr mLogSink; + bool mWantAllTraces; + bool mDisableLog; + bool mWantAfterProcessing; + nsCString mCurrentAddress; + mozilla::LinkedList mDescribers; + FILE* mCCLog; +}; + +NS_IMPL_ISUPPORTS(nsCycleCollectorLogger, nsICycleCollectorListener) + +already_AddRefed nsCycleCollector_createLogger() { + nsCOMPtr logger = new nsCycleCollectorLogger(); + return logger.forget(); +} + +static bool GCThingIsGrayCCThing(JS::GCCellPtr thing) { + return JS::IsCCTraceKind(thing.kind()) && JS::GCThingIsMarkedGrayInCC(thing); +} + +static bool ValueIsGrayCCThing(const JS::Value& value) { + return JS::IsCCTraceKind(value.traceKind()) && + JS::GCThingIsMarkedGray(value.toGCCellPtr()); +} + +//////////////////////////////////////////////////////////////////////// +// Bacon & Rajan's |MarkRoots| routine. +//////////////////////////////////////////////////////////////////////// + +class CCGraphBuilder final : public nsCycleCollectionTraversalCallback, + public nsCycleCollectionNoteRootCallback { + private: + CCGraph& mGraph; + CycleCollectorResults& mResults; + NodePool::Builder mNodeBuilder; + EdgePool::Builder mEdgeBuilder; + MOZ_INIT_OUTSIDE_CTOR PtrInfo* mCurrPi; + nsCycleCollectionParticipant* mJSParticipant; + nsCycleCollectionParticipant* mJSZoneParticipant; + nsCString mNextEdgeName; + RefPtr mLogger; + bool mMergeZones; + UniquePtr mCurrNode; + uint32_t mNoteChildCount; + + struct PtrInfoCache : public MruCache { + static HashNumber Hash(const void* aKey) { return HashGeneric(aKey); } + static bool Match(const void* aKey, const PtrInfo* aVal) { + return aVal->mPointer == aKey; + } + }; + + PtrInfoCache mGraphCache; + + public: + CCGraphBuilder(CCGraph& aGraph, CycleCollectorResults& aResults, + CycleCollectedJSRuntime* aCCRuntime, + nsCycleCollectorLogger* aLogger, bool aMergeZones); + virtual ~CCGraphBuilder(); + + bool WantAllTraces() const { + return nsCycleCollectionNoteRootCallback::WantAllTraces(); + } + + bool AddPurpleRoot(void* aRoot, nsCycleCollectionParticipant* aParti); + + // This is called when all roots have been added to the graph, to prepare for + // BuildGraph(). + void DoneAddingRoots(); + + // Do some work traversing nodes in the graph. Returns true if this graph + // building is finished. + bool BuildGraph(SliceBudget& aBudget); + + void RemoveCachedEntry(void* aPtr) { mGraphCache.Remove(aPtr); } + + private: + PtrInfo* AddNode(void* aPtr, nsCycleCollectionParticipant* aParticipant); + PtrInfo* AddWeakMapNode(JS::GCCellPtr aThing); + PtrInfo* AddWeakMapNode(JSObject* aObject); + + void SetFirstChild() { mCurrPi->SetFirstChild(mEdgeBuilder.Mark()); } + + void SetLastChild() { mCurrPi->SetLastChild(mEdgeBuilder.Mark()); } + + public: + // nsCycleCollectionNoteRootCallback methods. + NS_IMETHOD_(void) + NoteXPCOMRoot(nsISupports* aRoot, + nsCycleCollectionParticipant* aParticipant) override; + NS_IMETHOD_(void) NoteJSRoot(JSObject* aRoot) override; + NS_IMETHOD_(void) + NoteNativeRoot(void* aRoot, + nsCycleCollectionParticipant* aParticipant) override; + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, JSObject* aKdelegate, + JS::GCCellPtr aVal) override; + // This is used to create synthetic non-refcounted references to + // nsXPCWrappedJS from their wrapped JS objects. No map is needed, because + // the SubjectToFinalization list is like a known-black weak map, and + // no delegate is needed because the keys are all unwrapped objects. + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aKey, nsISupports* aVal, + nsCycleCollectionParticipant* aValParticipant) override; + + // nsCycleCollectionTraversalCallback methods. + NS_IMETHOD_(void) + DescribeRefCountedNode(nsrefcnt aRefCount, const char* aObjName) override; + NS_IMETHOD_(void) + DescribeGCedNode(bool aIsMarked, const char* aObjName, + uint64_t aCompartmentAddress) override; + + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) override; + NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aThing) override; + NS_IMETHOD_(void) + NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aParticipant) override; + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) override; + + private: + NS_IMETHOD_(void) + NoteRoot(void* aRoot, nsCycleCollectionParticipant* aParticipant) { + MOZ_ASSERT(aRoot); + MOZ_ASSERT(aParticipant); + + if (!aParticipant->CanSkipInCC(aRoot) || MOZ_UNLIKELY(WantAllTraces())) { + AddNode(aRoot, aParticipant); + } + } + + NS_IMETHOD_(void) + NoteChild(void* aChild, nsCycleCollectionParticipant* aCp, + nsCString& aEdgeName) { + PtrInfo* childPi = AddNode(aChild, aCp); + if (!childPi) { + return; + } + mEdgeBuilder.Add(childPi); + if (mLogger) { + mLogger->NoteEdge((uint64_t)aChild, aEdgeName.get()); + } + ++childPi->mInternalRefs; + } + + JS::Zone* MergeZone(JS::GCCellPtr aGcthing) { + if (!mMergeZones) { + return nullptr; + } + JS::Zone* zone = JS::GetTenuredGCThingZone(aGcthing); + if (js::IsSystemZone(zone)) { + return nullptr; + } + return zone; + } +}; + +CCGraphBuilder::CCGraphBuilder(CCGraph& aGraph, CycleCollectorResults& aResults, + CycleCollectedJSRuntime* aCCRuntime, + nsCycleCollectorLogger* aLogger, + bool aMergeZones) + : mGraph(aGraph), + mResults(aResults), + mNodeBuilder(aGraph.mNodes), + mEdgeBuilder(aGraph.mEdges), + mJSParticipant(nullptr), + mJSZoneParticipant(nullptr), + mLogger(aLogger), + mMergeZones(aMergeZones), + mNoteChildCount(0) { + // 4096 is an allocation bucket size. + static_assert(sizeof(CCGraphBuilder) <= 4096, + "Don't create too large CCGraphBuilder objects"); + + if (aCCRuntime) { + mJSParticipant = aCCRuntime->GCThingParticipant(); + mJSZoneParticipant = aCCRuntime->ZoneParticipant(); + } + + if (mLogger) { + mFlags |= nsCycleCollectionTraversalCallback::WANT_DEBUG_INFO; + if (mLogger->IsAllTraces()) { + mFlags |= nsCycleCollectionTraversalCallback::WANT_ALL_TRACES; + mWantAllTraces = true; // for nsCycleCollectionNoteRootCallback + } + } + + mMergeZones = mMergeZones && MOZ_LIKELY(!WantAllTraces()); + + MOZ_ASSERT(nsCycleCollectionNoteRootCallback::WantAllTraces() == + nsCycleCollectionTraversalCallback::WantAllTraces()); +} + +CCGraphBuilder::~CCGraphBuilder() = default; + +PtrInfo* CCGraphBuilder::AddNode(void* aPtr, + nsCycleCollectionParticipant* aParticipant) { + if (mGraph.mOutOfMemory) { + return nullptr; + } + + PtrInfoCache::Entry cached = mGraphCache.Lookup(aPtr); + if (cached) { +#ifdef DEBUG + if (cached.Data()->mParticipant != aParticipant) { + auto* parti1 = cached.Data()->mParticipant; + auto* parti2 = aParticipant; + NS_WARNING( + nsPrintfCString("cached participant: %s; AddNode participant: %s\n", + parti1 ? parti1->ClassName() : "null", + parti2 ? parti2->ClassName() : "null") + .get()); + } +#endif + MOZ_ASSERT(cached.Data()->mParticipant == aParticipant, + "nsCycleCollectionParticipant shouldn't change!"); + return cached.Data(); + } + + PtrInfo* result; + auto p = mGraph.mPtrInfoMap.lookupForAdd(aPtr); + if (!p) { + // New entry + result = mNodeBuilder.Add(aPtr, aParticipant); + if (!result) { + return nullptr; + } + + if (!mGraph.mPtrInfoMap.add(p, result)) { + // `result` leaks here, but we can't free it because it's + // pool-allocated within NodePool. + mGraph.mOutOfMemory = true; + MOZ_ASSERT(false, "OOM while building cycle collector graph"); + return nullptr; + } + + } else { + result = *p; + MOZ_ASSERT(result->mParticipant == aParticipant, + "nsCycleCollectionParticipant shouldn't change!"); + } + + cached.Set(result); + + return result; +} + +bool CCGraphBuilder::AddPurpleRoot(void* aRoot, + nsCycleCollectionParticipant* aParti) { + ToParticipant(aRoot, &aParti); + + if (WantAllTraces() || !aParti->CanSkipInCC(aRoot)) { + PtrInfo* pinfo = AddNode(aRoot, aParti); + if (!pinfo) { + return false; + } + } + + return true; +} + +void CCGraphBuilder::DoneAddingRoots() { + // We've finished adding roots, and everything in the graph is a root. + mGraph.mRootCount = mGraph.MapCount(); + + mCurrNode = MakeUnique(mGraph.mNodes); +} + +MOZ_NEVER_INLINE bool CCGraphBuilder::BuildGraph(SliceBudget& aBudget) { + MOZ_ASSERT(mCurrNode); + + while (!aBudget.isOverBudget() && !mCurrNode->IsDone()) { + mNoteChildCount = 0; + + PtrInfo* pi = mCurrNode->GetNext(); + if (!pi) { + MOZ_CRASH(); + } + + mCurrPi = pi; + + // We need to call SetFirstChild() even on deleted nodes, to set their + // firstChild() that may be read by a prior non-deleted neighbor. + SetFirstChild(); + + if (pi->mParticipant) { + nsresult rv = pi->mParticipant->TraverseNativeAndJS(pi->mPointer, *this); + MOZ_RELEASE_ASSERT(!NS_FAILED(rv), + "Cycle collector Traverse method failed"); + } + + if (mCurrNode->AtBlockEnd()) { + SetLastChild(); + } + + aBudget.step(mNoteChildCount + 1); + } + + if (!mCurrNode->IsDone()) { + return false; + } + + if (mGraph.mRootCount > 0) { + SetLastChild(); + } + + mCurrNode = nullptr; + + return true; +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteXPCOMRoot(nsISupports* aRoot, + nsCycleCollectionParticipant* aParticipant) { + MOZ_ASSERT(aRoot == CanonicalizeXPCOMParticipant(aRoot)); + +#ifdef DEBUG + nsXPCOMCycleCollectionParticipant* cp; + ToParticipant(aRoot, &cp); + MOZ_ASSERT(aParticipant == cp); +#endif + + NoteRoot(aRoot, aParticipant); +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteJSRoot(JSObject* aRoot) { + if (JS::Zone* zone = MergeZone(JS::GCCellPtr(aRoot))) { + NoteRoot(zone, mJSZoneParticipant); + } else { + NoteRoot(aRoot, mJSParticipant); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteNativeRoot(void* aRoot, + nsCycleCollectionParticipant* aParticipant) { + NoteRoot(aRoot, aParticipant); +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::DescribeRefCountedNode(nsrefcnt aRefCount, + const char* aObjName) { + mCurrPi->AnnotatedReleaseAssert(aRefCount != 0, + "CCed refcounted object has zero refcount"); + mCurrPi->AnnotatedReleaseAssert( + aRefCount != UINT32_MAX, + "CCed refcounted object has overflowing refcount"); + + mResults.mVisitedRefCounted++; + + if (mLogger) { + mLogger->NoteRefCountedObject((uint64_t)mCurrPi->mPointer, aRefCount, + aObjName); + } + + mCurrPi->mRefCount = aRefCount; +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::DescribeGCedNode(bool aIsMarked, const char* aObjName, + uint64_t aCompartmentAddress) { + uint32_t refCount = aIsMarked ? UINT32_MAX : 0; + mResults.mVisitedGCed++; + + if (mLogger) { + mLogger->NoteGCedObject((uint64_t)mCurrPi->mPointer, aIsMarked, aObjName, + aCompartmentAddress); + } + + mCurrPi->mRefCount = refCount; +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteXPCOMChild(nsISupports* aChild) { + nsCString edgeName; + if (WantDebugInfo()) { + edgeName.Assign(mNextEdgeName); + mNextEdgeName.Truncate(); + } + if (!aChild || !(aChild = CanonicalizeXPCOMParticipant(aChild))) { + return; + } + + ++mNoteChildCount; + + nsXPCOMCycleCollectionParticipant* cp; + ToParticipant(aChild, &cp); + if (cp && (!cp->CanSkipThis(aChild) || WantAllTraces())) { + NoteChild(aChild, cp, edgeName); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aParticipant) { + nsCString edgeName; + if (WantDebugInfo()) { + edgeName.Assign(mNextEdgeName); + mNextEdgeName.Truncate(); + } + if (!aChild) { + return; + } + + ++mNoteChildCount; + + MOZ_ASSERT(aParticipant, "Need a nsCycleCollectionParticipant!"); + if (!aParticipant->CanSkipThis(aChild) || WantAllTraces()) { + NoteChild(aChild, aParticipant, edgeName); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteJSChild(JS::GCCellPtr aChild) { + if (!aChild) { + return; + } + + ++mNoteChildCount; + + nsCString edgeName; + if (MOZ_UNLIKELY(WantDebugInfo())) { + edgeName.Assign(mNextEdgeName); + mNextEdgeName.Truncate(); + } + + if (GCThingIsGrayCCThing(aChild) || MOZ_UNLIKELY(WantAllTraces())) { + if (JS::Zone* zone = MergeZone(aChild)) { + NoteChild(zone, mJSZoneParticipant, edgeName); + } else { + NoteChild(aChild.asCell(), mJSParticipant, edgeName); + } + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteNextEdgeName(const char* aName) { + if (WantDebugInfo()) { + mNextEdgeName = aName; + } +} + +PtrInfo* CCGraphBuilder::AddWeakMapNode(JS::GCCellPtr aNode) { + MOZ_ASSERT(aNode, "Weak map node should be non-null."); + + if (!GCThingIsGrayCCThing(aNode) && !WantAllTraces()) { + return nullptr; + } + + if (JS::Zone* zone = MergeZone(aNode)) { + return AddNode(zone, mJSZoneParticipant); + } + return AddNode(aNode.asCell(), mJSParticipant); +} + +PtrInfo* CCGraphBuilder::AddWeakMapNode(JSObject* aObject) { + return AddWeakMapNode(JS::GCCellPtr(aObject)); +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteWeakMapping(JSObject* aMap, JS::GCCellPtr aKey, + JSObject* aKdelegate, JS::GCCellPtr aVal) { + // Don't try to optimize away the entry here, as we've already attempted to + // do that in TraceWeakMapping in nsXPConnect. + WeakMapping* mapping = mGraph.mWeakMaps.AppendElement(); + mapping->mMap = aMap ? AddWeakMapNode(aMap) : nullptr; + mapping->mKey = aKey ? AddWeakMapNode(aKey) : nullptr; + mapping->mKeyDelegate = + aKdelegate ? AddWeakMapNode(aKdelegate) : mapping->mKey; + mapping->mVal = aVal ? AddWeakMapNode(aVal) : nullptr; + + if (mLogger) { + mLogger->NoteWeakMapEntry((uint64_t)aMap, aKey ? aKey.unsafeAsInteger() : 0, + (uint64_t)aKdelegate, + aVal ? aVal.unsafeAsInteger() : 0); + } +} + +NS_IMETHODIMP_(void) +CCGraphBuilder::NoteWeakMapping(JSObject* aKey, nsISupports* aVal, + nsCycleCollectionParticipant* aValParticipant) { + MOZ_ASSERT(aKey, "Don't call NoteWeakMapping with a null key"); + MOZ_ASSERT(aVal, "Don't call NoteWeakMapping with a null value"); + WeakMapping* mapping = mGraph.mWeakMaps.AppendElement(); + mapping->mMap = nullptr; + mapping->mKey = AddWeakMapNode(aKey); + mapping->mKeyDelegate = mapping->mKey; + MOZ_ASSERT(js::UncheckedUnwrapWithoutExpose(aKey) == aKey); + mapping->mVal = AddNode(aVal, aValParticipant); + + if (mLogger) { + mLogger->NoteWeakMapEntry(0, (uint64_t)aKey, 0, (uint64_t)aVal); + } +} + +static bool AddPurpleRoot(CCGraphBuilder& aBuilder, void* aRoot, + nsCycleCollectionParticipant* aParti) { + return aBuilder.AddPurpleRoot(aRoot, aParti); +} + +// MayHaveChild() will be false after a Traverse if the object does +// not have any children the CC will visit. +class ChildFinder : public nsCycleCollectionTraversalCallback { + public: + ChildFinder() : mMayHaveChild(false) {} + + // The logic of the Note*Child functions must mirror that of their + // respective functions in CCGraphBuilder. + NS_IMETHOD_(void) NoteXPCOMChild(nsISupports* aChild) override; + NS_IMETHOD_(void) + NoteNativeChild(void* aChild, nsCycleCollectionParticipant* aHelper) override; + NS_IMETHOD_(void) NoteJSChild(JS::GCCellPtr aThing) override; + + NS_IMETHOD_(void) + NoteWeakMapping(JSObject* aKey, nsISupports* aVal, + nsCycleCollectionParticipant* aValParticipant) override {} + + NS_IMETHOD_(void) + DescribeRefCountedNode(nsrefcnt aRefcount, const char* aObjname) override {} + NS_IMETHOD_(void) + DescribeGCedNode(bool aIsMarked, const char* aObjname, + uint64_t aCompartmentAddress) override {} + NS_IMETHOD_(void) NoteNextEdgeName(const char* aName) override {} + bool MayHaveChild() { return mMayHaveChild; } + + private: + bool mMayHaveChild; +}; + +NS_IMETHODIMP_(void) +ChildFinder::NoteXPCOMChild(nsISupports* aChild) { + if (!aChild || !(aChild = CanonicalizeXPCOMParticipant(aChild))) { + return; + } + nsXPCOMCycleCollectionParticipant* cp; + ToParticipant(aChild, &cp); + if (cp && !cp->CanSkip(aChild, true)) { + mMayHaveChild = true; + } +} + +NS_IMETHODIMP_(void) +ChildFinder::NoteNativeChild(void* aChild, + nsCycleCollectionParticipant* aHelper) { + if (!aChild) { + return; + } + MOZ_ASSERT(aHelper, "Native child must have a participant"); + if (!aHelper->CanSkip(aChild, true)) { + mMayHaveChild = true; + } +} + +NS_IMETHODIMP_(void) +ChildFinder::NoteJSChild(JS::GCCellPtr aChild) { + if (aChild && JS::GCThingIsMarkedGray(aChild)) { + mMayHaveChild = true; + } +} + +static bool MayHaveChild(void* aObj, nsCycleCollectionParticipant* aCp) { + ChildFinder cf; + aCp->TraverseNativeAndJS(aObj, cf); + return cf.MayHaveChild(); +} + +// JSPurpleBuffer keeps references to GCThings which might affect the +// next cycle collection. It is owned only by itself and during unlink its +// self reference is broken down and the object ends up killing itself. +// If GC happens before CC, references to GCthings and the self reference are +// removed. +class JSPurpleBuffer { + ~JSPurpleBuffer() { + MOZ_ASSERT(mValues.IsEmpty()); + MOZ_ASSERT(mObjects.IsEmpty()); + } + + public: + explicit JSPurpleBuffer(RefPtr& aReferenceToThis) + : mReferenceToThis(aReferenceToThis), + mValues(kSegmentSize), + mObjects(kSegmentSize) { + mReferenceToThis = this; + mozilla::HoldJSObjects(this); + } + + void Destroy() { + RefPtr referenceToThis; + mReferenceToThis.swap(referenceToThis); + mValues.Clear(); + mObjects.Clear(); + mozilla::DropJSObjects(this); + } + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(JSPurpleBuffer) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(JSPurpleBuffer) + + RefPtr& mReferenceToThis; + + // These are raw pointers instead of Heap because we only need Heap for + // pointers which may point into the nursery. The purple buffer never contains + // pointers to the nursery because nursery gcthings can never be gray and only + // gray things can be inserted into the purple buffer. + static const size_t kSegmentSize = 512; + SegmentedVector mValues; + SegmentedVector mObjects; +}; + +NS_IMPL_CYCLE_COLLECTION_CLASS(JSPurpleBuffer) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSPurpleBuffer) + tmp->Destroy(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSPurpleBuffer) + CycleCollectionNoteChild(cb, tmp, "self"); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_TRACE_SEGMENTED_ARRAY(_field, _type) \ + { \ + for (auto iter = tmp->_field.Iter(); !iter.Done(); iter.Next()) { \ + js::gc::CallTraceCallbackOnNonHeap<_type, TraceCallbacks>( \ + &iter.Get(), aCallbacks, #_field, aClosure); \ + } \ + } + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(JSPurpleBuffer) + NS_TRACE_SEGMENTED_ARRAY(mValues, JS::Value) + NS_TRACE_SEGMENTED_ARRAY(mObjects, JSObject*) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +class SnowWhiteKiller : public TraceCallbacks { + struct SnowWhiteObject { + void* mPointer; + nsCycleCollectionParticipant* mParticipant; + nsCycleCollectingAutoRefCnt* mRefCnt; + }; + + // Segments are 4 KiB on 32-bit and 8 KiB on 64-bit. + static const size_t kSegmentSize = sizeof(void*) * 1024; + typedef SegmentedVector + ObjectsVector; + + public: + SnowWhiteKiller(nsCycleCollector* aCollector, js::SliceBudget* aBudget) + : mCollector(aCollector), + mObjects(kSegmentSize), + mBudget(aBudget), + mSawSnowWhiteObjects(false) { + MOZ_ASSERT(mCollector, "Calling SnowWhiteKiller after nsCC went away"); + } + + explicit SnowWhiteKiller(nsCycleCollector* aCollector) + : SnowWhiteKiller(aCollector, nullptr) {} + + ~SnowWhiteKiller() { + for (auto iter = mObjects.Iter(); !iter.Done(); iter.Next()) { + SnowWhiteObject& o = iter.Get(); + MaybeKillObject(o); + } + } + + private: + void MaybeKillObject(SnowWhiteObject& aObject) { + if (!aObject.mRefCnt->get() && !aObject.mRefCnt->IsInPurpleBuffer()) { + mCollector->RemoveObjectFromGraph(aObject.mPointer); + aObject.mRefCnt->stabilizeForDeletion(); + { + JS::AutoEnterCycleCollection autocc(mCollector->Runtime()->Runtime()); + aObject.mParticipant->Trace(aObject.mPointer, *this, nullptr); + } + aObject.mParticipant->DeleteCycleCollectable(aObject.mPointer); + } + } + + public: + bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) { + if (mBudget) { + if (mBudget->isOverBudget()) { + return false; + } + mBudget->step(); + } + + MOZ_ASSERT(aEntry->mObject, "Null object in purple buffer"); + if (!aEntry->mRefCnt->get()) { + mSawSnowWhiteObjects = true; + void* o = aEntry->mObject; + nsCycleCollectionParticipant* cp = aEntry->mParticipant; + ToParticipant(o, &cp); + SnowWhiteObject swo = {o, cp, aEntry->mRefCnt}; + if (!mBudget) { + mObjects.InfallibleAppend(swo); + } + aBuffer.Remove(aEntry); + if (mBudget) { + MaybeKillObject(swo); + } + } + return true; + } + + bool HasSnowWhiteObjects() const { return !mObjects.IsEmpty(); } + + bool SawSnowWhiteObjects() const { return mSawSnowWhiteObjects; } + + virtual void Trace(JS::Heap* aValue, const char* aName, + void* aClosure) const override { + const JS::Value& val = aValue->unbarrieredGet(); + if (val.isGCThing() && ValueIsGrayCCThing(val)) { + MOZ_ASSERT(!js::gc::IsInsideNursery(val.toGCThing())); + mCollector->GetJSPurpleBuffer()->mValues.InfallibleAppend(val); + } + } + + virtual void Trace(JS::Heap* aId, const char* aName, + void* aClosure) const override {} + + void AppendJSObjectToPurpleBuffer(JSObject* obj) const { + if (obj && JS::ObjectIsMarkedGray(obj)) { + MOZ_ASSERT(JS::ObjectIsTenured(obj)); + mCollector->GetJSPurpleBuffer()->mObjects.InfallibleAppend(obj); + } + } + + virtual void Trace(JS::Heap* aObject, const char* aName, + void* aClosure) const override { + AppendJSObjectToPurpleBuffer(aObject->unbarrieredGet()); + } + + virtual void Trace(nsWrapperCache* aWrapperCache, const char* aName, + void* aClosure) const override { + AppendJSObjectToPurpleBuffer(aWrapperCache->GetWrapperPreserveColor()); + } + + virtual void Trace(JS::TenuredHeap* aObject, const char* aName, + void* aClosure) const override { + AppendJSObjectToPurpleBuffer(aObject->unbarrieredGetPtr()); + } + + virtual void Trace(JS::Heap* aString, const char* aName, + void* aClosure) const override {} + + virtual void Trace(JS::Heap* aScript, const char* aName, + void* aClosure) const override {} + + virtual void Trace(JS::Heap* aFunction, const char* aName, + void* aClosure) const override {} + + private: + RefPtr mCollector; + ObjectsVector mObjects; + js::SliceBudget* mBudget; + bool mSawSnowWhiteObjects; +}; + +class RemoveSkippableVisitor : public SnowWhiteKiller { + public: + RemoveSkippableVisitor(nsCycleCollector* aCollector, js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb) + : SnowWhiteKiller(aCollector), + mBudget(aBudget), + mRemoveChildlessNodes(aRemoveChildlessNodes), + mAsyncSnowWhiteFreeing(aAsyncSnowWhiteFreeing), + mDispatchedDeferredDeletion(false), + mCallback(aCb) {} + + ~RemoveSkippableVisitor() { + // Note, we must call the callback before SnowWhiteKiller calls + // DeleteCycleCollectable! + if (mCallback) { + mCallback(); + } + if (HasSnowWhiteObjects()) { + // Effectively a continuation. + nsCycleCollector_dispatchDeferredDeletion(true); + } + } + + bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) { + if (mBudget.isOverBudget()) { + return false; + } + + // CanSkip calls can be a bit slow, so increase the likelihood that + // isOverBudget actually checks whether we're over the time budget. + mBudget.step(5); + MOZ_ASSERT(aEntry->mObject, "null mObject in purple buffer"); + if (!aEntry->mRefCnt->get()) { + if (!mAsyncSnowWhiteFreeing) { + SnowWhiteKiller::Visit(aBuffer, aEntry); + } else if (!mDispatchedDeferredDeletion) { + mDispatchedDeferredDeletion = true; + nsCycleCollector_dispatchDeferredDeletion(false); + } + return true; + } + void* o = aEntry->mObject; + nsCycleCollectionParticipant* cp = aEntry->mParticipant; + ToParticipant(o, &cp); + if (aEntry->mRefCnt->IsPurple() && !cp->CanSkip(o, false) && + (!mRemoveChildlessNodes || MayHaveChild(o, cp))) { + return true; + } + aBuffer.Remove(aEntry); + return true; + } + + private: + js::SliceBudget& mBudget; + bool mRemoveChildlessNodes; + bool mAsyncSnowWhiteFreeing; + bool mDispatchedDeferredDeletion; + CC_ForgetSkippableCallback mCallback; +}; + +void nsPurpleBuffer::RemoveSkippable(nsCycleCollector* aCollector, + js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing, + CC_ForgetSkippableCallback aCb) { + RemoveSkippableVisitor visitor(aCollector, aBudget, aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing, aCb); + VisitEntries(visitor); +} + +bool nsCycleCollector::FreeSnowWhite(bool aUntilNoSWInPurpleBuffer) { + CheckThreadSafety(); + + if (mFreeingSnowWhite) { + return false; + } + + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_FreeSnowWhite); + + AutoRestore ar(mFreeingSnowWhite); + mFreeingSnowWhite = true; + + bool hadSnowWhiteObjects = false; + do { + SnowWhiteKiller visitor(this); + mPurpleBuf.VisitEntries(visitor); + hadSnowWhiteObjects = hadSnowWhiteObjects || visitor.HasSnowWhiteObjects(); + if (!visitor.HasSnowWhiteObjects()) { + break; + } + } while (aUntilNoSWInPurpleBuffer); + return hadSnowWhiteObjects; +} + +bool nsCycleCollector::FreeSnowWhiteWithBudget(js::SliceBudget& aBudget) { + CheckThreadSafety(); + + if (mFreeingSnowWhite) { + return false; + } + + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_FreeSnowWhite); + AutoRestore ar(mFreeingSnowWhite); + mFreeingSnowWhite = true; + + SnowWhiteKiller visitor(this, &aBudget); + mPurpleBuf.VisitEntries(visitor); + return visitor.SawSnowWhiteObjects(); + ; +} + +void nsCycleCollector::ForgetSkippable(js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing) { + CheckThreadSafety(); + + if (mFreeingSnowWhite) { + return; + } + + mozilla::Maybe marker; + if (NS_IsMainThread()) { + marker.emplace("nsCycleCollector::ForgetSkippable", + MarkerStackRequest::NO_STACK); + } + + // If we remove things from the purple buffer during graph building, we may + // lose track of an object that was mutated during graph building. + MOZ_ASSERT(IsIdle()); + + if (mCCJSRuntime) { + mCCJSRuntime->PrepareForForgetSkippable(); + } + MOZ_ASSERT( + !mScanInProgress, + "Don't forget skippable or free snow-white while scan is in progress."); + mPurpleBuf.RemoveSkippable(this, aBudget, aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing, mForgetSkippableCB); +} + +MOZ_NEVER_INLINE void nsCycleCollector::MarkRoots(SliceBudget& aBudget) { + JS::AutoAssertNoGC nogc; + TimeLog timeLog; + AutoRestore ar(mScanInProgress); + MOZ_RELEASE_ASSERT(!mScanInProgress); + mScanInProgress = true; + MOZ_ASSERT(mIncrementalPhase == GraphBuildingPhase); + + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_BuildGraph); + JS::AutoEnterCycleCollection autocc(Runtime()->Runtime()); + bool doneBuilding = mBuilder->BuildGraph(aBudget); + + if (!doneBuilding) { + timeLog.Checkpoint("MarkRoots()"); + return; + } + + mBuilder = nullptr; + mIncrementalPhase = ScanAndCollectWhitePhase; + timeLog.Checkpoint("MarkRoots()"); +} + +//////////////////////////////////////////////////////////////////////// +// Bacon & Rajan's |ScanRoots| routine. +//////////////////////////////////////////////////////////////////////// + +struct ScanBlackVisitor { + ScanBlackVisitor(uint32_t& aWhiteNodeCount, bool& aFailed) + : mWhiteNodeCount(aWhiteNodeCount), mFailed(aFailed) {} + + bool ShouldVisitNode(PtrInfo const* aPi) { return aPi->mColor != black; } + + MOZ_NEVER_INLINE void VisitNode(PtrInfo* aPi) { + if (aPi->mColor == white) { + --mWhiteNodeCount; + } + aPi->mColor = black; + } + + void Failed() { mFailed = true; } + + private: + uint32_t& mWhiteNodeCount; + bool& mFailed; +}; + +static void FloodBlackNode(uint32_t& aWhiteNodeCount, bool& aFailed, + PtrInfo* aPi) { + GraphWalker(ScanBlackVisitor(aWhiteNodeCount, aFailed)) + .Walk(aPi); + MOZ_ASSERT(aPi->mColor == black || !aPi->WasTraversed(), + "FloodBlackNode should make aPi black"); +} + +// Iterate over the WeakMaps. If we mark anything while iterating +// over the WeakMaps, we must iterate over all of the WeakMaps again. +void nsCycleCollector::ScanWeakMaps() { + bool anyChanged; + bool failed = false; + do { + anyChanged = false; + for (uint32_t i = 0; i < mGraph.mWeakMaps.Length(); i++) { + WeakMapping* wm = &mGraph.mWeakMaps[i]; + + // If any of these are null, the original object was marked black. + uint32_t mColor = wm->mMap ? wm->mMap->mColor : black; + uint32_t kColor = wm->mKey ? wm->mKey->mColor : black; + uint32_t kdColor = wm->mKeyDelegate ? wm->mKeyDelegate->mColor : black; + uint32_t vColor = wm->mVal ? wm->mVal->mColor : black; + + MOZ_ASSERT(mColor != grey, "Uncolored weak map"); + MOZ_ASSERT(kColor != grey, "Uncolored weak map key"); + MOZ_ASSERT(kdColor != grey, "Uncolored weak map key delegate"); + MOZ_ASSERT(vColor != grey, "Uncolored weak map value"); + + if (mColor == black && kColor != black && kdColor == black) { + FloodBlackNode(mWhiteNodeCount, failed, wm->mKey); + anyChanged = true; + } + + if (mColor == black && kColor == black && vColor != black) { + FloodBlackNode(mWhiteNodeCount, failed, wm->mVal); + anyChanged = true; + } + } + } while (anyChanged); + + if (failed) { + MOZ_ASSERT(false, "Ran out of memory in ScanWeakMaps"); + CC_TELEMETRY(_OOM, true); + } +} + +// Flood black from any objects in the purple buffer that are in the CC graph. +class PurpleScanBlackVisitor { + public: + PurpleScanBlackVisitor(CCGraph& aGraph, nsCycleCollectorLogger* aLogger, + uint32_t& aCount, bool& aFailed) + : mGraph(aGraph), mLogger(aLogger), mCount(aCount), mFailed(aFailed) {} + + bool Visit(nsPurpleBuffer& aBuffer, nsPurpleBufferEntry* aEntry) { + MOZ_ASSERT(aEntry->mObject, + "Entries with null mObject shouldn't be in the purple buffer."); + MOZ_ASSERT(aEntry->mRefCnt->get() != 0, + "Snow-white objects shouldn't be in the purple buffer."); + + void* obj = aEntry->mObject; + + MOZ_ASSERT( + aEntry->mParticipant || + CanonicalizeXPCOMParticipant(static_cast(obj)) == obj, + "Suspect nsISupports pointer must be canonical"); + + PtrInfo* pi = mGraph.FindNode(obj); + if (!pi) { + return true; + } + MOZ_ASSERT(pi->mParticipant, + "No dead objects should be in the purple buffer."); + if (MOZ_UNLIKELY(mLogger)) { + mLogger->NoteIncrementalRoot((uint64_t)pi->mPointer); + } + if (pi->mColor == black) { + return true; + } + FloodBlackNode(mCount, mFailed, pi); + return true; + } + + private: + CCGraph& mGraph; + RefPtr mLogger; + uint32_t& mCount; + bool& mFailed; +}; + +// Objects that have been stored somewhere since the start of incremental graph +// building must be treated as live for this cycle collection, because we may +// not have accurate information about who holds references to them. +void nsCycleCollector::ScanIncrementalRoots() { + TimeLog timeLog; + + // Reference counted objects: + // We cleared the purple buffer at the start of the current ICC, so if a + // refcounted object is purple, it may have been AddRef'd during the current + // ICC. (It may also have only been released.) If that is the case, we cannot + // be sure that the set of things pointing to the object in the CC graph + // is accurate. Therefore, for safety, we treat any purple objects as being + // live during the current CC. We don't remove anything from the purple + // buffer here, so these objects will be suspected and freed in the next CC + // if they are garbage. + bool failed = false; + PurpleScanBlackVisitor purpleScanBlackVisitor(mGraph, mLogger, + mWhiteNodeCount, failed); + mPurpleBuf.VisitEntries(purpleScanBlackVisitor); + timeLog.Checkpoint("ScanIncrementalRoots::fix purple"); + + bool hasJSRuntime = !!mCCJSRuntime; + nsCycleCollectionParticipant* jsParticipant = + hasJSRuntime ? mCCJSRuntime->GCThingParticipant() : nullptr; + nsCycleCollectionParticipant* zoneParticipant = + hasJSRuntime ? mCCJSRuntime->ZoneParticipant() : nullptr; + bool hasLogger = !!mLogger; + + NodePool::Enumerator etor(mGraph.mNodes); + while (!etor.IsDone()) { + PtrInfo* pi = etor.GetNext(); + + // As an optimization, if an object has already been determined to be live, + // don't consider it further. We can't do this if there is a listener, + // because the listener wants to know the complete set of incremental roots. + if (pi->mColor == black && MOZ_LIKELY(!hasLogger)) { + continue; + } + + // Garbage collected objects: + // If a GCed object was added to the graph with a refcount of zero, and is + // now marked black by the GC, it was probably gray before and was exposed + // to active JS, so it may have been stored somewhere, so it needs to be + // treated as live. + if (pi->IsGrayJS() && MOZ_LIKELY(hasJSRuntime)) { + // If the object is still marked gray by the GC, nothing could have gotten + // hold of it, so it isn't an incremental root. + if (pi->mParticipant == jsParticipant) { + JS::GCCellPtr ptr(pi->mPointer, JS::GCThingTraceKind(pi->mPointer)); + if (GCThingIsGrayCCThing(ptr)) { + continue; + } + } else if (pi->mParticipant == zoneParticipant) { + JS::Zone* zone = static_cast(pi->mPointer); + if (js::ZoneGlobalsAreAllGray(zone)) { + continue; + } + } else { + MOZ_ASSERT(false, "Non-JS thing with 0 refcount? Treating as live."); + } + } else if (!pi->mParticipant && pi->WasTraversed()) { + // Dead traversed refcounted objects: + // If the object was traversed, it must have been alive at the start of + // the CC, and thus had a positive refcount. It is dead now, so its + // refcount must have decreased at some point during the CC. Therefore, + // it would be in the purple buffer if it wasn't dead, so treat it as an + // incremental root. + // + // This should not cause leaks because as the object died it should have + // released anything it held onto, which will add them to the purple + // buffer, which will cause them to be considered in the next CC. + } else { + continue; + } + + // At this point, pi must be an incremental root. + + // If there's a listener, tell it about this root. We don't bother with the + // optimization of skipping the Walk() if pi is black: it will just return + // without doing anything and there's no need to make this case faster. + if (MOZ_UNLIKELY(hasLogger) && pi->mPointer) { + // Dead objects aren't logged. See bug 1031370. + mLogger->NoteIncrementalRoot((uint64_t)pi->mPointer); + } + + FloodBlackNode(mWhiteNodeCount, failed, pi); + } + + timeLog.Checkpoint("ScanIncrementalRoots::fix nodes"); + + if (failed) { + NS_ASSERTION(false, "Ran out of memory in ScanIncrementalRoots"); + CC_TELEMETRY(_OOM, true); + } +} + +// Mark nodes white and make sure their refcounts are ok. +// No nodes are marked black during this pass to ensure that refcount +// checking is run on all nodes not marked black by ScanIncrementalRoots. +void nsCycleCollector::ScanWhiteNodes(bool aFullySynchGraphBuild) { + NodePool::Enumerator nodeEnum(mGraph.mNodes); + while (!nodeEnum.IsDone()) { + PtrInfo* pi = nodeEnum.GetNext(); + if (pi->mColor == black) { + // Incremental roots can be in a nonsensical state, so don't + // check them. This will miss checking nodes that are merely + // reachable from incremental roots. + MOZ_ASSERT(!aFullySynchGraphBuild, + "In a synch CC, no nodes should be marked black early on."); + continue; + } + MOZ_ASSERT(pi->mColor == grey); + + if (!pi->WasTraversed()) { + // This node was deleted before it was traversed, so there's no reason + // to look at it. + MOZ_ASSERT(!pi->mParticipant, + "Live nodes should all have been traversed"); + continue; + } + + if (pi->mInternalRefs == pi->mRefCount || pi->IsGrayJS()) { + pi->mColor = white; + ++mWhiteNodeCount; + continue; + } + + pi->AnnotatedReleaseAssert( + pi->mInternalRefs <= pi->mRefCount, + "More references to an object than its refcount"); + + // This node will get marked black in the next pass. + } +} + +// Any remaining grey nodes that haven't already been deleted must be alive, +// so mark them and their children black. Any nodes that are black must have +// already had their children marked black, so there's no need to look at them +// again. This pass may turn some white nodes to black. +void nsCycleCollector::ScanBlackNodes() { + bool failed = false; + NodePool::Enumerator nodeEnum(mGraph.mNodes); + while (!nodeEnum.IsDone()) { + PtrInfo* pi = nodeEnum.GetNext(); + if (pi->mColor == grey && pi->WasTraversed()) { + FloodBlackNode(mWhiteNodeCount, failed, pi); + } + } + + if (failed) { + NS_ASSERTION(false, "Ran out of memory in ScanBlackNodes"); + CC_TELEMETRY(_OOM, true); + } +} + +void nsCycleCollector::ScanRoots(bool aFullySynchGraphBuild) { + JS::AutoAssertNoGC nogc; + AutoRestore ar(mScanInProgress); + MOZ_RELEASE_ASSERT(!mScanInProgress); + mScanInProgress = true; + mWhiteNodeCount = 0; + MOZ_ASSERT(mIncrementalPhase == ScanAndCollectWhitePhase); + + JS::AutoEnterCycleCollection autocc(Runtime()->Runtime()); + + if (!aFullySynchGraphBuild) { + ScanIncrementalRoots(); + } + + TimeLog timeLog; + ScanWhiteNodes(aFullySynchGraphBuild); + timeLog.Checkpoint("ScanRoots::ScanWhiteNodes"); + + ScanBlackNodes(); + timeLog.Checkpoint("ScanRoots::ScanBlackNodes"); + + // Scanning weak maps must be done last. + ScanWeakMaps(); + timeLog.Checkpoint("ScanRoots::ScanWeakMaps"); + + if (mLogger) { + mLogger->BeginResults(); + + NodePool::Enumerator etor(mGraph.mNodes); + while (!etor.IsDone()) { + PtrInfo* pi = etor.GetNext(); + if (!pi->WasTraversed()) { + continue; + } + switch (pi->mColor) { + case black: + if (!pi->IsGrayJS() && !pi->IsBlackJS() && + pi->mInternalRefs != pi->mRefCount) { + mLogger->DescribeRoot((uint64_t)pi->mPointer, pi->mInternalRefs); + } + break; + case white: + mLogger->DescribeGarbage((uint64_t)pi->mPointer); + break; + case grey: + MOZ_ASSERT(false, "All traversed objects should be black or white"); + break; + } + } + + mLogger->End(); + mLogger = nullptr; + timeLog.Checkpoint("ScanRoots::listener"); + } +} + +//////////////////////////////////////////////////////////////////////// +// Bacon & Rajan's |CollectWhite| routine, somewhat modified. +//////////////////////////////////////////////////////////////////////// + +bool nsCycleCollector::CollectWhite() { + // Explanation of "somewhat modified": we have no way to collect the + // set of whites "all at once", we have to ask each of them to drop + // their outgoing links and assume this will cause the garbage cycle + // to *mostly* self-destruct (except for the reference we continue + // to hold). + // + // To do this "safely" we must make sure that the white nodes we're + // operating on are stable for the duration of our operation. So we + // make 3 sets of calls to language runtimes: + // + // - Root(whites), which should pin the whites in memory. + // - Unlink(whites), which drops outgoing links on each white. + // - Unroot(whites), which returns the whites to normal GC. + + // Segments are 4 KiB on 32-bit and 8 KiB on 64-bit. + static const size_t kSegmentSize = sizeof(void*) * 1024; + SegmentedVector whiteNodes( + kSegmentSize); + TimeLog timeLog; + + MOZ_ASSERT(mIncrementalPhase == ScanAndCollectWhitePhase); + + uint32_t numWhiteNodes = 0; + uint32_t numWhiteGCed = 0; + uint32_t numWhiteJSZones = 0; + + { + JS::AutoAssertNoGC nogc; + bool hasJSRuntime = !!mCCJSRuntime; + nsCycleCollectionParticipant* zoneParticipant = + hasJSRuntime ? mCCJSRuntime->ZoneParticipant() : nullptr; + + NodePool::Enumerator etor(mGraph.mNodes); + while (!etor.IsDone()) { + PtrInfo* pinfo = etor.GetNext(); + if (pinfo->mColor == white && pinfo->mParticipant) { + if (pinfo->IsGrayJS()) { + MOZ_ASSERT(mCCJSRuntime); + ++numWhiteGCed; + JS::Zone* zone; + if (MOZ_UNLIKELY(pinfo->mParticipant == zoneParticipant)) { + ++numWhiteJSZones; + zone = static_cast(pinfo->mPointer); + } else { + JS::GCCellPtr ptr(pinfo->mPointer, + JS::GCThingTraceKind(pinfo->mPointer)); + zone = JS::GetTenuredGCThingZone(ptr); + } + mCCJSRuntime->AddZoneWaitingForGC(zone); + } else { + whiteNodes.InfallibleAppend(pinfo); + pinfo->mParticipant->Root(pinfo->mPointer); + ++numWhiteNodes; + } + } + } + } + + mResults.mFreedRefCounted += numWhiteNodes; + mResults.mFreedGCed += numWhiteGCed; + mResults.mFreedJSZones += numWhiteJSZones; + + timeLog.Checkpoint("CollectWhite::Root"); + + if (mBeforeUnlinkCB) { + mBeforeUnlinkCB(); + timeLog.Checkpoint("CollectWhite::BeforeUnlinkCB"); + } + + // Unlink() can trigger a GC, so do not touch any JS or anything + // else not in whiteNodes after here. + + for (auto iter = whiteNodes.Iter(); !iter.Done(); iter.Next()) { + PtrInfo* pinfo = iter.Get(); + MOZ_ASSERT(pinfo->mParticipant, + "Unlink shouldn't see objects removed from graph."); + pinfo->mParticipant->Unlink(pinfo->mPointer); +#ifdef DEBUG + if (mCCJSRuntime) { + mCCJSRuntime->AssertNoObjectsToTrace(pinfo->mPointer); + } +#endif + } + timeLog.Checkpoint("CollectWhite::Unlink"); + + JS::AutoAssertNoGC nogc; + for (auto iter = whiteNodes.Iter(); !iter.Done(); iter.Next()) { + PtrInfo* pinfo = iter.Get(); + MOZ_ASSERT(pinfo->mParticipant, + "Unroot shouldn't see objects removed from graph."); + pinfo->mParticipant->Unroot(pinfo->mPointer); + } + timeLog.Checkpoint("CollectWhite::Unroot"); + + nsCycleCollector_dispatchDeferredDeletion(false, true); + timeLog.Checkpoint("CollectWhite::dispatchDeferredDeletion"); + + mIncrementalPhase = CleanupPhase; + + return numWhiteNodes > 0 || numWhiteGCed > 0 || numWhiteJSZones > 0; +} + +//////////////////////// +// Memory reporting +//////////////////////// + +MOZ_DEFINE_MALLOC_SIZE_OF(CycleCollectorMallocSizeOf) + +NS_IMETHODIMP +nsCycleCollector::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + size_t objectSize, graphSize, purpleBufferSize; + SizeOfIncludingThis(CycleCollectorMallocSizeOf, &objectSize, &graphSize, + &purpleBufferSize); + + if (objectSize > 0) { + MOZ_COLLECT_REPORT("explicit/cycle-collector/collector-object", KIND_HEAP, + UNITS_BYTES, objectSize, + "Memory used for the cycle collector object itself."); + } + + if (graphSize > 0) { + MOZ_COLLECT_REPORT( + "explicit/cycle-collector/graph", KIND_HEAP, UNITS_BYTES, graphSize, + "Memory used for the cycle collector's graph. This should be zero when " + "the collector is idle."); + } + + if (purpleBufferSize > 0) { + MOZ_COLLECT_REPORT("explicit/cycle-collector/purple-buffer", KIND_HEAP, + UNITS_BYTES, purpleBufferSize, + "Memory used for the cycle collector's purple buffer."); + } + + return NS_OK; +}; + +//////////////////////////////////////////////////////////////////////// +// Collector implementation +//////////////////////////////////////////////////////////////////////// + +nsCycleCollector::nsCycleCollector() + : mActivelyCollecting(false), + mFreeingSnowWhite(false), + mScanInProgress(false), + mCCJSRuntime(nullptr), + mIncrementalPhase(IdlePhase), +#ifdef DEBUG + mEventTarget(GetCurrentSerialEventTarget()), +#endif + mWhiteNodeCount(0), + mBeforeUnlinkCB(nullptr), + mForgetSkippableCB(nullptr), + mUnmergedNeeded(0), + mMergedInARow(0) { +} + +nsCycleCollector::~nsCycleCollector() { + MOZ_ASSERT(!mJSPurpleBuffer, "Didn't call JSPurpleBuffer::Destroy?"); + + UnregisterWeakMemoryReporter(this); +} + +void nsCycleCollector::SetCCJSRuntime(CycleCollectedJSRuntime* aCCRuntime) { + MOZ_RELEASE_ASSERT( + !mCCJSRuntime, + "Multiple registrations of CycleCollectedJSRuntime in cycle collector"); + mCCJSRuntime = aCCRuntime; + + if (!NS_IsMainThread()) { + return; + } + + // We can't register as a reporter in nsCycleCollector() because that runs + // before the memory reporter manager is initialized. So we do it here + // instead. + RegisterWeakMemoryReporter(this); +} + +void nsCycleCollector::ClearCCJSRuntime() { + MOZ_RELEASE_ASSERT(mCCJSRuntime, + "Clearing CycleCollectedJSRuntime in cycle collector " + "before a runtime was registered"); + mCCJSRuntime = nullptr; +} + +#ifdef DEBUG +static bool HasParticipant(void* aPtr, nsCycleCollectionParticipant* aParti) { + if (aParti) { + return true; + } + + nsXPCOMCycleCollectionParticipant* xcp; + ToParticipant(static_cast(aPtr), &xcp); + return xcp != nullptr; +} +#endif + +MOZ_ALWAYS_INLINE void nsCycleCollector::Suspect( + void* aPtr, nsCycleCollectionParticipant* aParti, + nsCycleCollectingAutoRefCnt* aRefCnt) { + CheckThreadSafety(); + + // Don't call AddRef or Release of a CCed object in a Traverse() method. + MOZ_ASSERT(!mScanInProgress, + "Attempted to call Suspect() while a scan was in progress"); + + if (MOZ_UNLIKELY(mScanInProgress)) { + return; + } + + MOZ_ASSERT(aPtr, "Don't suspect null pointers"); + + MOZ_ASSERT(HasParticipant(aPtr, aParti), + "Suspected nsISupports pointer must QI to " + "nsXPCOMCycleCollectionParticipant"); + + MOZ_ASSERT(aParti || CanonicalizeXPCOMParticipant( + static_cast(aPtr)) == aPtr, + "Suspect nsISupports pointer must be canonical"); + + mPurpleBuf.Put(aPtr, aParti, aRefCnt); +} + +void nsCycleCollector::SuspectNurseryEntries() { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + while (gNurseryPurpleBufferEntryCount) { + NurseryPurpleBufferEntry& entry = + gNurseryPurpleBufferEntry[--gNurseryPurpleBufferEntryCount]; + mPurpleBuf.Put(entry.mPtr, entry.mParticipant, entry.mRefCnt); + } +} + +void nsCycleCollector::CheckThreadSafety() { +#ifdef DEBUG + MOZ_ASSERT(mEventTarget->IsOnCurrentThread()); +#endif +} + +// The cycle collector uses the mark bitmap to discover what JS objects are +// reachable only from XPConnect roots that might participate in cycles. We ask +// the JS runtime whether we need to force a GC before this CC. It should only +// be true when UnmarkGray has run out of stack. We also force GCs on shutdown +// to collect cycles involving both DOM and JS, and in WantAllTraces CCs to +// prevent hijinks from ForgetSkippable and compartmental GCs. +void nsCycleCollector::FixGrayBits(bool aIsShutdown, TimeLog& aTimeLog) { + CheckThreadSafety(); + + if (!mCCJSRuntime) { + return; + } + + // If we're not forcing a GC anyways due to shutdown or an all traces CC, + // check to see if we still need to do one to fix the gray bits. + if (!(aIsShutdown || (mLogger && mLogger->IsAllTraces()))) { + mCCJSRuntime->FixWeakMappingGrayBits(); + aTimeLog.Checkpoint("FixWeakMappingGrayBits"); + + bool needGC = !mCCJSRuntime->AreGCGrayBitsValid(); + // Only do a telemetry ping for non-shutdown CCs. + CC_TELEMETRY(_NEED_GC, needGC); + if (!needGC) { + return; + } + } + + mResults.mForcedGC = true; + + uint32_t count = 0; + do { + if (aIsShutdown) { + mCCJSRuntime->GarbageCollect(JS::GCOptions::Shutdown, + JS::GCReason::SHUTDOWN_CC); + } else { + mCCJSRuntime->GarbageCollect(JS::GCOptions::Normal, + JS::GCReason::CC_FORCED); + } + + mCCJSRuntime->FixWeakMappingGrayBits(); + + // It's possible that FixWeakMappingGrayBits will hit OOM when unmarking + // gray and we will have to go round again. The second time there should not + // be any weak mappings to fix up so the loop body should run at most twice. + MOZ_RELEASE_ASSERT(count < 2); + count++; + } while (!mCCJSRuntime->AreGCGrayBitsValid()); + + aTimeLog.Checkpoint("FixGrayBits"); +} + +bool nsCycleCollector::IsIncrementalGCInProgress() { + return mCCJSRuntime && JS::IsIncrementalGCInProgress(mCCJSRuntime->Runtime()); +} + +void nsCycleCollector::FinishAnyIncrementalGCInProgress() { + if (IsIncrementalGCInProgress()) { + NS_WARNING("Finishing incremental GC in progress during CC"); + JSContext* cx = CycleCollectedJSContext::Get()->Context(); + JS::PrepareForIncrementalGC(cx); + JS::FinishIncrementalGC(cx, JS::GCReason::CC_FORCED); + } +} + +void nsCycleCollector::CleanupAfterCollection() { + TimeLog timeLog; + MOZ_ASSERT(mIncrementalPhase == CleanupPhase); + MOZ_RELEASE_ASSERT(!mScanInProgress); + mGraph.Clear(); + timeLog.Checkpoint("CleanupAfterCollection::mGraph.Clear()"); + + uint32_t interval = + (uint32_t)((TimeStamp::Now() - mCollectionStart).ToMilliseconds()); +#ifdef COLLECT_TIME_DEBUG + printf("cc: total cycle collector time was %ums in %u slices\n", interval, + mResults.mNumSlices); + printf( + "cc: visited %u ref counted and %u GCed objects, freed %d ref counted " + "and %d GCed objects", + mResults.mVisitedRefCounted, mResults.mVisitedGCed, + mResults.mFreedRefCounted, mResults.mFreedGCed); + uint32_t numVisited = mResults.mVisitedRefCounted + mResults.mVisitedGCed; + if (numVisited > 1000) { + uint32_t numFreed = mResults.mFreedRefCounted + mResults.mFreedGCed; + printf(" (%d%%)", 100 * numFreed / numVisited); + } + printf(".\ncc: \n"); +#endif + + CC_TELEMETRY(, interval); + CC_TELEMETRY(_VISITED_REF_COUNTED, mResults.mVisitedRefCounted); + CC_TELEMETRY(_VISITED_GCED, mResults.mVisitedGCed); + CC_TELEMETRY(_COLLECTED, mWhiteNodeCount); + timeLog.Checkpoint("CleanupAfterCollection::telemetry"); + + if (mCCJSRuntime) { + mCCJSRuntime->FinalizeDeferredThings( + mResults.mAnyManual ? CycleCollectedJSRuntime::FinalizeNow + : CycleCollectedJSRuntime::FinalizeIncrementally); + mCCJSRuntime->EndCycleCollectionCallback(mResults); + timeLog.Checkpoint("CleanupAfterCollection::EndCycleCollectionCallback()"); + } + mIncrementalPhase = IdlePhase; +} + +void nsCycleCollector::ShutdownCollect() { + FinishAnyIncrementalGCInProgress(); + CycleCollectedJSContext* ccJSContext = CycleCollectedJSContext::Get(); + JS::ShutdownAsyncTasks(ccJSContext->Context()); + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + uint32_t i; + bool collectedAny = true; + for (i = 0; i < DEFAULT_SHUTDOWN_COLLECTIONS && collectedAny; ++i) { + collectedAny = Collect(CCReason::SHUTDOWN, ccIsManual::CCIsManual, + unlimitedBudget, nullptr); + // Run any remaining tasks that may have been enqueued via RunInStableState + // or DispatchToMicroTask. These can hold alive CCed objects, and we want to + // clear them out before we run the CC again or finish shutting down. + ccJSContext->PerformMicroTaskCheckPoint(true); + ccJSContext->ProcessStableStateQueue(); + } + NS_WARNING_ASSERTION(i < NORMAL_SHUTDOWN_COLLECTIONS, "Extra shutdown CC"); +} + +static void PrintPhase(const char* aPhase) { +#ifdef DEBUG_PHASES + printf("cc: begin %s on %s\n", aPhase, + NS_IsMainThread() ? "mainthread" : "worker"); +#endif +} + +bool nsCycleCollector::Collect(CCReason aReason, ccIsManual aIsManual, + SliceBudget& aBudget, + nsICycleCollectorListener* aManualListener, + bool aPreferShorterSlices) { + AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Incremental CC", GCCC); + + CheckThreadSafety(); + + // This can legitimately happen in a few cases. See bug 383651. + if (mActivelyCollecting || mFreeingSnowWhite) { + return false; + } + mActivelyCollecting = true; + + MOZ_ASSERT(!IsIncrementalGCInProgress()); + + mozilla::Maybe marker; + if (NS_IsMainThread()) { + marker.emplace("nsCycleCollector::Collect", MarkerStackRequest::NO_STACK); + } + + bool startedIdle = IsIdle(); + bool collectedAny = false; + + // If the CC started idle, it will call BeginCollection, which + // will do FreeSnowWhite, so it doesn't need to be done here. + if (!startedIdle) { + TimeLog timeLog; + FreeSnowWhite(true); + timeLog.Checkpoint("Collect::FreeSnowWhite"); + } + + if (aIsManual == ccIsManual::CCIsManual) { + mResults.mAnyManual = true; + } + + ++mResults.mNumSlices; + + bool continueSlice = aBudget.isUnlimited() || !aPreferShorterSlices; + do { + switch (mIncrementalPhase) { + case IdlePhase: + PrintPhase("BeginCollection"); + BeginCollection(aReason, aIsManual, aManualListener); + break; + case GraphBuildingPhase: + PrintPhase("MarkRoots"); + MarkRoots(aBudget); + + // Only continue this slice if we're running synchronously or the + // next phase will probably be short, to reduce the max pause for this + // collection. + // (There's no need to check if we've finished graph building, because + // if we haven't, we've already exceeded our budget, and will finish + // this slice anyways.) + continueSlice = aBudget.isUnlimited() || + (mResults.mNumSlices < 3 && !aPreferShorterSlices); + break; + case ScanAndCollectWhitePhase: + // We do ScanRoots and CollectWhite in a single slice to ensure + // that we won't unlink a live object if a weak reference is + // promoted to a strong reference after ScanRoots has finished. + // See bug 926533. + { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_ScanRoots); + PrintPhase("ScanRoots"); + ScanRoots(startedIdle); + } + { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GCCC_CollectWhite); + PrintPhase("CollectWhite"); + collectedAny = CollectWhite(); + } + break; + case CleanupPhase: + PrintPhase("CleanupAfterCollection"); + CleanupAfterCollection(); + continueSlice = false; + break; + } + if (continueSlice) { + aBudget.stepAndForceCheck(); + continueSlice = !aBudget.isOverBudget(); + } + } while (continueSlice); + + // Clear mActivelyCollecting here to ensure that a recursive call to + // Collect() does something. + mActivelyCollecting = false; + + if (aIsManual && !startedIdle) { + // We were in the middle of an incremental CC (using its own listener). + // Somebody has forced a CC, so after having finished out the current CC, + // run the CC again using the new listener. + MOZ_ASSERT(IsIdle()); + if (Collect(aReason, ccIsManual::CCIsManual, aBudget, aManualListener)) { + collectedAny = true; + } + } + + MOZ_ASSERT_IF(aIsManual == CCIsManual, IsIdle()); + + return collectedAny; +} + +// Any JS objects we have in the graph could die when we GC, but we +// don't want to abandon the current CC, because the graph contains +// information about purple roots. So we synchronously finish off +// the current CC. +void nsCycleCollector::PrepareForGarbageCollection() { + if (IsIdle()) { + MOZ_ASSERT(mGraph.IsEmpty(), "Non-empty graph when idle"); + MOZ_ASSERT(!mBuilder, "Non-null builder when idle"); + if (mJSPurpleBuffer) { + mJSPurpleBuffer->Destroy(); + } + return; + } + + FinishAnyCurrentCollection(CCReason::GC_WAITING); +} + +void nsCycleCollector::FinishAnyCurrentCollection(CCReason aReason) { + if (IsIdle()) { + return; + } + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + PrintPhase("FinishAnyCurrentCollection"); + // Use CCIsNotManual because we only want to finish the CC in progress. + Collect(aReason, ccIsManual::CCIsNotManual, unlimitedBudget, nullptr); + + // It is only okay for Collect() to have failed to finish the + // current CC if we're reentering the CC at some point past + // graph building. We need to be past the point where the CC will + // look at JS objects so that it is safe to GC. + MOZ_ASSERT(IsIdle() || (mActivelyCollecting && + mIncrementalPhase != GraphBuildingPhase), + "Reentered CC during graph building"); +} + +// Don't merge too many times in a row, and do at least a minimum +// number of unmerged CCs in a row. +static const uint32_t kMinConsecutiveUnmerged = 3; +static const uint32_t kMaxConsecutiveMerged = 3; + +bool nsCycleCollector::ShouldMergeZones(ccIsManual aIsManual) { + if (!mCCJSRuntime) { + return false; + } + + MOZ_ASSERT(mUnmergedNeeded <= kMinConsecutiveUnmerged); + MOZ_ASSERT(mMergedInARow <= kMaxConsecutiveMerged); + + if (mMergedInARow == kMaxConsecutiveMerged) { + MOZ_ASSERT(mUnmergedNeeded == 0); + mUnmergedNeeded = kMinConsecutiveUnmerged; + } + + if (mUnmergedNeeded > 0) { + mUnmergedNeeded--; + mMergedInARow = 0; + return false; + } + + if (aIsManual == CCIsNotManual && mCCJSRuntime->UsefulToMergeZones()) { + mMergedInARow++; + return true; + } else { + mMergedInARow = 0; + return false; + } +} + +void nsCycleCollector::BeginCollection( + CCReason aReason, ccIsManual aIsManual, + nsICycleCollectorListener* aManualListener) { + TimeLog timeLog; + MOZ_ASSERT(IsIdle()); + MOZ_RELEASE_ASSERT(!mScanInProgress); + + mCollectionStart = TimeStamp::Now(); + + if (mCCJSRuntime) { + mCCJSRuntime->BeginCycleCollectionCallback(aReason); + timeLog.Checkpoint("BeginCycleCollectionCallback()"); + } + + bool isShutdown = (aReason == CCReason::SHUTDOWN); + if (isShutdown) { + mShutdownCount += 1; + } + + // Set up the listener for this CC. + MOZ_ASSERT_IF(isShutdown, !aManualListener); + MOZ_ASSERT(!mLogger, "Forgot to clear a previous listener?"); + + if (aManualListener) { + aManualListener->AsLogger(getter_AddRefs(mLogger)); + } + + aManualListener = nullptr; + if (!mLogger && mParams.LogThisCC(mShutdownCount)) { + mLogger = new nsCycleCollectorLogger(); + if (mParams.AllTracesThisCC(isShutdown)) { + mLogger->SetAllTraces(); + } + } + + // BeginCycleCollectionCallback() might have started an IGC, and we need + // to finish it before we run FixGrayBits. + FinishAnyIncrementalGCInProgress(); + timeLog.Checkpoint("Pre-FixGrayBits finish IGC"); + + FixGrayBits(isShutdown, timeLog); + if (mCCJSRuntime) { + mCCJSRuntime->CheckGrayBits(); + } + + FreeSnowWhite(true); + timeLog.Checkpoint("BeginCollection FreeSnowWhite"); + + if (mLogger && NS_FAILED(mLogger->Begin())) { + mLogger = nullptr; + } + + // FreeSnowWhite could potentially have started an IGC, which we need + // to finish before we look at any JS roots. + FinishAnyIncrementalGCInProgress(); + timeLog.Checkpoint("Post-FreeSnowWhite finish IGC"); + + // Set up the data structures for building the graph. + JS::AutoAssertNoGC nogc; + JS::AutoEnterCycleCollection autocc(mCCJSRuntime->Runtime()); + mGraph.Init(); + mResults.Init(); + mResults.mSuspectedAtCCStart = SuspectedCount(); + mResults.mAnyManual = aIsManual; + bool mergeZones = ShouldMergeZones(aIsManual); + mResults.mMergedZones = mergeZones; + + MOZ_ASSERT(!mBuilder, "Forgot to clear mBuilder"); + mBuilder = MakeUnique(mGraph, mResults, mCCJSRuntime, mLogger, + mergeZones); + timeLog.Checkpoint("BeginCollection prepare graph builder"); + + if (mCCJSRuntime) { + mCCJSRuntime->TraverseRoots(*mBuilder); + timeLog.Checkpoint("mJSContext->TraverseRoots()"); + } + + AutoRestore ar(mScanInProgress); + MOZ_RELEASE_ASSERT(!mScanInProgress); + mScanInProgress = true; + mPurpleBuf.SelectPointers(*mBuilder); + timeLog.Checkpoint("SelectPointers()"); + + mBuilder->DoneAddingRoots(); + mIncrementalPhase = GraphBuildingPhase; +} + +uint32_t nsCycleCollector::SuspectedCount() { + CheckThreadSafety(); + if (NS_IsMainThread()) { + return gNurseryPurpleBufferEntryCount + mPurpleBuf.Count(); + } + + return mPurpleBuf.Count(); +} + +void nsCycleCollector::Shutdown(bool aDoCollect) { + CheckThreadSafety(); + + if (NS_IsMainThread()) { + gNurseryPurpleBufferEnabled = false; + } + + // Always delete snow white objects. + FreeSnowWhite(true); + + if (aDoCollect) { + ShutdownCollect(); + } + + if (mJSPurpleBuffer) { + mJSPurpleBuffer->Destroy(); + } +} + +void nsCycleCollector::RemoveObjectFromGraph(void* aObj) { + if (IsIdle()) { + return; + } + + mGraph.RemoveObjectFromMap(aObj); + if (mBuilder) { + mBuilder->RemoveCachedEntry(aObj); + } +} + +void nsCycleCollector::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + size_t* aObjectSize, + size_t* aGraphSize, + size_t* aPurpleBufferSize) const { + *aObjectSize = aMallocSizeOf(this); + + *aGraphSize = mGraph.SizeOfExcludingThis(aMallocSizeOf); + + *aPurpleBufferSize = mPurpleBuf.SizeOfExcludingThis(aMallocSizeOf); + + // These fields are deliberately not measured: + // - mCCJSRuntime: because it's non-owning and measured by JS reporters. + // - mParams: because it only contains scalars. +} + +JSPurpleBuffer* nsCycleCollector::GetJSPurpleBuffer() { + if (!mJSPurpleBuffer) { + // The Release call here confuses the GC analysis. + JS::AutoSuppressGCAnalysis nogc; + // JSPurpleBuffer keeps itself alive, but we need to create it in such way + // that it ends up in the normal purple buffer. That happens when + // nsRefPtr goes out of the scope and calls Release. + RefPtr pb = new JSPurpleBuffer(mJSPurpleBuffer); + } + return mJSPurpleBuffer; +} + +//////////////////////////////////////////////////////////////////////// +// Module public API (exported in nsCycleCollector.h) +// Just functions that redirect into the singleton, once it's built. +//////////////////////////////////////////////////////////////////////// + +void nsCycleCollector_registerJSContext(CycleCollectedJSContext* aCx) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + // But we shouldn't already have a context. + MOZ_ASSERT(!data->mContext); + + data->mContext = aCx; + data->mCollector->SetCCJSRuntime(aCx->Runtime()); +} + +void nsCycleCollector_forgetJSContext() { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + // And we shouldn't have already forgotten our context. + MOZ_ASSERT(data->mContext); + + // But it may have shutdown already. + if (data->mCollector) { + data->mCollector->ClearCCJSRuntime(); + data->mContext = nullptr; + } else { + data->mContext = nullptr; + delete data; + sCollectorData.set(nullptr); + } +} + +/* static */ +CycleCollectedJSContext* CycleCollectedJSContext::Get() { + CollectorData* data = sCollectorData.get(); + if (data) { + return data->mContext; + } + return nullptr; +} + +MOZ_NEVER_INLINE static void SuspectAfterShutdown( + void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, bool* aShouldDelete) { + if (aRefCnt->get() == 0) { + if (!aShouldDelete) { + // The CC is shut down, so we can't be in the middle of an ICC. + ToParticipant(aPtr, &aCp); + aRefCnt->stabilizeForDeletion(); + aCp->DeleteCycleCollectable(aPtr); + } else { + *aShouldDelete = true; + } + } else { + // Make sure we'll get called again. + aRefCnt->RemoveFromPurpleBuffer(); + } +} + +void NS_CycleCollectorSuspect3(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete) { + CollectorData* data = sCollectorData.get(); + + // This assertion will happen if you AddRef or Release a cycle collected + // object on a thread that does not have an active cycle collector. + // This can happen in a few situations: + // 1. We never cycle collect on this thread. (The cycle collector is only + // run on the main thread and DOM worker threads.) + // 2. The cycle collector hasn't been initialized on this thread yet. + // 3. The cycle collector has already been shut down on this thread. + MOZ_DIAGNOSTIC_ASSERT( + data, + "Cycle collected object used on a thread without a cycle collector."); + + if (MOZ_LIKELY(data->mCollector)) { + data->mCollector->Suspect(aPtr, aCp, aRefCnt); + return; + } + SuspectAfterShutdown(aPtr, aCp, aRefCnt, aShouldDelete); +} + +void ClearNurseryPurpleBuffer() { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + CollectorData* data = sCollectorData.get(); + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + data->mCollector->SuspectNurseryEntries(); +} + +void NS_CycleCollectorSuspectUsingNursery(void* aPtr, + nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + if (!gNurseryPurpleBufferEnabled) { + NS_CycleCollectorSuspect3(aPtr, aCp, aRefCnt, aShouldDelete); + return; + } + + SuspectUsingNurseryPurpleBuffer(aPtr, aCp, aRefCnt); +} + +uint32_t nsCycleCollector_suspectedCount() { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + + if (!data->mCollector) { + return 0; + } + + return data->mCollector->SuspectedCount(); +} + +bool nsCycleCollector_init() { +#ifdef DEBUG + static bool sInitialized; + + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(!sInitialized, "Called twice!?"); + sInitialized = true; +#endif + + return sCollectorData.init(); +} + +void nsCycleCollector_startup() { + if (sCollectorData.get()) { + MOZ_CRASH(); + } + + CollectorData* data = new CollectorData; + data->mCollector = new nsCycleCollector(); + data->mContext = nullptr; + + sCollectorData.set(data); +} + +void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + data->mCollector->SetBeforeUnlinkCallback(aCB); +} + +void nsCycleCollector_setForgetSkippableCallback( + CC_ForgetSkippableCallback aCB) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + data->mCollector->SetForgetSkippableCallback(aCB); +} + +void nsCycleCollector_forgetSkippable(js::SliceBudget& aBudget, + bool aRemoveChildlessNodes, + bool aAsyncSnowWhiteFreeing) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + TimeLog timeLog; + data->mCollector->ForgetSkippable(aBudget, aRemoveChildlessNodes, + aAsyncSnowWhiteFreeing); + timeLog.Checkpoint("ForgetSkippable()"); +} + +void nsCycleCollector_dispatchDeferredDeletion(bool aContinuation, + bool aPurge) { + CycleCollectedJSRuntime* rt = CycleCollectedJSRuntime::Get(); + if (rt) { + rt->DispatchDeferredDeletion(aContinuation, aPurge); + } +} + +bool nsCycleCollector_doDeferredDeletion() { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + MOZ_ASSERT(data->mContext); + + return data->mCollector->FreeSnowWhite(false); +} + +bool nsCycleCollector_doDeferredDeletionWithBudget(js::SliceBudget& aBudget) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + MOZ_ASSERT(data->mContext); + + return data->mCollector->FreeSnowWhiteWithBudget(aBudget); +} + +already_AddRefed nsCycleCollector_createLogSink() { + nsCOMPtr sink = new nsCycleCollectorLogSinkToFile(); + return sink.forget(); +} + +bool nsCycleCollector_collect(CCReason aReason, + nsICycleCollectorListener* aManualListener) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + AUTO_PROFILER_LABEL("nsCycleCollector_collect", GCCC); + + SliceBudget unlimitedBudget = SliceBudget::unlimited(); + return data->mCollector->Collect(aReason, ccIsManual::CCIsManual, + unlimitedBudget, aManualListener); +} + +void nsCycleCollector_collectSlice(SliceBudget& budget, CCReason aReason, + bool aPreferShorterSlices) { + CollectorData* data = sCollectorData.get(); + + // We should have started the cycle collector by now. + MOZ_ASSERT(data); + MOZ_ASSERT(data->mCollector); + + AUTO_PROFILER_LABEL("nsCycleCollector_collectSlice", GCCC); + + data->mCollector->Collect(aReason, ccIsManual::CCIsNotManual, budget, nullptr, + aPreferShorterSlices); +} + +void nsCycleCollector_prepareForGarbageCollection() { + CollectorData* data = sCollectorData.get(); + + MOZ_ASSERT(data); + + if (!data->mCollector) { + return; + } + + data->mCollector->PrepareForGarbageCollection(); +} + +void nsCycleCollector_finishAnyCurrentCollection() { + CollectorData* data = sCollectorData.get(); + + MOZ_ASSERT(data); + + if (!data->mCollector) { + return; + } + + data->mCollector->FinishAnyCurrentCollection(CCReason::API); +} + +void nsCycleCollector_shutdown(bool aDoCollect) { + CollectorData* data = sCollectorData.get(); + + if (data) { + MOZ_ASSERT(data->mCollector); + AUTO_PROFILER_LABEL("nsCycleCollector_shutdown", OTHER); + + { + RefPtr collector = data->mCollector; + collector->Shutdown(aDoCollect); + data->mCollector = nullptr; + } + + if (!data->mContext) { + delete data; + sCollectorData.set(nullptr); + } + } +} diff --git a/xpcom/base/nsCycleCollector.h b/xpcom/base/nsCycleCollector.h new file mode 100644 index 0000000000..1c583e04cf --- /dev/null +++ b/xpcom/base/nsCycleCollector.h @@ -0,0 +1,74 @@ +/* -*- 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/. */ + +#ifndef nsCycleCollector_h__ +#define nsCycleCollector_h__ + +class nsICycleCollectorListener; +class nsICycleCollectorLogSink; +class nsISupports; +template +struct already_AddRefed; + +#include +#include "mozilla/Attributes.h" + +namespace js { +class SliceBudget; +} + +namespace mozilla { +class CycleCollectedJSContext; +} // namespace mozilla + +bool nsCycleCollector_init(); + +void nsCycleCollector_startup(); + +typedef void (*CC_BeforeUnlinkCallback)(void); +void nsCycleCollector_setBeforeUnlinkCallback(CC_BeforeUnlinkCallback aCB); + +typedef void (*CC_ForgetSkippableCallback)(void); +void nsCycleCollector_setForgetSkippableCallback( + CC_ForgetSkippableCallback aCB); + +void nsCycleCollector_forgetSkippable(js::SliceBudget& aBudget, + bool aRemoveChildlessNodes = false, + bool aAsyncSnowWhiteFreeing = false); + +void nsCycleCollector_prepareForGarbageCollection(); + +// If an incremental cycle collection is in progress, finish it. +void nsCycleCollector_finishAnyCurrentCollection(); + +void nsCycleCollector_dispatchDeferredDeletion(bool aContinuation = false, + bool aPurge = false); +bool nsCycleCollector_doDeferredDeletion(); +bool nsCycleCollector_doDeferredDeletionWithBudget(js::SliceBudget& aBudget); + +already_AddRefed nsCycleCollector_createLogSink(); +already_AddRefed nsCycleCollector_createLogger(); + +// Run a cycle collection and return whether anything was collected. +bool nsCycleCollector_collect(mozilla::CCReason aReason, + nsICycleCollectorListener* aManualListener); + +void nsCycleCollector_collectSlice(js::SliceBudget& budget, + mozilla::CCReason aReason, + bool aPreferShorterSlices = false); + +uint32_t nsCycleCollector_suspectedCount(); + +// If aDoCollect is true, then run the GC and CC a few times before +// shutting down the CC completely. +MOZ_CAN_RUN_SCRIPT +void nsCycleCollector_shutdown(bool aDoCollect = true); + +// Helpers for interacting with JS +void nsCycleCollector_registerJSContext(mozilla::CycleCollectedJSContext* aCx); +void nsCycleCollector_forgetJSContext(); + +#endif // nsCycleCollector_h__ diff --git a/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp new file mode 100644 index 0000000000..02bc92ddab --- /dev/null +++ b/xpcom/base/nsCycleCollectorTraceJSHelpers.cpp @@ -0,0 +1,89 @@ +/* -*- 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 "nsCycleCollectionParticipant.h" +#include "nsString.h" +#include "nsWrapperCacheInlines.h" +#include "jsapi.h" +#include "jsfriendapi.h" + +void CycleCollectionNoteEdgeNameImpl( + nsCycleCollectionTraversalCallback& aCallback, const char* aName, + uint32_t aFlags) { + nsAutoCString arrayEdgeName(aName); + if (aFlags & CycleCollectionEdgeNameArrayFlag) { + arrayEdgeName.AppendLiteral("[i]"); + } + aCallback.NoteNextEdgeName(arrayEdgeName.get()); +} + +void nsCycleCollectionParticipant::NoteJSChild(JS::GCCellPtr aGCThing, + const char* aName, + void* aClosure) { + nsCycleCollectionTraversalCallback* cb = + static_cast(aClosure); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, aName); + if (JS::IsCCTraceKind(aGCThing.kind())) { + cb->NoteJSChild(aGCThing); + } +} + +void TraceCallbackFunc::Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const { + if (aPtr->unbarrieredGet().isGCThing()) { + mCallback(aPtr->unbarrieredGet().toGCCellPtr(), aName, aClosure); + } +} + +void TraceCallbackFunc::Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const { + if (aPtr->unbarrieredGet().isGCThing()) { + mCallback(aPtr->unbarrieredGet().toGCCellPtr(), aName, aClosure); + } +} + +void TraceCallbackFunc::Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const { + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} + +void TraceCallbackFunc::Trace(nsWrapperCache* aPtr, const char* aName, + void* aClosure) const { + JSObject* obj = aPtr->GetWrapperPreserveColor(); + if (obj) { + mCallback(JS::GCCellPtr(obj), aName, aClosure); + } +} + +void TraceCallbackFunc::Trace(JS::TenuredHeap* aPtr, + const char* aName, void* aClosure) const { + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGetPtr()), aName, aClosure); + } +} + +void TraceCallbackFunc::Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const { + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} + +void TraceCallbackFunc::Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const { + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} + +void TraceCallbackFunc::Trace(JS::Heap* aPtr, const char* aName, + void* aClosure) const { + if (*aPtr) { + mCallback(JS::GCCellPtr(aPtr->unbarrieredGet()), aName, aClosure); + } +} diff --git a/xpcom/base/nsDebug.h b/xpcom/base/nsDebug.h new file mode 100644 index 0000000000..349c297ea8 --- /dev/null +++ b/xpcom/base/nsDebug.h @@ -0,0 +1,384 @@ +/* -*- 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/. */ + +#ifndef nsDebug_h___ +#define nsDebug_h___ + +#include "nscore.h" +#include "nsError.h" + +#include "nsXPCOM.h" +#include "mozilla/Assertions.h" +#include "mozilla/DbgMacro.h" +#include "mozilla/Likely.h" +#include + +#ifdef DEBUG +# include "mozilla/ErrorNames.h" +# include "mozilla/IntegerPrintfMacros.h" +# include "mozilla/Printf.h" +#endif + +/** + * Warn if the given condition is true. The condition is evaluated in both + * release and debug builds, and the result is an expression which can be + * used in subsequent expressions, such as: + * + * if (NS_WARN_IF(NS_FAILED(rv)) { + * return rv; + * } + * + * This explicit warning and return is preferred to the NS_ENSURE_* macros + * which hide the warning and the return control flow. + * + * This macro can also be used outside of conditions just to issue a warning, + * like so: + * + * Unused << NS_WARN_IF(NS_FAILED(FnWithSideEffects()); + * + * (The |Unused <<| is necessary because of the [[nodiscard]] annotation.) + * + * However, note that the argument to this macro is evaluated in all builds. If + * you just want a warning assertion, it is better to use NS_WARNING_ASSERTION + * (which evaluates the condition only in debug builds) like so: + * + * NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "operation failed"); + * + * @note This is C++-only + */ +#ifdef __cplusplus +# ifdef DEBUG +[[nodiscard]] inline bool NS_warn_if_impl(bool aCondition, const char* aExpr, + const char* aFile, int32_t aLine) { + if (MOZ_UNLIKELY(aCondition)) { + NS_DebugBreak(NS_DEBUG_WARNING, nullptr, aExpr, aFile, aLine); + } + return aCondition; +} +# define NS_WARN_IF(condition) \ + NS_warn_if_impl(condition, #condition, __FILE__, __LINE__) +# else +# define NS_WARN_IF(condition) (bool)(condition) +# endif +#endif + +/** + * Test an assertion for truth. If the expression is not true then + * emit a warning. + * + * Program execution continues past the usage of this macro. + * + * Note also that the non-debug version of this macro does not + * evaluate the message argument. + */ +#ifdef DEBUG +# define NS_WARNING_ASSERTION(_expr, _msg) \ + do { \ + if (!(_expr)) { \ + NS_DebugBreak(NS_DEBUG_WARNING, _msg, #_expr, __FILE__, __LINE__); \ + } \ + } while (false) +#else +# define NS_WARNING_ASSERTION(_expr, _msg) \ + do { /* nothing */ \ + } while (false) +#endif + +/** + * Test an assertion for truth. If the expression is not true then + * trigger a program failure. + * + * Note that the non-debug version of this macro does not + * evaluate the message argument. + */ +#ifdef DEBUG +inline void MOZ_PretendNoReturn() MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS {} +# define NS_ASSERTION(expr, str) \ + do { \ + if (!(expr)) { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, #expr, __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } \ + } while (0) +#else +# define NS_ASSERTION(expr, str) \ + do { /* nothing */ \ + } while (0) +#endif + +/** + * Log an error message. + */ +#ifdef DEBUG +# define NS_ERROR(str) \ + do { \ + NS_DebugBreak(NS_DEBUG_ASSERTION, str, "Error", __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while (0) +#else +# define NS_ERROR(str) \ + do { /* nothing */ \ + } while (0) +#endif + +/** + * Log a warning message. + */ +#ifdef DEBUG +# define NS_WARNING(str) \ + NS_DebugBreak(NS_DEBUG_WARNING, str, nullptr, __FILE__, __LINE__) +#else +# define NS_WARNING(str) \ + do { /* nothing */ \ + } while (0) +#endif + +/** + * Trigger a debugger breakpoint, only in debug builds. + */ +#ifdef DEBUG +# define NS_BREAK() \ + do { \ + NS_DebugBreak(NS_DEBUG_BREAK, nullptr, nullptr, __FILE__, __LINE__); \ + MOZ_PretendNoReturn(); \ + } while (0) +#else +# define NS_BREAK() \ + do { /* nothing */ \ + } while (0) +#endif + +/****************************************************************************** +** Macros for static assertions. These are used by the sixgill tool. +** When the tool is not running these macros are no-ops. +******************************************************************************/ + +/* Avoid name collision if included with other headers defining annotations. */ +#ifndef HAVE_STATIC_ANNOTATIONS +# define HAVE_STATIC_ANNOTATIONS + +# ifdef XGILL_PLUGIN + +# define STATIC_PRECONDITION(COND) __attribute__((precondition(#COND))) +# define STATIC_PRECONDITION_ASSUME(COND) \ + __attribute__((precondition_assume(#COND))) +# define STATIC_POSTCONDITION(COND) __attribute__((postcondition(#COND))) +# define STATIC_POSTCONDITION_ASSUME(COND) \ + __attribute__((postcondition_assume(#COND))) +# define STATIC_INVARIANT(COND) __attribute__((invariant(#COND))) +# define STATIC_INVARIANT_ASSUME(COND) \ + __attribute__((invariant_assume(#COND))) + +/* Used to make identifiers for assert/assume annotations in a function. */ +# define STATIC_PASTE2(X, Y) X##Y +# define STATIC_PASTE1(X, Y) STATIC_PASTE2(X, Y) + +# define STATIC_ASSUME(COND) \ + do { \ + __attribute__((assume_static(#COND), unused)) int STATIC_PASTE1( \ + assume_static_, __COUNTER__); \ + } while (false) + +# define STATIC_ASSERT_RUNTIME(COND) \ + do { \ + __attribute__((assert_static_runtime(#COND), \ + unused)) int STATIC_PASTE1(assert_static_runtime_, \ + __COUNTER__); \ + } while (false) + +# else /* XGILL_PLUGIN */ + +# define STATIC_PRECONDITION(COND) /* nothing */ +# define STATIC_PRECONDITION_ASSUME(COND) /* nothing */ +# define STATIC_POSTCONDITION(COND) /* nothing */ +# define STATIC_POSTCONDITION_ASSUME(COND) /* nothing */ +# define STATIC_INVARIANT(COND) /* nothing */ +# define STATIC_INVARIANT_ASSUME(COND) /* nothing */ + +# define STATIC_ASSUME(COND) \ + do { /* nothing */ \ + } while (false) +# define STATIC_ASSERT_RUNTIME(COND) \ + do { /* nothing */ \ + } while (false) + +# endif /* XGILL_PLUGIN */ + +# define STATIC_SKIP_INFERENCE STATIC_INVARIANT(skip_inference()) + +#endif /* HAVE_STATIC_ANNOTATIONS */ + +/****************************************************************************** +** Macros for terminating execution when an unrecoverable condition is +** reached. These need to be compiled regardless of the DEBUG flag. +******************************************************************************/ + +/* Macros for checking the trueness of an expression passed in within an + * interface implementation. These need to be compiled regardless of the + * DEBUG flag. New code should use NS_WARN_IF(condition) instead! + * @status deprecated + */ + +#define NS_ENSURE_TRUE(x, ret) \ + do { \ + if (MOZ_UNLIKELY(!(x))) { \ + NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \ + return ret; \ + } \ + } while (false) + +#define NS_ENSURE_FALSE(x, ret) NS_ENSURE_TRUE(!(x), ret) + +#define NS_ENSURE_TRUE_VOID(x) \ + do { \ + if (MOZ_UNLIKELY(!(x))) { \ + NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \ + return; \ + } \ + } while (false) + +#define NS_ENSURE_FALSE_VOID(x) NS_ENSURE_TRUE_VOID(!(x)) + +/****************************************************************************** +** Macros for checking results +******************************************************************************/ + +#if defined(DEBUG) && !defined(XPCOM_GLUE_AVOID_NSPR) + +# define NS_ENSURE_SUCCESS_BODY(res, ret) \ + const char* name = mozilla::GetStaticErrorName(__rv); \ + mozilla::SmprintfPointer msg = mozilla::Smprintf( \ + "NS_ENSURE_SUCCESS(%s, %s) failed with " \ + "result 0x%" PRIX32 "%s%s%s", \ + #res, #ret, static_cast(__rv), name ? " (" : "", \ + name ? name : "", name ? ")" : ""); \ + NS_WARNING(msg.get()); + +# define NS_ENSURE_SUCCESS_BODY_VOID(res) \ + const char* name = mozilla::GetStaticErrorName(__rv); \ + mozilla::SmprintfPointer msg = mozilla::Smprintf( \ + "NS_ENSURE_SUCCESS_VOID(%s) failed with " \ + "result 0x%" PRIX32 "%s%s%s", \ + #res, static_cast(__rv), name ? " (" : "", name ? name : "", \ + name ? ")" : ""); \ + NS_WARNING(msg.get()); + +#else + +# define NS_ENSURE_SUCCESS_BODY(res, ret) \ + NS_WARNING("NS_ENSURE_SUCCESS(" #res ", " #ret ") failed"); + +# define NS_ENSURE_SUCCESS_BODY_VOID(res) \ + NS_WARNING("NS_ENSURE_SUCCESS_VOID(" #res ") failed"); + +#endif + +#define NS_ENSURE_SUCCESS(res, ret) \ + do { \ + nsresult __rv = res; /* Don't evaluate |res| more than once */ \ + if (NS_FAILED(__rv)) { \ + NS_ENSURE_SUCCESS_BODY(res, ret) \ + return ret; \ + } \ + } while (false) + +#define NS_ENSURE_SUCCESS_VOID(res) \ + do { \ + nsresult __rv = res; \ + if (NS_FAILED(__rv)) { \ + NS_ENSURE_SUCCESS_BODY_VOID(res) \ + return; \ + } \ + } while (false) + +/****************************************************************************** +** Macros for checking state and arguments upon entering interface boundaries +******************************************************************************/ + +#define NS_ENSURE_ARG(arg) NS_ENSURE_TRUE(arg, NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_ARG_POINTER(arg) NS_ENSURE_TRUE(arg, NS_ERROR_INVALID_POINTER) + +#define NS_ENSURE_ARG_MIN(arg, min) \ + NS_ENSURE_TRUE((arg) >= min, NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_ARG_MAX(arg, max) \ + NS_ENSURE_TRUE((arg) <= max, NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_ARG_RANGE(arg, min, max) \ + NS_ENSURE_TRUE(((arg) >= min) && ((arg) <= max), NS_ERROR_INVALID_ARG) + +#define NS_ENSURE_STATE(state) NS_ENSURE_TRUE(state, NS_ERROR_UNEXPECTED) + +/*****************************************************************************/ + +#if (defined(DEBUG) || (defined(NIGHTLY_BUILD) && !defined(MOZ_PROFILING))) && \ + !defined(XPCOM_GLUE_AVOID_NSPR) +# define MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED 1 +#endif + +#ifdef MOZILLA_INTERNAL_API +void NS_ABORT_OOM(size_t aSize); +#else +inline void NS_ABORT_OOM(size_t) { MOZ_CRASH(); } +#endif + +/* When compiling the XPCOM Glue on Windows, we pretend that it's going to + * be linked with a static CRT (-MT) even when it's not. This means that we + * cannot link to data exports from the CRT, only function exports. So, + * instead of referencing "stderr" directly, use fdopen. + */ +#ifdef __cplusplus +extern "C" { +#endif + +/** + * printf_stderr(...) is much like fprintf(stderr, ...), except that: + * - on Android and Firefox OS, *instead* of printing to stderr, it + * prints to logcat. (Newlines in the string lead to multiple lines + * of logcat, but each function call implicitly completes a line even + * if the string does not end with a newline.) + * - on Windows, if a debugger is present, it calls OutputDebugString + * in *addition* to writing to stderr + */ +void printf_stderr(const char* aFmt, ...) MOZ_FORMAT_PRINTF(1, 2); + +/** + * Same as printf_stderr, but taking va_list instead of varargs + */ +void vprintf_stderr(const char* aFmt, va_list aArgs) MOZ_FORMAT_PRINTF(1, 0); + +/** + * fprintf_stderr is like fprintf, except that if its file argument + * is stderr, it invokes printf_stderr instead. + * + * This is useful for general debugging code that logs information to a + * file, but that you would like to be useful on Android and Firefox OS. + * If you use fprintf_stderr instead of fprintf in such debugging code, + * then callers can pass stderr to get logging that works on Android and + * Firefox OS (and also the other side-effects of using printf_stderr). + * + * Code that is structured this way needs to be careful not to split a + * line of output across multiple calls to fprintf_stderr, since doing + * so will cause it to appear in multiple lines in logcat output. + * (Producing multiple lines at once is fine.) + */ +void fprintf_stderr(FILE* aFile, const char* aFmt, ...) MOZ_FORMAT_PRINTF(2, 3); + +/* + * print_stderr and fprint_stderr are like printf_stderr and fprintf_stderr, + * except they deal with Android logcat line length limitations. They do this + * by printing individual lines out of the provided stringstream using separate + * calls to logcat. + */ +void print_stderr(std::stringstream& aStr); +void fprint_stderr(FILE* aFile, std::stringstream& aStr); + +#ifdef __cplusplus +} +#endif + +#endif /* nsDebug_h___ */ diff --git a/xpcom/base/nsDebugImpl.cpp b/xpcom/base/nsDebugImpl.cpp new file mode 100644 index 0000000000..4023efd0ec --- /dev/null +++ b/xpcom/base/nsDebugImpl.cpp @@ -0,0 +1,675 @@ +/* -*- 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/. */ + +// Chromium headers must come before Mozilla headers. +#include "base/process_util.h" + +#include "mozilla/Atomics.h" +#include "mozilla/IntentionalCrash.h" +#include "mozilla/Printf.h" +#include "mozilla/ProfilerMarkers.h" + +#include "MainThreadUtils.h" +#include "nsDebugImpl.h" +#include "nsDebug.h" +#include "nsExceptionHandler.h" +#include "nsString.h" +#include "nsXULAppAPI.h" +#include "prerror.h" +#include "prerr.h" +#include "prenv.h" + +#ifdef ANDROID +# include +#endif + +#ifdef _WIN32 +/* for getenv() */ +# include +#endif + +#include "mozilla/StackWalk.h" + +#if defined(XP_UNIX) +# include +#endif + +#if defined(XP_WIN) +# include +# include "nsString.h" +#endif + +#if defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__OpenBSD__) +# include +# include +# include +# include +#endif + +#if defined(__OpenBSD__) +# include +#endif + +#if defined(__DragonFly__) || defined(__FreeBSD__) +# include +#endif + +#if defined(__NetBSD__) +# undef KERN_PROC +# define KERN_PROC KERN_PROC2 +# define KINFO_PROC struct kinfo_proc2 +#else +# define KINFO_PROC struct kinfo_proc +#endif + +#if defined(XP_MACOSX) +# define KP_FLAGS kp_proc.p_flag +#elif defined(__DragonFly__) +# define KP_FLAGS kp_flags +#elif defined(__FreeBSD__) +# define KP_FLAGS ki_flag +#elif defined(__OpenBSD__) && !defined(_P_TRACED) +# define KP_FLAGS p_psflags +# define P_TRACED PS_TRACED +#else +# define KP_FLAGS p_flag +#endif + +static void Abort(const char* aMsg); + +static void RealBreak(); + +static void Break(const char* aMsg); + +#if defined(_WIN32) +# include +# include +# include // for _alloca +#endif + +using namespace mozilla; + +static const char* sMultiprocessDescription = nullptr; + +static Atomic gAssertionCount; + +NS_IMPL_QUERY_INTERFACE(nsDebugImpl, nsIDebug2) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsDebugImpl::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +nsDebugImpl::Release() { return 1; } + +NS_IMETHODIMP +nsDebugImpl::Assertion(const char* aStr, const char* aExpr, const char* aFile, + int32_t aLine) { + NS_DebugBreak(NS_DEBUG_ASSERTION, aStr, aExpr, aFile, aLine); + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::Warning(const char* aStr, const char* aFile, int32_t aLine) { + NS_DebugBreak(NS_DEBUG_WARNING, aStr, nullptr, aFile, aLine); + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::Break(const char* aFile, int32_t aLine) { + NS_DebugBreak(NS_DEBUG_BREAK, nullptr, nullptr, aFile, aLine); + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::Abort(const char* aFile, int32_t aLine) { + NS_DebugBreak(NS_DEBUG_ABORT, nullptr, nullptr, aFile, aLine); + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::CrashWithOOM() { + NS_ABORT_OOM(-1); + return NS_OK; +} + +// From toolkit/library/rust/lib.rs +extern "C" void intentional_panic(const char* message); + +NS_IMETHODIMP +nsDebugImpl::RustPanic(const char* aMessage) { + intentional_panic(aMessage); + return NS_OK; +} + +// From toolkit/library/rust/lib.rs +extern "C" void debug_log(const char* target, const char* message); + +NS_IMETHODIMP +nsDebugImpl::RustLog(const char* aTarget, const char* aMessage) { + debug_log(aTarget, aMessage); + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::GetIsDebugBuild(bool* aResult) { +#ifdef DEBUG + *aResult = true; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::GetAssertionCount(int32_t* aResult) { + *aResult = gAssertionCount; + return NS_OK; +} + +NS_IMETHODIMP +nsDebugImpl::GetIsDebuggerAttached(bool* aResult) { + *aResult = false; + +#if defined(__OpenBSD__) && defined(MOZ_SANDBOX) + // no access to KERN_PROC_PID sysctl when pledge'd + return NS_OK; +#endif +#if defined(XP_WIN) + *aResult = ::IsDebuggerPresent(); +#elif defined(XP_MACOSX) || defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__NetBSD__) || defined(__OpenBSD__) + // Specify the info we're looking for + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), +# if defined(__NetBSD__) || defined(__OpenBSD__) + sizeof(KINFO_PROC), + 1, +# endif + }; + u_int mibSize = sizeof(mib) / sizeof(int); + + KINFO_PROC info; + size_t infoSize = sizeof(info); + memset(&info, 0, infoSize); + + if (sysctl(mib, mibSize, &info, &infoSize, nullptr, 0)) { + // if the call fails, default to false + *aResult = false; + return NS_OK; + } + + if (info.KP_FLAGS & P_TRACED) { + *aResult = true; + } +#endif + + return NS_OK; +} + +/* static */ +void nsDebugImpl::SetMultiprocessMode(const char* aDesc) { + sMultiprocessDescription = aDesc; +} + +/* static */ const char* nsDebugImpl::GetMultiprocessMode() { + return sMultiprocessDescription; +} + +/** + * Implementation of the nsDebug methods. Note that this code is + * always compiled in, in case some other module that uses it is + * compiled with debugging even if this library is not. + */ +enum nsAssertBehavior { + NS_ASSERT_UNINITIALIZED, + NS_ASSERT_WARN, + NS_ASSERT_SUSPEND, + NS_ASSERT_STACK, + NS_ASSERT_TRAP, + NS_ASSERT_ABORT, + NS_ASSERT_STACK_AND_ABORT +}; + +static nsAssertBehavior GetAssertBehavior() { + static nsAssertBehavior gAssertBehavior = NS_ASSERT_UNINITIALIZED; + if (gAssertBehavior != NS_ASSERT_UNINITIALIZED) { + return gAssertBehavior; + } + + gAssertBehavior = NS_ASSERT_WARN; + + const char* assertString = PR_GetEnv("XPCOM_DEBUG_BREAK"); + if (!assertString || !*assertString) { + return gAssertBehavior; + } + if (!strcmp(assertString, "warn")) { + return gAssertBehavior = NS_ASSERT_WARN; + } + if (!strcmp(assertString, "suspend")) { + return gAssertBehavior = NS_ASSERT_SUSPEND; + } + if (!strcmp(assertString, "stack")) { + return gAssertBehavior = NS_ASSERT_STACK; + } + if (!strcmp(assertString, "abort")) { + return gAssertBehavior = NS_ASSERT_ABORT; + } + if (!strcmp(assertString, "trap") || !strcmp(assertString, "break")) { + return gAssertBehavior = NS_ASSERT_TRAP; + } + if (!strcmp(assertString, "stack-and-abort")) { + return gAssertBehavior = NS_ASSERT_STACK_AND_ABORT; + } + + fprintf(stderr, "Unrecognized value of XPCOM_DEBUG_BREAK\n"); + return gAssertBehavior; +} + +struct FixedBuffer final : public mozilla::PrintfTarget { + FixedBuffer() : curlen(0) { buffer[0] = '\0'; } + + char buffer[764]; + uint32_t curlen; + + bool append(const char* sp, size_t len) override; +}; + +bool FixedBuffer::append(const char* aBuf, size_t aLen) { + if (!aLen) { + return true; + } + + if (curlen + aLen >= sizeof(buffer)) { + aLen = sizeof(buffer) - curlen - 1; + } + + if (aLen) { + memcpy(buffer + curlen, aBuf, aLen); + curlen += aLen; + buffer[curlen] = '\0'; + } + + return true; +} + +namespace geckoprofiler::markers { + +struct DebugBreakMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("DebugBreak"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + uint32_t aSeverity, + const ProfilerString8View& aStr, + const ProfilerString8View& aExpr, + const ProfilerString8View& aFile, + int32_t aLine) { + nsAutoCString sevString("WARNING"); + switch (aSeverity) { + case NS_DEBUG_ASSERTION: + sevString = "ASSERTION"; + break; + + case NS_DEBUG_BREAK: + sevString = "BREAK"; + break; + + case NS_DEBUG_ABORT: + sevString = "ABORT"; + break; + } + aWriter.StringProperty("Severity", sevString); + // The 'name' property is searchable on the front-end. + if (aStr.Length() != 0) { + aWriter.StringProperty("Message", aStr); + aWriter.StringProperty("name", aStr); + } else if (aExpr.Length() != 0) { + aWriter.StringProperty("name", aExpr); + } + if (aExpr.Length() != 0) { + aWriter.StringProperty("Expression", aExpr); + } + if (aFile.Length() != 0) { + aWriter.StringProperty("File", aFile); + } + if (aLine != 0) { + aWriter.IntProperty("Line", aLine); + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::TimelineOverview, MS::Location::MarkerChart, + MS::Location::MarkerTable}; + schema.SetAllLabels("{marker.data.Severity}: {marker.data.name}"); + schema.AddKeyFormat("Message", MS::Format::String); + schema.AddKeyFormat("Severity", MS::Format::String); + schema.AddKeyFormat("Expression", MS::Format::String); + schema.AddKeyFormat("File", MS::Format::String); + schema.AddKeyFormat("Line", MS::Format::Integer); + return schema; + } +}; + +} // namespace geckoprofiler::markers + +EXPORT_XPCOM_API(void) +NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, + const char* aFile, int32_t aLine) { + FixedBuffer nonPIDBuf; + FixedBuffer buf; + const char* sevString = "WARNING"; + + switch (aSeverity) { + case NS_DEBUG_ASSERTION: + sevString = "###!!! ASSERTION"; + break; + + case NS_DEBUG_BREAK: + sevString = "###!!! BREAK"; + break; + + case NS_DEBUG_ABORT: + sevString = "###!!! ABORT"; + break; + + default: + aSeverity = NS_DEBUG_WARNING; + } + + nonPIDBuf.print("%s: ", sevString); + if (aStr) { + nonPIDBuf.print("%s: ", aStr); + } + if (aExpr) { + nonPIDBuf.print("'%s', ", aExpr); + } + if (aFile || aLine != -1) { + nonPIDBuf.print("file %s:%d", aFile ? aFile : "", + aLine != -1 ? aLine : 0); + } + + // Print "[PID]" or "[Desc PID]" at the beginning of the message. + buf.print("["); + if (sMultiprocessDescription) { + buf.print("%s ", sMultiprocessDescription); + } + + bool isMainthread = (NS_IsMainThreadTLSInitialized() && NS_IsMainThread()); + PRThread* currentThread = PR_GetCurrentThread(); + const char* currentThreadName = + isMainthread ? "Main Thread" : PR_GetThreadName(currentThread); + if (currentThreadName) { + buf.print("%" PRIPID ", %s] %s", base::GetCurrentProcId(), + currentThreadName, nonPIDBuf.buffer); + } else { + buf.print("%" PRIPID ", Unnamed thread %p] %s", base::GetCurrentProcId(), + currentThread, nonPIDBuf.buffer); + } + + // errors on platforms without a debugdlg ring a bell on stderr +#if !defined(XP_WIN) + if (aSeverity != NS_DEBUG_WARNING) { + fprintf(stderr, "\07"); + } +#endif + +#ifdef ANDROID + __android_log_print(ANDROID_LOG_INFO, "Gecko", "%s", buf.buffer); +#endif + + PROFILER_MARKER("NS_DebugBreak", OTHER, MarkerStack::Capture(), + DebugBreakMarker, aSeverity, + ProfilerString8View::WrapNullTerminatedString(aStr), + ProfilerString8View::WrapNullTerminatedString(aExpr), + ProfilerString8View::WrapNullTerminatedString(aFile), aLine); + + // Write the message to stderr unless it's a warning and MOZ_IGNORE_WARNINGS + // is set. + if (!(PR_GetEnv("MOZ_IGNORE_WARNINGS") && aSeverity == NS_DEBUG_WARNING)) { + fprintf(stderr, "%s\n", buf.buffer); + fflush(stderr); + } + + switch (aSeverity) { + case NS_DEBUG_WARNING: + return; + + case NS_DEBUG_BREAK: + Break(buf.buffer); + return; + + case NS_DEBUG_ABORT: { + // Updating crash annotations in the child causes us to do IPC. This can + // really cause trouble if we're asserting from within IPC code. So we + // have to do without the annotations in that case. + if (XRE_IsParentProcess()) { + // Don't include the PID in the crash report annotation to + // allow faceting on crash-stats.mozilla.org. + nsCString note("xpcom_runtime_abort("); + note += nonPIDBuf.buffer; + note += ")"; + CrashReporter::AppendAppNotesToCrashReport(note); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::AbortMessage, + nsDependentCString(nonPIDBuf.buffer)); + } + +#if defined(DEBUG) && defined(_WIN32) + RealBreak(); +#endif +#if defined(DEBUG) + MozWalkTheStack(stderr); +#endif + Abort(buf.buffer); + return; + } + } + + // Now we deal with assertions + gAssertionCount++; + + switch (GetAssertBehavior()) { + case NS_ASSERT_WARN: + return; + + case NS_ASSERT_SUSPEND: +#ifdef XP_UNIX + fprintf(stderr, "Suspending process; attach with the debugger.\n"); + kill(0, SIGSTOP); +#else + Break(buf.buffer); +#endif + return; + + case NS_ASSERT_STACK: + MozWalkTheStack(stderr); + return; + + case NS_ASSERT_STACK_AND_ABORT: + MozWalkTheStack(stderr); + // Fall through to abort + [[fallthrough]]; + + case NS_ASSERT_ABORT: + Abort(buf.buffer); + return; + + case NS_ASSERT_TRAP: + case NS_ASSERT_UNINITIALIZED: // Default to "trap" behavior + Break(buf.buffer); + return; + } +} + +static void Abort(const char* aMsg) { + NoteIntentionalCrash(XRE_GetProcessTypeString()); + MOZ_CRASH_UNSAFE(aMsg); +} + +static void RealBreak() { +#if defined(_WIN32) + ::DebugBreak(); +#elif defined(XP_MACOSX) + raise(SIGTRAP); +#elif defined(__GNUC__) && \ + (defined(__i386__) || defined(__i386) || defined(__x86_64__)) + asm("int $3"); +#elif defined(__arm__) + asm( +# ifdef __ARM_ARCH_4T__ + /* ARMv4T doesn't support the BKPT instruction, so if the compiler target + * is ARMv4T, we want to ensure the assembler will understand that ARMv5T + * instruction, while keeping the resulting object tagged as ARMv4T. + */ + ".arch armv5t\n" + ".object_arch armv4t\n" +# endif + "BKPT #0"); +#elif defined(__aarch64__) + asm("brk #0"); +#elif defined(SOLARIS) +# if defined(__i386__) || defined(__i386) || defined(__x86_64__) + asm("int $3"); +# else + raise(SIGTRAP); +# endif +#else +# warning do not know how to break on this platform +#endif +} + +// Abort() calls this function, don't call it! +static void Break(const char* aMsg) { +#if defined(_WIN32) + static int ignoreDebugger; + if (!ignoreDebugger) { + const char* shouldIgnoreDebugger = getenv("XPCOM_DEBUG_DLG"); + ignoreDebugger = + 1 + (shouldIgnoreDebugger && !strcmp(shouldIgnoreDebugger, "1")); + } + if ((ignoreDebugger == 2) || !::IsDebuggerPresent()) { + DWORD code = IDRETRY; + + /* Create the debug dialog out of process to avoid the crashes caused by + * Windows events leaking into our event loop from an in process dialog. + * We do this by launching windbgdlg.exe (built in xpcom/windbgdlg). + * See http://bugzilla.mozilla.org/show_bug.cgi?id=54792 + */ + PROCESS_INFORMATION pi; + STARTUPINFOW si; + wchar_t executable[MAX_PATH]; + wchar_t* pName; + + memset(&pi, 0, sizeof(pi)); + + memset(&si, 0, sizeof(si)); + si.cb = sizeof(si); + si.wShowWindow = SW_SHOW; + + // 2nd arg of CreateProcess is in/out + wchar_t* msgCopy = (wchar_t*)_alloca((strlen(aMsg) + 1) * sizeof(wchar_t)); + wcscpy(msgCopy, NS_ConvertUTF8toUTF16(aMsg).get()); + + if (GetModuleFileNameW(GetModuleHandleW(L"xpcom.dll"), executable, + MAX_PATH) && + (pName = wcsrchr(executable, '\\')) != nullptr && + wcscpy(pName + 1, L"windbgdlg.exe") && + CreateProcessW(executable, msgCopy, nullptr, nullptr, false, + DETACHED_PROCESS | NORMAL_PRIORITY_CLASS, nullptr, + nullptr, &si, &pi)) { + WaitForSingleObject(pi.hProcess, INFINITE); + GetExitCodeProcess(pi.hProcess, &code); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + + switch (code) { + case IDABORT: + // This should exit us + raise(SIGABRT); + // If we are ignored exit this way.. + _exit(3); + + case IDIGNORE: + return; + } + } + + RealBreak(); +#elif defined(XP_MACOSX) + /* Note that we put this Mac OS X test above the GNUC/x86 test because the + * GNUC/x86 test is also true on Intel Mac OS X and we want the PPC/x86 + * impls to be the same. + */ + RealBreak(); +#elif defined(__GNUC__) && \ + (defined(__i386__) || defined(__i386) || defined(__x86_64__)) + RealBreak(); +#elif defined(__arm__) || defined(__aarch64__) + RealBreak(); +#elif defined(SOLARIS) + RealBreak(); +#else +# warning do not know how to break on this platform +#endif +} + +nsresult nsDebugImpl::Create(const nsIID& aIID, void** aInstancePtr) { + static const nsDebugImpl* sImpl; + + if (!sImpl) { + sImpl = new nsDebugImpl(); + } + + return const_cast(sImpl)->QueryInterface(aIID, aInstancePtr); +} + +//////////////////////////////////////////////////////////////////////////////// + +nsresult NS_ErrorAccordingToNSPR() { + PRErrorCode err = PR_GetError(); + switch (err) { + case PR_OUT_OF_MEMORY_ERROR: + return NS_ERROR_OUT_OF_MEMORY; + case PR_WOULD_BLOCK_ERROR: + return NS_BASE_STREAM_WOULD_BLOCK; + case PR_FILE_NOT_FOUND_ERROR: + return NS_ERROR_FILE_NOT_FOUND; + case PR_READ_ONLY_FILESYSTEM_ERROR: + return NS_ERROR_FILE_READ_ONLY; + case PR_NOT_DIRECTORY_ERROR: + return NS_ERROR_FILE_NOT_DIRECTORY; + case PR_IS_DIRECTORY_ERROR: + return NS_ERROR_FILE_IS_DIRECTORY; + case PR_LOOP_ERROR: + return NS_ERROR_FILE_UNRESOLVABLE_SYMLINK; + case PR_FILE_EXISTS_ERROR: + return NS_ERROR_FILE_ALREADY_EXISTS; + case PR_FILE_IS_LOCKED_ERROR: + return NS_ERROR_FILE_IS_LOCKED; + case PR_FILE_TOO_BIG_ERROR: + return NS_ERROR_FILE_TOO_BIG; + case PR_NO_DEVICE_SPACE_ERROR: + return NS_ERROR_FILE_NO_DEVICE_SPACE; + case PR_NAME_TOO_LONG_ERROR: + return NS_ERROR_FILE_NAME_TOO_LONG; + case PR_DIRECTORY_NOT_EMPTY_ERROR: + return NS_ERROR_FILE_DIR_NOT_EMPTY; + case PR_NO_ACCESS_RIGHTS_ERROR: + return NS_ERROR_FILE_ACCESS_DENIED; + default: + return NS_ERROR_FAILURE; + } +} + +void NS_ABORT_OOM(size_t aSize) { + CrashReporter::AnnotateOOMAllocationSize(aSize); + MOZ_CRASH("OOM"); +} diff --git a/xpcom/base/nsDebugImpl.h b/xpcom/base/nsDebugImpl.h new file mode 100644 index 0000000000..a3adcad3d7 --- /dev/null +++ b/xpcom/base/nsDebugImpl.h @@ -0,0 +1,42 @@ +/* -*- 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/. */ + +#ifndef nsDebugImpl_h +#define nsDebugImpl_h + +#include "nsIDebug2.h" + +class nsDebugImpl : public nsIDebug2 { + public: + nsDebugImpl() = default; + NS_DECL_ISUPPORTS + NS_DECL_NSIDEBUG2 + + static nsresult Create(const nsIID& aIID, void** aInstancePtr); + + /* + * If we are in multiprocess mode, return the process name. + */ + static const char* GetMultiprocessMode(); + + /* + * Inform nsDebugImpl that we're in multiprocess mode. + * + * If aDesc is not nullptr, the string it points to must be + * statically-allocated (i.e., it must be a string literal). + */ + static void SetMultiprocessMode(const char* aDesc); +}; + +#define NS_DEBUG_CONTRACTID "@mozilla.org/xpcom/debug;1" +#define NS_DEBUG_CID \ + { /* cb6cdb94-e417-4601-b4a5-f991bf41453d */ \ + 0xcb6cdb94, 0xe417, 0x4601, { \ + 0xb4, 0xa5, 0xf9, 0x91, 0xbf, 0x41, 0x45, 0x3d \ + } \ + } + +#endif // nsDebugImpl_h diff --git a/xpcom/base/nsDumpUtils.cpp b/xpcom/base/nsDumpUtils.cpp new file mode 100644 index 0000000000..c7bbbf4eb3 --- /dev/null +++ b/xpcom/base/nsDumpUtils.cpp @@ -0,0 +1,488 @@ +/* -*- 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 "nsDumpUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include +#include "prenv.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Unused.h" +#include "SpecialSystemDirectory.h" + +#ifdef XP_UNIX // { +# include "mozilla/Preferences.h" +# include +# include +# include + +using namespace mozilla; + +/* + * The following code supports triggering a registered callback upon + * receiving a specific signal. + * + * Take about:memory for example, we register + * 1. doGCCCDump for doMemoryReport + * 2. doMemoryReport for sDumpAboutMemorySignum(SIGRTMIN) + * and sDumpAboutMemoryAfterMMUSignum(SIGRTMIN+1). + * + * When we receive one of these signals, we write the signal number to a pipe. + * The IO thread then notices that the pipe has been written to, and kicks off + * the appropriate task on the main thread. + * + * This scheme is similar to using signalfd(), except it's portable and it + * doesn't require the use of sigprocmask, which is problematic because it + * masks signals received by child processes. + * + * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this. + * But that uses libevent, which does not handle the realtime signals (bug + * 794074). + */ + +// This is the write-end of a pipe that we use to notice when a +// specific signal occurs. +static Atomic sDumpPipeWriteFd(-1); + +const char FifoWatcher::kPrefName[] = "memory_info_dumper.watch_fifo.enabled"; + +static void DumpSignalHandler(int aSignum) { + // This is a signal handler, so everything in here needs to be + // async-signal-safe. Be careful! + + if (sDumpPipeWriteFd != -1) { + uint8_t signum = static_cast(aSignum); + Unused << write(sDumpPipeWriteFd, &signum, sizeof(signum)); + } +} + +NS_IMPL_ISUPPORTS(FdWatcher, nsIObserver); + +void FdWatcher::Init() { + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr os = services::GetObserverService(); + os->AddObserver(this, "xpcom-shutdown", /* ownsWeak = */ false); + + XRE_GetIOMessageLoop()->PostTask(NewRunnableMethod( + "FdWatcher::StartWatching", this, &FdWatcher::StartWatching)); +} + +// Implementations may call this function multiple times if they ensure that +// it's safe to call OpenFd() multiple times and they call StopWatching() +// first. +void FdWatcher::StartWatching() { + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + MOZ_ASSERT(mFd == -1); + + mFd = OpenFd(); + if (mFd == -1) { + LOG("FdWatcher: OpenFd failed."); + return; + } + + MessageLoopForIO::current()->WatchFileDescriptor(mFd, /* persistent = */ true, + MessageLoopForIO::WATCH_READ, + &mReadWatcher, this); +} + +// Since implementations can call StartWatching() multiple times, they can of +// course call StopWatching() multiple times. +void FdWatcher::StopWatching() { + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + mReadWatcher.StopWatchingFileDescriptor(); + if (mFd != -1) { + close(mFd); + mFd = -1; + } +} + +StaticRefPtr SignalPipeWatcher::sSingleton; + +/* static */ +SignalPipeWatcher* SignalPipeWatcher::GetSingleton() { + if (!sSingleton) { + sSingleton = new SignalPipeWatcher(); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } + return sSingleton; +} + +void SignalPipeWatcher::RegisterCallback(uint8_t aSignal, + PipeCallback aCallback) { + MutexAutoLock lock(mSignalInfoLock); + + for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); ++i) { + if (mSignalInfo[i].mSignal == aSignal) { + LOG("Register Signal(%d) callback failed! (DUPLICATE)", aSignal); + return; + } + } + SignalInfo signalInfo = {aSignal, aCallback}; + mSignalInfo.AppendElement(signalInfo); + RegisterSignalHandler(signalInfo.mSignal); +} + +void SignalPipeWatcher::RegisterSignalHandler(uint8_t aSignal) { + struct sigaction action; + memset(&action, 0, sizeof(action)); + sigemptyset(&action.sa_mask); + action.sa_handler = DumpSignalHandler; + + if (aSignal) { + if (sigaction(aSignal, &action, nullptr)) { + LOG("SignalPipeWatcher failed to register sig %d.", aSignal); + } + } else { + MutexAutoLock lock(mSignalInfoLock); + for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) { + if (sigaction(mSignalInfo[i].mSignal, &action, nullptr)) { + LOG("SignalPipeWatcher failed to register signal(%d) " + "dump signal handler.", + mSignalInfo[i].mSignal); + } + } + } +} + +SignalPipeWatcher::~SignalPipeWatcher() { + if (sDumpPipeWriteFd != -1) { + StopWatching(); + } +} + +int SignalPipeWatcher::OpenFd() { + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + // Create a pipe. When we receive a signal in our signal handler, we'll + // write the signum to the write-end of this pipe. + int pipeFds[2]; + if (pipe(pipeFds)) { + LOG("SignalPipeWatcher failed to create pipe."); + return -1; + } + + // Close this pipe on calls to exec(). + fcntl(pipeFds[0], F_SETFD, FD_CLOEXEC); + fcntl(pipeFds[1], F_SETFD, FD_CLOEXEC); + + int readFd = pipeFds[0]; + sDumpPipeWriteFd = pipeFds[1]; + + RegisterSignalHandler(); + return readFd; +} + +void SignalPipeWatcher::StopWatching() { + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + // Close sDumpPipeWriteFd /after/ setting the fd to -1. + // Otherwise we have the (admittedly far-fetched) race where we + // + // 1) close sDumpPipeWriteFd + // 2) open a new fd with the same number as sDumpPipeWriteFd + // had. + // 3) receive a signal, then write to the fd. + int pipeWriteFd = sDumpPipeWriteFd.exchange(-1); + close(pipeWriteFd); + + FdWatcher::StopWatching(); +} + +void SignalPipeWatcher::OnFileCanReadWithoutBlocking(int aFd) { + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + uint8_t signum; + ssize_t numReceived = read(aFd, &signum, sizeof(signum)); + if (numReceived != sizeof(signum)) { + LOG("Error reading from buffer in " + "SignalPipeWatcher::OnFileCanReadWithoutBlocking."); + return; + } + + { + MutexAutoLock lock(mSignalInfoLock); + for (SignalInfoArray::index_type i = 0; i < mSignalInfo.Length(); i++) { + if (signum == mSignalInfo[i].mSignal) { + mSignalInfo[i].mCallback(signum); + return; + } + } + } + LOG("SignalPipeWatcher got unexpected signum."); +} + +StaticRefPtr FifoWatcher::sSingleton; + +/* static */ +FifoWatcher* FifoWatcher::GetSingleton() { + if (!sSingleton) { + nsAutoCString dirPath; + Preferences::GetCString("memory_info_dumper.watch_fifo.directory", dirPath); + sSingleton = new FifoWatcher(dirPath); + sSingleton->Init(); + ClearOnShutdown(&sSingleton); + } + return sSingleton; +} + +/* static */ +bool FifoWatcher::MaybeCreate() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!XRE_IsParentProcess()) { + // We want this to be main-process only, since two processes can't listen + // to the same fifo. + return false; + } + + if (!Preferences::GetBool(kPrefName, false)) { + LOG("Fifo watcher disabled via pref."); + return false; + } + + // The FifoWatcher is held alive by the observer service. + if (!sSingleton) { + GetSingleton(); + } + return true; +} + +void FifoWatcher::RegisterCallback(const nsCString& aCommand, + FifoCallback aCallback) { + MutexAutoLock lock(mFifoInfoLock); + + for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); ++i) { + if (mFifoInfo[i].mCommand.Equals(aCommand)) { + LOG("Register command(%s) callback failed! (DUPLICATE)", aCommand.get()); + return; + } + } + FifoInfo aFifoInfo = {aCommand, aCallback}; + mFifoInfo.AppendElement(aFifoInfo); +} + +FifoWatcher::~FifoWatcher() = default; + +int FifoWatcher::OpenFd() { + // If the memory_info_dumper.directory pref is specified, put the fifo + // there. Otherwise, put it into the system's tmp directory. + + nsCOMPtr file; + + nsresult rv; + if (mDirPath.Length() > 0) { + rv = XRE_GetFileFromPath(mDirPath.get(), getter_AddRefs(file)); + if (NS_FAILED(rv)) { + LOG("FifoWatcher failed to open file \"%s\"", mDirPath.get()); + return -1; + } + } else { + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return -1; + } + } + + rv = file->AppendNative("debug_info_trigger"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return -1; + } + + nsAutoCString path; + rv = file->GetNativePath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return -1; + } + + // unlink might fail because the file doesn't exist, or for other reasons. + // But we don't care it fails; any problems will be detected later, when we + // try to mkfifo or open the file. + if (unlink(path.get())) { + LOG("FifoWatcher::OpenFifo unlink failed; errno=%d. " + "Continuing despite error.", + errno); + } + + if (mkfifo(path.get(), 0766)) { + LOG("FifoWatcher::OpenFifo mkfifo failed; errno=%d", errno); + return -1; + } + +# ifdef ANDROID + // Android runs with a umask, so we need to chmod our fifo to make it + // world-writable. + chmod(path.get(), 0666); +# endif + + int fd; + do { + // The fifo will block until someone else has written to it. In + // particular, open() will block until someone else has opened it for + // writing! We want open() to succeed and read() to block, so we open + // with NONBLOCK and then fcntl that away. + fd = open(path.get(), O_RDONLY | O_NONBLOCK); + } while (fd == -1 && errno == EINTR); + + if (fd == -1) { + LOG("FifoWatcher::OpenFifo open failed; errno=%d", errno); + return -1; + } + + // Make fd blocking now that we've opened it. + if (fcntl(fd, F_SETFL, 0)) { + close(fd); + return -1; + } + + return fd; +} + +void FifoWatcher::OnFileCanReadWithoutBlocking(int aFd) { + MOZ_ASSERT(XRE_GetIOMessageLoop() == MessageLoopForIO::current()); + + char buf[1024]; + int nread; + do { + // sizeof(buf) - 1 to leave space for the null-terminator. + nread = read(aFd, buf, sizeof(buf)); + } while (nread == -1 && errno == EINTR); + + if (nread == -1) { + // We want to avoid getting into a situation where + // OnFileCanReadWithoutBlocking is called in an infinite loop, so when + // something goes wrong, stop watching the fifo altogether. + LOG("FifoWatcher hit an error (%d) and is quitting.", errno); + StopWatching(); + return; + } + + if (nread == 0) { + // If we get EOF, that means that the other side closed the fifo. We need + // to close and re-open the fifo; if we don't, + // OnFileCanWriteWithoutBlocking will be called in an infinite loop. + + LOG("FifoWatcher closing and re-opening fifo."); + StopWatching(); + StartWatching(); + return; + } + + nsAutoCString inputStr; + inputStr.Append(buf, nread); + + // Trimming whitespace is important because if you do + // |echo "foo" >> debug_info_trigger|, + // it'll actually write "foo\n" to the fifo. + inputStr.Trim("\b\t\r\n"); + + { + MutexAutoLock lock(mFifoInfoLock); + + for (FifoInfoArray::index_type i = 0; i < mFifoInfo.Length(); i++) { + const nsCString commandStr = mFifoInfo[i].mCommand; + if (inputStr == commandStr.get()) { + mFifoInfo[i].mCallback(inputStr); + return; + } + } + } + LOG("Got unexpected value from fifo; ignoring it."); +} + +#endif // XP_UNIX } + +// In Android case, this function will open a file named aFilename under +// /data/local/tmp/"aFoldername". +// Otherwise, it will open a file named aFilename under "NS_OS_TEMP_DIR". +/* static */ +nsresult nsDumpUtils::OpenTempFile(const nsACString& aFilename, nsIFile** aFile, + const nsACString& aFoldername, Mode aMode) { +#ifdef ANDROID + // For Android, first try the downloads directory which is world-readable + // rather than the temp directory which is not. + if (!*aFile) { + char* env = PR_GetEnv("DOWNLOADS_DIRECTORY"); + if (env) { + NS_NewNativeLocalFile(nsCString(env), /* followLinks = */ true, aFile); + } + } +#endif + nsresult rv; + if (!*aFile) { + if (NS_IsMainThread()) { + // This allows tests to override, but isn't safe off-mainthread. + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, aFile); + } else { + rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, aFile); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + +#ifdef ANDROID + // /data/local/tmp is a true tmp directory; anyone can create a file there, + // but only the user which created the file can remove it. We want non-root + // users to be able to remove these files, so we write them into a + // subdirectory of the temp directory and chmod 777 that directory. + if (!aFoldername.IsEmpty()) { + rv = (*aFile)->AppendNative(aFoldername); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // It's OK if this fails; that probably just means that the directory + // already exists. + Unused << (*aFile)->Create(nsIFile::DIRECTORY_TYPE, 0777); + + nsAutoCString dirPath; + rv = (*aFile)->GetNativePath(dirPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + while (chmod(dirPath.get(), 0777) == -1 && errno == EINTR) { + } + } +#endif + + nsCOMPtr file(*aFile); + + rv = file->AppendNative(aFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aMode == CREATE_UNIQUE) { + rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666); + } else { + rv = file->Create(nsIFile::NORMAL_FILE_TYPE, 0666); + } + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifdef ANDROID + // Make this file world-read/writable; the permissions passed to the + // CreateUnique call above are not sufficient on Android, which runs with a + // umask. + nsAutoCString path; + rv = file->GetNativePath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + while (chmod(path.get(), 0666) == -1 && errno == EINTR) { + } +#endif + + return NS_OK; +} diff --git a/xpcom/base/nsDumpUtils.h b/xpcom/base/nsDumpUtils.h new file mode 100644 index 0000000000..7aa9c6b735 --- /dev/null +++ b/xpcom/base/nsDumpUtils.h @@ -0,0 +1,184 @@ +/* -*- 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/. */ + +#ifndef mozilla_nsDumpUtils_h +#define mozilla_nsDumpUtils_h + +#include "nsIObserver.h" +#include "base/message_loop.h" +#include "nsXULAppAPI.h" +#include "nsThreadUtils.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticPtr.h" +#include "nsTArray.h" + +#ifdef LOG +# undef LOG +#endif + +#ifdef ANDROID +# include "android/log.h" +# define LOG(...) \ + __android_log_print(ANDROID_LOG_INFO, "Gecko:DumpUtils", ##__VA_ARGS__) +#else +# define LOG(...) +#endif + +#ifdef XP_UNIX // { + +/** + * Abstract base class for something which watches an fd and takes action when + * we can read from it without blocking. + */ +class FdWatcher : public MessageLoopForIO::Watcher, public nsIObserver { + protected: + MessageLoopForIO::FileDescriptorWatcher mReadWatcher; + int mFd; + + virtual ~FdWatcher() { + // StopWatching should have run. + MOZ_ASSERT(mFd == -1); + } + + public: + FdWatcher() : mFd(-1) { MOZ_ASSERT(NS_IsMainThread()); } + + /** + * Open the fd to watch. If we encounter an error, return -1. + */ + virtual int OpenFd() = 0; + + /** + * Called when you can read() from the fd without blocking. Note that this + * function is also called when you're at eof (read() returns 0 in this case). + */ + virtual void OnFileCanReadWithoutBlocking(int aFd) override = 0; + virtual void OnFileCanWriteWithoutBlocking(int aFd) override{}; + + NS_DECL_THREADSAFE_ISUPPORTS + + /** + * Initialize this object. This should be called right after the object is + * constructed. (This would go in the constructor, except we interact with + * XPCOM, which we can't do from a constructor because our refcount is 0 at + * that point.) + */ + void Init(); + + // Implementations may call this function multiple times if they ensure that + + virtual void StartWatching(); + + // Since implementations can call StartWatching() multiple times, they can of + // course call StopWatching() multiple times. + virtual void StopWatching(); + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!strcmp(aTopic, "xpcom-shutdown")); + + XRE_GetIOMessageLoop()->PostTask(mozilla::NewRunnableMethod( + "FdWatcher::StopWatching", this, &FdWatcher::StopWatching)); + + return NS_OK; + } +}; + +typedef void (*FifoCallback)(const nsCString& aInputStr); +struct FifoInfo { + nsCString mCommand; + FifoCallback mCallback; +}; +typedef nsTArray FifoInfoArray; + +class FifoWatcher : public FdWatcher { + public: + /** + * The name of the preference used to enable/disable the FifoWatcher. + */ + // The length of this array must match the size of the string constant in + // the definition in nsDumpUtils.cpp. A mismatch will result in a compile-time + // error. + static const char kPrefName[38]; + + static FifoWatcher* GetSingleton(); + + static bool MaybeCreate(); + + void RegisterCallback(const nsCString& aCommand, FifoCallback aCallback); + + virtual ~FifoWatcher(); + + virtual int OpenFd() override; + + virtual void OnFileCanReadWithoutBlocking(int aFd) override; + + private: + nsAutoCString mDirPath; + + static mozilla::StaticRefPtr sSingleton; + + explicit FifoWatcher(nsCString aPath) + : mDirPath(aPath), mFifoInfoLock("FifoWatcher.mFifoInfoLock") {} + + mozilla::Mutex mFifoInfoLock; // protects mFifoInfo + FifoInfoArray mFifoInfo MOZ_GUARDED_BY(mFifoInfoLock); +}; + +typedef void (*PipeCallback)(const uint8_t aRecvSig); +struct SignalInfo { + uint8_t mSignal; + PipeCallback mCallback; +}; +typedef nsTArray SignalInfoArray; + +class SignalPipeWatcher : public FdWatcher { + public: + static SignalPipeWatcher* GetSingleton(); + + void RegisterCallback(uint8_t aSignal, PipeCallback aCallback); + + void RegisterSignalHandler(uint8_t aSignal = 0); + + virtual ~SignalPipeWatcher(); + + virtual int OpenFd() override; + + virtual void StopWatching() override; + + virtual void OnFileCanReadWithoutBlocking(int aFd) override; + + private: + static mozilla::StaticRefPtr sSingleton; + + SignalPipeWatcher() : mSignalInfoLock("SignalPipeWatcher.mSignalInfoLock") { + MOZ_ASSERT(NS_IsMainThread()); + } + + mozilla::Mutex mSignalInfoLock; // protects mSignalInfo + SignalInfoArray mSignalInfo MOZ_GUARDED_BY(mSignalInfoLock); +}; + +#endif // XP_UNIX } + +class nsDumpUtils { + public: + enum Mode { CREATE, CREATE_UNIQUE }; + + /** + * This function creates a new unique file based on |aFilename| in a + * world-readable temp directory. This is the system temp directory + * or, in the case of Android, the downloads directory. If |aFile| is + * non-null, it is assumed to point to a folder, and that folder is used + * instead. + */ + static nsresult OpenTempFile(const nsACString& aFilename, nsIFile** aFile, + const nsACString& aFoldername = ""_ns, + Mode aMode = CREATE_UNIQUE); +}; + +#endif diff --git a/xpcom/base/nsError.h b/xpcom/base/nsError.h new file mode 100644 index 0000000000..040cd4612c --- /dev/null +++ b/xpcom/base/nsError.h @@ -0,0 +1,90 @@ +/* -*- 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/. */ + +#ifndef nsError_h__ +#define nsError_h__ + +#ifndef __cplusplus +# error nsError.h no longer supports C sources +#endif + +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" + +#include + +#define NS_ERROR_SEVERITY_SUCCESS 0 +#define NS_ERROR_SEVERITY_ERROR 1 + +#include "ErrorList.h" // IWYU pragma: export + +/** + * @name Standard Error Handling Macros + * @return 0 or 1 (false/true with bool type for C++) + */ + +inline uint32_t NS_FAILED_impl(nsresult aErr) { + return static_cast(aErr) & 0x80000000; +} +#define NS_FAILED(_nsresult) ((bool)MOZ_UNLIKELY(NS_FAILED_impl(_nsresult))) +#define NS_SUCCEEDED(_nsresult) ((bool)MOZ_LIKELY(!NS_FAILED_impl(_nsresult))) + +/* Check that our enum type is actually uint32_t as expected */ +static_assert(((nsresult)0) < ((nsresult)-1), + "nsresult must be an unsigned type"); +static_assert(sizeof(nsresult) == sizeof(uint32_t), "nsresult must be 32 bits"); + +#define MOZ_ALWAYS_SUCCEEDS(expr) MOZ_ALWAYS_TRUE(NS_SUCCEEDED(expr)) + +/** + * @name Standard Error Generating Macros + */ + +#define NS_ERROR_GENERATE(sev, module, code) \ + (nsresult)(((uint32_t)(sev) << 31) | \ + ((uint32_t)(module + NS_ERROR_MODULE_BASE_OFFSET) << 16) | \ + ((uint32_t)(code))) + +#define NS_ERROR_GENERATE_SUCCESS(module, code) \ + NS_ERROR_GENERATE(NS_ERROR_SEVERITY_SUCCESS, module, code) + +#define NS_ERROR_GENERATE_FAILURE(module, code) \ + NS_ERROR_GENERATE(NS_ERROR_SEVERITY_ERROR, module, code) + +/* + * This will return the nsresult corresponding to the most recent NSPR failure + * returned by PR_GetError. + * + *********************************************************************** + * Do not depend on this function. It will be going away! + *********************************************************************** + */ +extern nsresult NS_ErrorAccordingToNSPR(); + +/** + * @name Standard Macros for retrieving error bits + */ + +inline constexpr uint16_t NS_ERROR_GET_CODE(nsresult aErr) { + return uint32_t(aErr) & 0xffff; +} +inline constexpr uint16_t NS_ERROR_GET_MODULE(nsresult aErr) { + return ((uint32_t(aErr) >> 16) - NS_ERROR_MODULE_BASE_OFFSET) & 0x1fff; +} +inline bool NS_ERROR_GET_SEVERITY(nsresult aErr) { + return uint32_t(aErr) >> 31; +} + +#ifdef _MSC_VER +# pragma warning(disable : 4251) /* 'nsCOMPtr' needs to \ + have dll-interface to be used by clients \ + of class 'nsInputStream' */ +# pragma warning( \ + disable : 4275) /* non dll-interface class 'nsISupports' used as base \ + for dll-interface class 'nsIRDFNode' */ +#endif + +#endif diff --git a/xpcom/base/nsGZFileWriter.cpp b/xpcom/base/nsGZFileWriter.cpp new file mode 100644 index 0000000000..2d8e14e944 --- /dev/null +++ b/xpcom/base/nsGZFileWriter.cpp @@ -0,0 +1,97 @@ +/* -*- 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 "nsGZFileWriter.h" +#include "nsIFile.h" +#include "nsString.h" +#include "zlib.h" + +#ifdef XP_WIN +# include +# define _dup dup +#else +# include +#endif + +nsGZFileWriter::nsGZFileWriter(Operation aMode) + : mMode(aMode), mInitialized(false), mFinished(false), mGZFile(nullptr) {} + +nsGZFileWriter::~nsGZFileWriter() { + if (mInitialized && !mFinished) { + Finish(); + } +} + +nsresult nsGZFileWriter::Init(nsIFile* aFile) { + if (NS_WARN_IF(mInitialized) || NS_WARN_IF(mFinished)) { + return NS_ERROR_FAILURE; + } + + // Get a FILE out of our nsIFile. Convert that into a file descriptor which + // gzip can own. Then close our FILE, leaving only gzip's fd open. + + FILE* file; + nsresult rv = aFile->OpenANSIFileDesc(mMode == Create ? "wb" : "ab", &file); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return InitANSIFileDesc(file); +} + +nsresult nsGZFileWriter::InitANSIFileDesc(FILE* aFile) { + if (NS_WARN_IF(mInitialized) || NS_WARN_IF(mFinished)) { + return NS_ERROR_FAILURE; + } + + mGZFile = gzdopen(dup(fileno(aFile)), mMode == Create ? "wb" : "ab"); + fclose(aFile); + + // gzdopen returns nullptr on error. + if (NS_WARN_IF(!mGZFile)) { + return NS_ERROR_FAILURE; + } + + mInitialized = true; + + return NS_OK; +} + +nsresult nsGZFileWriter::Write(const nsACString& aStr) { + if (NS_WARN_IF(!mInitialized) || NS_WARN_IF(mFinished)) { + return NS_ERROR_FAILURE; + } + + // gzwrite uses a return value of 0 to indicate failure. Otherwise, it + // returns the number of uncompressed bytes written. To ensure we can + // distinguish between success and failure, don't call gzwrite when we have 0 + // bytes to write. + if (aStr.IsEmpty()) { + return NS_OK; + } + + // gzwrite never does a short write -- that is, the return value should + // always be either 0 or aStr.Length(), and we shouldn't have to call it + // multiple times in order to get it to read the whole buffer. + int rv = gzwrite(mGZFile, aStr.BeginReading(), aStr.Length()); + if (NS_WARN_IF(rv != static_cast(aStr.Length()))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsGZFileWriter::Finish() { + if (NS_WARN_IF(!mInitialized) || NS_WARN_IF(mFinished)) { + return NS_ERROR_FAILURE; + } + + mFinished = true; + gzclose(mGZFile); + + // Ignore errors from gzclose; it's not like there's anything we can do about + // it, at this point! + return NS_OK; +} diff --git a/xpcom/base/nsGZFileWriter.h b/xpcom/base/nsGZFileWriter.h new file mode 100644 index 0000000000..dc33b4a2de --- /dev/null +++ b/xpcom/base/nsGZFileWriter.h @@ -0,0 +1,79 @@ +/* -*- 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/. */ + +#ifndef nsGZFileWriter_h +#define nsGZFileWriter_h + +#include "nsISupportsImpl.h" +#include "zlib.h" +#include "nsDependentString.h" +#include + +/** + * A simple class for writing to a .gz file. + * + * Note that the file that this interface produces has a different format than + * what you'd get if you compressed your data as a gzip stream and dumped the + * result to a file. + * + * The standard gunzip tool cannot decompress a raw gzip stream, but can handle + * the files produced by this interface. + */ +class nsGZFileWriter final { + virtual ~nsGZFileWriter(); + + public: + enum Operation { Append, Create }; + + explicit nsGZFileWriter(Operation aMode = Create); + + NS_INLINE_DECL_REFCOUNTING(nsGZFileWriter) + + /** + * Initialize this object. We'll write our gzip'ed data to the given file, + * overwriting its contents if the file exists. + * + * Init() will return an error if called twice. It's an error to call any + * other method on this interface without first calling Init(). + */ + [[nodiscard]] nsresult Init(nsIFile* aFile); + + /** + * Alternate version of Init() for use when the file is already opened; + * e.g., with a FileDescriptor passed over IPC. + */ + [[nodiscard]] nsresult InitANSIFileDesc(FILE* aFile); + + /** + * Write the given string to the file. + */ + [[nodiscard]] nsresult Write(const nsACString& aStr); + + /** + * Write |length| bytes of |str| to the file. + */ + [[nodiscard]] nsresult Write(const char* aStr, uint32_t aLen) { + return Write(nsDependentCSubstring(aStr, aLen)); + } + + /** + * Close this nsGZFileWriter. This method will be run when the underlying + * object is destroyed, so it's not strictly necessary to explicitly call it + * from your code. + * + * It's an error to call this method twice, and it's an error to call Write() + * after Finish() has been called. + */ + nsresult Finish(); + + private: + Operation mMode; + bool mInitialized; + bool mFinished; + gzFile mGZFile; +}; + +#endif diff --git a/xpcom/base/nsIAvailableMemoryWatcherBase.idl b/xpcom/base/nsIAvailableMemoryWatcherBase.idl new file mode 100644 index 0000000000..07d48e8571 --- /dev/null +++ b/xpcom/base/nsIAvailableMemoryWatcherBase.idl @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * nsITabUnloader: interface to represent TabUnloader + * + * nsIAvailableMemoryWatcherBase: interface to watch the system's memory + * status and invoke a registered TabUnloader when it detected a low-memory + * and high-memory situation. The logic to detect such a memory situation + * is defined per platform. + */ + +[scriptable, uuid(2e530956-6054-464f-9f4c-0ae6f8de5523)] +interface nsITabUnloader : nsISupports +{ + /* + * Unload the least-recently-used tab. + * JS implementation of this interface TabUnloader.unloadTabAsync takes + * one parameter that defines a threshold to exclude fresh tabs from the + * unloading candidate tabs. Currently the memory watcher is the only one + * caller of this interface and it always expects the default threshold, + * so this interface takes no parameter. + */ + void unloadTabAsync(); +}; + +[scriptable, uuid(b0b5701e-239d-49db-9009-37e89f86441c)] +interface nsIAvailableMemoryWatcherBase : nsISupports +{ + void registerTabUnloader(in nsITabUnloader aTabUnloader); + void onUnloadAttemptCompleted(in nsresult aResult); +}; diff --git a/xpcom/base/nsIClassInfoImpl.h b/xpcom/base/nsIClassInfoImpl.h new file mode 100644 index 0000000000..045e75e5af --- /dev/null +++ b/xpcom/base/nsIClassInfoImpl.h @@ -0,0 +1,193 @@ +/* -*- 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/. */ + +#ifndef nsIClassInfoImpl_h__ +#define nsIClassInfoImpl_h__ + +#include "mozilla/Alignment.h" +#include "mozilla/Assertions.h" +#include "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" +#include "nsIClassInfo.h" +#include "nsISupportsImpl.h" + +#include + +/** + * This header file provides macros which help you make your class implement + * nsIClassInfo. Implementing nsIClassInfo is particularly helpful if you have + * a C++ class which implements multiple interfaces and which you access from + * JavaScript. If that class implements nsIClassInfo, the JavaScript code + * won't have to call QueryInterface on instances of the class; all methods + * from all interfaces returned by GetInterfaces() will be available + * automagically. + * + * Here's all you need to do. Given a class + * + * class nsFooBar : public nsIFoo, public nsIBar { }; + * + * you should already have the following nsISupports implementation in its cpp + * file: + * + * NS_IMPL_ISUPPORTS(nsFooBar, nsIFoo, nsIBar). + * + * Change this to + * + * NS_IMPL_CLASSINFO(nsFooBar, nullptr, 0, NS_FOOBAR_CID) + * NS_IMPL_ISUPPORTS_CI(nsFooBar, nsIFoo, nsIBar) + * + * If nsFooBar is threadsafe, change the 0 above to nsIClassInfo::THREADSAFE. + * If it's a singleton, use nsIClassInfo::SINGLETON. The full list of flags is + * in nsIClassInfo.idl. + * + * The nullptr parameter is there so you can pass a function for converting + * from an XPCOM object to a scriptable helper. Unless you're doing + * specialized JS work, you can probably leave this as nullptr. + * + * This file also defines the NS_IMPL_QUERY_INTERFACE_CI macro, which you can + * use to replace NS_IMPL_QUERY_INTERFACE, if you use that instead of + * NS_IMPL_ISUPPORTS. + * + * That's it! The rest is gory details. + * + * + * Notice that nsFooBar didn't need to inherit from nsIClassInfo in order to + * "implement" it. However, after adding these macros to nsFooBar, you you can + * QueryInterface an instance of nsFooBar to nsIClassInfo. How can this be? + * + * The answer lies in the NS_IMPL_ISUPPORTS_CI macro. It modifies nsFooBar's + * QueryInterface implementation such that, if we ask to QI to nsIClassInfo, it + * returns a singleton object associated with the class. (That singleton is + * defined by NS_IMPL_CLASSINFO.) So all nsFooBar instances will return the + * same object when QI'ed to nsIClassInfo. (You can see this in + * NS_IMPL_QUERY_CLASSINFO below.) + * + * This hack breaks XPCOM's rules, since if you take an instance of nsFooBar, + * QI it to nsIClassInfo, and then try to QI to nsIFoo, that will fail. On the + * upside, implementing nsIClassInfo doesn't add a vtable pointer to instances + * of your class. + * + * In principal, you can also implement nsIClassInfo by inheriting from the + * interface. But some code expects that when it QI's an object to + * nsIClassInfo, it gets back a singleton which isn't attached to any + * particular object. If a class were to implement nsIClassInfo through + * inheritance, that code might QI to nsIClassInfo and keep the resulting + * object alive, thinking it was only keeping alive the classinfo singleton, + * but in fact keeping a whole instance of the class alive. See, e.g., bug + * 658632. + * + * Unless you specifically need to have a different nsIClassInfo instance for + * each instance of your class, you should probably just implement nsIClassInfo + * as a singleton. + */ + +class GenericClassInfo : public nsIClassInfo { + public: + struct ClassInfoData { + // This function pointer uses NS_CALLBACK_ because it's always set to an + // NS_IMETHOD function, which uses __stdcall on Win32. + typedef NS_CALLBACK_(nsresult, GetInterfacesProc)(nsTArray& aArray); + GetInterfacesProc getinterfaces; + + // This function pointer doesn't use NS_CALLBACK_ because it's always set to + // a vanilla function. + typedef nsresult (*GetScriptableHelperProc)(nsIXPCScriptable** aHelper); + GetScriptableHelperProc getscriptablehelper; + + uint32_t flags; + nsCID cid; + }; + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSICLASSINFO + + explicit GenericClassInfo(const ClassInfoData* aData) : mData(aData) {} + + private: + const ClassInfoData* mData; +}; + +#define NS_CLASSINFO_NAME(_class) g##_class##_classInfoGlobal +#define NS_CI_INTERFACE_GETTER_NAME(_class) _class##_GetInterfacesHelper +#define NS_DECL_CI_INTERFACE_GETTER(_class) \ + extern NS_IMETHODIMP NS_CI_INTERFACE_GETTER_NAME(_class)(nsTArray & \ + array); + +#define NS_IMPL_CLASSINFO(_class, _getscriptablehelper, _flags, _cid) \ + NS_DECL_CI_INTERFACE_GETTER(_class) \ + static const GenericClassInfo::ClassInfoData k##_class##ClassInfoData = { \ + NS_CI_INTERFACE_GETTER_NAME(_class), \ + _getscriptablehelper, \ + _flags | nsIClassInfo::SINGLETON_CLASSINFO, \ + _cid, \ + }; \ + mozilla::AlignedStorage2 k##_class##ClassInfoDataPlace; \ + nsIClassInfo* NS_CLASSINFO_NAME(_class) = nullptr; + +#define NS_IMPL_QUERY_CLASSINFO(_class) \ + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { \ + if (!NS_CLASSINFO_NAME(_class)) \ + NS_CLASSINFO_NAME(_class) = new (k##_class##ClassInfoDataPlace.addr()) \ + GenericClassInfo(&k##_class##ClassInfoData); \ + foundInterface = NS_CLASSINFO_NAME(_class); \ + } else + +#define NS_CLASSINFO_HELPER_BEGIN(_class, _c) \ + NS_IMETHODIMP \ + NS_CI_INTERFACE_GETTER_NAME(_class)(nsTArray & array) { \ + array.Clear(); \ + array.SetCapacity(_c); + +#define NS_CLASSINFO_HELPER_ENTRY(_interface) \ + array.AppendElement(NS_GET_IID(_interface)); + +#define NS_CLASSINFO_HELPER_END \ + return NS_OK; \ + } + +#define NS_IMPL_CI_INTERFACE_GETTER(aClass, ...) \ + static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \ + "Need more arguments to NS_IMPL_CI_INTERFACE_GETTER"); \ + NS_CLASSINFO_HELPER_BEGIN(aClass, MOZ_ARG_COUNT(__VA_ARGS__)) \ + MOZ_FOR_EACH(NS_CLASSINFO_HELPER_ENTRY, (), (__VA_ARGS__)) \ + NS_CLASSINFO_HELPER_END + +#define NS_IMPL_CI_INTERFACE_GETTER0(aClass) \ + NS_CLASSINFO_HELPER_BEGIN(aClass, 0) \ + NS_CLASSINFO_HELPER_END + +// Note that this macro is an internal implementation of this header and +// should not be used outside it. It does not end the interface map as this +// is done in NS_IMPL_QUERY_INTERFACE_CI or the _INHERITED variant. +#define NS_IMPL_QUERY_INTERFACE_CI_GUTS(aClass, ...) \ + static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \ + "Need more arguments to NS_IMPL_QUERY_INTERFACE_CI"); \ + NS_INTERFACE_MAP_BEGIN(aClass) \ + MOZ_FOR_EACH(NS_INTERFACE_MAP_ENTRY, (), (__VA_ARGS__)) \ + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, MOZ_ARG_1(__VA_ARGS__)) \ + NS_IMPL_QUERY_CLASSINFO(aClass) + +#define NS_IMPL_QUERY_INTERFACE_CI(aClass, ...) \ + NS_IMPL_QUERY_INTERFACE_CI_GUTS(aClass, __VA_ARGS__) \ + NS_INTERFACE_MAP_END + +#define NS_IMPL_QUERY_INTERFACE_CI_INHERITED(aClass, aSuper, ...) \ + NS_IMPL_QUERY_INTERFACE_CI_GUTS(aClass, __VA_ARGS__) \ + NS_INTERFACE_MAP_END_INHERITING \ + (aSuper) + +#define NS_IMPL_QUERY_INTERFACE_CI_INHERITED0(aClass, aSuper) \ + NS_INTERFACE_MAP_BEGIN(aClass) \ + NS_IMPL_QUERY_CLASSINFO(aClass) \ + NS_INTERFACE_MAP_END_INHERITING(aSuper) + +#define NS_IMPL_ISUPPORTS_CI(aClass, ...) \ + NS_IMPL_ADDREF(aClass) \ + NS_IMPL_RELEASE(aClass) \ + NS_IMPL_QUERY_INTERFACE_CI(aClass, __VA_ARGS__) \ + NS_IMPL_CI_INTERFACE_GETTER(aClass, __VA_ARGS__) + +#endif // nsIClassInfoImpl_h__ diff --git a/xpcom/base/nsIConsoleListener.idl b/xpcom/base/nsIConsoleListener.idl new file mode 100644 index 0000000000..45e6fcb1fc --- /dev/null +++ b/xpcom/base/nsIConsoleListener.idl @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* + * Used by the console service to notify listeners of new console messages. + */ + +#include "nsISupports.idl" + +interface nsIConsoleMessage; + +[scriptable, function, uuid(35c400a4-5792-438c-b915-65e30d58d557)] +interface nsIConsoleListener : nsISupports +{ + void observe(in nsIConsoleMessage aMessage); +}; diff --git a/xpcom/base/nsIConsoleMessage.idl b/xpcom/base/nsIConsoleMessage.idl new file mode 100644 index 0000000000..ec9b901aaa --- /dev/null +++ b/xpcom/base/nsIConsoleMessage.idl @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +/** + * This is intended as a base interface; implementations may want to + * provide an object that can be qi'ed to provide more specific + * message information. + */ +[scriptable, uuid(3aba9617-10e2-4839-83ae-2e6fc4df428b)] +interface nsIConsoleMessage : nsISupports +{ + /** Log level constants. */ + const uint32_t debug = 0; + const uint32_t info = 1; + const uint32_t warn = 2; + const uint32_t error = 3; + + /** + * The log level of this message. + */ + readonly attribute uint32_t logLevel; + + /** + * The time (in milliseconds from the Epoch) that the message instance + * was initialised. + * The timestamp is initialized as JS_now/1000 so that it can be + * compared to Date.now in Javascript. + */ + readonly attribute long long timeStamp; + + /** + * The time (in microseconds from the Epoch) that the message instance + * was initialised. + * The timestamp is initialized as JS_now. + */ + readonly attribute long long microSecondTimeStamp; + + [binaryname(MessageMoz)] readonly attribute AString message; + + attribute boolean isForwardedFromContentProcess; + + AUTF8String toString(); +}; + +%{ C++ +#define NS_CONSOLEMESSAGE_CID \ +{ 0x024efc9e, 0x54dc, 0x4844, { 0x80, 0x4b, 0x41, 0xd3, 0xf3, 0x69, 0x90, 0x73 }} +%} diff --git a/xpcom/base/nsIConsoleService.idl b/xpcom/base/nsIConsoleService.idl new file mode 100644 index 0000000000..1122875aa5 --- /dev/null +++ b/xpcom/base/nsIConsoleService.idl @@ -0,0 +1,78 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIConsoleListener; +interface nsIConsoleMessage; + +[scriptable, builtinclass, uuid(0eb81d20-c37e-42d4-82a8-ca9ae96bdf52)] +interface nsIConsoleService : nsISupports +{ + void logMessage(in nsIConsoleMessage message); + + // This helper function executes `function` and redirects any exception + // that may be thrown while running it to the DevTools Console currently + // debugging `targetGlobal`. + // + // This helps flag the nsIScriptError with a particular innerWindowID + // which is especially useful for WebExtension content scripts + // where script are running in a Sandbox whose prototype is the content window. + // We expect content script exception to be flaged with the content window + // innerWindowID in order to appear in the tab's DevTools. + [implicit_jscontext] + jsval callFunctionAndLogException(in jsval targetGlobal, in jsval function); + + // This is a variant of LogMessage which allows the caller to determine + // if the message should be output to an OS-specific log. This is used on + // B2G to control whether the message is logged to the android log or not. + cenum OutputMode : 8 { + SuppressLog = 0, + OutputToLog + }; + void logMessageWithMode(in nsIConsoleMessage message, + in nsIConsoleService_OutputMode mode); + + /** + * Convenience method for logging simple messages. + */ + void logStringMessage(in wstring message); + + /** + * Get an array of all the messages logged so far. + */ + Array getMessageArray(); + + /** + * To guard against stack overflows from listeners that could log + * messages (it's easy to do this inadvertently from listeners + * implemented in JavaScript), we don't call any listeners when + * another error is already being logged. + */ + void registerListener(in nsIConsoleListener listener); + + /** + * Each registered listener should also be unregistered. + */ + void unregisterListener(in nsIConsoleListener listener); + + /** + * Clear the message buffer (e.g. for privacy reasons). + */ + void reset(); + + /** + * Clear the message buffer for a given window. + */ + void resetWindow(in uint64_t windowInnerId); +}; + + +%{ C++ +#define NS_CONSOLESERVICE_CID \ +{ 0x7e3ff85c, 0x1dd2, 0x11b2, { 0x8d, 0x4b, 0xeb, 0x45, 0x2c, 0xb0, 0xff, 0x40 }} + +#define NS_CONSOLESERVICE_CONTRACTID "@mozilla.org/consoleservice;1" +%} diff --git a/xpcom/base/nsICycleCollectorListener.idl b/xpcom/base/nsICycleCollectorListener.idl new file mode 100644 index 0000000000..5e0b059a13 --- /dev/null +++ b/xpcom/base/nsICycleCollectorListener.idl @@ -0,0 +1,166 @@ +/* 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 "nsISupports.idl" + +%{C++ +#include + +class nsCycleCollectorLogger; +%} + +[ptr] native FILE(FILE); +[ptr] native nsCycleCollectorLoggerPtr(nsCycleCollectorLogger); +interface nsIFile; + +/** + * A set of interfaces for recording the cycle collector's work. An instance + * of nsICycleCollectorListener can be configured to enable various + * options, then passed to the cycle collector when it runs. + * Note that additional logging options are available by setting environment + * variables, as described at the top of nsCycleCollector.cpp. + */ + +/** + * nsICycleCollectorHandler is the interface JS code should implement to + * receive the results logged by an nsICycleCollectorListener + * instance. Pass an instance of this to the logger's 'processNext' method + * after the collection has run. This will describe the objects the cycle + * collector visited, the edges it found, and the conclusions it reached + * about the liveness of objects. + * + * In more detail: + * - For each node in the graph: + * - a call is made to either |noteRefCountedObject| or |noteGCedObject|, to + * describe the node itself; and + * - for each edge starting at that node, a call is made to |noteEdge|. + * + * - Then, a series of calls are made to: + * - |describeRoot|, for reference-counted nodes that the CC has identified as + * being alive because there are unknown references to those nodes. + * - |describeGarbage|, for nodes the cycle collector has identified as garbage. + * + * Any node not mentioned in a call to |describeRoot| or |describeGarbage| is + * neither a root nor garbage. The cycle collector was able to find all of the + * edges implied by the node's reference count. + */ +[scriptable, uuid(7f093367-1492-4b89-87af-c01dbc831246)] +interface nsICycleCollectorHandler : nsISupports +{ + void noteRefCountedObject(in ACString aAddress, + in unsigned long aRefCount, + in ACString aObjectDescription); + void noteGCedObject(in ACString aAddress, + in boolean aMarked, + in ACString aObjectDescription, + in ACString aCompartmentAddress); + void noteEdge(in ACString aFromAddress, + in ACString aToAddress, + in ACString aEdgeName); + void describeRoot(in ACString aAddress, + in unsigned long aKnownEdges); + void describeGarbage(in ACString aAddress); +}; + + +/** + * This interface allows replacing the log-writing backend for an + * nsICycleCollectorListener. As this interface is also called while + * the cycle collector is running, it cannot be implemented in JS. + */ +[scriptable, builtinclass, uuid(3ad9875f-d0e4-4ac2-87e3-f127f6c02ce1)] +interface nsICycleCollectorLogSink : nsISupports +{ + [noscript] void open(out FILE aGCLog, out FILE aCCLog); + void closeGCLog(); + void closeCCLog(); + + // This string will appear somewhere in the log's filename. + attribute AString filenameIdentifier; + + // This is the process ID; it can be changed if logging is on behalf + // of another process. + attribute int32_t processIdentifier; + + // The GC log file, if logging to files. + readonly attribute nsIFile gcLog; + + // The CC log file, if logging to files. + readonly attribute nsIFile ccLog; +}; + + +/** + * This interface is used to configure some reporting options for the cycle + * collector. This interface cannot be implemented by JavaScript code, as it + * is called while the cycle collector is running. + * + * To analyze cycle collection data in JS: + * + * - Create an instance of nsICycleCollectorListener, which implements this + * interface. In C++, this can be done by calling + * nsCycleCollector_createLogger(). In JS, this can be done by calling + * Components.utils.createCCLogger(). + * + * - Set its |disableLog| property to true. This prevents the logger from + * printing messages about each method call to a temporary log file. + * + * - Set its |wantAfterProcessing| property to true. This tells the logger + * to record calls to its methods in memory. The |processNext| method + * returns events from this record. + * + * - Perform a collection using the logger. For example, call + * |nsIDOMWindowUtils|'s |garbageCollect| method, passing the logger as + * the |aListener| argument. + * + * - When the collection is complete, loop calling the logger's + * |processNext| method, passing a JavaScript object that implements + * nsICycleCollectorHandler. This JS code is free to allocate and operate + * on objects however it pleases: the cycle collector has finished its + * work, and the JS code is simply consuming recorded data. + */ +[scriptable, builtinclass, uuid(703b53b6-24f6-40c6-9ea9-aeb2dc53d170)] +interface nsICycleCollectorListener : nsISupports +{ + // Return a listener that directs the cycle collector to traverse + // objects that it knows won't be collectable. + // + // Note that even this listener will not visit every node in the heap; + // the cycle collector can't see the entire heap. But while this + // listener is in use, the collector disables some optimizations it + // normally uses to avoid certain classes of objects that are certainly + // alive. So, if your purpose is to get a view of the portion of the + // heap that is of interest to the cycle collector, and not simply find + // garbage, then you should use the listener this returns. + // + // Note that this does not necessarily return a new listener; rather, it may + // simply set a flag on this listener (a side effect!) and return it. + nsICycleCollectorListener allTraces(); + + // True if this listener will behave like one returned by allTraces(). + readonly attribute boolean wantAllTraces; + + // If true, do not log each method call to a temporary file. + // Initially false. + attribute boolean disableLog; + + // If |disableLog| is false, this object will be sent the log text. + attribute nsICycleCollectorLogSink logSink; + + // If true, record all method calls in memory, to be retrieved later + // using |processNext|. Initially false. + attribute boolean wantAfterProcessing; + + // Report the next recorded event to |aHandler|, and remove it from the + // record. Return false if there isn't anything more to process. + // + // Note that we only record events to report here if our + // |wantAfterProcessing| property is true. + boolean processNext(in nsICycleCollectorHandler aHandler); + + // Return the current object as an nsCycleCollectorLogger*, which is the + // only class that should be implementing this interface. We need the + // concrete implementation type to help the GC rooting analysis. + [noscript] nsCycleCollectorLoggerPtr asLogger(); +}; diff --git a/xpcom/base/nsID.cpp b/xpcom/base/nsID.cpp new file mode 100644 index 0000000000..701bf647d1 --- /dev/null +++ b/xpcom/base/nsID.cpp @@ -0,0 +1,239 @@ +/* -*- 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 "nsID.h" + +#include + +#include "MainThreadUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/RandomNum.h" +#include "mozilla/Sprintf.h" +#include "nss.h" +#include "ScopedNSSTypes.h" + +[[nodiscard]] static bool GenerateRandomBytesFromNSS(void* aBuffer, + size_t aLength) { + MOZ_ASSERT(aBuffer); + + // Bounds check that we can safely truncate size_t `aLength` to an int. + if (aLength == 0 || aLength > INT_MAX) { + MOZ_ASSERT_UNREACHABLE("Bad aLength"); + return false; + } + int len = static_cast(aLength); + + // Only try to use NSS on the main thread. + if (!NS_IsMainThread() || !NSS_IsInitialized()) { + return false; + } + + mozilla::UniquePK11SlotInfo slot(PK11_GetInternalSlot()); + if (!slot) { + MOZ_ASSERT_UNREACHABLE("Null slot"); + return false; + } + + SECStatus srv = PK11_GenerateRandomOnSlot( + slot.get(), static_cast(aBuffer), len); + MOZ_ASSERT(srv == SECSuccess); + return (srv == SECSuccess); +} + +nsresult nsID::GenerateUUIDInPlace(nsID& aId) { + // Firefox needs to generate some UUIDs before NSS has been initialized. We + // prefer NSS's RNG, but if NSS is not available yet or returns an error, fall + // back to MFBT's GenerateRandomBytes(). + if (!GenerateRandomBytesFromNSS(&aId, sizeof(nsID)) && + !mozilla::GenerateRandomBytesFromOS(&aId, sizeof(nsID))) { + MOZ_ASSERT_UNREACHABLE("GenerateRandomBytesFromOS() failed"); + return NS_ERROR_NOT_AVAILABLE; + } + + // Put in the version + aId.m2 &= 0x0fff; + aId.m2 |= 0x4000; + + // Put in the variant + aId.m3[0] &= 0x3f; + aId.m3[0] |= 0x80; + + return NS_OK; +} + +nsID nsID::GenerateUUID() { + nsID uuid; + nsresult rv = GenerateUUIDInPlace(uuid); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + return uuid; +} + +void nsID::Clear() { + m0 = 0; + m1 = 0; + m2 = 0; + memset(m3, 0, sizeof(m3)); +} + +/** + * Multiplies the_int_var with 16 (0x10) and adds the value of the + * hexadecimal digit the_char. If it fails it returns false from + * the function it's used in. + */ + +#define ADD_HEX_CHAR_TO_INT_OR_RETURN_FALSE(the_char, the_int_var) \ + the_int_var = (the_int_var << 4) + the_char; \ + if (the_char >= '0' && the_char <= '9') \ + the_int_var -= '0'; \ + else if (the_char >= 'a' && the_char <= 'f') \ + the_int_var -= 'a' - 10; \ + else if (the_char >= 'A' && the_char <= 'F') \ + the_int_var -= 'A' - 10; \ + else \ + return false + +/** + * Parses number_of_chars characters from the char_pointer pointer and + * puts the number in the dest_variable. The pointer is moved to point + * at the first character after the parsed ones. If it fails it returns + * false from the function the macro is used in. + */ + +#define PARSE_CHARS_TO_NUM(char_pointer, dest_variable, number_of_chars) \ + do { \ + int32_t _i = number_of_chars; \ + dest_variable = 0; \ + while (_i) { \ + ADD_HEX_CHAR_TO_INT_OR_RETURN_FALSE(*char_pointer, dest_variable); \ + char_pointer++; \ + _i--; \ + } \ + } while (0) + +/** + * Parses a hyphen from the char_pointer string. If there is no hyphen there + * the function returns false from the function it's used in. The + * char_pointer is advanced one step. + */ + +#define PARSE_HYPHEN(char_pointer) \ + if (*(char_pointer++) != '-') return false + +/* + * Turns a {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} string into + * an nsID. It can also handle the old format without the { and }. + */ + +bool nsID::Parse(const char* aIDStr) { + /* Optimized for speed */ + if (!aIDStr) { + return false; + } + + bool expectFormat1 = (aIDStr[0] == '{'); + if (expectFormat1) { + ++aIDStr; + } + + PARSE_CHARS_TO_NUM(aIDStr, m0, 8); + PARSE_HYPHEN(aIDStr); + PARSE_CHARS_TO_NUM(aIDStr, m1, 4); + PARSE_HYPHEN(aIDStr); + PARSE_CHARS_TO_NUM(aIDStr, m2, 4); + PARSE_HYPHEN(aIDStr); + int i; + for (i = 0; i < 2; ++i) { + PARSE_CHARS_TO_NUM(aIDStr, m3[i], 2); + } + PARSE_HYPHEN(aIDStr); + while (i < 8) { + PARSE_CHARS_TO_NUM(aIDStr, m3[i], 2); + i++; + } + + return expectFormat1 ? *aIDStr == '}' : true; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR + +/* + * Returns a managed string in {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + * format. + */ +nsIDToCString nsID::ToString() const { return nsIDToCString(*this); } + +static const char sHexChars[256 * 2 + 1] = + "000102030405060708090a0b0c0d0e0f" + "101112131415161718191a1b1c1d1e1f" + "202122232425262728292a2b2c2d2e2f" + "303132333435363738393a3b3c3d3e3f" + "404142434445464748494a4b4c4d4e4f" + "505152535455565758595a5b5c5d5e5f" + "606162636465666768696a6b6c6d6e6f" + "707172737475767778797a7b7c7d7e7f" + "808182838485868788898a8b8c8d8e8f" + "909192939495969798999a9b9c9d9e9f" + "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf" + "b0b1b2b3b4b5b6b7b8b9babbbcbdbebf" + "c0c1c2c3c4c5c6c7c8c9cacbcccdcecf" + "d0d1d2d3d4d5d6d7d8d9dadbdcdddedf" + "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" + "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; + +// Writes a zero-padded 8-bit integer as lowercase hex into aDest[0..2] +static void ToHex8Bit(uint8_t aValue, char* aDest) { + aDest[0] = sHexChars[2 * aValue]; + aDest[1] = sHexChars[2 * aValue + 1]; +} + +// Writes a zero-padded 16-bit integer as lowercase hex into aDest[0..4] +static void ToHex16Bit(uint16_t aValue, char* aDest) { + const uint8_t hi = (aValue >> 8); + const uint8_t lo = aValue; + ToHex8Bit(hi, &aDest[0]); + ToHex8Bit(lo, &aDest[2]); +} + +// Writes a zero-padded 32-bit integer as lowercase hex into aDest[0..8] +static void ToHex32Bit(uint32_t aValue, char* aDest) { + const uint16_t hi = (aValue >> 16); + const uint16_t lo = aValue; + ToHex16Bit(hi, &aDest[0]); + ToHex16Bit(lo, &aDest[4]); +} + +void nsID::ToProvidedString(char (&aDest)[NSID_LENGTH]) const { + // Stringify manually, for best performance. + // + // "{%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}" + // "{ecc35fd2-9029-4287-a826-b0a241d48d31}" + aDest[0] = '{'; + ToHex32Bit(m0, &aDest[1]); + aDest[9] = '-'; + ToHex16Bit(m1, &aDest[10]); + aDest[14] = '-'; + ToHex16Bit(m2, &aDest[15]); + aDest[19] = '-'; + ToHex8Bit(m3[0], &aDest[20]); + ToHex8Bit(m3[1], &aDest[22]); + aDest[24] = '-'; + ToHex8Bit(m3[2], &aDest[25]); + ToHex8Bit(m3[3], &aDest[27]); + ToHex8Bit(m3[4], &aDest[29]); + ToHex8Bit(m3[5], &aDest[31]); + ToHex8Bit(m3[6], &aDest[33]); + ToHex8Bit(m3[7], &aDest[35]); + aDest[37] = '}'; + aDest[38] = '\0'; +} + +#endif // XPCOM_GLUE_AVOID_NSPR + +nsID* nsID::Clone() const { + auto id = static_cast(moz_xmalloc(sizeof(nsID))); + *id = *this; + return id; +} diff --git a/xpcom/base/nsID.h b/xpcom/base/nsID.h new file mode 100644 index 0000000000..c7feddd1cc --- /dev/null +++ b/xpcom/base/nsID.h @@ -0,0 +1,173 @@ +/* -*- 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/. */ + +#ifndef nsID_h__ +#define nsID_h__ + +#include + +#include "nscore.h" + +#define NSID_LENGTH 39 + +#ifndef XPCOM_GLUE_AVOID_NSPR +class nsIDToCString; +#endif + +/** + * A "unique identifier". This is modeled after OSF DCE UUIDs. + */ + +struct nsID { + uint32_t m0; + uint16_t m1; + uint16_t m2; + uint8_t m3[8]; + + /** + * Create a new random UUID. + * GenerateUUIDInPlace() is fallible, whereas GenerateUUID() will abort in + * the unlikely case that the OS RNG returns an error. + */ + [[nodiscard]] static nsresult GenerateUUIDInPlace(nsID& aId); + static nsID GenerateUUID(); + + /** + * Ensures everything is zeroed out. + */ + void Clear(); + + /** + * Equivalency method. Compares this nsID with another. + * @return true if they are the same, false if not. + */ + + inline bool Equals(const nsID& aOther) const { + // At the time of this writing, modern compilers (namely Clang) inline this + // memcmp call into a single SIMD operation on x86/x86-64 architectures (as + // long as we compare to zero rather than returning the full integer return + // value), which is measurably more efficient to any manual comparisons we + // can do directly. +#if defined(__x86_64__) || defined(__i386__) + return !memcmp(this, &aOther, sizeof *this); +#else + // However, on ARM architectures, compilers still tend to generate a direct + // memcmp call, which we'd like to avoid. + return (((uint32_t*)&m0)[0] == ((uint32_t*)&aOther.m0)[0]) && + (((uint32_t*)&m0)[1] == ((uint32_t*)&aOther.m0)[1]) && + (((uint32_t*)&m0)[2] == ((uint32_t*)&aOther.m0)[2]) && + (((uint32_t*)&m0)[3] == ((uint32_t*)&aOther.m0)[3]); +#endif + } + + inline bool operator==(const nsID& aOther) const { return Equals(aOther); } + + /** + * nsID Parsing method. Turns a {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + * string into an nsID + */ + bool Parse(const char* aIDStr); + +#ifndef XPCOM_GLUE_AVOID_NSPR + /** + * nsID string encoder. Returns a managed string in + * {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format. + */ + nsIDToCString ToString() const; + + /** + * nsID string encoder. Builds a string in + * {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} format, into a char[NSID_LENGTH] + * buffer provided by the caller (for instance, on the stack). + */ + void ToProvidedString(char (&aDest)[NSID_LENGTH]) const; + +#endif // XPCOM_GLUE_AVOID_NSPR + + // Infallibly duplicate an nsID. Must be freed with free(). + nsID* Clone() const; +}; + +#ifndef XPCOM_GLUE_AVOID_NSPR +/** + * A stack helper class to convert a nsID to a string. Useful + * for printing nsIDs. For example: + * nsID aID = ...; + * printf("%s", nsIDToCString(aID).get()); + */ +class nsIDToCString { + public: + explicit nsIDToCString(const nsID& aID) { + aID.ToProvidedString(mStringBytes); + } + + const char* get() const { return mStringBytes; } + + protected: + char mStringBytes[NSID_LENGTH]; +}; +#endif + +/* + * Class IDs + */ + +typedef nsID nsCID; + +// Define an CID +#define NS_DEFINE_CID(_name, _cidspec) const nsCID _name = _cidspec + +#define NS_DEFINE_NAMED_CID(_name) static const nsCID k##_name = _name + +#define REFNSCID const nsCID& + +/** + * An "interface id" which can be used to uniquely identify a given + * interface. + */ + +typedef nsID nsIID; + +/** + * A macro shorthand for const nsIID& + */ + +#define REFNSIID const nsIID& + +/** + * A macro to build the static const IID accessor method. The Dummy + * template parameter only exists so that the kIID symbol will be linked + * properly (weak symbol on linux, gnu_linkonce on mac, multiple-definitions + * merged on windows). Dummy should always be instantiated as "void". + */ + +#define NS_DECLARE_STATIC_IID_ACCESSOR(the_iid) \ + template \ + struct COMTypeInfo; + +#define NS_DEFINE_STATIC_IID_ACCESSOR(the_interface, the_iid) \ + template \ + struct the_interface::COMTypeInfo { \ + static const nsIID kIID NS_HIDDEN; \ + }; \ + template \ + const nsIID the_interface::COMTypeInfo::kIID NS_HIDDEN = \ + the_iid; + +/** + * A macro to build the static const CID accessor method + */ + +#define NS_DEFINE_STATIC_CID_ACCESSOR(the_cid) \ + static const nsID& GetCID() { \ + static const nsID cid = the_cid; \ + return cid; \ + } + +#define NS_GET_IID(T) (T::COMTypeInfo::kIID) +#define NS_GET_TEMPLATE_IID(T) (T::template COMTypeInfo::kIID) + +#endif diff --git a/xpcom/base/nsIDUtils.h b/xpcom/base/nsIDUtils.h new file mode 100644 index 0000000000..ffb9d02f19 --- /dev/null +++ b/xpcom/base/nsIDUtils.h @@ -0,0 +1,32 @@ +/* -*- 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/. */ + +#ifndef XPCOM_BASE_NSIDUTILS_H_ +#define XPCOM_BASE_NSIDUTILS_H_ + +#include "nsID.h" +#include "nsString.h" +#include "nsTString.h" + +/** + * Trims the brackets around the nsID string, since it wraps its ID with + * brackets: {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}. + */ +template +class NSID_TrimBrackets : public nsTAutoString { + public: + explicit NSID_TrimBrackets(const nsID& aID) { + nsIDToCString toCString(aID); + // The string from nsIDToCString::get() includes a null terminator. + // Thus this trims 3 characters: `{`, `}`, and the null terminator. + this->AssignASCII(toCString.get() + 1, NSID_LENGTH - 3); + } +}; + +using NSID_TrimBracketsUTF16 = NSID_TrimBrackets; +using NSID_TrimBracketsASCII = NSID_TrimBrackets; + +#endif // XPCOM_BASE_NSIDUTILS_H_ diff --git a/xpcom/base/nsIDebug2.idl b/xpcom/base/nsIDebug2.idl new file mode 100644 index 0000000000..4672fea7ea --- /dev/null +++ b/xpcom/base/nsIDebug2.idl @@ -0,0 +1,100 @@ +/* vim: set shiftwidth=4 tabstop=8 autoindent cindent expandtab: */ +/* 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/. */ + +/* interface to expose information about calls to NS_DebugBreak */ + +#include "nsISupports.idl" + +/** + * @note C/C++ consumers who are planning to use the nsIDebug2 interface with + * the "@mozilla.org/xpcom;1" contract should use NS_DebugBreak from xpcom + * glue instead. + * + */ + +[builtinclass, scriptable, uuid(9641dc15-10fb-42e3-a285-18be90a5c10b)] +interface nsIDebug2 : nsISupports +{ + /** + * Whether XPCOM was compiled with DEBUG defined. This often + * correlates to whether other code (e.g., Firefox, XULRunner) was + * compiled with DEBUG defined. + */ + readonly attribute boolean isDebugBuild; + + /** + * The number of assertions since process start. + */ + readonly attribute long assertionCount; + + /** + * Whether a debugger is currently attached. + * Supports Windows + Mac + */ + readonly attribute bool isDebuggerAttached; + + /** + * Show an assertion and trigger nsIDebug2.break(). + * + * @param aStr assertion message + * @param aExpr expression that failed + * @param aFile file containing assertion + * @param aLine line number of assertion + */ + void assertion(in string aStr, + in string aExpr, + in string aFile, + in long aLine); + + /** + * Show a warning. + * + * @param aStr warning message + * @param aFile file containing assertion + * @param aLine line number of assertion + */ + void warning(in string aStr, + in string aFile, + in long aLine); + + /** + * Request to break into a debugger. + * + * @param aFile file containing break request + * @param aLine line number of break request + */ + void break(in string aFile, + in long aLine); + + /** + * Request the process to trigger a fatal abort. + * + * @param aFile file containing abort request + * @param aLine line number of abort request + */ + void abort(in string aFile, + in long aLine); + + /** + * Request the process to trigger a fatal panic!() from Rust code. + * + * @param aMessage the string to pass to panic!(). + */ + void rustPanic(in string aMessage); + + /** + * Request the process to log a message for a target and level from Rust code. + * + * @param aTarget the string representing the log target. + * @param aMessage the string representing the log message. + */ + void rustLog(in string aTarget, + in string aMessage); + + /** + * Cause an Out of Memory Crash. + */ + void crashWithOOM(); +}; diff --git a/xpcom/base/nsIException.idl b/xpcom/base/nsIException.idl new file mode 100644 index 0000000000..7f529e1c08 --- /dev/null +++ b/xpcom/base/nsIException.idl @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* + * Interfaces for representing cross-language exceptions and stack traces. + */ +#include "nsISupports.idl" + +[ptr] native JSContext(JSContext); +native StackFrameRef(already_AddRefed); + +[scriptable, builtinclass, uuid(28bfb2a2-5ea6-4738-918b-049dc4d51f0b)] +interface nsIStackFrame : nsISupports +{ + [implicit_jscontext, binaryname(FilenameXPCOM)] + readonly attribute AString filename; + [implicit_jscontext, binaryname(NameXPCOM)] + readonly attribute AString name; + // Unique identifier of the script source for the frame, or zero. + [implicit_jscontext, binaryname(SourceIdXPCOM)] + readonly attribute int32_t sourceId; + // Valid line numbers begin at '1'. '0' indicates unknown. + [implicit_jscontext, binaryname(LineNumberXPCOM)] + readonly attribute int32_t lineNumber; + [implicit_jscontext, binaryname(ColumnNumberXPCOM)] + readonly attribute int32_t columnNumber; + readonly attribute AUTF8String sourceLine; + [implicit_jscontext, binaryname(AsyncCauseXPCOM)] + readonly attribute AString asyncCause; + [implicit_jscontext, binaryname(AsyncCallerXPCOM)] + readonly attribute nsIStackFrame asyncCaller; + [implicit_jscontext, binaryname(CallerXPCOM)] + readonly attribute nsIStackFrame caller; + + // Returns a formatted stack string that looks like the sort of + // string that would be returned by .stack on JS Error objects. + // Only works on JS-language stack frames. + [implicit_jscontext, binaryname(FormattedStackXPCOM)] + readonly attribute AString formattedStack; + + // Returns the underlying SavedFrame object for native JavaScript stacks, + // or null if this is not a native JavaScript stack frame. + readonly attribute jsval nativeSavedFrame; + + [implicit_jscontext, binaryname(ToStringXPCOM)] + AUTF8String toString(); + + // Infallible things to be called from C++. + [notxpcom, nostdcall] + void getFilename(in JSContext aCx, out AString aFilename); + [notxpcom, nostdcall] + void getName(in JSContext aCx, out AString aName); + [notxpcom, nostdcall] + int32_t getSourceId(in JSContext aCx); + [notxpcom, nostdcall] + int32_t getLineNumber(in JSContext aCx); + [notxpcom, nostdcall] + int32_t getColumnNumber(in JSContext aCx); + [notxpcom, nostdcall] + void getAsyncCause(in JSContext aCx, out AString aAsyncCause); + [notxpcom, nostdcall] + StackFrameRef getAsyncCaller(in JSContext aCx); + [notxpcom, nostdcall] + StackFrameRef getCaller(in JSContext aCx); + [notxpcom, nostdcall] + void getFormattedStack(in JSContext aCx, out AString aFormattedStack); + [notxpcom, nostdcall, binaryname(ToString)] + void toStringInfallible(in JSContext aCx, out AUTF8String aString); +}; + +// This interface only exists because of all the JS consumers doing +// "instanceof Ci.nsIException". We should switch them to something else and +// get rid of it; bug 1435856 tracks that. C++ code should NOT use this; use +// mozilla::dom::Exception instead. +[scriptable, builtinclass, uuid(4371b5bf-6845-487f-8d9d-3f1e4a9badd2)] +interface nsIException : nsISupports +{ +}; diff --git a/xpcom/base/nsIInterfaceRequestor.idl b/xpcom/base/nsIInterfaceRequestor.idl new file mode 100644 index 0000000000..e0c4c0d7bb --- /dev/null +++ b/xpcom/base/nsIInterfaceRequestor.idl @@ -0,0 +1,35 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsISupports.idl" + +/** + * The nsIInterfaceRequestor interface defines a generic interface for + * requesting interfaces that a given object might provide access to. + * This is very similar to QueryInterface found in nsISupports. + * The main difference is that interfaces returned from GetInterface() + * are not required to provide a way back to the object implementing this + * interface. The semantics of QI() dictate that given an interface A that + * you QI() on to get to interface B, you must be able to QI on B to get back + * to A. This interface however allows you to obtain an interface C from A + * that may or most likely will not have the ability to get back to A. + */ + +[scriptable, uuid(033A1470-8B2A-11d3-AF88-00A024FFC08C)] +interface nsIInterfaceRequestor : nsISupports +{ + /** + * Retrieves the specified interface pointer. + * + * @param uuid The IID of the interface being requested. + * @param result [out] The interface pointer to be filled in if + * the interface is accessible. + * @throws NS_NOINTERFACE - interface not accessible. + * @throws NS_ERROR* - method failure. + */ + void getInterface(in nsIIDRef uuid, + [iid_is(uuid),retval] out nsQIResult result); +}; diff --git a/xpcom/base/nsIInterfaceRequestorUtils.cpp b/xpcom/base/nsIInterfaceRequestorUtils.cpp new file mode 100644 index 0000000000..44dd51921d --- /dev/null +++ b/xpcom/base/nsIInterfaceRequestorUtils.cpp @@ -0,0 +1,32 @@ +/* -*- 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 "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" + +nsresult nsGetInterface::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status; + + if (mSource) { + nsCOMPtr factoryPtr = do_QueryInterface(mSource); + if (factoryPtr) { + status = factoryPtr->GetInterface(aIID, aInstancePtr); + } else { + status = NS_ERROR_NO_INTERFACE; + } + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} diff --git a/xpcom/base/nsIInterfaceRequestorUtils.h b/xpcom/base/nsIInterfaceRequestorUtils.h new file mode 100644 index 0000000000..c3116ea9f2 --- /dev/null +++ b/xpcom/base/nsIInterfaceRequestorUtils.h @@ -0,0 +1,40 @@ +/* -*- 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/. */ + +#ifndef __nsInterfaceRequestorUtils_h +#define __nsInterfaceRequestorUtils_h + +#include "nsCOMPtr.h" + +// a type-safe shortcut for calling the |GetInterface()| member function +// T must inherit from nsIInterfaceRequestor, but the cast may be ambiguous. +template +inline nsresult CallGetInterface(T* aSource, DestinationType** aDestination) { + MOZ_ASSERT(aSource, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return aSource->GetInterface(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +class MOZ_STACK_CLASS nsGetInterface final : public nsCOMPtr_helper { + public: + nsGetInterface(nsISupports* aSource, nsresult* aError) + : mSource(aSource), mErrorPtr(aError) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + nsISupports* MOZ_NON_OWNING_REF mSource; + nsresult* mErrorPtr; +}; + +inline const nsGetInterface do_GetInterface(nsISupports* aSource, + nsresult* aError = 0) { + return nsGetInterface(aSource, aError); +} + +#endif // __nsInterfaceRequestorUtils_h diff --git a/xpcom/base/nsIMacPreferencesReader.idl b/xpcom/base/nsIMacPreferencesReader.idl new file mode 100644 index 0000000000..470fd92ec9 --- /dev/null +++ b/xpcom/base/nsIMacPreferencesReader.idl @@ -0,0 +1,34 @@ +/* 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 "nsISupportsPrimitives.idl" + +%{C++ +#define ENTERPRISE_POLICIES_ENABLED_KEY "EnterprisePoliciesEnabled" +%} + +/** + * This interface is designed to provide scriptable access to the macOS + * preferences system. + * + * This interface is highly macOS specific. + */ +[builtinclass, scriptable, uuid(b0f20595-88ce-4738-a1a4-24de78eb8051)] +interface nsIMacPreferencesReader : nsISupports +{ + /** + * This method checks whether macOS policies are enabled. + * + * @return true if macOS policies are enabled, false otherwise. + */ + bool policiesEnabled(); + + /** + * This method reads and returns the macOS preferences. + * + * @return A JSON object containing all macOS preferences. + */ + [implicit_jscontext] + jsval readPreferences(); +}; diff --git a/xpcom/base/nsIMemoryInfoDumper.idl b/xpcom/base/nsIMemoryInfoDumper.idl new file mode 100644 index 0000000000..79b03a709d --- /dev/null +++ b/xpcom/base/nsIMemoryInfoDumper.idl @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +interface nsIFile; +interface nsICycleCollectorLogSink; + +[scriptable, function, uuid(2dea18fc-fbfa-4bf7-ad45-0efaf5495f5e)] +interface nsIFinishDumpingCallback : nsISupports +{ + void callback(in nsISupports data); +}; + +/** + * Callback interface for |dumpGCAndCCLogsToFile|, below. Note that + * these method calls can occur before |dumpGCAndCCLogsToFile| + * returns. + */ +[scriptable, uuid(dc1b2b24-65bd-441b-b6bd-cb5825a7ed14)] +interface nsIDumpGCAndCCLogsCallback : nsISupports +{ + /** + * Called whenever a process has successfully finished dumping its GC/CC logs. + * Incomplete dumps (e.g., if the child crashes or is killed due to memory + * exhaustion) are not reported. + * + * @param aGCLog The file that the GC log was written to. + * + * @param aCCLog The file that the CC log was written to. + * + * @param aIsParent indicates whether this log file pair is from the + * parent process. + */ + void onDump(in nsIFile aGCLog, + in nsIFile aCCLog, + in bool aIsParent); + + /** + * Called when GC/CC logging has finished, after all calls to |onDump|. + */ + void onFinish(); +}; + +[scriptable, builtinclass, uuid(48541b74-47ee-4a62-9557-7f4b809bda5c)] +interface nsIMemoryInfoDumper : nsISupports +{ + /** + * This dumps gzipped memory reports for this process and its child + * processes. If a file of the given name exists, it will be overwritten. + * + * @param aFilename The output file. + * + * @param aFinishDumping The callback called on completion. + * + * @param aFinishDumpingData The environment for the callback. + * + * @param aAnonymize Should the reports be anonymized? + * + * @param aMinimizeMemoryUsage indicates whether we should run a series of + * GC/CC's in an attempt to reduce our memory usage before collecting our + * memory report. + * + * Sample output, annotated with comments for explanatory purposes. + * + * { + * // The version number of the format, which will be incremented each time + * // backwards-incompatible changes are made. A mandatory integer. + * "version": 1 + * + * // Equal to nsIMemoryReporterManager::hasMozMallocUsableSize. A + * // mandatory boolean. + * "hasMozMallocUsableSize": true, + * + * // The memory reports. A mandatory array. + * "reports": [ + * // The properties correspond to the arguments of + * // nsIHandleReportCallback::callback. Every one is mandatory. + * {"process":"Main Process (pid 12345)", "path":"explicit/foo/bar", + * "kind":1, "units":0, "amount":2000000, "description":"Foo bar."}, + * {"process":"Main Process (pid 12345)", "path":"heap-allocated", + * "kind":1, "units":0, "amount":3000000, "description":"Heap allocated."}, + * {"process":"Main Process (pid 12345)", "path":"vsize", + * "kind":1, "units":0, "amount":10000000, "description":"Vsize."} + * ] + * } + */ + void dumpMemoryReportsToNamedFile(in AString aFilename, + in nsIFinishDumpingCallback aFinishDumping, + in nsISupports aFinishDumpingData, + in boolean aAnonymize, + in boolean aMinimizeMemoryUsage); + + /** + * Similar to dumpMemoryReportsToNamedFile, this method dumps gzipped memory + * reports for this process and its child processes to files in the tmp + * directory called memory-reports--.json.gz (or something + * similar, such as memory-reports---1.json.gz; no existing + * file will be overwritten). + * + * If DMD is enabled, this method also dumps gzipped DMD output for this + * process and its child processes to files in the tmp directory called + * dmd--.txt.gz (or something similar; again, no existing + * file will be overwritten). + * + * @param aIdentifier this identifier will appear in the filename of our + * about:memory dump and those of our children. + * + * If the identifier is empty, the implementation may set it arbitrarily + * and use that new value for its own dump and the dumps of its child + * processes. For example, the implementation may set |aIdentifier| to the + * number of seconds since the epoch. + * + * @param aAnonymize Should the reports be anonymized? + * + * @param aMinimizeMemoryUsage indicates whether we should run a series of + * GC/CC's in an attempt to reduce our memory usage before collecting our + * memory report. + */ + void dumpMemoryInfoToTempDir( + in AString aIdentifier, + in boolean aAnonymize, + in boolean aMinimizeMemoryUsage); + + /** + * Dump GC and CC logs to files in the OS's temp directory (or in + * $MOZ_CC_LOG_DIRECTORY, if that environment variable is specified). + * + * @param aIdentifier If aIdentifier is non-empty, this string will appear in + * the filenames of the logs we create (both for this process and, if + * aDumpChildProcesses is true, for our child processes). + * + * If aIdentifier is empty, the implementation may set it to an + * arbitrary value; for example, it may set aIdentifier to the number + * of seconds since the epoch. + * + * @param aDumpAllTraces indicates whether we should run an all-traces CC + * log. An all-traces log visits all objects currently eligible for cycle + * collection, while a non-all-traces log avoids visiting some objects + * which we know are reachable. + * + * All-traces logs are much bigger than the alternative, but they may be + * helpful when trying to understand why a particular object is alive. For + * example, a non-traces-log will skip references held by an active + * document; if your object is being held alive by such a document, you + * probably want to see those references. + * + * @param aDumpChildProcesses indicates whether we should call + * DumpGCAndCCLogsToFile in our child processes. If so, the child processes + * will dump their children, and so on. + * + */ + void dumpGCAndCCLogsToFile(in AString aIdentifier, + in bool aDumpAllTraces, + in bool aDumpChildProcesses, + in nsIDumpGCAndCCLogsCallback aCallback); + + /** + * Like |dumpGCAndCCLogsToFile|, but sends the logs to the given log + * sink object instead of accessing the filesystem directly, and + * dumps the current process only. + */ + void dumpGCAndCCLogsToSink(in bool aDumpAllTraces, + in nsICycleCollectorLogSink aSink); +}; diff --git a/xpcom/base/nsIMemoryReporter.idl b/xpcom/base/nsIMemoryReporter.idl new file mode 100644 index 0000000000..05b5701519 --- /dev/null +++ b/xpcom/base/nsIMemoryReporter.idl @@ -0,0 +1,615 @@ +/* -*- 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 "nsISupports.idl" +%{C++ +#include +%} + +interface mozIDOMWindowProxy; +interface nsIRunnable; +interface nsISimpleEnumerator; +[ptr] native FILE(FILE); + +/* + * Memory reporters measure Firefox's memory usage. They are primarily used to + * generate the about:memory page. You should read + * https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Memory_reporting + * before writing a memory reporter. + */ + +[scriptable, function, uuid(62ef0e1c-dbd6-11e3-aa75-3c970e9f4238)] +interface nsIHandleReportCallback : nsISupports +{ + /* + * The arguments to the callback are as follows. + * + * + * |process| The name of the process containing this reporter. Each + * reporter initially has "" in this field, indicating that it applies to the + * current process. (This is true even for reporters in a child process.) + * When a reporter from a child process is copied into the main process, the + * copy has its 'process' field set appropriately. + * + * + * |path| The path that this memory usage should be reported under. Paths + * are '/'-delimited, eg. "a/b/c". + * + * Each reporter can be viewed as representing a leaf node in a tree. + * Internal nodes of the tree don't have reporters. So, for example, the + * reporters "explicit/a/b", "explicit/a/c", "explicit/d/e", and + * "explicit/d/f" define this tree: + * + * explicit + * |--a + * | |--b [*] + * | \--c [*] + * \--d + * |--e [*] + * \--f [*] + * + * Nodes marked with a [*] have a reporter. Notice that the internal + * nodes are implicitly defined by the paths. + * + * Nodes within a tree should not overlap measurements, otherwise the + * parent node measurements will be double-counted. So in the example + * above, |b| should not count any allocations counted by |c|, and vice + * versa. + * + * All nodes within each tree must have the same units. + * + * If you want to include a '/' not as a path separator, e.g. because the + * path contains a URL, you need to convert each '/' in the URL to a '\'. + * Consumers of the path will undo this change. Any other '\' character + * in a path will also be changed. This is clumsy but hasn't caused any + * problems so far. + * + * The paths of all reporters form a set of trees. Trees can be + * "degenerate", i.e. contain a single entry with no '/'. + * + * + * |kind| There are three kinds of memory reporters. + * + * - HEAP: reporters measuring memory allocated by the heap allocator, + * e.g. by calling malloc, calloc, realloc, memalign, operator new, or + * operator new[]. Reporters in this category must have units + * UNITS_BYTES. + * + * - NONHEAP: reporters measuring memory which the program explicitly + * allocated, but does not live on the heap. Such memory is commonly + * allocated by calling one of the OS's memory-mapping functions (e.g. + * mmap, VirtualAlloc, or vm_allocate). Reporters in this category + * must have units UNITS_BYTES. + * + * - OTHER: reporters which don't fit into either of these categories. + * They can have any units. + * + * The kind only matters for reporters in the "explicit" tree; + * aboutMemory.js uses it to calculate "heap-unclassified". + * + * + * |units| The units on the reporter's amount. One of the following. + * + * - BYTES: The amount contains a number of bytes. + * + * - COUNT: The amount is an instantaneous count of things currently in + * existence. For instance, the number of tabs currently open would have + * units COUNT. + * + * - COUNT_CUMULATIVE: The amount contains the number of times some event + * has occurred since the application started up. For instance, the + * number of times the user has opened a new tab would have units + * COUNT_CUMULATIVE. + * + * The amount returned by a reporter with units COUNT_CUMULATIVE must + * never decrease over the lifetime of the application. + * + * - PERCENTAGE: The amount contains a fraction that should be expressed as + * a percentage. NOTE! The |amount| field should be given a value 100x + * the actual percentage; this number will be divided by 100 when shown. + * This allows a fractional percentage to be shown even though |amount| is + * an integer. E.g. if the actual percentage is 12.34%, |amount| should + * be 1234. + * + * Values greater than 100% are allowed. + * + * + * |amount| The numeric value reported by this memory reporter. Accesses + * can fail if something goes wrong when getting the amount. + * + * + * |description| A human-readable description of this memory usage report. + */ + void callback(in ACString process, in AUTF8String path, in int32_t kind, + in int32_t units, in int64_t amount, + in AUTF8String description, in nsISupports data); +}; + +/* + * An nsIMemoryReporter reports one or more memory measurements via a + * callback function which is called once for each measurement. + * + * An nsIMemoryReporter that reports a single measurement is sometimes called a + * "uni-reporter". One that reports multiple measurements is sometimes called + * a "multi-reporter". + * + * aboutMemory.js is the most important consumer of memory reports. It + * places the following constraints on reports. + * + * - All reports within a single sub-tree must have the same units. + * + * - There may be an "explicit" tree. If present, it represents + * non-overlapping regions of memory that have been explicitly allocated with + * an OS-level allocation (e.g. mmap/VirtualAlloc/vm_allocate) or a + * heap-level allocation (e.g. malloc/calloc/operator new). Reporters in + * this tree must have kind HEAP or NONHEAP, units BYTES. + * + * It is preferred, but not required, that report descriptions use complete + * sentences (i.e. start with a capital letter and end with a period, or + * similar). + */ +[scriptable, uuid(92a36db1-46bd-4fe6-988e-47db47236d8b)] +interface nsIMemoryReporter : nsISupports +{ + /* + * Run the reporter. + * + * If |anonymize| is true, the memory reporter should anonymize any + * privacy-sensitive details in memory report paths, by replacing them with a + * string such as "". Anonymized memory reports may be sent + * automatically via crash reports or telemetry. + * + * The following things are considered privacy-sensitive. + * + * - Content domains and URLs, and information derived from them. + * - Content data, such as strings. + * - Details about content code, such as filenames, function names or stack + * traces. + * - Details about or data from the user's system, such as filenames. + * - Running apps. + * + * In short, anything that could identify parts of the user's browsing + * history is considered privacy-sensitive. + * + * The following thing are not considered privacy-sensitive. + * + * - Chrome domains and URLs. + * - Information about installed extensions. + */ + void collectReports(in nsIHandleReportCallback callback, + in nsISupports data, + in boolean anonymize); + + /* + * Kinds. See the |kind| comment in nsIHandleReportCallback. + */ + const int32_t KIND_NONHEAP = 0; + const int32_t KIND_HEAP = 1; + const int32_t KIND_OTHER = 2; + + /* + * Units. See the |units| comment in nsIHandleReportCallback. + */ + const int32_t UNITS_BYTES = 0; + const int32_t UNITS_COUNT = 1; + const int32_t UNITS_COUNT_CUMULATIVE = 2; + const int32_t UNITS_PERCENTAGE = 3; +}; + +[scriptable, function, uuid(548b3909-c04d-4ca6-8466-b8bee3837457)] +interface nsIFinishReportingCallback : nsISupports +{ + void callback(in nsISupports data); +}; + +[scriptable, function, uuid(1a80cd0f-0d9e-4397-be69-68ad28fe5175)] +interface nsIHeapAllocatedCallback : nsISupports +{ + void callback(in int64_t bytesAllocated); +}; + +[scriptable, builtinclass, uuid(2998574d-8993-407a-b1a5-8ad7417653e1)] +interface nsIMemoryReporterManager : nsISupports +{ + /* + * Initialize. + */ + [must_use] void init(); + + /* + * Register the given nsIMemoryReporter. The Manager service will hold a + * strong reference to the given reporter, and will be responsible for freeing + * the reporter at shutdown. You may manually unregister the reporter with + * unregisterStrongReporter() at any point. + */ + void registerStrongReporter(in nsIMemoryReporter reporter); + void registerStrongAsyncReporter(in nsIMemoryReporter reporter); + + /* + * Like registerReporter, but the Manager service will hold a weak reference + * via a raw pointer to the given reporter. The reporter should be + * unregistered before shutdown. + * You cannot register JavaScript components with this function! Always + * register your JavaScript components with registerStrongReporter(). + */ + void registerWeakReporter(in nsIMemoryReporter reporter); + void registerWeakAsyncReporter(in nsIMemoryReporter reporter); + + /* + * Unregister the given memory reporter, which must have been registered with + * registerStrongReporter(). You normally don't need to unregister your + * strong reporters, as nsIMemoryReporterManager will take care of that at + * shutdown. + */ + void unregisterStrongReporter(in nsIMemoryReporter reporter); + + /* + * Unregister the given memory reporter, which must have been registered with + * registerWeakReporter(). + */ + void unregisterWeakReporter(in nsIMemoryReporter reporter); + + /* + * These functions should only be used for testing purposes. + */ + void blockRegistrationAndHideExistingReporters(); + void unblockRegistrationAndRestoreOriginalReporters(); + void registerStrongReporterEvenIfBlocked(in nsIMemoryReporter aReporter); + + /* + * Get memory reports for the current process and all child processes. + * |handleReport| is called for each report, and |finishReporting| is called + * once all reports have been handled. + * + * |finishReporting| is called even if, for example, some child processes + * fail to report back. However, calls to this method will silently and + * immediately abort -- and |finishReporting| will not be called -- if a + * previous getReports() call is still in flight, i.e. if it has not yet + * finished invoking |finishReporting|. The silent abort is because the + * in-flight request will finish soon, and the caller would very likely just + * catch and ignore any error anyway. + * + * If |anonymize| is true, it indicates that the memory reporters should + * anonymize any privacy-sensitive data (see above). + */ + void getReports(in nsIHandleReportCallback handleReport, + in nsISupports handleReportData, + in nsIFinishReportingCallback finishReporting, + in nsISupports finishReportingData, + in boolean anonymize); + + /* + * As above, but: If |minimizeMemoryUsage| is true, then each process will + * minimize its memory usage (see the |minimizeMemoryUsage| method) before + * gathering its report. If DMD is enabled and |DMDDumpIdent| is non-empty + * then write a DMD report to a file in the usual temporary directory (see + * |dumpMemoryInfoToTempDir| in |nsIMemoryInfoDumper|.) + */ + [noscript] void + getReportsExtended(in nsIHandleReportCallback handleReport, + in nsISupports handleReportData, + in nsIFinishReportingCallback finishReporting, + in nsISupports finishReportingData, + in boolean anonymize, + in boolean minimizeMemoryUsage, + in AString DMDDumpIdent); + + /* + * As above, but if DMD is enabled and |DMDFile| is non-null then + * write a DMD report to that file and close it. + */ + [noscript] void + getReportsForThisProcessExtended(in nsIHandleReportCallback handleReport, + in nsISupports handleReportData, + in boolean anonymize, + in FILE DMDFile, + in nsIFinishReportingCallback finishReporting, + in nsISupports finishReportingData); + + /* + * Called by an asynchronous memory reporter upon completion. + */ + [noscript] void endReport(); + + /* + * The memory reporter manager, for the most part, treats reporters + * registered with it as a black box. However, there are some + * "distinguished" amounts (as could be reported by a memory reporter) that + * the manager provides as attributes, because they are sufficiently + * interesting that we want external code (e.g. telemetry) to be able to rely + * on them. + * + * Note that these are not reporters and so getReports() does not look at + * them. However, distinguished amounts can be embedded in a reporter. + * + * Access to these attributes can fail. In particular, some of them are not + * available on all platforms. + * + * If you add a new distinguished amount, please update + * toolkit/components/aboutmemory/tests/test_memoryReporters.xul. + * + * |vsize| (UNITS_BYTES) The virtual size, i.e. the amount of address space + * taken up. + * + * |vsizeMaxContiguous| (UNITS_BYTES) The size of the largest contiguous + * block of virtual memory. + * + * |resident| (UNITS_BYTES) The resident size (a.k.a. RSS or physical memory + * used). + * + * |residentFast| (UNITS_BYTES) This is like |resident|, but on Mac OS + * |resident| can purge pages, which is slow. It also affects the result of + * |residentFast|, and so |resident| and |residentFast| should not be used + * together. + * + * |residentPeak| (UNITS_BYTES) The peak resident size. + * + * |residentUnique| (UNITS_BYTES) The unique set size (a.k.a. USS). + * + * |heapAllocated| (UNITS_BYTES) Memory mapped by the heap allocator. + * + * |heapOverheadFraction| (UNITS_PERCENTAGE) In the heap allocator, this is + * the fraction of committed heap bytes that are overhead. Like all + * UNITS_PERCENTAGE measurements, its amount is multiplied by 100x so it can + * be represented by an int64_t. + * + * |JSMainRuntimeGCHeap| (UNITS_BYTES) Size of the main JS runtime's GC + * heap. + * + * |JSMainRuntimeTemporaryPeak| (UNITS_BYTES) Peak size of the transient + * storage in the main JSRuntime. + * + * |JSMainRuntimeCompartments{System,User}| (UNITS_COUNT) The number of + * {system,user} compartments in the main JS runtime. + * + * |JSMainRuntimeRealms{System,User}| (UNITS_COUNT) The number of + * {system,user} realms in the main JS runtime. + * + * |imagesContentUsedUncompressed| (UNITS_BYTES) Memory used for decoded + * raster images in content. + * + * |storageSQLite| (UNITS_BYTES) Memory used by SQLite. + * + * |lowMemoryEventsPhysical| (UNITS_COUNT_CUMULATIVE) + * The number of low-physical-memory events that have occurred since the + * process started. + * + * |ghostWindows| (UNITS_COUNT) A cached value of the number of ghost + * windows. This should have been updated within the past 60s. + * + * |pageFaultsHard| (UNITS_COUNT_CUMULATIVE) The number of hard (a.k.a. + * major) page faults that have occurred since the process started. + */ + [must_use] readonly attribute int64_t vsize; + [must_use] readonly attribute int64_t vsizeMaxContiguous; + [must_use] readonly attribute int64_t resident; + [must_use] readonly attribute int64_t residentFast; + [must_use] readonly attribute int64_t residentPeak; + [must_use] readonly attribute int64_t residentUnique; + + [must_use] readonly attribute int64_t heapAllocated; + [must_use] readonly attribute int64_t heapOverheadFraction; + + [must_use] readonly attribute int64_t JSMainRuntimeGCHeap; + [must_use] readonly attribute int64_t JSMainRuntimeTemporaryPeak; + [must_use] readonly attribute int64_t JSMainRuntimeCompartmentsSystem; + [must_use] readonly attribute int64_t JSMainRuntimeCompartmentsUser; + [must_use] readonly attribute int64_t JSMainRuntimeRealmsSystem; + [must_use] readonly attribute int64_t JSMainRuntimeRealmsUser; + + [must_use] readonly attribute int64_t imagesContentUsedUncompressed; + + [must_use] readonly attribute int64_t storageSQLite; + + [must_use] readonly attribute int64_t lowMemoryEventsPhysical; + + [must_use] readonly attribute int64_t ghostWindows; + + [must_use] readonly attribute int64_t pageFaultsHard; + + /* + * This attribute indicates if moz_malloc_usable_size() works. + */ + [infallible] readonly attribute boolean hasMozMallocUsableSize; + + /* + * These attributes indicate DMD's status. "Enabled" means enabled at + * build-time. + */ + [infallible] readonly attribute boolean isDMDEnabled; + [infallible] readonly attribute boolean isDMDRunning; + + /* + * Run a series of GC/CC's in an attempt to minimize the application's memory + * usage. When we're finished doing this for the current process, we invoke + * the given runnable if it's not null. We do not wait for any child processes + * that might be doing their own minimization via child-mmu-request to finish. + */ + [must_use] void minimizeMemoryUsage(in nsIRunnable callback); + + /* + * Measure the memory that is known to be owned by this tab, split up into + * several broad categories. Note that this will be an underestimate of the + * true number, due to imperfect memory reporter coverage (corresponding to + * about:memory's "heap-unclassified"), and due to some memory shared between + * tabs not being counted. + * + * The time taken for the measurement (split into JS and non-JS parts) is + * also returned. + */ + [must_use] + void sizeOfTab(in mozIDOMWindowProxy window, + out int64_t jsObjectsSize, out int64_t jsStringsSize, + out int64_t jsOtherSize, out int64_t domSize, + out int64_t styleSize, out int64_t otherSize, + out int64_t totalSize, + out double jsMilliseconds, out double nonJSMilliseconds); +}; + +%{C++ + +#include "js/TypeDecls.h" +#include "nsString.h" +#include "nsTArray.h" + +class nsPIDOMWindowOuter; + +namespace mozilla { + +// All the following registration/unregistration functions don't use +// [[nodiscard]] because ignoring failures is common and reasonable. + +// Register a memory reporter. The manager service will hold a strong +// reference to this reporter. +XPCOM_API(nsresult) RegisterStrongMemoryReporter(nsIMemoryReporter* aReporter); +XPCOM_API(nsresult) RegisterStrongAsyncMemoryReporter(nsIMemoryReporter* aReporter); + +// Register a memory reporter. The manager service will hold a weak reference +// to this reporter. +XPCOM_API(nsresult) RegisterWeakMemoryReporter(nsIMemoryReporter* aReporter); +XPCOM_API(nsresult) RegisterWeakAsyncMemoryReporter(nsIMemoryReporter* aReporter); + +// Unregister a strong memory reporter. +XPCOM_API(nsresult) UnregisterStrongMemoryReporter(nsIMemoryReporter* aReporter); + +// Unregister a weak memory reporter. +XPCOM_API(nsresult) UnregisterWeakMemoryReporter(nsIMemoryReporter* aReporter); + +// The memory reporter manager provides access to several distinguished +// amounts via attributes. Some of these amounts are provided by Gecko +// components that cannot be accessed directly from XPCOM code. So we provide +// the following functions for those components to be registered with the +// manager. + +typedef int64_t (*InfallibleAmountFn)(); + +#define DECL_REGISTER_DISTINGUISHED_AMOUNT(kind, name) \ + nsresult Register##name##DistinguishedAmount(kind##AmountFn aAmountFn); +#define DECL_UNREGISTER_DISTINGUISHED_AMOUNT(name) \ + nsresult Unregister##name##DistinguishedAmount(); + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsSystem) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsSystem) +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsUser) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, ImagesContentUsedUncompressed) +DECL_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite) +DECL_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical) + +DECL_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows) + +#undef DECL_REGISTER_DISTINGUISHED_AMOUNT +#undef DECL_UNREGISTER_DISTINGUISHED_AMOUNT + +// Likewise for per-tab measurement. + +typedef nsresult (*JSSizeOfTabFn)(JSObject* aObj, + size_t* aJsObjectsSize, + size_t* aJsStringSize, + size_t* aJsPrivateSize, + size_t* aJsOtherSize); +typedef nsresult (*NonJSSizeOfTabFn)(nsPIDOMWindowOuter* aWindow, + size_t* aDomSize, + size_t* aStyleSize, + size_t* aOtherSize); + +nsresult RegisterJSSizeOfTab(JSSizeOfTabFn aSizeOfTabFn); +nsresult RegisterNonJSSizeOfTab(NonJSSizeOfTabFn aSizeOfTabFn); + +} + +#if defined(MOZ_DMD) +#if !defined(MOZ_MEMORY) +#error "MOZ_DMD requires MOZ_MEMORY" +#endif + +#include "DMD.h" + +#define MOZ_REPORT(ptr) mozilla::dmd::Report(ptr) +#define MOZ_REPORT_ON_ALLOC(ptr) mozilla::dmd::ReportOnAlloc(ptr) + +#else + +#define MOZ_REPORT(ptr) +#define MOZ_REPORT_ON_ALLOC(ptr) + +#endif // defined(MOZ_DMD) + +// Functions generated via this macro should be used by all traversal-based +// memory reporters. Such functions return |moz_malloc_size_of(ptr)|; this +// will always be zero on some obscure platforms. +// +// You might be wondering why we have a macro that creates multiple functions +// that differ only in their name, instead of a single MallocSizeOf function. +// It's mostly to help with DMD integration, though it sometimes also helps +// with debugging and temporary ad hoc profiling. The function name chosen +// doesn't matter greatly, but it's best to make it similar to the path used by +// the relevant memory reporter(s). +#define MOZ_DEFINE_MALLOC_SIZE_OF(fn) \ + static size_t fn(const void* aPtr) \ + { \ + MOZ_REPORT(aPtr); \ + return moz_malloc_size_of(aPtr); \ + } + +// This is an alternative to MOZ_DEFINE_MALLOC_SIZE_OF that defines a +// MallocSizeOf function that can handle interior pointers. +#ifdef MOZ_MEMORY + +#include "mozmemory.h" + +#define MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(fn) \ + static size_t fn(const void* aPtr) \ + { \ + jemalloc_ptr_info_t info; \ + jemalloc_ptr_info(aPtr, &info); \ + MOZ_REPORT(info.addr); \ + return jemalloc_ptr_is_live(&info) ? info.size : 0; \ + } + +#else + +#define MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(fn) \ + static size_t fn(const void* aPtr) \ + { \ + return 0; \ + } + +#endif + +// Functions generated by the next two macros should be used by wrapping +// allocators that report heap blocks as soon as they are allocated and +// unreport them as soon as they are freed. Such allocators are used in cases +// where we have third-party code that we cannot modify. The two functions +// must always be used in tandem. +#define MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(fn) \ + static size_t fn(const void* aPtr) \ + { \ + MOZ_REPORT_ON_ALLOC(aPtr); \ + return moz_malloc_size_of(aPtr); \ + } +#define MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(fn) \ + static size_t fn(const void* aPtr) \ + { \ + return moz_malloc_size_of(aPtr); \ + } + +// This macro assumes the presence of appropriate |aHandleReport| and |aData| +// variables. The (void) is there because we should always ignore the return +// value of the callback, because callback failures aren't fatal. +#define MOZ_COLLECT_REPORT(path, kind, units, amount, description) \ + (void)aHandleReport->Callback(""_ns, nsLiteralCString(path), \ + kind, units, amount, \ + nsLiteralCString(description), aData) + +%} diff --git a/xpcom/base/nsIMessageLoop.idl b/xpcom/base/nsIMessageLoop.idl new file mode 100644 index 0000000000..070883ed89 --- /dev/null +++ b/xpcom/base/nsIMessageLoop.idl @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +interface nsIRunnable; + +/** + * This service allows access to the current thread's Chromium MessageLoop + * instance, with some extra sugar added. If you're calling from C++, it may + * or may not make sense for you to use this interface. If you're calling from + * JS, you don't have a choice! + * + * Right now, you can only call PostIdleTask(), and the wrath of khuey is + * stopping you from adding other methods. + * + * nsIMessageLoop's contractid is "@mozilla.org/message-loop;1". + */ +[builtinclass, scriptable, uuid(3E8C58E8-E52C-43E0-8E66-669CA788FF5F)] +interface nsIMessageLoop : nsISupports +{ + /** + * Posts a task to be run when this thread's message loop is idle, or after + * ensureRunsAfterMS milliseconds have elapsed. (That is, the task is + * guaranteed to run /eventually/.) + * + * Note that if the event loop is busy, we will hold a reference to the task + * until ensureRunsAfterMS milliseconds have elapsed. Be careful when + * specifying long timeouts and tasks which hold references to windows or + * other large objects, because you can leak memory in a difficult-to-detect + * way! + */ + void postIdleTask(in nsIRunnable task, in uint32_t ensureRunsAfterMS); +}; diff --git a/xpcom/base/nsINIParser.cpp b/xpcom/base/nsINIParser.cpp new file mode 100644 index 0000000000..c1eec56f10 --- /dev/null +++ b/xpcom/base/nsINIParser.cpp @@ -0,0 +1,324 @@ +/* -*- 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/. */ + +// Moz headers (alphabetical) +#include "nsCRTGlue.h" +#include "nsError.h" +#include "nsIFile.h" +#include "nsINIParser.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/URLPreloader.h" + +using namespace mozilla; + +nsresult nsINIParser::Init(nsIFile* aFile) { + nsCString result; + MOZ_TRY_VAR(result, URLPreloader::ReadFile(aFile)); + + return InitFromString(result); +} + +static const char kNL[] = "\r\n"; +static const char kEquals[] = "="; +static const char kWhitespace[] = " \t"; +static const char kRBracket[] = "]"; + +nsresult nsINIParser::InitFromString(const nsCString& aStr) { + nsCString fileContents; + char* buffer; + + if (StringHead(aStr, 3) == "\xEF\xBB\xBF") { + // Someone set us up the Utf-8 BOM + // This case is easy, since we assume that BOM-less + // files are Utf-8 anyway. Just skip the BOM and process as usual. + fileContents.Append(aStr); + buffer = fileContents.BeginWriting() + 3; + } else { + if (StringHead(aStr, 2) == "\xFF\xFE") { + // Someone set us up the Utf-16LE BOM + nsDependentSubstring str(reinterpret_cast(aStr.get()), + aStr.Length() / 2); + + AppendUTF16toUTF8(Substring(str, 1), fileContents); + } else { + fileContents.Append(aStr); + } + + buffer = fileContents.BeginWriting(); + } + + char* currSection = nullptr; + + // outer loop tokenizes into lines + while (char* token = NS_strtok(kNL, &buffer)) { + if (token[0] == '#' || token[0] == ';') { // it's a comment + continue; + } + + token = (char*)NS_strspnp(kWhitespace, token); + if (!*token) { // empty line + continue; + } + + if (token[0] == '[') { // section header! + ++token; + currSection = token; + + char* rb = NS_strtok(kRBracket, &token); + if (!rb || NS_strtok(kWhitespace, &token)) { + // there's either an unclosed [Section or a [Section]Moretext! + // we could frankly decide that this INI file is malformed right + // here and stop, but we won't... keep going, looking for + // a well-formed [section] to continue working with + currSection = nullptr; + } + + continue; + } + + if (!currSection) { + // If we haven't found a section header (or we found a malformed + // section header), don't bother parsing this line. + continue; + } + + char* key = token; + char* e = NS_strtok(kEquals, &token); + if (!e || !token) { + continue; + } + + SetString(currSection, key, token); + } + + return NS_OK; +} + +bool nsINIParser::IsValidSection(const char* aSection) { + if (aSection[0] == '\0') { + return false; + } + + const char* found = strpbrk(aSection, "\r\n[]"); + return found == nullptr; +} + +bool nsINIParser::IsValidKey(const char* aKey) { + if (aKey[0] == '\0') { + return false; + } + + const char* found = strpbrk(aKey, "\r\n="); + return found == nullptr; +} + +bool nsINIParser::IsValidValue(const char* aValue) { + const char* found = strpbrk(aValue, "\r\n"); + return found == nullptr; +} + +nsresult nsINIParser::GetString(const char* aSection, const char* aKey, + nsACString& aResult) { + if (!IsValidSection(aSection) || !IsValidKey(aKey)) { + return NS_ERROR_INVALID_ARG; + } + + INIValue* val; + mSections.Get(aSection, &val); + + while (val) { + if (strcmp(val->key, aKey) == 0) { + aResult.Assign(val->value); + return NS_OK; + } + + val = val->next.get(); + } + + return NS_ERROR_FAILURE; +} + +nsresult nsINIParser::GetString(const char* aSection, const char* aKey, + char* aResult, uint32_t aResultLen) { + if (!IsValidSection(aSection) || !IsValidKey(aKey)) { + return NS_ERROR_INVALID_ARG; + } + + INIValue* val; + mSections.Get(aSection, &val); + + while (val) { + if (strcmp(val->key, aKey) == 0) { + strncpy(aResult, val->value, aResultLen); + aResult[aResultLen - 1] = '\0'; + if (strlen(val->value) >= aResultLen) { + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + + return NS_OK; + } + + val = val->next.get(); + } + + return NS_ERROR_FAILURE; +} + +nsresult nsINIParser::GetSections(INISectionCallback aCB, void* aClosure) { + for (const auto& key : mSections.Keys()) { + if (!aCB(key, aClosure)) { + break; + } + } + return NS_OK; +} + +nsresult nsINIParser::GetStrings(const char* aSection, INIStringCallback aCB, + void* aClosure) { + if (!IsValidSection(aSection)) { + return NS_ERROR_INVALID_ARG; + } + + INIValue* val; + + for (mSections.Get(aSection, &val); val; val = val->next.get()) { + if (!aCB(val->key, val->value, aClosure)) { + return NS_OK; + } + } + + return NS_OK; +} + +nsresult nsINIParser::SetString(const char* aSection, const char* aKey, + const char* aValue) { + if (!IsValidSection(aSection) || !IsValidKey(aKey) || !IsValidValue(aValue)) { + return NS_ERROR_INVALID_ARG; + } + + mSections.WithEntryHandle(aSection, [&](auto&& entry) { + if (!entry) { + entry.Insert(MakeUnique(aKey, aValue)); + return; + } + + INIValue* v = entry->get(); + + // Check whether this key has already been specified; overwrite + // if so, or append if not. + while (v) { + if (!strcmp(aKey, v->key)) { + v->SetValue(aValue); + break; + } + if (!v->next) { + v->next = MakeUnique(aKey, aValue); + break; + } + v = v->next.get(); + } + NS_ASSERTION(v, "v should never be null coming out of this loop"); + }); + + return NS_OK; +} + +nsresult nsINIParser::DeleteString(const char* aSection, const char* aKey) { + if (!IsValidSection(aSection) || !IsValidKey(aKey)) { + return NS_ERROR_INVALID_ARG; + } + + INIValue* val; + if (!mSections.Get(aSection, &val)) { + return NS_ERROR_FAILURE; + } + + // Special case the first result + if (strcmp(val->key, aKey) == 0) { + if (!val->next) { + mSections.Remove(aSection); + } else { + mSections.InsertOrUpdate(aSection, std::move(val->next)); + delete val; + } + return NS_OK; + } + + while (val->next) { + if (strcmp(val->next->key, aKey) == 0) { + val->next = std::move(val->next->next); + + return NS_OK; + } + + val = val->next.get(); + } + + return NS_ERROR_FAILURE; +} + +nsresult nsINIParser::DeleteSection(const char* aSection) { + if (!IsValidSection(aSection)) { + return NS_ERROR_INVALID_ARG; + } + + if (!mSections.Remove(aSection)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +nsresult nsINIParser::RenameSection(const char* aSection, + const char* aNewName) { + if (!IsValidSection(aSection) || !IsValidSection(aNewName)) { + return NS_ERROR_INVALID_ARG; + } + + if (mSections.Contains(aNewName)) { + return NS_ERROR_ILLEGAL_VALUE; + } + + mozilla::UniquePtr val; + if (mSections.Remove(aSection, &val)) { + mSections.InsertOrUpdate(aNewName, std::move(val)); + } else { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult nsINIParser::WriteToFile(nsIFile* aFile) { + nsCString buffer; + + WriteToString(buffer); + + FILE* writeFile; + nsresult rv = aFile->OpenANSIFileDesc("w", &writeFile); + NS_ENSURE_SUCCESS(rv, rv); + + unsigned int length = buffer.Length(); + + if (fwrite(buffer.get(), sizeof(char), length, writeFile) != length) { + fclose(writeFile); + return NS_ERROR_UNEXPECTED; + } + + fclose(writeFile); + return NS_OK; +} + +void nsINIParser::WriteToString(nsACString& aOutput) { + for (const auto& entry : mSections) { + aOutput.AppendPrintf("[%s]\n", entry.GetKey()); + INIValue* val = entry.GetWeak(); + while (val) { + aOutput.AppendPrintf("%s=%s\n", val->key, val->value); + val = val->next.get(); + } + aOutput.AppendLiteral("\n"); + } +} diff --git a/xpcom/base/nsINIParser.h b/xpcom/base/nsINIParser.h new file mode 100644 index 0000000000..3b26a0d511 --- /dev/null +++ b/xpcom/base/nsINIParser.h @@ -0,0 +1,168 @@ +/* -*- 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/. */ + +// This file was shamelessly copied from mozilla/xpinstall/wizard/unix/src2 + +#ifndef nsINIParser_h__ +#define nsINIParser_h__ + +#ifdef MOZILLA_INTERNAL_API +# define nsINIParser nsINIParser_internal +#endif + +#include "nscore.h" +#include "nsClassHashtable.h" +#include "mozilla/UniquePtr.h" + +#include + +class nsIFile; + +class nsINIParser { + public: + nsINIParser() {} + ~nsINIParser() = default; + + /** + * Initialize the INIParser with a nsIFile. If this method fails, no + * other methods should be called. This method reads and parses the file, + * the class does not hold a file handle open. An instance must only be + * initialized once. + */ + nsresult Init(nsIFile* aFile); + + nsresult InitFromString(const nsCString& aStr); + + /** + * Callback for GetSections + * @return false to stop enumeration, or true to continue. + */ + typedef bool (*INISectionCallback)(const char* aSection, void* aClosure); + + /** + * Enumerate the sections within the INI file. + */ + nsresult GetSections(INISectionCallback aCB, void* aClosure); + + /** + * Callback for GetStrings + * @return false to stop enumeration, or true to continue + */ + typedef bool (*INIStringCallback)(const char* aString, const char* aValue, + void* aClosure); + + /** + * Enumerate the strings within a section. If the section does + * not exist, this function will silently return. + */ + nsresult GetStrings(const char* aSection, INIStringCallback aCB, + void* aClosure); + + /** + * Get the value of the specified key in the specified section + * of the INI file represented by this instance. + * + * @param aSection section name + * @param aKey key name + * @param aResult the value found + * @throws NS_ERROR_FAILURE if the specified section/key could not be + * found. + */ + nsresult GetString(const char* aSection, const char* aKey, + nsACString& aResult); + + /** + * Alternate signature of GetString that uses a pre-allocated buffer + * instead of a nsACString (for use in the standalone glue before + * the glue is initialized). + * + * @throws NS_ERROR_LOSS_OF_SIGNIFICANT_DATA if the aResult buffer is not + * large enough for the data. aResult will be filled with as + * much data as possible. + * + * @see GetString [1] + */ + nsresult GetString(const char* aSection, const char* aKey, char* aResult, + uint32_t aResultLen); + + /** + * Sets the value of the specified key in the specified section. The section + * is created if it does not already exist. + * + * @oaram aSection section name + * @param aKey key name + * @param aValue the value to set + */ + nsresult SetString(const char* aSection, const char* aKey, + const char* aValue); + + /** + * Deletes the value of the specified key in the specified section. + * + * @param aSection section name + * @param aKey key name + * + * @throws NS_ERROR_FAILURE if the string was not set. + */ + nsresult DeleteString(const char* aSection, const char* aKey); + + /** + * Deletes the specified section. + * + * @param aSection section name + * + * @throws NS_ERROR_FAILURE if the section did not exist. + */ + nsresult DeleteSection(const char* aSection); + + /** + * Renames the specified section. + * + * @param aSection section name + * @param aNewName new section name + * + * @throws NS_ERROR_FAILURE if the section did not exist. + * @throws NS_ERROR_ILLEGAL_VALUE if the new section name already exists. + */ + nsresult RenameSection(const char* aSection, const char* aNewName); + + /** + * Writes the ini data to disk. + * @param aFile the file to write to + * @throws NS_ERROR_FAILURE on failure. + */ + nsresult WriteToFile(nsIFile* aFile); + + void WriteToString(nsACString& aOutput); + + private: + struct INIValue { + INIValue(const char* aKey, const char* aValue) + : key(strdup(aKey)), value(strdup(aValue)) {} + + ~INIValue() { + delete key; + delete value; + } + + void SetValue(const char* aValue) { + delete value; + value = strdup(aValue); + } + + const char* key; + const char* value; + mozilla::UniquePtr next; + }; + + nsClassHashtable mSections; + + bool IsValidSection(const char* aSection); + bool IsValidKey(const char* aKey); + bool IsValidValue(const char* aValue); +}; + +#endif /* nsINIParser_h__ */ diff --git a/xpcom/base/nsISecurityConsoleMessage.idl b/xpcom/base/nsISecurityConsoleMessage.idl new file mode 100644 index 0000000000..917968d83b --- /dev/null +++ b/xpcom/base/nsISecurityConsoleMessage.idl @@ -0,0 +1,20 @@ +/* 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 "nsISupports.idl" + +/* + * Holds localization message tag and message category + * for security related console messages. + */ +[uuid(FE9FC9B6-DDE2-11E2-A8F1-0A326188709B)] +interface nsISecurityConsoleMessage : nsISupports +{ + attribute AString tag; + attribute AString category; +}; + +%{ C++ +#define NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID "@mozilla.org/securityconsole/message;1" +%} diff --git a/xpcom/base/nsISizeOf.h b/xpcom/base/nsISizeOf.h new file mode 100644 index 0000000000..7d0ef4d74f --- /dev/null +++ b/xpcom/base/nsISizeOf.h @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +#ifndef nsISizeOf_h___ +#define nsISizeOf_h___ + +#include "mozilla/MemoryReporting.h" +#include "nsISupports.h" + +#define NS_ISIZEOF_IID \ + { \ + 0x61d05579, 0xd7ec, 0x485c, { \ + 0xa4, 0x0c, 0x31, 0xc7, 0x9a, 0x5c, 0xf9, 0xf3 \ + } \ + } + +class nsISizeOf : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISIZEOF_IID) + + /** + * Measures the size of the things pointed to by the object. + */ + virtual size_t SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const = 0; + + /** + * Like SizeOfExcludingThis, but also includes the size of the object itself. + */ + virtual size_t SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsISizeOf, NS_ISIZEOF_IID) + +#endif /* nsISizeOf_h___ */ diff --git a/xpcom/base/nsISupports.idl b/xpcom/base/nsISupports.idl new file mode 100644 index 0000000000..45c6c5d589 --- /dev/null +++ b/xpcom/base/nsISupports.idl @@ -0,0 +1,60 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/** + * The mother of all xpcom interfaces. + */ + +#include "nsrootidl.idl" + +/** + * Basic component object model interface. Objects which implement + * this interface support runtime interface discovery (QueryInterface) + * and a reference counted memory model (AddRef/Release). This is + * modelled after the win32 IUnknown API. + * + * Historically, nsISupports needed to be binary compatible with COM's + * IUnknown, so the IID of nsISupports is the same as it. That is no + * longer a goal, and hopefully nobody depends on it. We may break + * this compatibility at any time. + */ +[scriptable, uuid(00000000-0000-0000-c000-000000000046)] +interface nsISupports { + + /** + * A run time mechanism for interface discovery. + * @param aIID [in] A requested interface IID + * @param aInstancePtr [out] A pointer to an interface pointer to + * receive the result. + * @return NS_OK if the interface is supported by the associated + * instance, NS_NOINTERFACE if it is not. + * + * aInstancePtr must not be null. + */ + void QueryInterface(in nsIIDRef aIID, + [iid_is(aIID), retval] out nsQIResult aInstancePtr); + + /** + * Increases the reference count for this interface. + * The associated instance will not be deleted unless + * the reference count is returned to zero. + * + * @return The resulting reference count. + */ + [noscript, notxpcom] MozExternalRefCountType AddRef(); + + /** + * Decreases the reference count for this interface. + * Generally, if the reference count returns to zero, + * the associated instance is deleted. + * + * @return The resulting reference count. + */ + [noscript, notxpcom] MozExternalRefCountType Release(); +}; + +%{C++ +#include "nsISupportsUtils.h" +%} diff --git a/xpcom/base/nsISupportsImpl.cpp b/xpcom/base/nsISupportsImpl.cpp new file mode 100644 index 0000000000..c282ec14db --- /dev/null +++ b/xpcom/base/nsISupportsImpl.cpp @@ -0,0 +1,144 @@ +/* -*- 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 "nsISupportsImpl.h" + +#include "mozilla/Assertions.h" +#ifndef XPCOM_GLUE_AVOID_NSPR +# include "nsPrintfCString.h" +# include "nsThreadUtils.h" +#endif + +using namespace mozilla; + +nsresult NS_FASTCALL NS_TableDrivenQI(void* aThis, REFNSIID aIID, + void** aInstancePtr, + const QITableEntry* aEntries) { + do { + if (aIID.Equals(*aEntries->iid)) { + nsISupports* r = reinterpret_cast( + reinterpret_cast(aThis) + aEntries->offset); + NS_ADDREF(r); + *aInstancePtr = r; + return NS_OK; + } + + ++aEntries; + } while (aEntries->iid); + + *aInstancePtr = nullptr; + return NS_ERROR_NO_INTERFACE; +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +# ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED +nsAutoOwningThread::nsAutoOwningThread() : mThread(PR_GetCurrentThread()) {} + +void nsAutoOwningThread::AssertCurrentThreadOwnsMe(const char* msg) const { + if (MOZ_UNLIKELY(!IsCurrentThread())) { + // `msg` is a string literal by construction. + MOZ_CRASH_UNSAFE(msg); + } +} + +bool nsAutoOwningThread::IsCurrentThread() const { + return mThread == PR_GetCurrentThread(); +} + +nsAutoOwningEventTarget::nsAutoOwningEventTarget() + : mTarget(GetCurrentSerialEventTarget()) { + mTarget->AddRef(); +} + +nsAutoOwningEventTarget::~nsAutoOwningEventTarget() { + nsCOMPtr target = dont_AddRef(mTarget); +} + +void nsAutoOwningEventTarget ::AssertCurrentThreadOwnsMe( + const char* msg) const { + if (MOZ_UNLIKELY(!IsCurrentThread())) { + // `msg` is a string literal by construction. + MOZ_CRASH_UNSAFE(msg); + } +} + +bool nsAutoOwningEventTarget::IsCurrentThread() const { + return mTarget->IsOnCurrentThread(); +} + +# endif + +namespace mozilla::detail { +class ProxyDeleteVoidRunnable final : public CancelableRunnable { + public: + ProxyDeleteVoidRunnable(const char* aName, void* aPtr, + DeleteVoidFunction* aDeleteFunc) + : CancelableRunnable(aName), mPtr(aPtr), mDeleteFunc(aDeleteFunc) {} + + NS_IMETHOD Run() override { + if (mPtr) { + mDeleteFunc(mPtr); + mPtr = nullptr; + } + return NS_OK; + } + + // Mimics the behaviour in `ProxyRelease`, freeing the resource when + // cancelled. + nsresult Cancel() override { return Run(); } + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + if (mName) { + aName.Append(mName); + } else { + aName.AssignLiteral("ProxyDeleteVoidRunnable"); + } + return NS_OK; + } +# endif + + private: + ~ProxyDeleteVoidRunnable() { + if (mPtr) { +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_WARNING( + nsPrintfCString( + "ProxyDeleteVoidRunnable for '%s' never run, leaking!", mName) + .get()); +# else + NS_WARNING("ProxyDeleteVoidRunnable never run, leaking!"); +# endif + } + } + + void* mPtr; + DeleteVoidFunction* mDeleteFunc; +}; + +void ProxyDeleteVoid(const char* aName, nsISerialEventTarget* aTarget, + void* aPtr, DeleteVoidFunction* aDeleteFunc) { + MOZ_ASSERT(aName); + MOZ_ASSERT(aPtr); + MOZ_ASSERT(aDeleteFunc); + if (!aTarget) { + NS_WARNING(nsPrintfCString("no target for '%s', leaking!", aName).get()); + return; + } + + if (aTarget->IsOnCurrentThread()) { + aDeleteFunc(aPtr); + return; + } + nsresult rv = aTarget->Dispatch( + MakeAndAddRef(aName, aPtr, aDeleteFunc), + NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING(nsPrintfCString("failed to post '%s', leaking!", aName).get()); + } +} +} // namespace mozilla::detail +#endif diff --git a/xpcom/base/nsISupportsImpl.h b/xpcom/base/nsISupportsImpl.h new file mode 100644 index 0000000000..6a232daae9 --- /dev/null +++ b/xpcom/base/nsISupportsImpl.h @@ -0,0 +1,1539 @@ +/* -*- 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/. */ +// IWYU pragma: private, include "nsISupports.h" + +#ifndef nsISupportsImpl_h__ +#define nsISupportsImpl_h__ + +#include "nscore.h" +#include "nsISupports.h" +#include "nsISupportsUtils.h" + +#if !defined(XPCOM_GLUE_AVOID_NSPR) +# include "prthread.h" /* needed for cargo-culting headers */ +#endif + +#include "nsDebug.h" +#include "nsXPCOM.h" +#include +#include +#include "mozilla/Attributes.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Compiler.h" +#include "mozilla/Likely.h" +#include "mozilla/MacroArgs.h" +#include "mozilla/MacroForEach.h" + +#define MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(X) \ + static_assert(!std::is_destructible_v, \ + "Reference-counted class " #X \ + " should not have a public destructor. " \ + "Make this class's destructor non-public"); + +inline nsISupports* ToSupports(decltype(nullptr)) { return nullptr; } + +inline nsISupports* ToSupports(nsISupports* aSupports) { return aSupports; } + +//////////////////////////////////////////////////////////////////////////////// +// Macros to help detect thread-safety: + +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +# include "prthread.h" /* needed for thread-safety checks */ + +class nsAutoOwningThread { + public: + nsAutoOwningThread(); + + // We move the actual assertion checks out-of-line to minimize code bloat, + // but that means we have to pass a non-literal string to MOZ_CRASH_UNSAFE. + // To make that more safe, the public interface requires a literal string + // and passes that to the private interface; we can then be assured that we + // effectively are passing a literal string to MOZ_CRASH_UNSAFE. + template + void AssertOwnership(const char (&aMsg)[N]) const { + AssertCurrentThreadOwnsMe(aMsg); + } + + bool IsCurrentThread() const; + + private: + void AssertCurrentThreadOwnsMe(const char* aMsg) const; + + void* mThread; +}; + +class nsISerialEventTarget; +class nsAutoOwningEventTarget { + public: + nsAutoOwningEventTarget(); + ~nsAutoOwningEventTarget(); + + // We move the actual assertion checks out-of-line to minimize code bloat, + // but that means we have to pass a non-literal string to MOZ_CRASH_UNSAFE. + // To make that more safe, the public interface requires a literal string + // and passes that to the private interface; we can then be assured that we + // effectively are passing a literal string to MOZ_CRASH_UNSAFE. + template + void AssertOwnership(const char (&aMsg)[N]) const { + AssertCurrentThreadOwnsMe(aMsg); + } + + bool IsCurrentThread() const; + + private: + void AssertCurrentThreadOwnsMe(const char* aMsg) const; + + nsISerialEventTarget* mTarget; +}; + +# define NS_DECL_OWNINGTHREAD nsAutoOwningThread _mOwningThread; +# define NS_DECL_OWNINGEVENTTARGET nsAutoOwningEventTarget _mOwningThread; +# define NS_ASSERT_OWNINGTHREAD(_class) \ + _mOwningThread.AssertOwnership(#_class " not thread-safe") +#else // !MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +# define NS_DECL_OWNINGTHREAD /* nothing */ +# define NS_DECL_OWNINGEVENTTARGET /* nothing */ +# define NS_ASSERT_OWNINGTHREAD(_class) ((void)0) + +#endif // MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +// Macros for reference-count and constructor logging + +#if defined(NS_BUILD_REFCNT_LOGGING) + +# define NS_LOG_ADDREF(_p, _rc, _type, _size) \ + NS_LogAddRef((_p), (_rc), (_type), (uint32_t)(_size)) + +# define NS_LOG_RELEASE(_p, _rc, _type) NS_LogRelease((_p), (_rc), (_type)) + +# define MOZ_ASSERT_CLASSNAME(_type) \ + static_assert(std::is_class_v<_type>, \ + "Token '" #_type "' is not a class type.") + +# define MOZ_ASSERT_NOT_ISUPPORTS(_type) \ + static_assert(!std::is_base_of::value, \ + "nsISupports classes don't need to call MOZ_COUNT_CTOR or " \ + "MOZ_COUNT_DTOR"); + +// Note that the following constructor/destructor logging macros are redundant +// for refcounted objects that log via the NS_LOG_ADDREF/NS_LOG_RELEASE macros. +// Refcount logging is preferred. +# define MOZ_COUNT_CTOR(_type) \ + do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_NOT_ISUPPORTS(_type); \ + NS_LogCtor((void*)this, #_type, sizeof(*this)); \ + } while (0) + +# define MOZ_COUNT_CTOR_INHERITED(_type, _base) \ + do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_CLASSNAME(_base); \ + MOZ_ASSERT_NOT_ISUPPORTS(_type); \ + NS_LogCtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \ + } while (0) + +# define MOZ_LOG_CTOR(_ptr, _name, _size) \ + do { \ + NS_LogCtor((void*)_ptr, _name, _size); \ + } while (0) + +# define MOZ_COUNT_DTOR(_type) \ + do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_NOT_ISUPPORTS(_type); \ + NS_LogDtor((void*)this, #_type, sizeof(*this)); \ + } while (0) + +# define MOZ_COUNT_DTOR_INHERITED(_type, _base) \ + do { \ + MOZ_ASSERT_CLASSNAME(_type); \ + MOZ_ASSERT_CLASSNAME(_base); \ + MOZ_ASSERT_NOT_ISUPPORTS(_type); \ + NS_LogDtor((void*)this, #_type, sizeof(*this) - sizeof(_base)); \ + } while (0) + +# define MOZ_LOG_DTOR(_ptr, _name, _size) \ + do { \ + NS_LogDtor((void*)_ptr, _name, _size); \ + } while (0) + +# define MOZ_COUNTED_DEFAULT_CTOR(_type) \ + _type() { MOZ_COUNT_CTOR(_type); } + +# define MOZ_COUNTED_DTOR_META(_type, _prefix, _postfix) \ + _prefix ~_type() _postfix { MOZ_COUNT_DTOR(_type); } +# define MOZ_COUNTED_DTOR_NESTED(_type, _nestedName) \ + ~_type() { MOZ_COUNT_DTOR(_nestedName); } + +/* nsCOMPtr.h allows these macros to be defined by clients + * These logging functions require dynamic_cast, so they don't + * do anything useful if we don't have dynamic_cast. + * Note: The explicit comparison to nullptr is needed to avoid warnings + * when _p is a nullptr itself. */ +# define NSCAP_LOG_ASSIGNMENT(_c, _p) \ + if (_p != nullptr) NS_LogCOMPtrAddRef((_c), ToSupports(_p)) + +# define NSCAP_LOG_RELEASE(_c, _p) \ + if (_p) NS_LogCOMPtrRelease((_c), ToSupports(_p)) + +#else /* !NS_BUILD_REFCNT_LOGGING */ + +# define NS_LOG_ADDREF(_p, _rc, _type, _size) +# define NS_LOG_RELEASE(_p, _rc, _type) +# define MOZ_COUNT_CTOR(_type) +# define MOZ_COUNT_CTOR_INHERITED(_type, _base) +# define MOZ_LOG_CTOR(_ptr, _name, _size) +# define MOZ_COUNT_DTOR(_type) +# define MOZ_COUNT_DTOR_INHERITED(_type, _base) +# define MOZ_LOG_DTOR(_ptr, _name, _size) +# define MOZ_COUNTED_DEFAULT_CTOR(_type) _type() = default; +# define MOZ_COUNTED_DTOR_META(_type, _prefix, _postfix) \ + _prefix ~_type() _postfix = default; +# define MOZ_COUNTED_DTOR_NESTED(_type, _nestedName) ~_type() = default; + +#endif /* NS_BUILD_REFCNT_LOGGING */ + +#define MOZ_COUNTED_DTOR(_type) MOZ_COUNTED_DTOR_META(_type, , ) +#define MOZ_COUNTED_DTOR_OVERRIDE(_type) \ + MOZ_COUNTED_DTOR_META(_type, , override) +#define MOZ_COUNTED_DTOR_FINAL(_type) MOZ_COUNTED_DTOR_META(_type, , final) +#define MOZ_COUNTED_DTOR_VIRTUAL(_type) MOZ_COUNTED_DTOR_META(_type, virtual, ) + +// Support for ISupports classes which interact with cycle collector. + +#define NS_NUMBER_OF_FLAGS_IN_REFCNT 2 +#define NS_IN_PURPLE_BUFFER (1 << 0) +#define NS_IS_PURPLE (1 << 1) +#define NS_REFCOUNT_CHANGE (1 << NS_NUMBER_OF_FLAGS_IN_REFCNT) +#define NS_REFCOUNT_VALUE(_val) (_val >> NS_NUMBER_OF_FLAGS_IN_REFCNT) + +class nsCycleCollectingAutoRefCnt { + public: + typedef void (*Suspect)(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete); + + nsCycleCollectingAutoRefCnt() : mRefCntAndFlags(0) {} + + explicit nsCycleCollectingAutoRefCnt(uintptr_t aValue) + : mRefCntAndFlags(aValue << NS_NUMBER_OF_FLAGS_IN_REFCNT) {} + + nsCycleCollectingAutoRefCnt(const nsCycleCollectingAutoRefCnt&) = delete; + void operator=(const nsCycleCollectingAutoRefCnt&) = delete; + + template + MOZ_ALWAYS_INLINE uintptr_t incr(nsISupports* aOwner) { + return incr(aOwner, nullptr); + } + + template + MOZ_ALWAYS_INLINE uintptr_t incr(void* aOwner, + nsCycleCollectionParticipant* aCp) { + mRefCntAndFlags += NS_REFCOUNT_CHANGE; + mRefCntAndFlags &= ~NS_IS_PURPLE; + // For incremental cycle collection, use the purple buffer to track objects + // that have been AddRef'd. + if (!IsInPurpleBuffer()) { + mRefCntAndFlags |= NS_IN_PURPLE_BUFFER; + // Refcount isn't zero, so Suspect won't delete anything. + MOZ_ASSERT(get() > 0); + suspect(aOwner, aCp, this, nullptr); + } + return NS_REFCOUNT_VALUE(mRefCntAndFlags); + } + + MOZ_ALWAYS_INLINE void stabilizeForDeletion() { + // Set refcnt to 1 and mark us to be in the purple buffer. + // This way decr won't call suspect again. + mRefCntAndFlags = NS_REFCOUNT_CHANGE | NS_IN_PURPLE_BUFFER; + } + + template + MOZ_ALWAYS_INLINE uintptr_t decr(nsISupports* aOwner, + bool* aShouldDelete = nullptr) { + return decr(aOwner, nullptr, aShouldDelete); + } + + template + MOZ_ALWAYS_INLINE uintptr_t decr(void* aOwner, + nsCycleCollectionParticipant* aCp, + bool* aShouldDelete = nullptr) { + MOZ_ASSERT(get() > 0); + if (!IsInPurpleBuffer()) { + mRefCntAndFlags -= NS_REFCOUNT_CHANGE; + mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE); + uintptr_t retval = NS_REFCOUNT_VALUE(mRefCntAndFlags); + // Suspect may delete 'aOwner' and 'this'! + suspect(aOwner, aCp, this, aShouldDelete); + return retval; + } + mRefCntAndFlags -= NS_REFCOUNT_CHANGE; + mRefCntAndFlags |= (NS_IN_PURPLE_BUFFER | NS_IS_PURPLE); + return NS_REFCOUNT_VALUE(mRefCntAndFlags); + } + + MOZ_ALWAYS_INLINE void RemovePurple() { + MOZ_ASSERT(IsPurple(), "must be purple"); + mRefCntAndFlags &= ~NS_IS_PURPLE; + } + + MOZ_ALWAYS_INLINE void RemoveFromPurpleBuffer() { + MOZ_ASSERT(IsInPurpleBuffer()); + mRefCntAndFlags &= ~(NS_IS_PURPLE | NS_IN_PURPLE_BUFFER); + } + + MOZ_ALWAYS_INLINE bool IsPurple() const { + return !!(mRefCntAndFlags & NS_IS_PURPLE); + } + + MOZ_ALWAYS_INLINE bool IsInPurpleBuffer() const { + return !!(mRefCntAndFlags & NS_IN_PURPLE_BUFFER); + } + + MOZ_ALWAYS_INLINE nsrefcnt get() const { + return NS_REFCOUNT_VALUE(mRefCntAndFlags); + } + + MOZ_ALWAYS_INLINE operator nsrefcnt() const { return get(); } + + private: + uintptr_t mRefCntAndFlags; +}; + +class nsAutoRefCnt { + public: + nsAutoRefCnt() : mValue(0) {} + explicit nsAutoRefCnt(nsrefcnt aValue) : mValue(aValue) {} + + nsAutoRefCnt(const nsAutoRefCnt&) = delete; + void operator=(const nsAutoRefCnt&) = delete; + + // only support prefix increment/decrement + nsrefcnt operator++() { return ++mValue; } + nsrefcnt operator--() { return --mValue; } + + nsrefcnt operator=(nsrefcnt aValue) { return (mValue = aValue); } + operator nsrefcnt() const { return mValue; } + nsrefcnt get() const { return mValue; } + + static const bool isThreadSafe = false; + + private: + nsrefcnt operator++(int) = delete; + nsrefcnt operator--(int) = delete; + nsrefcnt mValue; +}; + +namespace mozilla { +class ThreadSafeAutoRefCnt { + public: + ThreadSafeAutoRefCnt() : mValue(0) {} + explicit ThreadSafeAutoRefCnt(nsrefcnt aValue) : mValue(aValue) {} + + ThreadSafeAutoRefCnt(const ThreadSafeAutoRefCnt&) = delete; + void operator=(const ThreadSafeAutoRefCnt&) = delete; + + // only support prefix increment/decrement + MOZ_ALWAYS_INLINE nsrefcnt operator++() { + // Memory synchronization is not required when incrementing a + // reference count. The first increment of a reference count on a + // thread is not important, since the first use of the object on a + // thread can happen before it. What is important is the transfer + // of the pointer to that thread, which may happen prior to the + // first increment on that thread. The necessary memory + // synchronization is done by the mechanism that transfers the + // pointer between threads. + return mValue.fetch_add(1, std::memory_order_relaxed) + 1; + } + MOZ_ALWAYS_INLINE nsrefcnt operator--() { + // Since this may be the last release on this thread, we need + // release semantics so that prior writes on this thread are visible + // to the thread that destroys the object when it reads mValue with + // acquire semantics. + nsrefcnt result = mValue.fetch_sub(1, std::memory_order_release) - 1; + if (result == 0) { + // We're going to destroy the object on this thread, so we need + // acquire semantics to synchronize with the memory released by + // the last release on other threads, that is, to ensure that + // writes prior to that release are now visible on this thread. +#ifdef MOZ_TSAN + // TSan doesn't understand std::atomic_thread_fence, so in order + // to avoid a false positive for every time a refcounted object + // is deleted, we replace the fence with an atomic operation. + mValue.load(std::memory_order_acquire); +#else + std::atomic_thread_fence(std::memory_order_acquire); +#endif + } + return result; + } + + MOZ_ALWAYS_INLINE nsrefcnt operator=(nsrefcnt aValue) { + // Use release semantics since we're not sure what the caller is + // doing. + mValue.store(aValue, std::memory_order_release); + return aValue; + } + MOZ_ALWAYS_INLINE operator nsrefcnt() const { return get(); } + MOZ_ALWAYS_INLINE nsrefcnt get() const { + // Use acquire semantics since we're not sure what the caller is + // doing. + return mValue.load(std::memory_order_acquire); + } + + static const bool isThreadSafe = true; + + private: + nsrefcnt operator++(int) = delete; + nsrefcnt operator--(int) = delete; + std::atomic mValue; +}; + +} // namespace mozilla + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Declare the reference count variable and the implementations of the + * AddRef and QueryInterface methods. + */ + +#define NS_DECL_ISUPPORTS \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +#define NS_DECL_ISUPPORTS_ONEVENTTARGET \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGEVENTTARGET \ + public: + +#define NS_DECL_THREADSAFE_ISUPPORTS \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; \ + using HasThreadSafeRefCnt = std::true_type; \ + \ + protected: \ + ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS \ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(override) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void); \ + \ + public: + +#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL \ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(final) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void); \ + \ + public: + +#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL_DELETECYCLECOLLECTABLE \ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(override) \ + NS_IMETHOD_(void) DeleteCycleCollectable(void) final; \ + \ + public: + +#define NS_DECL_CYCLE_COLLECTING_ISUPPORTS_META(...) \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) __VA_ARGS__; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) __VA_ARGS__; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) __VA_ARGS__; \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsCycleCollectingAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +/////////////////////////////////////////////////////////////////////////////// + +/* + * Implementation of AddRef and Release for non-nsISupports (ie "native") + * cycle-collected classes that use the purple buffer to avoid leaks. + */ + +#define NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = \ + mRefCnt.incr(static_cast(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; + +#define NS_IMPL_CC_MAIN_THREAD_ONLY_NATIVE_ADDREF_BODY(_class) \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = mRefCnt.incr( \ + static_cast(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; + +#define NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = \ + mRefCnt.decr(static_cast(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; + +#define NS_IMPL_CC_MAIN_THREAD_ONLY_NATIVE_RELEASE_BODY(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = mRefCnt.decr( \ + static_cast(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; + +#define NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(_class) \ + NS_METHOD_(MozExternalRefCountType) _class::AddRef(void) { \ + NS_IMPL_CC_NATIVE_ADDREF_BODY(_class) \ + } + +#define NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE_WITH_LAST_RELEASE(_class, \ + _last) \ + NS_METHOD_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsrefcnt count = \ + mRefCnt.decr(static_cast(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant(), \ + &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(static_cast(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + _last; \ + mRefCnt.decr(static_cast(this), \ + _class::NS_CYCLE_COLLECTION_INNERCLASS::GetParticipant()); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } + +#define NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(_class) \ + NS_METHOD_(MozExternalRefCountType) _class::Release(void) { \ + NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ + } + +#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(_class) \ + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, NS_METHOD_) + +#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_VIRTUAL(_class) \ + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, NS_IMETHOD_) + +#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_INHERITED(_class) \ + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, NS_METHOD_, \ + override) + +#define NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING_META(_class, _decl, \ + ...) \ + public: \ + _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__{ \ + NS_IMPL_CC_NATIVE_ADDREF_BODY(_class)} _decl(MozExternalRefCountType) \ + Release(void) __VA_ARGS__ { \ + NS_IMPL_CC_NATIVE_RELEASE_BODY(_class) \ + } \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsCycleCollectingAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +#define NS_INLINE_DECL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_NATIVE_REFCOUNTING( \ + _class) \ + public: \ + NS_METHOD_(MozExternalRefCountType) \ + AddRef(void){NS_IMPL_CC_MAIN_THREAD_ONLY_NATIVE_ADDREF_BODY( \ + _class)} NS_METHOD_(MozExternalRefCountType) Release(void) { \ + NS_IMPL_CC_MAIN_THREAD_ONLY_NATIVE_RELEASE_BODY(_class) \ + } \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsCycleCollectingAutoRefCnt mRefCnt; \ + NS_DECL_OWNINGTHREAD \ + public: + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM _class. + * + * @param _class The name of the class implementing the method + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * @param _decl Name of the macro to be used for the return type of the + * AddRef & Release methods (typically NS_IMETHOD_ or NS_METHOD_). + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_REFCOUNTING_META(_class, _decl, _destroy, _owning, ...) \ + public: \ + _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__ { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + ++mRefCnt; \ + NS_LOG_ADDREF(this, mRefCnt, #_class, sizeof(*this)); \ + return mRefCnt; \ + } \ + _decl(MozExternalRefCountType) Release(void) __VA_ARGS__ { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + --mRefCnt; \ + NS_LOG_RELEASE(this, mRefCnt, #_class); \ + if (mRefCnt == 0) { \ + mRefCnt = 1; /* stabilize */ \ + _destroy; \ + return 0; \ + } \ + return mRefCnt; \ + } \ + using HasThreadSafeRefCnt = std::false_type; \ + \ + protected: \ + nsAutoRefCnt mRefCnt; \ + _owning public: + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM _class. + * + * @param _class The name of the class implementing the method + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \ + NS_INLINE_DECL_REFCOUNTING_META(_class, NS_METHOD_, _destroy, \ + NS_DECL_OWNINGTHREAD, __VA_ARGS__) + +/** + * Like NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY with AddRef & Release declared + * virtual. + */ +#define NS_INLINE_DECL_VIRTUAL_REFCOUNTING_WITH_DESTROY(_class, _destroy, ...) \ + NS_INLINE_DECL_REFCOUNTING_META(_class, NS_IMETHOD_, _destroy, \ + NS_DECL_OWNINGTHREAD, __VA_ARGS__) + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM _class. + * + * @param _class The name of the class implementing the method + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_REFCOUNTING(_class, ...) \ + NS_INLINE_DECL_REFCOUNTING_WITH_DESTROY(_class, delete (this), __VA_ARGS__) + +/** + * Like NS_INLINE_DECL_REFCOUNTING, however the thread safety check will work + * with any nsISerialEventTarget. This is a workaround until bug 1648031 is + * properly resolved. Once this is done, it will be possible to use + * NS_INLINE_DECL_REFCOUNTING under all circumstances. + */ +#define NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(_class, ...) \ + NS_INLINE_DECL_REFCOUNTING_META(_class, NS_METHOD_, delete (this), \ + NS_DECL_OWNINGEVENTTARGET, __VA_ARGS__) + +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, _decl, _destroy, \ + ...) \ + public: \ + _decl(MozExternalRefCountType) AddRef(void) __VA_ARGS__ { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + nsrefcnt count = ++mRefCnt; \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return (nsrefcnt)count; \ + } \ + _decl(MozExternalRefCountType) Release(void) __VA_ARGS__ { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + nsrefcnt count = --mRefCnt; \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + _destroy; \ + return 0; \ + } \ + return count; \ + } \ + using HasThreadSafeRefCnt = std::true_type; \ + \ + protected: \ + ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ + \ + public: + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM _class in a threadsafe manner. + * + * DOES NOT DO REFCOUNT STABILIZATION! + * + * @param _class The name of the class implementing the method + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(_class, _destroy, \ + ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, NS_METHOD_, _destroy, \ + __VA_ARGS__) + +/** + * Like NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY with AddRef & Release + * declared virtual. + */ +#define NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING_WITH_DESTROY( \ + _class, _destroy, ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_META(_class, NS_IMETHOD_, _destroy, \ + __VA_ARGS__) + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM _class in a threadsafe manner. + * + * DOES NOT DO REFCOUNT STABILIZATION! + * + * @param _class The name of the class implementing the method + * @param optional override Mark the AddRef & Release methods as overrides. + */ +#define NS_INLINE_DECL_THREADSAFE_REFCOUNTING(_class, ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(_class, delete (this), \ + __VA_ARGS__) + +/** + * Like NS_INLINE_DECL_THREADSAFE_REFCOUNTING with AddRef & Release declared + * virtual. + */ +#define NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(_class, ...) \ + NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING_WITH_DESTROY( \ + _class, delete (this), __VA_ARGS__) + +#if !defined(XPCOM_GLUE_AVOID_NSPR) +class nsISerialEventTarget; +namespace mozilla { +// Forward-declare `GetMainThreadSerialEventTarget`, as `nsISupportsImpl.h` +// cannot include `nsThreadUtils.h`. +nsISerialEventTarget* GetMainThreadSerialEventTarget(); + +namespace detail { +using DeleteVoidFunction = void(void*); +void ProxyDeleteVoid(const char* aRunnableName, + nsISerialEventTarget* aEventTarget, void* aSelf, + DeleteVoidFunction* aDeleteFunc); +} // namespace detail +} // namespace mozilla + +/** + * Helper for _WITH_DELETE_ON_EVENT_TARGET threadsafe refcounting macros which + * provides an implementation of `_destroy` + */ +# define NS_PROXY_DELETE_TO_EVENT_TARGET(_class, _target) \ + ::mozilla::detail::ProxyDeleteVoid( \ + "ProxyDelete " #_class, _target, this, \ + [](void* self) { delete static_cast<_class*>(self); }) + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM _class in a threadsafe manner, ensuring the + * destructor runs on a specific nsISerialEventTarget. + * + * DOES NOT DO REFCOUNT STABILIZATION! + * + * @param _class The name of the class implementing the method + * @param _target nsISerialEventTarget to run the class's destructor on + * @param optional override Mark the AddRef & Release methods as overrides + */ +# define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET( \ + _class, _target, ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY( \ + _class, NS_PROXY_DELETE_TO_EVENT_TARGET(_class, _target), __VA_ARGS__) + +/** + * Use this macro to declare and implement the AddRef & Release methods for a + * given non-XPCOM _class in a threadsafe manner, ensuring the + * destructor runs on the main thread. + * + * DOES NOT DO REFCOUNT STABILIZATION! + * + * @param _class The name of the class implementing the method + * @param optional override Mark the AddRef & Release methods as overrides + */ +# define NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_MAIN_THREAD( \ + _class, ...) \ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET( \ + _class, ::mozilla::GetMainThreadSerialEventTarget(), __VA_ARGS__) +#endif + +/** + * Use this macro in interface classes that you want to be able to reference + * using RefPtr, but don't want to provide a refcounting implemenation. The + * refcounting implementation can be provided by concrete subclasses that + * implement the interface. + */ +#define NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING \ + public: \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; \ + \ + public: + +/** + * Use this macro to implement the AddRef method for a given _class + * @param _class The name of the class implementing the method + * @param _name The class name to be passed to XPCOM leak checking + */ +#define NS_IMPL_NAMED_ADDREF(_class, _name) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + MOZ_ASSERT(_name != nullptr, "Must specify a name"); \ + if (!mRefCnt.isThreadSafe) NS_ASSERT_OWNINGTHREAD(_class); \ + nsrefcnt count = ++mRefCnt; \ + NS_LOG_ADDREF(this, count, _name, sizeof(*this)); \ + return count; \ + } + +/** + * Use this macro to implement the AddRef method for a given _class + * @param _class The name of the class implementing the method + */ +#define NS_IMPL_ADDREF(_class) NS_IMPL_NAMED_ADDREF(_class, #_class) + +/** + * Use this macro to implement the AddRef method for a given _class + * implemented as a wholly owned aggregated object intended to implement + * interface(s) for its owner + * @param _class The name of the class implementing the method + * @param _aggregator the owning/containing object + */ +#define NS_IMPL_ADDREF_USING_AGGREGATOR(_class, _aggregator) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(_aggregator, "null aggregator"); \ + return (_aggregator)->AddRef(); \ + } + +// We decrement the refcnt before logging the actual release, but when logging +// named things, accessing the name may not be valid after the refcnt +// decrement, because the object may have been destroyed on a different thread. +// Use this macro to ensure that we have a local copy of the name prior to +// the refcnt decrement. (We use a macro to make absolutely sure the name +// isn't loaded in builds where it wouldn't be used.) +#ifdef NS_BUILD_REFCNT_LOGGING +# define NS_LOAD_NAME_BEFORE_RELEASE(localname, _name) \ + const char* const localname = _name +#else +# define NS_LOAD_NAME_BEFORE_RELEASE(localname, _name) +#endif + +/** + * Use this macro to implement the Release method for a given + * _class. + * @param _class The name of the class implementing the method + * @param _name The class name to be passed to XPCOM leak checking + * @param _destroy A statement that is executed when the object's + * refcount drops to zero. + * + * For example, + * + * NS_IMPL_RELEASE_WITH_DESTROY(Foo, "Foo", Destroy(this)) + * + * will cause + * + * Destroy(this); + * + * to be invoked when the object's refcount drops to zero. This + * allows for arbitrary teardown activity to occur (e.g., deallocation + * of object allocated with placement new). + */ +#define NS_IMPL_NAMED_RELEASE_WITH_DESTROY(_class, _name, _destroy) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + MOZ_ASSERT(_name != nullptr, "Must specify a name"); \ + if (!mRefCnt.isThreadSafe) NS_ASSERT_OWNINGTHREAD(_class); \ + NS_LOAD_NAME_BEFORE_RELEASE(nametmp, _name); \ + nsrefcnt count = --mRefCnt; \ + NS_LOG_RELEASE(this, count, nametmp); \ + if (count == 0) { \ + mRefCnt = 1; /* stabilize */ \ + _destroy; \ + return 0; \ + } \ + return count; \ + } + +#define NS_IMPL_RELEASE_WITH_DESTROY(_class, _destroy) \ + NS_IMPL_NAMED_RELEASE_WITH_DESTROY(_class, #_class, _destroy) + +/** + * Use this macro to implement the Release method for a given _class + * @param _class The name of the class implementing the method + * + * A note on the 'stabilization' of the refcnt to one. At that point, + * the object's refcount will have gone to zero. The object's + * destructor may trigger code that attempts to QueryInterface() and + * Release() 'this' again. Doing so will temporarily increment and + * decrement the refcount. (Only a logic error would make one try to + * keep a permanent hold on 'this'.) To prevent re-entering the + * destructor, we make sure that no balanced refcounting can return + * the refcount to |0|. + */ +#define NS_IMPL_RELEASE(_class) \ + NS_IMPL_RELEASE_WITH_DESTROY(_class, delete (this)) + +#define NS_IMPL_NAMED_RELEASE(_class, _name) \ + NS_IMPL_NAMED_RELEASE_WITH_DESTROY(_class, _name, delete (this)) + +/** + * Use this macro to implement the Release method for a given _class + * implemented as a wholly owned aggregated object intended to implement + * interface(s) for its owner + * @param _class The name of the class implementing the method + * @param _aggregator the owning/containing object + */ +#define NS_IMPL_RELEASE_USING_AGGREGATOR(_class, _aggregator) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(_aggregator, "null aggregator"); \ + return (_aggregator)->Release(); \ + } + +#define NS_IMPL_CYCLE_COLLECTING_ADDREF(_class) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.incr(base); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; \ + } + +#define NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_ADDREF(_class) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::AddRef(void) { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.incr(base); \ + NS_LOG_ADDREF(this, count, #_class, sizeof(*this)); \ + return count; \ + } + +#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(_class, _destroy) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { _destroy; } + +#define NS_IMPL_CYCLE_COLLECTING_RELEASE(_class) \ + NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(_class, delete (this)) + +// _LAST_RELEASE can be useful when certain resources should be released +// as soon as we know the object will be deleted. +#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(_class, _last) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base, &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(base); \ + _last; \ + mRefCnt.decr(base); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { delete this; } + +// This macro is same as NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE +// except it doesn't have DeleteCycleCollectable. +#define NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE_AND_DESTROY( \ + _class, _last, _destroy) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base, &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(base); \ + _last; \ + mRefCnt.decr(base); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { _destroy; } + +#define NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE(_class) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr(base); \ + NS_LOG_RELEASE(this, count, #_class); \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { delete this; } + +// _LAST_RELEASE can be useful when certain resources should be released +// as soon as we know the object will be deleted. +#define NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE( \ + _class, _last) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr( \ + base, &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(base); \ + _last; \ + mRefCnt.decr(base); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { delete this; } + +// _WITH_INTERRUPTABLE_LAST_RELEASE can be useful when certain resources +// should be released as soon as we know the object will be deleted and the +// instance may be cached for reuse. +// _last is performed for cleaning up its resources. Then, _maybeInterrupt is +// tested and when it returns true, this stops deleting the instance. +// (Note that it's not allowed to grab the instance with nsCOMPtr or RefPtr +// during _last is performed.) +// Therefore, when _maybeInterrupt returns true, the instance has to be grabbed +// by nsCOMPtr or RefPtr. +#define NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_INTERRUPTABLE_LAST_RELEASE( \ + _class, _last, _maybeInterrupt) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr( \ + base, &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(base); \ + _last; \ + mRefCnt.decr(base); \ + if (_maybeInterrupt) { \ + MOZ_ASSERT(mRefCnt.get() > 0); \ + return mRefCnt.get(); \ + } \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { delete this; } + +// _LAST_RELEASE can be useful when certain resources should be released +// as soon as we know the object will be deleted. +#define NS_IMPL_MAIN_THREAD_ONLY_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE_AND_DESTROY( \ + _class, _last, _destroy) \ + NS_IMETHODIMP_(MozExternalRefCountType) _class::Release(void) { \ + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + NS_ASSERT_OWNINGTHREAD(_class); \ + bool shouldDelete = false; \ + nsISupports* base = NS_CYCLE_COLLECTION_CLASSNAME(_class)::Upcast(this); \ + nsrefcnt count = mRefCnt.decr( \ + base, &shouldDelete); \ + NS_LOG_RELEASE(this, count, #_class); \ + if (count == 0) { \ + mRefCnt.incr(base); \ + _last; \ + mRefCnt.decr(base); \ + if (shouldDelete) { \ + mRefCnt.stabilizeForDeletion(); \ + DeleteCycleCollectable(); \ + } \ + } \ + return count; \ + } \ + NS_IMETHODIMP_(void) _class::DeleteCycleCollectable(void) { _destroy; } + +/////////////////////////////////////////////////////////////////////////////// + +/** + * There are two ways of implementing QueryInterface, and we use both: + * + * Table-driven QueryInterface uses a static table of IID->offset mappings + * and a shared helper function. Using it tends to reduce codesize and improve + * runtime performance (due to processor cache hits). + * + * Macro-driven QueryInterface generates a QueryInterface function directly + * using common macros. This is necessary if special QueryInterface features + * are being used (such as tearoffs and conditional interfaces). + * + * These methods can be combined into a table-driven function call followed + * by custom code for tearoffs and conditionals. + */ + +struct QITableEntry { + const nsIID* iid; // null indicates end of the QITableEntry array + int32_t offset; +}; + +nsresult NS_FASTCALL NS_TableDrivenQI(void* aThis, REFNSIID aIID, + void** aInstancePtr, + const QITableEntry* aEntries); + +/** + * Implement table-driven queryinterface + */ + +#define NS_INTERFACE_TABLE_HEAD(_class) \ + NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) { \ + NS_ASSERTION(aInstancePtr, \ + "QueryInterface requires a non-NULL destination!"); \ + nsresult rv = NS_ERROR_FAILURE; + +#define NS_INTERFACE_TABLE_BEGIN static const QITableEntry table[] = { +#define NS_INTERFACE_TABLE_ENTRY(_class, _interface) \ + {&NS_GET_IID(_interface), \ + int32_t( \ + reinterpret_cast(static_cast<_interface*>((_class*)0x1000)) - \ + reinterpret_cast((_class*)0x1000))}, + +#define NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(_class, _interface, _implClass) \ + {&NS_GET_IID(_interface), \ + int32_t(reinterpret_cast(static_cast<_interface*>( \ + static_cast<_implClass*>((_class*)0x1000))) - \ + reinterpret_cast((_class*)0x1000))}, + +/* + * XXX: we want to use mozilla::ArrayLength (or equivalent, + * MOZ_ARRAY_LENGTH) in this condition, but some versions of GCC don't + * see that the static_assert condition is actually constant in those + * cases, even with constexpr support (?). + */ +#define NS_INTERFACE_TABLE_END_WITH_PTR(_ptr) \ + { nullptr, 0 } \ + } \ + ; \ + static_assert((sizeof(table) / sizeof(table[0])) > 1, \ + "need at least 1 interface"); \ + rv = NS_TableDrivenQI(static_cast(_ptr), aIID, aInstancePtr, table); + +#define NS_INTERFACE_TABLE_END \ + NS_INTERFACE_TABLE_END_WITH_PTR \ + (this) + +#define NS_INTERFACE_TABLE_TAIL \ + return rv; \ + } + +#define NS_INTERFACE_TABLE_TAIL_INHERITING(_baseclass) \ + if (NS_SUCCEEDED(rv)) return rv; \ + return _baseclass::QueryInterface(aIID, aInstancePtr); \ + } + +#define NS_INTERFACE_TABLE_TAIL_USING_AGGREGATOR(_aggregator) \ + if (NS_SUCCEEDED(rv)) return rv; \ + NS_ASSERTION(_aggregator, "null aggregator"); \ + return _aggregator->QueryInterface(aIID, aInstancePtr) \ + } + +/** + * This implements query interface with two assumptions: First, the + * class in question implements nsISupports and its own interface and + * nothing else. Second, the implementation of the class's primary + * inheritance chain leads to its own interface. + * + * @param _class The name of the class implementing the method + * @param _classiiddef The name of the #define symbol that defines the IID + * for the class (e.g. NS_ISUPPORTS_IID) + */ + +#define NS_IMPL_QUERY_HEAD(_class) \ + NS_IMETHODIMP _class::QueryInterface(REFNSIID aIID, void** aInstancePtr) { \ + NS_ASSERTION(aInstancePtr, \ + "QueryInterface requires a non-NULL destination!"); \ + nsISupports* foundInterface; + +#define NS_IMPL_QUERY_BODY(_interface) \ + if (aIID.Equals(NS_GET_IID(_interface))) \ + foundInterface = static_cast<_interface*>(this); \ + else + +#define NS_IMPL_QUERY_BODY_CONDITIONAL(_interface, condition) \ + if ((condition) && aIID.Equals(NS_GET_IID(_interface))) \ + foundInterface = static_cast<_interface*>(this); \ + else + +#define NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass) \ + if (aIID.Equals(NS_GET_IID(_interface))) \ + foundInterface = static_cast<_interface*>(static_cast<_implClass*>(this)); \ + else + +// Use this for querying to concrete class types which cannot be unambiguously +// cast to nsISupports. See also nsQueryObject.h. +#define NS_IMPL_QUERY_BODY_CONCRETE(_class) \ + if (aIID.Equals(NS_GET_IID(_class))) { \ + *aInstancePtr = do_AddRef(static_cast<_class*>(this)).take(); \ + return NS_OK; \ + } else + +#define NS_IMPL_QUERY_BODY_AGGREGATED(_interface, _aggregate) \ + if (aIID.Equals(NS_GET_IID(_interface))) \ + foundInterface = static_cast<_interface*>(_aggregate); \ + else + +#define NS_IMPL_QUERY_TAIL_GUTS \ + foundInterface = 0; \ + nsresult status; \ + if (!foundInterface) { \ + /* nsISupports should be handled by this point. If not, fail. */ \ + MOZ_ASSERT(!aIID.Equals(NS_GET_IID(nsISupports))); \ + status = NS_NOINTERFACE; \ + } else { \ + NS_ADDREF(foundInterface); \ + status = NS_OK; \ + } \ + *aInstancePtr = foundInterface; \ + return status; \ + } + +#define NS_IMPL_QUERY_TAIL_INHERITING(_baseclass) \ + foundInterface = 0; \ + nsresult status; \ + if (!foundInterface) \ + status = _baseclass::QueryInterface(aIID, (void**)&foundInterface); \ + else { \ + NS_ADDREF(foundInterface); \ + status = NS_OK; \ + } \ + *aInstancePtr = foundInterface; \ + return status; \ + } + +#define NS_IMPL_QUERY_TAIL_USING_AGGREGATOR(_aggregator) \ + foundInterface = 0; \ + nsresult status; \ + if (!foundInterface) { \ + NS_ASSERTION(_aggregator, "null aggregator"); \ + status = _aggregator->QueryInterface(aIID, (void**)&foundInterface); \ + } else { \ + NS_ADDREF(foundInterface); \ + status = NS_OK; \ + } \ + *aInstancePtr = foundInterface; \ + return status; \ + } + +#define NS_IMPL_QUERY_TAIL(_supports_interface) \ + NS_IMPL_QUERY_BODY_AMBIGUOUS(nsISupports, _supports_interface) \ + NS_IMPL_QUERY_TAIL_GUTS + +/* + This is the new scheme. Using this notation now will allow us to switch to + a table driven mechanism when it's ready. Note the difference between this + and the (currently) underlying NS_IMPL_QUERY_INTERFACE mechanism. You must + explicitly mention |nsISupports| when using the interface maps. +*/ +#define NS_INTERFACE_MAP_BEGIN(_implClass) NS_IMPL_QUERY_HEAD(_implClass) +#define NS_INTERFACE_MAP_ENTRY(_interface) NS_IMPL_QUERY_BODY(_interface) +#define NS_INTERFACE_MAP_ENTRY_CONDITIONAL(_interface, condition) \ + NS_IMPL_QUERY_BODY_CONDITIONAL(_interface, condition) +#define NS_INTERFACE_MAP_ENTRY_AGGREGATED(_interface, _aggregate) \ + NS_IMPL_QUERY_BODY_AGGREGATED(_interface, _aggregate) + +#define NS_INTERFACE_MAP_END NS_IMPL_QUERY_TAIL_GUTS +#define NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(_interface, _implClass) \ + NS_IMPL_QUERY_BODY_AMBIGUOUS(_interface, _implClass) +#define NS_INTERFACE_MAP_ENTRY_CONCRETE(_class) \ + NS_IMPL_QUERY_BODY_CONCRETE(_class) +#define NS_INTERFACE_MAP_END_INHERITING(_baseClass) \ + NS_IMPL_QUERY_TAIL_INHERITING(_baseClass) +#define NS_INTERFACE_MAP_END_AGGREGATED(_aggregator) \ + NS_IMPL_QUERY_TAIL_USING_AGGREGATOR(_aggregator) + +#define NS_INTERFACE_TABLE0(_class) \ + NS_INTERFACE_TABLE_BEGIN \ + NS_INTERFACE_TABLE_ENTRY(_class, nsISupports) \ + NS_INTERFACE_TABLE_END + +#define NS_INTERFACE_TABLE(aClass, ...) \ + static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \ + "Need more arguments to NS_INTERFACE_TABLE"); \ + NS_INTERFACE_TABLE_BEGIN \ + MOZ_FOR_EACH(NS_INTERFACE_TABLE_ENTRY, (aClass, ), (__VA_ARGS__)) \ + NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(aClass, nsISupports, \ + MOZ_ARG_1(__VA_ARGS__)) \ + NS_INTERFACE_TABLE_END + +#define NS_IMPL_QUERY_INTERFACE0(_class) \ + NS_INTERFACE_TABLE_HEAD(_class) \ + NS_INTERFACE_TABLE0(_class) \ + NS_INTERFACE_TABLE_TAIL + +#define NS_IMPL_QUERY_INTERFACE(aClass, ...) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE(aClass, __VA_ARGS__) \ + NS_INTERFACE_TABLE_TAIL + +/** + * Declare that you're going to inherit from something that already + * implements nsISupports, but also implements an additional interface, thus + * causing an ambiguity. In this case you don't need another mRefCnt, you + * just need to forward the definitions to the appropriate superclass. E.g. + * + * class Bar : public Foo, public nsIBar { // both provide nsISupports + * public: + * NS_DECL_ISUPPORTS_INHERITED + * ...other nsIBar and Bar methods... + * }; + */ +#define NS_DECL_ISUPPORTS_INHERITED \ + public: \ + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; \ + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; \ + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; + +/** + * These macros can be used in conjunction with NS_DECL_ISUPPORTS_INHERITED + * to implement the nsISupports methods, forwarding the invocations to a + * superclass that already implements nsISupports. Don't do anything for + * subclasses of Runnable because it deals with subclass logging in its own + * way, using the mName field. + * + * Note that I didn't make these inlined because they're virtual methods. + */ + +namespace mozilla { +class Runnable; +namespace detail { +class SupportsThreadSafeWeakPtrBase; + +// Don't NS_LOG_{ADDREF,RELEASE} when inheriting from `Runnable*` or types with +// thread safe weak references, as it will generate incorrect refcnt logs due to +// the thread-safe `Upgrade()` call's refcount modifications not calling through +// the derived class' `AddRef()` and `Release()` methods. +template +constexpr bool ShouldLogInheritedRefcnt = + !std::is_convertible_v && + !std::is_base_of_v; +} +} // namespace mozilla + +#define NS_IMPL_ADDREF_INHERITED_GUTS(Class, Super) \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(Class) \ + nsrefcnt r = Super::AddRef(); \ + if constexpr (::mozilla::detail::ShouldLogInheritedRefcnt) { \ + NS_LOG_ADDREF(this, r, #Class, sizeof(*this)); \ + } \ + return r /* Purposefully no trailing semicolon */ + +#define NS_IMPL_ADDREF_INHERITED(Class, Super) \ + NS_IMETHODIMP_(MozExternalRefCountType) Class::AddRef(void) { \ + NS_IMPL_ADDREF_INHERITED_GUTS(Class, Super); \ + } + +#define NS_IMPL_RELEASE_INHERITED_GUTS(Class, Super) \ + nsrefcnt r = Super::Release(); \ + if constexpr (::mozilla::detail::ShouldLogInheritedRefcnt) { \ + NS_LOG_RELEASE(this, r, #Class); \ + } \ + return r /* Purposefully no trailing semicolon */ + +#define NS_IMPL_RELEASE_INHERITED(Class, Super) \ + NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) { \ + NS_IMPL_RELEASE_INHERITED_GUTS(Class, Super); \ + } + +/** + * As above but not logging the addref/release; needed if the base + * class might be aggregated. + */ +#define NS_IMPL_NONLOGGING_ADDREF_INHERITED(Class, Super) \ + NS_IMETHODIMP_(MozExternalRefCountType) Class::AddRef(void) { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(Class) \ + return Super::AddRef(); \ + } + +#define NS_IMPL_NONLOGGING_RELEASE_INHERITED(Class, Super) \ + NS_IMETHODIMP_(MozExternalRefCountType) Class::Release(void) { \ + return Super::Release(); \ + } + +#define NS_INTERFACE_TABLE_INHERITED0(Class) /* Nothing to do here */ + +#define NS_INTERFACE_TABLE_INHERITED(aClass, ...) \ + static_assert(MOZ_ARG_COUNT(__VA_ARGS__) > 0, \ + "Need more arguments to NS_INTERFACE_TABLE_INHERITED"); \ + NS_INTERFACE_TABLE_BEGIN \ + MOZ_FOR_EACH(NS_INTERFACE_TABLE_ENTRY, (aClass, ), (__VA_ARGS__)) \ + NS_INTERFACE_TABLE_END + +#define NS_IMPL_QUERY_INTERFACE_INHERITED(aClass, aSuper, ...) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE_INHERITED(aClass, __VA_ARGS__) \ + NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) + +/** + * Convenience macros for implementing all nsISupports methods for + * a simple class. + * @param _class The name of the class implementing the method + * @param _classiiddef The name of the #define symbol that defines the IID + * for the class (e.g. NS_ISUPPORTS_IID) + */ + +#define NS_IMPL_ISUPPORTS0(_class) \ + NS_IMPL_ADDREF(_class) \ + NS_IMPL_RELEASE(_class) \ + NS_IMPL_QUERY_INTERFACE0(_class) + +#define NS_IMPL_ISUPPORTS(aClass, ...) \ + NS_IMPL_ADDREF(aClass) \ + NS_IMPL_RELEASE(aClass) \ + NS_IMPL_QUERY_INTERFACE(aClass, __VA_ARGS__) + +// When possible, prefer NS_INLINE_DECL_REFCOUNTING_INHERITED to +// NS_IMPL_ISUPPORTS_INHERITED0. +#define NS_IMPL_ISUPPORTS_INHERITED0(aClass, aSuper) \ + NS_INTERFACE_TABLE_HEAD(aClass) \ + NS_INTERFACE_TABLE_TAIL_INHERITING(aSuper) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) + +#define NS_IMPL_ISUPPORTS_INHERITED(aClass, aSuper, ...) \ + NS_IMPL_QUERY_INTERFACE_INHERITED(aClass, aSuper, __VA_ARGS__) \ + NS_IMPL_ADDREF_INHERITED(aClass, aSuper) \ + NS_IMPL_RELEASE_INHERITED(aClass, aSuper) + +/** + * A macro to declare and implement inherited addref/release for a class which + * doesn't have or need to override QueryInterface from its base class. + * + * Note: This macro always overrides the `AddRef` and `Release` methods, + * including when refcount logging is disabled, meaning that it will implement + * the `AddRef` or `Release` method from another virtual base class. + */ +#define NS_INLINE_DECL_REFCOUNTING_INHERITED(Class, Super) \ + NS_IMETHOD_(MozExternalRefCountType) AddRef() override { \ + NS_IMPL_ADDREF_INHERITED_GUTS(Class, Super); \ + } \ + NS_IMETHOD_(MozExternalRefCountType) Release() override { \ + NS_IMPL_RELEASE_INHERITED_GUTS(Class, Super); \ + } + +/* + * Macro to glue together a QI that starts with an interface table + * and segues into an interface map (e.g. it uses singleton classinfo + * or tearoffs). + */ +#define NS_INTERFACE_TABLE_TO_MAP_SEGUE \ + if (rv == NS_OK) return rv; \ + nsISupports* foundInterface; + +/////////////////////////////////////////////////////////////////////////////// + +/** + * Macro to generate nsIClassInfo methods for classes which do not have + * corresponding nsIFactory implementations. + */ +#define NS_IMPL_THREADSAFE_CI(_class) \ + NS_IMETHODIMP \ + _class::GetInterfaces(nsTArray& _array) { \ + return NS_CI_INTERFACE_GETTER_NAME(_class)(_array); \ + } \ + \ + NS_IMETHODIMP \ + _class::GetScriptableHelper(nsIXPCScriptable** _retval) { \ + *_retval = nullptr; \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetContractID(nsACString& _contractID) { \ + _contractID.SetIsVoid(true); \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetClassDescription(nsACString& _classDescription) { \ + _classDescription.SetIsVoid(true); \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetClassID(nsCID** _classID) { \ + *_classID = nullptr; \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetFlags(uint32_t* _flags) { \ + *_flags = nsIClassInfo::THREADSAFE; \ + return NS_OK; \ + } \ + \ + NS_IMETHODIMP \ + _class::GetClassIDNoAlloc(nsCID* _classIDNoAlloc) { \ + return NS_ERROR_NOT_AVAILABLE; \ + } + +#endif diff --git a/xpcom/base/nsISupportsUtils.h b/xpcom/base/nsISupportsUtils.h new file mode 100644 index 0000000000..a885c5cb14 --- /dev/null +++ b/xpcom/base/nsISupportsUtils.h @@ -0,0 +1,142 @@ +/* -*- 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/. */ + +#ifndef nsISupportsUtils_h__ +#define nsISupportsUtils_h__ + +#include + +#include "nscore.h" +#include "nsIOutputStream.h" +#include "nsISupports.h" +#include "nsError.h" +#include "nsDebug.h" +#include "nsISupportsImpl.h" +#include "mozilla/RefPtr.h" + +/** + * Macro for adding a reference to an interface. + * @param _ptr The interface pointer. + */ +#define NS_ADDREF(_ptr) (_ptr)->AddRef() + +/** + * Macro for adding a reference to this. This macro should be used + * because NS_ADDREF (when tracing) may require an ambiguous cast + * from the pointers primary type to nsISupports. This macro sidesteps + * that entire problem. + */ +#define NS_ADDREF_THIS() AddRef() + +// Making this a |inline| |template| allows |aExpr| to be evaluated only once, +// yet still denies you the ability to |AddRef()| an |nsCOMPtr|. +template +inline void ns_if_addref(T aExpr) { + if (aExpr) { + aExpr->AddRef(); + } +} + +/** + * Macro for adding a reference to an interface that checks for nullptr. + * @param _expr The interface pointer. + */ +#define NS_IF_ADDREF(_expr) ns_if_addref(_expr) + +/* + * Given these declarations, it explicitly OK and efficient to end a `getter' + * with: + * + * NS_IF_ADDREF(*result = mThing); + * + * even if |mThing| is an |nsCOMPtr|. If |mThing| is an |nsCOMPtr|, however, it + * is still _illegal_ to say |NS_IF_ADDREF(mThing)|. + */ + +/** + * Macro for releasing a reference to an interface. + * @param _ptr The interface pointer. + */ +#define NS_RELEASE(_ptr) \ + do { \ + (_ptr)->Release(); \ + (_ptr) = 0; \ + } while (0) + +/** + * Macro for releasing a reference to this interface. + */ +#define NS_RELEASE_THIS() Release() + +/** + * Macro for releasing a reference to an interface, except that this + * macro preserves the return value from the underlying Release call. + * The interface pointer argument will only be NULLed if the reference count + * goes to zero. + * + * @param _ptr The interface pointer. + * @param _rc The reference count. + */ +#define NS_RELEASE2(_ptr, _rc) \ + do { \ + _rc = (_ptr)->Release(); \ + if (0 == (_rc)) (_ptr) = 0; \ + } while (0) + +/** + * Macro for releasing a reference to an interface that checks for nullptr; + * @param _ptr The interface pointer. + */ +#define NS_IF_RELEASE(_ptr) \ + do { \ + if (_ptr) { \ + (_ptr)->Release(); \ + (_ptr) = 0; \ + } \ + } while (0) + +/* + * Often you have to cast an implementation pointer, e.g., |this|, to an + * |nsISupports*|, but because you have multiple inheritance, a simple cast + * is ambiguous. One could simply say, e.g., (given a base |nsIBase|), + * |static_cast(this)|; but that disguises the fact that what + * you are really doing is disambiguating the |nsISupports|. You could make + * that more obvious with a double cast, e.g., |static_cast + (* + static_cast(this))|, but that is bulky and harder to read... + * + * The following macro is clean, short, and obvious. In the example above, + * you would use it like this: |NS_ISUPPORTS_CAST(nsIBase*, this)|. + */ + +#define NS_ISUPPORTS_CAST(__unambiguousBase, __expr) \ + static_cast(static_cast<__unambiguousBase>(__expr)) + +// a type-safe shortcut for calling the |QueryInterface()| member function +template +inline nsresult CallQueryInterface(T* aSource, DestinationType** aDestination) { + // We permit nsISupports-to-nsISupports here so that one can still obtain + // the canonical nsISupports pointer with CallQueryInterface. + static_assert( + !(std::is_same_v || + std::is_base_of::value) || + std::is_same_v, + "don't use CallQueryInterface for compile-time-determinable casts"); + + MOZ_ASSERT(aSource, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return aSource->QueryInterface(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult CallQueryInterface(RefPtr& aSourcePtr, + DestinationType** aDestPtr) { + return CallQueryInterface(aSourcePtr.get(), aDestPtr); +} + +#endif /* __nsISupportsUtils_h */ diff --git a/xpcom/base/nsIUUIDGenerator.idl b/xpcom/base/nsIUUIDGenerator.idl new file mode 100644 index 0000000000..22087adfa8 --- /dev/null +++ b/xpcom/base/nsIUUIDGenerator.idl @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 50; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +[ptr] native nsNonConstIDPtr(nsID); + +/** + * nsIUUIDGenerator is implemented by a service that can generate + * universally unique identifiers, ideally using any platform-native + * method for generating UUIDs. + */ +[builtinclass, scriptable, uuid(138ad1b2-c694-41cc-b201-333ce936d8b8)] +interface nsIUUIDGenerator : nsISupports +{ + /** + * Obtains a new UUID using appropriate platform-specific methods to + * obtain a nsID that can be considered to be globally unique. + * + * @returns an nsID filled in with a new UUID. + * + * @throws NS_ERROR_FAILURE if a UUID cannot be generated (e.g. if + * an underlying source of randomness is not available) + */ + nsIDPtr generateUUID(); + + /** + * Obtain a new UUID like the generateUUID method, but place it in + * the provided nsID pointer instead of allocating a new nsID. + * + * @param id an existing nsID pointer where the UUID will be stored. + * + * @throws NS_ERROR_FAILURE if a UUID cannot be generated (e.g. if + * an underlying source of randomness is not available) + */ + [noscript] void generateUUIDInPlace(in nsNonConstIDPtr id); +}; diff --git a/xpcom/base/nsIVersionComparator.idl b/xpcom/base/nsIVersionComparator.idl new file mode 100644 index 0000000000..462a07dff3 --- /dev/null +++ b/xpcom/base/nsIVersionComparator.idl @@ -0,0 +1,48 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +/** + * Version strings are dot-separated sequences of version-parts. + * + * A version-part consists of up to four parts, all of which are optional: + * + * + * + * A version-part may also consist of a single asterisk "*" which indicates + * "infinity". + * + * Numbers are base-10, and are zero if left out. + * Strings are compared bytewise. + * + * For additional backwards compatibility, if "string-b" is "+" then + * "number-a" is incremented by 1 and "string-b" becomes "pre". + * + * 1.0pre1 + * < 1.0pre2 + * < 1.0 == 1.0.0 == 1.0.0.0 + * < 1.1pre == 1.1pre0 == 1.0+ + * < 1.1pre1a + * < 1.1pre1 + * < 1.1pre10a + * < 1.1pre10 + * + * Although not required by this interface, it is recommended that + * numbers remain within the limits of a signed char, i.e. -127 to 128. + */ +[builtinclass, scriptable, uuid(e6cd620a-edbb-41d2-9e42-9a2ffc8107f3)] +interface nsIVersionComparator : nsISupports +{ + /** + * Compare two version strings + * @param A The first version + * @param B The second version + * @returns < 0 if A < B + * = 0 if A == B + * > 0 if A > B + */ + long compare(in ACString A, in ACString B); +}; diff --git a/xpcom/base/nsIWeakReference.idl b/xpcom/base/nsIWeakReference.idl new file mode 100644 index 0000000000..723fa84344 --- /dev/null +++ b/xpcom/base/nsIWeakReference.idl @@ -0,0 +1,114 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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 "nsISupports.idl" + +%{C++ +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReporting.h" + +// For MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED. +#include "nsDebug.h" + +#ifdef MOZ_THREAD_SAFETY_OWNERSHIP_CHECKS_SUPPORTED + +#define MOZ_WEAKREF_DECL_OWNINGTHREAD nsAutoOwningThread _mWeakRefOwningThread; +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD \ + _mWeakRefOwningThread.AssertOwnership("nsWeakReference not thread-safe") +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(that) \ + (that)->_mWeakRefOwningThread.AssertOwnership("nsWeakReference not thread-safe") + +#else + +#define MOZ_WEAKREF_DECL_OWNINGTHREAD +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD do { } while (false) +#define MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(that) do { } while (false) + +#endif + +%} + +native MallocSizeOf(mozilla::MallocSizeOf); + +/** + * An instance of |nsIWeakReference| is a proxy object that cooperates with + * its referent to give clients a non-owning, non-dangling reference. Clients + * own the proxy, and should generally manage it with an |nsCOMPtr| (see the + * type |nsWeakPtr| for a |typedef| name that stands out) as they would any + * other XPCOM object. The |QueryReferent| member function provides a + * (hopefully short-lived) owning reference on demand, through which clients + * can get useful access to the referent, while it still exists. + * + * @version 1.0 + * @see nsISupportsWeakReference + * @see nsWeakReference + * @see nsWeakPtr + */ +[scriptable, builtinclass, uuid(9188bc85-f92e-11d2-81ef-0060083a0bcf)] +interface nsIWeakReference : nsISupports + { + /** + * |QueryReferent| queries the referent, if it exists, and like |QueryInterface|, produces + * an owning reference to the desired interface. It is designed to look and act exactly + * like (a proxied) |QueryInterface|. Don't hold on to the produced interface permanently; + * that would defeat the purpose of using a non-owning |nsIWeakReference| in the first place. + */ + [binaryname(QueryReferentFromScript)] + void QueryReferent( in nsIIDRef uuid, [iid_is(uuid), retval] out nsQIResult result ); + + [notxpcom, nostdcall] size_t sizeOfOnlyThis(in MallocSizeOf aMallocSizeOf); +%{C++ + /** + * Returns true if the referring object is alive. Otherwise, false. + */ + bool IsAlive() const + { + return !!mObject; + } + + nsresult QueryReferent(const nsIID& aIID, void** aInstancePtr); + +protected: + friend class nsSupportsWeakReference; + + nsIWeakReference(nsISupports* aObject) + : mObject(aObject) + { + } + + nsIWeakReference() = delete; + + MOZ_WEAKREF_DECL_OWNINGTHREAD + + // The object we're holding a weak reference to. + nsISupports* MOZ_NON_OWNING_REF mObject; +%} + }; + + +/** + * |nsISupportsWeakReference| is a factory interface which produces appropriate + * instances of |nsIWeakReference|. Weak references in this scheme can only be + * produced for objects that implement this interface. + * + * @version 1.0 + * @see nsIWeakReference + * @see nsSupportsWeakReference + */ +[scriptable, uuid(9188bc86-f92e-11d2-81ef-0060083a0bcf)] +interface nsISupportsWeakReference : nsISupports + { + /** + * |GetWeakReference| produces an appropriate instance of |nsIWeakReference|. + * As with all good XPCOM `getters', you own the resulting interface and should + * manage it with an |nsCOMPtr|. + * + * @see nsIWeakReference + * @see nsWeakPtr + * @see nsCOMPtr + */ + nsIWeakReference GetWeakReference(); + }; diff --git a/xpcom/base/nsIWeakReferenceUtils.h b/xpcom/base/nsIWeakReferenceUtils.h new file mode 100644 index 0000000000..b76303096e --- /dev/null +++ b/xpcom/base/nsIWeakReferenceUtils.h @@ -0,0 +1,84 @@ +/* -*- 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/. */ + +#ifndef nsIWeakReferenceUtils_h__ +#define nsIWeakReferenceUtils_h__ + +#include "nsCOMPtr.h" +#include "nsIWeakReference.h" + +typedef nsCOMPtr nsWeakPtr; + +/** + * + */ + +// a type-safe shortcut for calling the |QueryReferent()| member function +// T must inherit from nsIWeakReference, but the cast may be ambiguous. +template +inline nsresult CallQueryReferent(T* aSource, DestinationType** aDestination) { + MOZ_ASSERT(aSource, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return aSource->QueryReferent(NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +inline const nsQueryReferent do_QueryReferent(nsIWeakReference* aRawPtr, + nsresult* aError = 0) { + return nsQueryReferent(aRawPtr, aError); +} + +/** + * Deprecated, use |do_GetWeakReference| instead. + */ +extern nsIWeakReference* NS_GetWeakReference(nsISupports*, + nsresult* aResult = 0); +extern nsIWeakReference* NS_GetWeakReference(nsISupportsWeakReference*, + nsresult* aResult = 0); + +/** + * |do_GetWeakReference| is a convenience function that bundles up all the work + * needed to get a weak reference to an arbitrary object, i.e., the + * |QueryInterface|, test, and call through to |GetWeakReference|, and put it + * into your |nsCOMPtr|. It is specifically designed to cooperate with + * |nsCOMPtr| (or |nsWeakPtr|) like so: |nsWeakPtr myWeakPtr = + * do_GetWeakReference(aPtr);|. + */ +inline already_AddRefed do_GetWeakReference( + nsISupports* aRawPtr, nsresult* aError = 0) { + return dont_AddRef(NS_GetWeakReference(aRawPtr, aError)); +} + +inline already_AddRefed do_GetWeakReference( + nsISupportsWeakReference* aRawPtr, nsresult* aError = 0) { + return dont_AddRef(NS_GetWeakReference(aRawPtr, aError)); +} + +inline void do_GetWeakReference(nsIWeakReference* aRawPtr, + nsresult* aError = 0) { + // This signature exists solely to _stop_ you from doing a bad thing. + // Saying |do_GetWeakReference()| on a weak reference itself, + // is very likely to be a programmer error. +} + +template +inline void do_GetWeakReference(already_AddRefed&) { + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_GetWeakReference()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See + // . +} + +template +inline void do_GetWeakReference(already_AddRefed&, nsresult*) { + // This signature exists solely to _stop_ you from doing the bad thing. + // Saying |do_GetWeakReference()| on a pointer that is not otherwise owned by + // someone else is an automatic leak. See + // . +} + +#endif diff --git a/xpcom/base/nsInterfaceRequestorAgg.cpp b/xpcom/base/nsInterfaceRequestorAgg.cpp new file mode 100644 index 0000000000..490341aac2 --- /dev/null +++ b/xpcom/base/nsInterfaceRequestorAgg.cpp @@ -0,0 +1,74 @@ +/* -*- 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 "nsInterfaceRequestorAgg.h" +#include "nsIInterfaceRequestor.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "nsThreadUtils.h" +#include "nsProxyRelease.h" + +class nsInterfaceRequestorAgg final : public nsIInterfaceRequestor { + public: + // XXX This needs to support threadsafe refcounting until we fix bug 243591. + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + + nsInterfaceRequestorAgg(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIEventTarget* aConsumerTarget = nullptr) + : mFirst(aFirst), mSecond(aSecond), mConsumerTarget(aConsumerTarget) { + if (!mConsumerTarget) { + mConsumerTarget = mozilla::GetCurrentSerialEventTarget(); + } + } + + private: + ~nsInterfaceRequestorAgg(); + + nsCOMPtr mFirst, mSecond; + nsCOMPtr mConsumerTarget; +}; + +NS_IMPL_ISUPPORTS(nsInterfaceRequestorAgg, nsIInterfaceRequestor) + +NS_IMETHODIMP +nsInterfaceRequestorAgg::GetInterface(const nsIID& aIID, void** aResult) { + nsresult rv = NS_ERROR_NO_INTERFACE; + if (mFirst) { + rv = mFirst->GetInterface(aIID, aResult); + } + if (mSecond && NS_FAILED(rv)) { + rv = mSecond->GetInterface(aIID, aResult); + } + return rv; +} + +nsInterfaceRequestorAgg::~nsInterfaceRequestorAgg() { + NS_ProxyRelease("nsInterfaceRequestorAgg::mFirst", mConsumerTarget, + mFirst.forget()); + NS_ProxyRelease("nsInterfaceRequestorAgg::mSecond", mConsumerTarget, + mSecond.forget()); +} + +nsresult NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIInterfaceRequestor** aResult) { + *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond); + + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult NS_NewInterfaceRequestorAggregation(nsIInterfaceRequestor* aFirst, + nsIInterfaceRequestor* aSecond, + nsIEventTarget* aTarget, + nsIInterfaceRequestor** aResult) { + *aResult = new nsInterfaceRequestorAgg(aFirst, aSecond, aTarget); + + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/xpcom/base/nsInterfaceRequestorAgg.h b/xpcom/base/nsInterfaceRequestorAgg.h new file mode 100644 index 0000000000..06c6b3d3a3 --- /dev/null +++ b/xpcom/base/nsInterfaceRequestorAgg.h @@ -0,0 +1,35 @@ +/* -*- 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/. */ + +#ifndef nsInterfaceRequestorAgg_h__ +#define nsInterfaceRequestorAgg_h__ + +#include "nsError.h" + +class nsIEventTarget; +class nsIInterfaceRequestor; + +/** + * This function returns an instance of nsIInterfaceRequestor that aggregates + * two nsIInterfaceRequestor instances. Its GetInterface method queries + * aFirst for the requested interface and will query aSecond only if aFirst + * failed to supply the requested interface. Both aFirst and aSecond may + * be null, and will be released on the main thread when the aggregator is + * destroyed. + */ +extern nsresult NS_NewInterfaceRequestorAggregation( + nsIInterfaceRequestor* aFirst, nsIInterfaceRequestor* aSecond, + nsIInterfaceRequestor** aResult); + +/** + * Like the previous method, but aFirst and aSecond will be released on the + * provided target thread. + */ +extern nsresult NS_NewInterfaceRequestorAggregation( + nsIInterfaceRequestor* aFirst, nsIInterfaceRequestor* aSecond, + nsIEventTarget* aTarget, nsIInterfaceRequestor** aResult); + +#endif // !defined( nsInterfaceRequestorAgg_h__ ) diff --git a/xpcom/base/nsMacPreferencesReader.h b/xpcom/base/nsMacPreferencesReader.h new file mode 100644 index 0000000000..40d5af553a --- /dev/null +++ b/xpcom/base/nsMacPreferencesReader.h @@ -0,0 +1,34 @@ +/* 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/. */ + +#ifndef MacPreferencesReader_h__ +#define MacPreferencesReader_h__ + +//----------------------------------------------------------------------------- + +#include "nsIMacPreferencesReader.h" + +#define NS_MACPREFERENCESREADER_CID \ + { \ + 0xb0f20595, 0x88ce, 0x4738, { \ + 0xa1, 0xa4, 0x24, 0xde, 0x78, 0xeb, 0x80, 0x51 \ + } \ + } +#define NS_MACPREFERENCESREADER_CONTRACTID \ + "@mozilla.org/mac-preferences-reader;1" + +//----------------------------------------------------------------------------- + +class nsMacPreferencesReader : public nsIMacPreferencesReader { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMACPREFERENCESREADER + + nsMacPreferencesReader(){}; + + protected: + virtual ~nsMacPreferencesReader() = default; +}; + +#endif // MacPreferencesReader_h__ diff --git a/xpcom/base/nsMacPreferencesReader.mm b/xpcom/base/nsMacPreferencesReader.mm new file mode 100644 index 0000000000..ad9a59a9f4 --- /dev/null +++ b/xpcom/base/nsMacPreferencesReader.mm @@ -0,0 +1,79 @@ +/* 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 "MacStringHelpers.h" +#include "nsMacPreferencesReader.h" +#include "nsString.h" + +#include "js/JSON.h" +#include "js/RootingAPI.h" +#include "js/Value.h" +#include "mozilla/JSONStringWriteFuncs.h" + +NS_IMPL_ISUPPORTS(nsMacPreferencesReader, nsIMacPreferencesReader) + +using namespace mozilla; + +static void EvaluateDict(JSONWriter* aWriter, NSDictionary* aDict); + +static void EvaluateArray(JSONWriter* aWriter, NSArray* aArray) { + for (id elem in aArray) { + if ([elem isKindOfClass:[NSString class]]) { + aWriter->StringElement(MakeStringSpan([elem UTF8String])); + } else if ([elem isKindOfClass:[NSNumber class]]) { + aWriter->IntElement([elem longLongValue]); + } else if ([elem isKindOfClass:[NSArray class]]) { + aWriter->StartArrayElement(); + EvaluateArray(aWriter, elem); + aWriter->EndArray(); + } else if ([elem isKindOfClass:[NSDictionary class]]) { + aWriter->StartObjectElement(); + EvaluateDict(aWriter, elem); + aWriter->EndObject(); + } + } +} + +static void EvaluateDict(JSONWriter* aWriter, NSDictionary* aDict) { + for (NSString* key in aDict) { + id value = aDict[key]; + if ([value isKindOfClass:[NSString class]]) { + aWriter->StringProperty(MakeStringSpan([key UTF8String]), MakeStringSpan([value UTF8String])); + } else if ([value isKindOfClass:[NSNumber class]]) { + aWriter->IntProperty(MakeStringSpan([key UTF8String]), [value longLongValue]); + } else if ([value isKindOfClass:[NSArray class]]) { + aWriter->StartArrayProperty(MakeStringSpan([key UTF8String])); + EvaluateArray(aWriter, value); + aWriter->EndArray(); + } else if ([value isKindOfClass:[NSDictionary class]]) { + aWriter->StartObjectProperty(MakeStringSpan([key UTF8String])); + EvaluateDict(aWriter, value); + aWriter->EndObject(); + } + } +} + +NS_IMETHODIMP +nsMacPreferencesReader::PoliciesEnabled(bool* aPoliciesEnabled) { + NSString* policiesEnabledStr = [NSString stringWithUTF8String:ENTERPRISE_POLICIES_ENABLED_KEY]; + *aPoliciesEnabled = [[NSUserDefaults standardUserDefaults] boolForKey:policiesEnabledStr] == YES; + return NS_OK; +} + +NS_IMETHODIMP +nsMacPreferencesReader::ReadPreferences(JSContext* aCx, JS::MutableHandle aResult) { + JSONStringWriteFunc jsonStr; + JSONWriter w(jsonStr); + w.Start(); + EvaluateDict(&w, [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]); + w.End(); + + NS_ConvertUTF8toUTF16 jsonStr16(jsonStr.StringCRef()); + + JS::RootedValue val(aCx); + MOZ_ALWAYS_TRUE(JS_ParseJSON(aCx, jsonStr16.get(), jsonStr16.Length(), &val)); + + aResult.set(val); + return NS_OK; +} diff --git a/xpcom/base/nsMacUtilsImpl.cpp b/xpcom/base/nsMacUtilsImpl.cpp new file mode 100644 index 0000000000..86227b7aca --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.cpp @@ -0,0 +1,544 @@ +/* -*- 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 "nsMacUtilsImpl.h" + +#include "base/command_line.h" +#include "base/process_util.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Omnijar.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsIFile.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "prenv.h" + +#if defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +#endif + +#include +#include +#if defined(__aarch64__) +# include +#endif +#include + +using mozilla::StaticMutexAutoLock; +using mozilla::Unused; + +#if defined(MOZ_SANDBOX) || defined(__aarch64__) +// For thread safe setting/checking of sCachedAppPath +static StaticMutex sCachedAppPathMutex; + +// Cache the appDir returned from GetAppPath to avoid doing I/O +static StaticAutoPtr sCachedAppPath + MOZ_GUARDED_BY(sCachedAppPathMutex); +#endif + +// The cached machine architectures of the .app bundle which can +// be multiple architectures for universal binaries. +static std::atomic sBundleArchMaskAtomic = 0; + +#if defined(__aarch64__) +// Limit XUL translation to one attempt +static std::atomic sIsXULTranslated = false; +#endif + +// Info.plist key associated with the developer repo path +#define MAC_DEV_REPO_KEY "MozillaDeveloperRepoPath" +// Info.plist key associated with the developer repo object directory +#define MAC_DEV_OBJ_KEY "MozillaDeveloperObjPath" + +// Workaround this constant not being available in the macOS SDK +#define kCFBundleExecutableArchitectureARM64 0x0100000c + +enum TCSMStatus { TCSM_Unknown = 0, TCSM_Available, TCSM_Unavailable }; + +// Initialize with Unknown until we've checked if TCSM is available to set +static Atomic sTCSMStatus(TCSM_Unknown); + +#if defined(MOZ_SANDBOX) || defined(__aarch64__) + +// Utility method to call ClearOnShutdown() on the main thread +static nsresult ClearCachedAppPathOnShutdown() { + MOZ_ASSERT(NS_IsMainThread()); + ClearOnShutdown(&sCachedAppPath); + return NS_OK; +} + +// Get the path to the .app directory (aka bundle) for the parent process. +// When executing in the child process, this is the outer .app (such as +// Firefox.app) and not the inner .app containing the child process +// executable. We don't rely on the actual .app extension to allow for the +// bundle being renamed. +bool nsMacUtilsImpl::GetAppPath(nsCString& aAppPath) { + StaticMutexAutoLock lock(sCachedAppPathMutex); + if (sCachedAppPath) { + aAppPath.Assign(*sCachedAppPath); + return true; + } + + nsAutoCString appPath; + nsAutoCString appBinaryPath( + (CommandLine::ForCurrentProcess()->argv()[0]).c_str()); + + // The binary path resides within the .app dir in Contents/MacOS, + // e.g., Firefox.app/Contents/MacOS/firefox. Search backwards in + // the binary path for the end of .app path. + auto pattern = "/Contents/MacOS/"_ns; + nsAutoCString::const_iterator start, end; + appBinaryPath.BeginReading(start); + appBinaryPath.EndReading(end); + if (RFindInReadable(pattern, start, end)) { + end = start; + appBinaryPath.BeginReading(start); + + // If we're executing in a child process, get the parent .app path + // by searching backwards once more. The child executable resides + // in Firefox.app/Contents/MacOS/plugin-container/Contents/MacOS. + if (!XRE_IsParentProcess()) { + if (RFindInReadable(pattern, start, end)) { + end = start; + appBinaryPath.BeginReading(start); + } else { + return false; + } + } + + appPath.Assign(Substring(start, end)); + } else { + return false; + } + + nsCOMPtr app; + nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(appPath), true, + getter_AddRefs(app)); + if (NS_FAILED(rv)) { + return false; + } + + rv = app->Normalize(); + if (NS_FAILED(rv)) { + return false; + } + app->GetNativePath(aAppPath); + + if (!sCachedAppPath) { + sCachedAppPath = new nsCString(aAppPath); + + if (NS_IsMainThread()) { + ClearCachedAppPathOnShutdown(); + } else { + NS_DispatchToMainThread( + NS_NewRunnableFunction("ClearCachedAppPathOnShutdown", + [] { ClearCachedAppPathOnShutdown(); })); + } + } + + return true; +} + +#endif /* MOZ_SANDBOX || __aarch64__ */ + +#if defined(MOZ_SANDBOX) && defined(DEBUG) +// If XPCOM_MEM_BLOAT_LOG or XPCOM_MEM_LEAK_LOG is set to a log file +// path, return the path to the parent directory (where sibling log +// files will be saved.) +nsresult nsMacUtilsImpl::GetBloatLogDir(nsCString& aDirectoryPath) { + nsAutoCString bloatLog(PR_GetEnv("XPCOM_MEM_BLOAT_LOG")); + if (bloatLog.IsEmpty()) { + bloatLog = PR_GetEnv("XPCOM_MEM_LEAK_LOG"); + } + if (!bloatLog.IsEmpty() && bloatLog != "1" && bloatLog != "2") { + return GetDirectoryPath(bloatLog.get(), aDirectoryPath); + } + return NS_OK; +} + +// Given a path to a file, return the directory which contains it. +nsresult nsMacUtilsImpl::GetDirectoryPath(const char* aPath, + nsCString& aDirectoryPath) { + nsresult rv = NS_OK; + nsCOMPtr file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = file->InitWithNativePath(nsDependentCString(aPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr directoryFile; + rv = file->GetParent(getter_AddRefs(directoryFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directoryFile->Normalize(); + NS_ENSURE_SUCCESS(rv, rv); + + if (NS_FAILED(directoryFile->GetNativePath(aDirectoryPath))) { + MOZ_CRASH("Failed to get path for an nsIFile"); + } + return NS_OK; +} +#endif /* MOZ_SANDBOX && DEBUG */ + +/* static */ +bool nsMacUtilsImpl::IsTCSMAvailable() { + if (sTCSMStatus == TCSM_Unknown) { + uint32_t oldVal = 0; + size_t oldValSize = sizeof(oldVal); + int rv = sysctlbyname("kern.tcsm_available", &oldVal, &oldValSize, NULL, 0); + TCSMStatus newStatus; + if (rv < 0 || oldVal == 0) { + newStatus = TCSM_Unavailable; + } else { + newStatus = TCSM_Available; + } + // The value of sysctl kern.tcsm_available is the same for all + // threads within the same process. If another thread raced with us + // and initialized sTCSMStatus first (changing it from + // TCSM_Unknown), we can continue without needing to update it + // again. Hence, we ignore compareExchange's return value. + Unused << sTCSMStatus.compareExchange(TCSM_Unknown, newStatus); + } + return (sTCSMStatus == TCSM_Available); +} + +static nsresult EnableTCSM() { + uint32_t newVal = 1; + int rv = sysctlbyname("kern.tcsm_enable", NULL, 0, &newVal, sizeof(newVal)); + if (rv < 0) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +#if defined(DEBUG) +static bool IsTCSMEnabled() { + uint32_t oldVal = 0; + size_t oldValSize = sizeof(oldVal); + int rv = sysctlbyname("kern.tcsm_enable", &oldVal, &oldValSize, NULL, 0); + return (rv == 0) && (oldVal != 0); +} +#endif + +/* + * Intentionally return void so that failures will be ignored in non-debug + * builds. This method uses new sysctls which may not be as thoroughly tested + * and we don't want to cause crashes handling the failure due to an OS bug. + */ +/* static */ +void nsMacUtilsImpl::EnableTCSMIfAvailable() { + if (IsTCSMAvailable()) { + if (NS_FAILED(EnableTCSM())) { + NS_WARNING("Failed to enable TCSM"); + } + MOZ_ASSERT(IsTCSMEnabled()); + } +} + +// Returns 0 on error. +/* static */ +uint32_t nsMacUtilsImpl::GetPhysicalCPUCount() { + uint32_t oldVal = 0; + size_t oldValSize = sizeof(oldVal); + int rv = sysctlbyname("hw.physicalcpu_max", &oldVal, &oldValSize, NULL, 0); + if (rv == -1) { + return 0; + } + return oldVal; +} + +/* + * Helper function to read a string value for a given key from the .app's + * Info.plist. + */ +static nsresult GetStringValueFromBundlePlist(const nsAString& aKey, + nsAutoCString& aValue) { + CFBundleRef mainBundle = CFBundleGetMainBundle(); + if (mainBundle == nullptr) { + return NS_ERROR_FAILURE; + } + + // Read this app's bundle Info.plist as a dictionary + CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(mainBundle); + if (bundleInfoDict == nullptr) { + return NS_ERROR_FAILURE; + } + + nsAutoCString keyAutoCString = NS_ConvertUTF16toUTF8(aKey); + CFStringRef key = CFStringCreateWithCString( + kCFAllocatorDefault, keyAutoCString.get(), kCFStringEncodingUTF8); + if (key == nullptr) { + return NS_ERROR_FAILURE; + } + + CFStringRef value = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, key); + CFRelease(key); + if (value == nullptr) { + return NS_ERROR_FAILURE; + } + + CFIndex valueLength = CFStringGetLength(value); + if (valueLength == 0) { + return NS_ERROR_FAILURE; + } + + const char* valueCString = + CFStringGetCStringPtr(value, kCFStringEncodingUTF8); + if (valueCString) { + aValue.Assign(valueCString); + return NS_OK; + } + + CFIndex maxLength = + CFStringGetMaximumSizeForEncoding(valueLength, kCFStringEncodingUTF8) + 1; + char* valueBuffer = static_cast(moz_xmalloc(maxLength)); + + if (!CFStringGetCString(value, valueBuffer, maxLength, + kCFStringEncodingUTF8)) { + free(valueBuffer); + return NS_ERROR_FAILURE; + } + + aValue.Assign(valueBuffer); + free(valueBuffer); + return NS_OK; +} + +/* + * Helper function for reading a path string from the .app's Info.plist + * and returning a directory object for that path with symlinks resolved. + */ +static nsresult GetDirFromBundlePlist(const nsAString& aKey, nsIFile** aDir) { + nsresult rv; + + nsAutoCString dirPath; + rv = GetStringValueFromBundlePlist(aKey, dirPath); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr dir; + rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(dirPath), false, + getter_AddRefs(dir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dir->Normalize(); + NS_ENSURE_SUCCESS(rv, rv); + + bool isDirectory = false; + rv = dir->IsDirectory(&isDirectory); + NS_ENSURE_SUCCESS(rv, rv); + if (!isDirectory) { + return NS_ERROR_FILE_NOT_DIRECTORY; + } + + dir.swap(*aDir); + return NS_OK; +} + +nsresult nsMacUtilsImpl::GetRepoDir(nsIFile** aRepoDir) { +#if defined(MOZ_SANDBOX) + MOZ_ASSERT(!mozilla::IsPackagedBuild()); +#endif + return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_REPO_KEY), + aRepoDir); +} + +nsresult nsMacUtilsImpl::GetObjDir(nsIFile** aObjDir) { +#if defined(MOZ_SANDBOX) + MOZ_ASSERT(!mozilla::IsPackagedBuild()); +#endif + return GetDirFromBundlePlist(NS_LITERAL_STRING_FROM_CSTRING(MAC_DEV_OBJ_KEY), + aObjDir); +} + +/* static */ +nsresult nsMacUtilsImpl::GetArchitecturesForBundle(uint32_t* aArchMask) { + MOZ_ASSERT(aArchMask); + + *aArchMask = sBundleArchMaskAtomic; + if (*aArchMask != 0) { + return NS_OK; + } + + CFBundleRef mainBundle = ::CFBundleGetMainBundle(); + if (!mainBundle) { + return NS_ERROR_FAILURE; + } + + CFArrayRef archList = ::CFBundleCopyExecutableArchitectures(mainBundle); + if (!archList) { + return NS_ERROR_FAILURE; + } + + CFIndex archCount = ::CFArrayGetCount(archList); + for (CFIndex i = 0; i < archCount; i++) { + CFNumberRef arch = + static_cast(::CFArrayGetValueAtIndex(archList, i)); + + int archInt = 0; + if (!::CFNumberGetValue(arch, kCFNumberIntType, &archInt)) { + ::CFRelease(archList); + return NS_ERROR_FAILURE; + } + + if (archInt == kCFBundleExecutableArchitecturePPC) { + *aArchMask |= base::PROCESS_ARCH_PPC; + } else if (archInt == kCFBundleExecutableArchitectureI386) { + *aArchMask |= base::PROCESS_ARCH_I386; + } else if (archInt == kCFBundleExecutableArchitecturePPC64) { + *aArchMask |= base::PROCESS_ARCH_PPC_64; + } else if (archInt == kCFBundleExecutableArchitectureX86_64) { + *aArchMask |= base::PROCESS_ARCH_X86_64; + } else if (archInt == kCFBundleExecutableArchitectureARM64) { + *aArchMask |= base::PROCESS_ARCH_ARM_64; + } + } + + ::CFRelease(archList); + + sBundleArchMaskAtomic = *aArchMask; + + return NS_OK; +} + +/* static */ +nsresult nsMacUtilsImpl::GetArchitecturesForBinary(const char* aPath, + uint32_t* aArchMask) { + MOZ_ASSERT(aArchMask); + + *aArchMask = 0; + + CFURLRef url = ::CFURLCreateFromFileSystemRepresentation( + kCFAllocatorDefault, (const UInt8*)aPath, strlen(aPath), false); + if (!url) { + return NS_ERROR_FAILURE; + } + + CFArrayRef archs = ::CFBundleCopyExecutableArchitecturesForURL(url); + if (!archs) { + CFRelease(url); + return NS_ERROR_FAILURE; + } + + CFIndex archCount = ::CFArrayGetCount(archs); + for (CFIndex i = 0; i < archCount; i++) { + CFNumberRef currentArch = + static_cast(::CFArrayGetValueAtIndex(archs, i)); + int currentArchInt = 0; + if (!::CFNumberGetValue(currentArch, kCFNumberIntType, ¤tArchInt)) { + continue; + } + switch (currentArchInt) { + case kCFBundleExecutableArchitectureX86_64: + *aArchMask |= base::PROCESS_ARCH_X86_64; + break; + case kCFBundleExecutableArchitectureARM64: + *aArchMask |= base::PROCESS_ARCH_ARM_64; + break; + default: + break; + } + } + + CFRelease(url); + CFRelease(archs); + + // We expect x86 or ARM64 or both. + if (*aArchMask == 0) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +#if defined(__aarch64__) +// Pre-translate XUL so that x64 child processes launched after this +// translation will not incur the translation overhead delaying startup. +// Returns 1 if translation is in progress, -1 on an error encountered before +// translation, and otherwise returns the result of rosetta_translate_binaries. +/* static */ +int nsMacUtilsImpl::PreTranslateXUL() { + bool expected = false; + if (!sIsXULTranslated.compare_exchange_strong(expected, true)) { + // Translation is already done or in progress. + return 1; + } + + // Get the path to XUL by first getting the + // outer .app path and appending the path to XUL. + nsCString xulPath; + if (!GetAppPath(xulPath)) { + return -1; + } + xulPath.Append("/Contents/MacOS/XUL"); + + return PreTranslateBinary(xulPath); +} + +// Use Chromium's method to pre-translate the provided binary using the +// undocumented function "rosetta_translate_binaries" from libRosetta.dylib. +// Re-translating the same binary does not cause translation to occur again. +// Returns -1 on an error encountered before translation, otherwise returns +// the rosetta_translate_binaries result. This method is partly copied from +// Chromium code. +/* static */ +int nsMacUtilsImpl::PreTranslateBinary(nsCString aBinaryPath) { + // Do not attempt to use this in child processes. Child + // processes executing should already be translated and + // sandboxing may interfere with translation. + MOZ_ASSERT(XRE_IsParentProcess()); + if (!XRE_IsParentProcess()) { + return -1; + } + + // Translation can take several seconds and therefore + // should not be done on the main thread. + MOZ_ASSERT(!NS_IsMainThread()); + if (NS_IsMainThread()) { + return -1; + } + + // @available() is not available for macOS 11 at this time so use + // -Wunguarded-availability-new to avoid compiler warnings caused + // by an earlier minimum SDK. ARM64 builds require the 11.0 SDK and + // can not be run on earlier OS versions so this is not a concern. +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunguarded-availability-new" + // If Rosetta is not installed, do not proceed. + if (!CFBundleIsArchitectureLoadable(CPU_TYPE_X86_64)) { + return -1; + } +# pragma clang diagnostic pop + + if (aBinaryPath.IsEmpty()) { + return -1; + } + + // int rosetta_translate_binaries(const char*[] paths, int npaths) + using rosetta_translate_binaries_t = int (*)(const char*[], int); + + static auto rosetta_translate_binaries = []() { + void* libRosetta = + dlopen("/usr/lib/libRosetta.dylib", RTLD_LAZY | RTLD_LOCAL); + if (!libRosetta) { + return static_cast(nullptr); + } + + return reinterpret_cast( + dlsym(libRosetta, "rosetta_translate_binaries")); + }(); + + if (!rosetta_translate_binaries) { + return -1; + } + + const char* pathPtr = aBinaryPath.get(); + return rosetta_translate_binaries(&pathPtr, 1); +} + +#endif diff --git a/xpcom/base/nsMacUtilsImpl.h b/xpcom/base/nsMacUtilsImpl.h new file mode 100644 index 0000000000..9a20b539d1 --- /dev/null +++ b/xpcom/base/nsMacUtilsImpl.h @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +#ifndef nsMacUtilsImpl_h___ +#define nsMacUtilsImpl_h___ + +#include "nsString.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/StaticPtr.h" + +using mozilla::Atomic; +using mozilla::StaticAutoPtr; +using mozilla::StaticMutex; + +class nsIFile; + +namespace nsMacUtilsImpl { + +// Return the repo directory and the repo object directory respectively. +// These should only be used on Mac developer builds to determine the path +// to the repo or object directory. +nsresult GetRepoDir(nsIFile** aRepoDir); +nsresult GetObjDir(nsIFile** aObjDir); + +#if defined(MOZ_SANDBOX) || defined(__aarch64__) +bool GetAppPath(nsCString& aAppPath); +#endif /* MOZ_SANDBOX || __aarch64__ */ + +#if defined(MOZ_SANDBOX) && defined(DEBUG) +nsresult GetBloatLogDir(nsCString& aDirectoryPath); +nsresult GetDirectoryPath(const char* aPath, nsCString& aDirectoryPath); +#endif /* MOZ_SANDBOX && DEBUG */ + +void EnableTCSMIfAvailable(); +bool IsTCSMAvailable(); +uint32_t GetPhysicalCPUCount(); +nsresult GetArchitecturesForBundle(uint32_t* aArchMask); +nsresult GetArchitecturesForBinary(const char* aPath, uint32_t* aArchMask); + +#if defined(__aarch64__) +// Pre-translate binaries to avoid translation delays when launching +// x64 child process instances for the first time. i.e. on first launch +// after installation or after an update. Translations are cached so +// repeated launches of the binaries do not encounter delays. +int PreTranslateXUL(); +int PreTranslateBinary(nsCString aBinaryPath); +#endif +} // namespace nsMacUtilsImpl + +#endif /* nsMacUtilsImpl_h___ */ diff --git a/xpcom/base/nsMaybeWeakPtr.h b/xpcom/base/nsMaybeWeakPtr.h new file mode 100644 index 0000000000..165a8c23c1 --- /dev/null +++ b/xpcom/base/nsMaybeWeakPtr.h @@ -0,0 +1,170 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsMaybeWeakPtr_h_ +#define nsMaybeWeakPtr_h_ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsIWeakReferenceUtils.h" +#include "nsTArray.h" +#include "nsCycleCollectionNoteChild.h" + +// nsMaybeWeakPtr is a helper object to hold a strong-or-weak reference +// to the template class. It's pretty minimal, but sufficient. + +template +class nsMaybeWeakPtr { + public: + nsMaybeWeakPtr() = default; + MOZ_IMPLICIT nsMaybeWeakPtr(T* aRef) : mPtr(aRef), mWeak(false) {} + MOZ_IMPLICIT nsMaybeWeakPtr(const nsCOMPtr& aRef) + : mPtr(aRef), mWeak(true) {} + + nsMaybeWeakPtr& operator=(T* aRef) { + mPtr = aRef; + mWeak = false; + return *this; + } + + nsMaybeWeakPtr& operator=(const nsCOMPtr& aRef) { + mPtr = aRef; + mWeak = true; + return *this; + } + + bool operator==(const nsMaybeWeakPtr& other) const { + return mPtr == other.mPtr; + } + + nsISupports* GetRawValue() const { return mPtr.get(); } + bool IsWeak() const { return mWeak; } + + const nsCOMPtr GetValue() const; + + private: + nsCOMPtr mPtr; + bool mWeak; +}; + +// nsMaybeWeakPtrArray is an array of MaybeWeakPtr objects, that knows how to +// grab a weak reference to a given object if requested. It only allows a +// given object to appear in the array once. + +template +class nsMaybeWeakPtrArray : public CopyableTArray> { + typedef nsTArray> MaybeWeakArray; + + nsresult SetMaybeWeakPtr(nsMaybeWeakPtr& aRef, T* aElement, + bool aOwnsWeak) { + nsresult rv = NS_OK; + + if (aOwnsWeak) { + aRef = do_GetWeakReference(aElement, &rv); + } else { + aRef = aElement; + } + + return rv; + } + + public: + nsresult AppendWeakElement(T* aElement, bool aOwnsWeak) { + nsMaybeWeakPtr ref; + MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak)); + + MaybeWeakArray::AppendElement(ref); + return NS_OK; + } + + nsresult AppendWeakElementUnlessExists(T* aElement, bool aOwnsWeak) { + nsMaybeWeakPtr ref; + MOZ_TRY(SetMaybeWeakPtr(ref, aElement, aOwnsWeak)); + + if (MaybeWeakArray::Contains(ref)) { + return NS_ERROR_INVALID_ARG; + } + + MaybeWeakArray::AppendElement(ref); + return NS_OK; + } + + nsresult RemoveWeakElement(T* aElement) { + if (MaybeWeakArray::RemoveElement(aElement)) { + return NS_OK; + } + + // Don't use do_GetWeakReference; it should only be called if we know + // the object supports weak references. + nsCOMPtr supWeakRef = do_QueryInterface(aElement); + if (!supWeakRef) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr weakRef; + nsresult rv = supWeakRef->GetWeakReference(getter_AddRefs(weakRef)); + NS_ENSURE_SUCCESS(rv, rv); + + if (MaybeWeakArray::RemoveElement(weakRef)) { + return NS_OK; + } + + return NS_ERROR_INVALID_ARG; + } +}; + +template +const nsCOMPtr nsMaybeWeakPtr::GetValue() const { + if (!mPtr) { + return nullptr; + } + + nsCOMPtr ref; + nsresult rv; + + if (mWeak) { + nsCOMPtr weakRef = do_QueryInterface(mPtr); + if (weakRef) { + ref = do_QueryReferent(weakRef, &rv); + if (NS_SUCCEEDED(rv)) { + return ref; + } + } + } else { + ref = do_QueryInterface(mPtr, &rv); + if (NS_SUCCEEDED(rv)) { + return ref; + } + } + + return nullptr; +} + +template +inline void ImplCycleCollectionUnlink(nsMaybeWeakPtrArray& aField) { + aField.Clear(); +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsMaybeWeakPtrArray& aField, const char* aName, uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + size_t length = aField.Length(); + for (size_t i = 0; i < length; ++i) { + CycleCollectionNoteChild(aCallback, aField[i].GetRawValue(), aName, aFlags); + } +} + +// Call a method on each element in the array, but only if the element is +// non-null. + +#define ENUMERATE_WEAKARRAY(array, type, method) \ + for (uint32_t array_idx = 0; array_idx < array.Length(); ++array_idx) { \ + const nsCOMPtr& e = array.ElementAt(array_idx).GetValue(); \ + if (e) e->method; \ + } + +#endif diff --git a/xpcom/base/nsMemory.h b/xpcom/base/nsMemory.h new file mode 100644 index 0000000000..7fda7ae837 --- /dev/null +++ b/xpcom/base/nsMemory.h @@ -0,0 +1,62 @@ +/* -*- 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/. */ + +#ifndef nsMemory_h__ +#define nsMemory_h__ + +#include "nsError.h" + +/** + * + * A client that wishes to be notified of low memory situations (for + * example, because the client maintains a large memory cache that + * could be released when memory is tight) should register with the + * observer service (see nsIObserverService) using the topic + * "memory-pressure". There are specific types of notifications + * that can occur. These types will be passed as the |aData| + * parameter of the of the "memory-pressure" notification: + * + * "low-memory" + * This will be passed as the extra data when the pressure + * observer is being asked to flush for low-memory conditions. + * + * "low-memory-ongoing" + * This will be passed when we continue to be in a low-memory + * condition and we want to flush caches and do other cheap + * forms of memory minimization, but heavy handed approaches like + * a GC are unlikely to succeed. + * + * "heap-minimize" + * This will be passed as the extra data when the pressure + * observer is being asked to flush because of a heap minimize + * call. + */ + +// This is implemented in nsMemoryImpl.cpp. + +namespace nsMemory { + +/** + * Attempts to shrink the heap. + * @param immediate - if true, heap minimization will occur + * immediately if the call was made on the main thread. If + * false, the flush will be scheduled to happen when the app is + * idle. + * @throws NS_ERROR_FAILURE if 'immediate' is set and the call + * was not on the application's main thread. + */ +nsresult HeapMinimize(bool aImmediate); + +/** + * This predicate can be used to determine if the platform is a "low-memory" + * platform. Callers may use this to dynamically tune their behaviour + * to favour reduced memory usage at the expense of performance. The value + * returned by this function will not change over the lifetime of the process. + */ +bool IsLowMemoryPlatform(); +} // namespace nsMemory + +#endif // nsMemory_h__ diff --git a/xpcom/base/nsMemoryImpl.cpp b/xpcom/base/nsMemoryImpl.cpp new file mode 100644 index 0000000000..4996d27c7a --- /dev/null +++ b/xpcom/base/nsMemoryImpl.cpp @@ -0,0 +1,131 @@ +/* -*- 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 "nsMemory.h" +#include "nsThreadUtils.h" + +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIRunnable.h" +#include "nsISimpleEnumerator.h" + +#include "nsCOMPtr.h" +#include "mozilla/Services.h" +#include "mozilla/Atomics.h" + +#ifdef ANDROID +# include + +// Minimum memory threshold for a device to be considered +// a low memory platform. This value has be in sync with +// Java's equivalent threshold, defined in HardwareUtils.java +# define LOW_MEMORY_THRESHOLD_KB (384 * 1024) +#endif + +static mozilla::Atomic sIsFlushing; +static PRIntervalTime sLastFlushTime = 0; + +// static +bool nsMemory::IsLowMemoryPlatform() { +#ifdef ANDROID + static int sLowMemory = + -1; // initialize to unknown, lazily evaluate to 0 or 1 + if (sLowMemory == -1) { + sLowMemory = 0; // assume "not low memory" in case file operations fail + + // check if MemTotal from /proc/meminfo is less than LOW_MEMORY_THRESHOLD_KB + FILE* fd = fopen("/proc/meminfo", "r"); + if (!fd) { + return false; + } + uint64_t mem = 0; + int rv = fscanf(fd, "MemTotal: %" PRIu64 " kB", &mem); + if (fclose(fd)) { + return false; + } + if (rv != 1) { + return false; + } + sLowMemory = (mem < LOW_MEMORY_THRESHOLD_KB) ? 1 : 0; + } + return (sLowMemory == 1); +#else + return false; +#endif +} + +static void RunFlushers(const char16_t* aReason) { + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + // Instead of: + // os->NotifyObservers(this, "memory-pressure", aReason); + // we are going to do this manually to see who/what is + // deallocating. + + nsCOMPtr e; + os->EnumerateObservers("memory-pressure", getter_AddRefs(e)); + + if (e) { + nsCOMPtr observer; + bool loop = true; + + while (NS_SUCCEEDED(e->HasMoreElements(&loop)) && loop) { + nsCOMPtr supports; + e->GetNext(getter_AddRefs(supports)); + + if (!supports) { + continue; + } + + observer = do_QueryInterface(supports); + observer->Observe(observer, "memory-pressure", aReason); + } + } + } + + sIsFlushing = false; +} + +static nsresult FlushMemory(const char16_t* aReason, bool aImmediate) { + if (aImmediate) { + // They've asked us to run the flusher *immediately*. We've + // got to be on the UI main thread for us to be able to do + // that...are we? + if (!NS_IsMainThread()) { + NS_ERROR("can't synchronously flush memory: not on UI thread"); + return NS_ERROR_FAILURE; + } + } + + bool lastVal = sIsFlushing.exchange(true); + if (lastVal) { + return NS_OK; + } + + PRIntervalTime now = PR_IntervalNow(); + + // Run the flushers immediately if we can; otherwise, proxy to the + // UI thread and run 'em asynchronously. + nsresult rv = NS_OK; + if (aImmediate) { + RunFlushers(aReason); + } else { + // Don't broadcast more than once every 1000ms to avoid being noisy + if (PR_IntervalToMicroseconds(now - sLastFlushTime) > 1000) { + nsCOMPtr runnable(NS_NewRunnableFunction( + "FlushMemory", + [reason = aReason]() -> void { RunFlushers(reason); })); + NS_DispatchToMainThread(runnable.forget()); + } + } + + sLastFlushTime = now; + return rv; +} + +nsresult nsMemory::HeapMinimize(bool aImmediate) { + return FlushMemory(u"heap-minimize", aImmediate); +} diff --git a/xpcom/base/nsMemoryInfoDumper.cpp b/xpcom/base/nsMemoryInfoDumper.cpp new file mode 100644 index 0000000000..28af408178 --- /dev/null +++ b/xpcom/base/nsMemoryInfoDumper.cpp @@ -0,0 +1,742 @@ +/* -*- 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/JSONWriter.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/nsMemoryInfoDumper.h" +#include "mozilla/DebugOnly.h" +#include "nsDumpUtils.h" + +#include "mozilla/Unused.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "nsIConsoleService.h" +#include "nsCycleCollector.h" +#include "nsICycleCollectorListener.h" +#include "nsIMemoryReporter.h" +#include "nsDirectoryServiceDefs.h" +#include "nsGZFileWriter.h" +#include "nsJSEnvironment.h" +#include "nsPrintfCString.h" +#include "nsServiceManagerUtils.h" +#include "nsIFile.h" + +#ifdef XP_WIN +# include +# ifndef getpid +# define getpid _getpid +# endif +#else +# include +#endif + +#ifdef XP_UNIX +# define MOZ_SUPPORTS_FIFO 1 +#endif + +// Some Android devices seem to send RT signals to Firefox so we want to avoid +// consuming those as they're not user triggered. +#if !defined(ANDROID) && (defined(XP_LINUX) || defined(__FreeBSD__)) +# define MOZ_SUPPORTS_RT_SIGNALS 1 +#endif + +#if defined(MOZ_SUPPORTS_RT_SIGNALS) +# include +# include +# include +#endif + +#if defined(MOZ_SUPPORTS_FIFO) +# include "mozilla/Preferences.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; + +namespace { + +class DumpMemoryInfoToTempDirRunnable : public Runnable { + public: + DumpMemoryInfoToTempDirRunnable(const nsAString& aIdentifier, bool aAnonymize, + bool aMinimizeMemoryUsage) + : mozilla::Runnable("DumpMemoryInfoToTempDirRunnable"), + mIdentifier(aIdentifier), + mAnonymize(aAnonymize), + mMinimizeMemoryUsage(aMinimizeMemoryUsage) {} + + NS_IMETHOD Run() override { + nsCOMPtr dumper = + do_GetService("@mozilla.org/memory-info-dumper;1"); + dumper->DumpMemoryInfoToTempDir(mIdentifier, mAnonymize, + mMinimizeMemoryUsage); + return NS_OK; + } + + private: + const nsString mIdentifier; + const bool mAnonymize; + const bool mMinimizeMemoryUsage; +}; + +class GCAndCCLogDumpRunnable final : public Runnable, + public nsIDumpGCAndCCLogsCallback { + public: + NS_DECL_ISUPPORTS_INHERITED + + GCAndCCLogDumpRunnable(const nsAString& aIdentifier, bool aDumpAllTraces, + bool aDumpChildProcesses) + : mozilla::Runnable("GCAndCCLogDumpRunnable"), + mIdentifier(aIdentifier), + mDumpAllTraces(aDumpAllTraces), + mDumpChildProcesses(aDumpChildProcesses) {} + + NS_IMETHOD Run() override { + nsCOMPtr dumper = + do_GetService("@mozilla.org/memory-info-dumper;1"); + + dumper->DumpGCAndCCLogsToFile(mIdentifier, mDumpAllTraces, + mDumpChildProcesses, this); + return NS_OK; + } + + NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override { + return NS_OK; + } + + NS_IMETHOD OnFinish() override { return NS_OK; } + + private: + ~GCAndCCLogDumpRunnable() = default; + + const nsString mIdentifier; + const bool mDumpAllTraces; + const bool mDumpChildProcesses; +}; + +NS_IMPL_ISUPPORTS_INHERITED(GCAndCCLogDumpRunnable, Runnable, + nsIDumpGCAndCCLogsCallback) + +} // namespace + +#if defined(MOZ_SUPPORTS_RT_SIGNALS) // { +namespace { + +/* + * The following code supports dumping about:memory upon receiving a signal. + * + * We listen for the following signals: + * + * - SIGRTMIN: Dump our memory reporters (and those of our child + * processes), + * - SIGRTMIN + 1: Dump our memory reporters (and those of our child + * processes) after minimizing memory usage, and + * - SIGRTMIN + 2: Dump the GC and CC logs in this and our child processes. + * + * When we receive one of these signals, we write the signal number to a pipe. + * The IO thread then notices that the pipe has been written to, and kicks off + * the appropriate task on the main thread. + * + * This scheme is similar to using signalfd(), except it's portable and it + * doesn't require the use of sigprocmask, which is problematic because it + * masks signals received by child processes. + * + * In theory, we could use Chromium's MessageLoopForIO::CatchSignal() for this. + * But that uses libevent, which does not handle the realtime signals (bug + * 794074). + */ + +// It turns out that at least on some systems, SIGRTMIN is not a compile-time +// constant, so these have to be set at runtime. +static uint8_t sDumpAboutMemorySignum; // SIGRTMIN +static uint8_t sDumpAboutMemoryAfterMMUSignum; // SIGRTMIN + 1 +static uint8_t sGCAndCCDumpSignum; // SIGRTMIN + 2 + +void doMemoryReport(const uint8_t aRecvSig) { + // Dump our memory reports (but run this on the main thread!). + bool minimize = aRecvSig == sDumpAboutMemoryAfterMMUSignum; + LOG("SignalWatcher(sig %d) dispatching memory report runnable.", aRecvSig); + RefPtr runnable = + new DumpMemoryInfoToTempDirRunnable(/* identifier = */ u""_ns, + /* anonymize = */ false, minimize); + NS_DispatchToMainThread(runnable); +} + +void doGCCCDump(const uint8_t aRecvSig) { + LOG("SignalWatcher(sig %d) dispatching GC/CC log runnable.", aRecvSig); + // Dump GC and CC logs (from the main thread). + RefPtr runnable = + new GCAndCCLogDumpRunnable(/* identifier = */ u""_ns, + /* allTraces = */ true, + /* dumpChildProcesses = */ true); + NS_DispatchToMainThread(runnable); +} + +} // namespace +#endif // MOZ_SUPPORTS_RT_SIGNALS } + +#if defined(MOZ_SUPPORTS_FIFO) // { +namespace { + +void doMemoryReport(const nsCString& aInputStr) { + bool minimize = aInputStr.EqualsLiteral("minimize memory report"); + LOG("FifoWatcher(command:%s) dispatching memory report runnable.", + aInputStr.get()); + RefPtr runnable = + new DumpMemoryInfoToTempDirRunnable(/* identifier = */ u""_ns, + /* anonymize = */ false, minimize); + NS_DispatchToMainThread(runnable); +} + +void doGCCCDump(const nsCString& aInputStr) { + bool doAllTracesGCCCDump = aInputStr.EqualsLiteral("gc log"); + LOG("FifoWatcher(command:%s) dispatching GC/CC log runnable.", + aInputStr.get()); + RefPtr runnable = new GCAndCCLogDumpRunnable( + /* identifier = */ u""_ns, doAllTracesGCCCDump, + /* dumpChildProcesses = */ true); + NS_DispatchToMainThread(runnable); +} + +bool SetupFifo() { +# ifdef DEBUG + static bool fifoCallbacksRegistered = false; +# endif + + if (!FifoWatcher::MaybeCreate()) { + return false; + } + + MOZ_ASSERT(!fifoCallbacksRegistered, + "FifoWatcher callbacks should be registered only once"); + + FifoWatcher* fw = FifoWatcher::GetSingleton(); + // Dump our memory reports (but run this on the main thread!). + fw->RegisterCallback("memory report"_ns, doMemoryReport); + fw->RegisterCallback("minimize memory report"_ns, doMemoryReport); + // Dump GC and CC logs (from the main thread). + fw->RegisterCallback("gc log"_ns, doGCCCDump); + fw->RegisterCallback("abbreviated gc log"_ns, doGCCCDump); + +# ifdef DEBUG + fifoCallbacksRegistered = true; +# endif + return true; +} + +void OnFifoEnabledChange(const char* /*unused*/, void* /*unused*/) { + LOG("%s changed", FifoWatcher::kPrefName); + if (SetupFifo()) { + Preferences::UnregisterCallback(OnFifoEnabledChange, + FifoWatcher::kPrefName); + } +} + +} // namespace +#endif // MOZ_SUPPORTS_FIFO } + +NS_IMPL_ISUPPORTS(nsMemoryInfoDumper, nsIMemoryInfoDumper) + +nsMemoryInfoDumper::nsMemoryInfoDumper() = default; + +nsMemoryInfoDumper::~nsMemoryInfoDumper() = default; + +/* static */ +void nsMemoryInfoDumper::Initialize() { +#if defined(MOZ_SUPPORTS_RT_SIGNALS) + SignalPipeWatcher* sw = SignalPipeWatcher::GetSingleton(); + + // Dump memory reporters (and those of our child processes) + sDumpAboutMemorySignum = SIGRTMIN; + sw->RegisterCallback(sDumpAboutMemorySignum, doMemoryReport); + // Dump our memory reporters after minimizing memory usage + sDumpAboutMemoryAfterMMUSignum = SIGRTMIN + 1; + sw->RegisterCallback(sDumpAboutMemoryAfterMMUSignum, doMemoryReport); + // Dump the GC and CC logs in this and our child processes. + sGCAndCCDumpSignum = SIGRTMIN + 2; + sw->RegisterCallback(sGCAndCCDumpSignum, doGCCCDump); +#endif + +#if defined(MOZ_SUPPORTS_FIFO) + if (!SetupFifo()) { + // NB: This gets loaded early enough that it's possible there is a user pref + // set to enable the fifo watcher that has not been loaded yet. Register + // to attempt to initialize if the fifo watcher becomes enabled by + // a user pref. + Preferences::RegisterCallback(OnFifoEnabledChange, FifoWatcher::kPrefName); + } +#endif +} + +static void EnsureNonEmptyIdentifier(nsAString& aIdentifier) { + if (!aIdentifier.IsEmpty()) { + return; + } + + // If the identifier is empty, set it to the number of whole seconds since the + // epoch. This identifier will appear in the files that this process + // generates and also the files generated by this process's children, allowing + // us to identify which files are from the same memory report request. + aIdentifier.AppendInt(static_cast(PR_Now()) / 1000000); +} + +// Use XPCOM refcounting to fire |onFinish| when all reference-holders +// (remote dump actors or the |DumpGCAndCCLogsToFile| activation itself) +// have gone away. +class nsDumpGCAndCCLogsCallbackHolder final + : public nsIDumpGCAndCCLogsCallback { + public: + NS_DECL_ISUPPORTS + + explicit nsDumpGCAndCCLogsCallbackHolder( + nsIDumpGCAndCCLogsCallback* aCallback) + : mCallback(aCallback) {} + + NS_IMETHOD OnFinish() override { return NS_ERROR_UNEXPECTED; } + + NS_IMETHOD OnDump(nsIFile* aGCLog, nsIFile* aCCLog, bool aIsParent) override { + return mCallback->OnDump(aGCLog, aCCLog, aIsParent); + } + + private: + ~nsDumpGCAndCCLogsCallbackHolder() { Unused << mCallback->OnFinish(); } + + nsCOMPtr mCallback; +}; + +NS_IMPL_ISUPPORTS(nsDumpGCAndCCLogsCallbackHolder, nsIDumpGCAndCCLogsCallback) + +NS_IMETHODIMP +nsMemoryInfoDumper::DumpGCAndCCLogsToFile( + const nsAString& aIdentifier, bool aDumpAllTraces, bool aDumpChildProcesses, + nsIDumpGCAndCCLogsCallback* aCallback) { + nsString identifier(aIdentifier); + EnsureNonEmptyIdentifier(identifier); + nsCOMPtr callbackHolder = + new nsDumpGCAndCCLogsCallbackHolder(aCallback); + + if (aDumpChildProcesses) { + nsTArray children; + ContentParent::GetAll(children); + for (uint32_t i = 0; i < children.Length(); i++) { + ContentParent* cp = children[i]; + nsCOMPtr logSink = + nsCycleCollector_createLogSink(); + + logSink->SetFilenameIdentifier(identifier); + logSink->SetProcessIdentifier(cp->Pid()); + + Unused << cp->CycleCollectWithLogs(aDumpAllTraces, logSink, + callbackHolder); + } + } + + nsCOMPtr logger = nsCycleCollector_createLogger(); + + if (aDumpAllTraces) { + nsCOMPtr allTracesLogger; + logger->AllTraces(getter_AddRefs(allTracesLogger)); + logger = allTracesLogger; + } + + nsCOMPtr logSink; + logger->GetLogSink(getter_AddRefs(logSink)); + + logSink->SetFilenameIdentifier(identifier); + + nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, logger); + + nsCOMPtr gcLog, ccLog; + logSink->GetGcLog(getter_AddRefs(gcLog)); + logSink->GetCcLog(getter_AddRefs(ccLog)); + callbackHolder->OnDump(gcLog, ccLog, /* parent = */ true); + + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryInfoDumper::DumpGCAndCCLogsToSink(bool aDumpAllTraces, + nsICycleCollectorLogSink* aSink) { + nsCOMPtr logger = nsCycleCollector_createLogger(); + + if (aDumpAllTraces) { + nsCOMPtr allTracesLogger; + logger->AllTraces(getter_AddRefs(allTracesLogger)); + logger = allTracesLogger; + } + + logger->SetLogSink(aSink); + + nsJSContext::CycleCollectNow(CCReason::DUMP_HEAP, logger); + + return NS_OK; +} + +static void MakeFilename(const char* aPrefix, const nsAString& aIdentifier, + int aPid, const char* aSuffix, nsACString& aResult) { + aResult = + nsPrintfCString("%s-%s-%d.%s", aPrefix, + NS_ConvertUTF16toUTF8(aIdentifier).get(), aPid, aSuffix); +} + +// This class wraps GZFileWriter so it can be used with JSONWriter, overcoming +// the following two problems: +// - It provides a JSONWriterFunc::Write() that calls nsGZFileWriter::Write(). +// - It can be stored as a UniquePtr, whereas nsGZFileWriter is refcounted. +class GZWriterWrapper final : public JSONWriteFunc { + public: + explicit GZWriterWrapper(nsGZFileWriter* aGZWriter) : mGZWriter(aGZWriter) {} + + void Write(const Span& aStr) final { + // Ignore any failure because JSONWriteFunc doesn't have a mechanism for + // handling errors. + Unused << mGZWriter->Write(aStr.data(), aStr.size()); + } + + nsresult Finish() { return mGZWriter->Finish(); } + + private: + RefPtr mGZWriter; +}; + +// We need two callbacks: one that handles reports, and one that is called at +// the end of reporting. Both the callbacks need access to the same JSONWriter, +// so we implement both of them in this one class. +class HandleReportAndFinishReportingCallbacks final + : public nsIHandleReportCallback, + public nsIFinishReportingCallback { + public: + NS_DECL_ISUPPORTS + + HandleReportAndFinishReportingCallbacks( + UniquePtr aWriter, nsIFinishDumpingCallback* aFinishDumping, + nsISupports* aFinishDumpingData) + : mWriter(std::move(aWriter)), + mFinishDumping(aFinishDumping), + mFinishDumpingData(aFinishDumpingData) {} + + // This is the callback for nsIHandleReportCallback. + NS_IMETHOD Callback(const nsACString& aProcess, const nsACString& aPath, + int32_t aKind, int32_t aUnits, int64_t aAmount, + const nsACString& aDescription, + nsISupports* aData) override { + nsAutoCString process; + if (aProcess.IsEmpty()) { + // If the process is empty, the report originated with the process doing + // the dumping. In that case, generate the process identifier, which is + // of the form "$PROCESS_NAME (pid $PID)", or just "(pid $PID)" if we + // don't have a process name. If we're the main process, we let + // $PROCESS_NAME be "Main Process". + // + // `appendAboutMemoryMain()` in aboutMemory.js does much the same thing + // for live memory reports. + if (XRE_IsParentProcess()) { + // We're the main process. + process.AssignLiteral("Main Process"); + } else if (ContentChild* cc = ContentChild::GetSingleton()) { + // Try to get the process name from ContentChild. + cc->GetProcessName(process); + } + ContentChild::AppendProcessId(process); + + } else { + // Otherwise, the report originated with another process and already has a + // process name. Just use that. + process = aProcess; + } + + mWriter->StartObjectElement(); + { + mWriter->StringProperty("process", process); + mWriter->StringProperty("path", PromiseFlatCString(aPath)); + mWriter->IntProperty("kind", aKind); + mWriter->IntProperty("units", aUnits); + mWriter->IntProperty("amount", aAmount); + mWriter->StringProperty("description", PromiseFlatCString(aDescription)); + } + mWriter->EndObject(); + + return NS_OK; + } + + // This is the callback for nsIFinishReportingCallback. + NS_IMETHOD Callback(nsISupports* aData) override { + mWriter->EndArray(); // end of "reports" array + mWriter->End(); + + // The call to Finish() deallocates the memory allocated by the first Write + // call. Because that memory was live while the memory reporters ran and + // was measured by them -- by "heap-allocated" if nothing else -- we want + // DMD to see it as well. So we deliberately don't call Finish() until + // after DMD finishes. + nsresult rv = static_cast(mWriter->WriteFunc()).Finish(); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mFinishDumping) { + return NS_OK; + } + + return mFinishDumping->Callback(mFinishDumpingData); + } + + private: + ~HandleReportAndFinishReportingCallbacks() = default; + + UniquePtr mWriter; + nsCOMPtr mFinishDumping; + nsCOMPtr mFinishDumpingData; +}; + +NS_IMPL_ISUPPORTS(HandleReportAndFinishReportingCallbacks, + nsIHandleReportCallback, nsIFinishReportingCallback) + +class TempDirFinishCallback final : public nsIFinishDumpingCallback { + public: + NS_DECL_ISUPPORTS + + TempDirFinishCallback(nsIFile* aReportsTmpFile, + const nsCString& aReportsFinalFilename) + : mReportsTmpFile(aReportsTmpFile), + mReportsFilename(aReportsFinalFilename) {} + + NS_IMETHOD Callback(nsISupports* aData) override { + // Rename the memory reports file, now that we're done writing all the + // files. Its final name is "memory-report<-identifier>-.json.gz". + + nsCOMPtr reportsFinalFile; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, + getter_AddRefs(reportsFinalFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifdef ANDROID + rv = reportsFinalFile->AppendNative("memory-reports"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + + rv = reportsFinalFile->AppendNative(mReportsFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = reportsFinalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoString reportsFinalFilename; + rv = reportsFinalFile->GetLeafName(reportsFinalFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mReportsTmpFile->MoveTo(/* directory */ nullptr, reportsFinalFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Write a message to the console. + + nsCOMPtr cs = + do_GetService(NS_CONSOLESERVICE_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString path; + mReportsTmpFile->GetPath(path); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString msg = u"nsIMemoryInfoDumper dumped reports to "_ns; + msg.Append(path); + return cs->LogStringMessage(msg.get()); + } + + private: + ~TempDirFinishCallback() = default; + + nsCOMPtr mReportsTmpFile; + nsCString mReportsFilename; +}; + +NS_IMPL_ISUPPORTS(TempDirFinishCallback, nsIFinishDumpingCallback) + +static nsresult DumpMemoryInfoToFile(nsIFile* aReportsFile, + nsIFinishDumpingCallback* aFinishDumping, + nsISupports* aFinishDumpingData, + bool aAnonymize, bool aMinimizeMemoryUsage, + nsAString& aDMDIdentifier) { + RefPtr gzWriter = new nsGZFileWriter(); + nsresult rv = gzWriter->Init(aReportsFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + auto jsonWriter = + MakeUnique(MakeUnique(gzWriter)); + + nsCOMPtr mgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + + // This is the first write to the file, and it causes |aWriter| to allocate + // over 200 KiB of memory. + jsonWriter->Start(); + { + // Increment this number if the format changes. + jsonWriter->IntProperty("version", 1); + jsonWriter->BoolProperty("hasMozMallocUsableSize", + mgr->GetHasMozMallocUsableSize()); + jsonWriter->StartArrayProperty("reports"); + } + + RefPtr + handleReportAndFinishReporting = + new HandleReportAndFinishReportingCallbacks( + std::move(jsonWriter), aFinishDumping, aFinishDumpingData); + rv = mgr->GetReportsExtended( + handleReportAndFinishReporting, nullptr, handleReportAndFinishReporting, + nullptr, aAnonymize, aMinimizeMemoryUsage, aDMDIdentifier); + return rv; +} + +NS_IMETHODIMP +nsMemoryInfoDumper::DumpMemoryReportsToNamedFile( + const nsAString& aFilename, nsIFinishDumpingCallback* aFinishDumping, + nsISupports* aFinishDumpingData, bool aAnonymize, + bool aMinimizeMemoryUsage) { + MOZ_ASSERT(!aFilename.IsEmpty()); + + // Create the file. + + nsCOMPtr reportsFile; + nsresult rv = NS_NewLocalFile(aFilename, false, getter_AddRefs(reportsFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + reportsFile->InitWithPath(aFilename); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + bool exists; + rv = reportsFile->Exists(&exists); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!exists) { + rv = reportsFile->Create(nsIFile::NORMAL_FILE_TYPE, 0644); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + nsString dmdIdent; + return DumpMemoryInfoToFile(reportsFile, aFinishDumping, aFinishDumpingData, + aAnonymize, aMinimizeMemoryUsage, dmdIdent); +} + +NS_IMETHODIMP +nsMemoryInfoDumper::DumpMemoryInfoToTempDir(const nsAString& aIdentifier, + bool aAnonymize, + bool aMinimizeMemoryUsage) { + nsString identifier(aIdentifier); + EnsureNonEmptyIdentifier(identifier); + + // Open a new file named something like + // + // incomplete-memory-report--.json.gz + // + // in NS_OS_TEMP_DIR for writing. When we're finished writing the report, + // we'll rename this file and get rid of the "incomplete-" prefix. + // + // We do this because we don't want scripts which poll the filesystem + // looking for memory report dumps to grab a file before we're finished + // writing to it. + + // The "unified" indicates that we merge the memory reports from all + // processes and write out one file, rather than a separate file for + // each process as was the case before bug 946407. This is so that + // the get_about_memory.py script in the B2G repository can + // determine when it's done waiting for files to appear. + nsCString reportsFinalFilename; + MakeFilename("unified-memory-report", identifier, getpid(), "json.gz", + reportsFinalFilename); + + nsCOMPtr reportsTmpFile; + nsresult rv; + // In Android case, this function will open a file named aFilename under + // specific folder (/data/local/tmp/memory-reports). Otherwise, it will + // open a file named aFilename under "NS_OS_TEMP_DIR". + rv = nsDumpUtils::OpenTempFile("incomplete-"_ns + reportsFinalFilename, + getter_AddRefs(reportsTmpFile), + "memory-reports"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + RefPtr finishDumping = + new TempDirFinishCallback(reportsTmpFile, reportsFinalFilename); + + return DumpMemoryInfoToFile(reportsTmpFile, finishDumping, nullptr, + aAnonymize, aMinimizeMemoryUsage, identifier); +} + +#ifdef MOZ_DMD +dmd::DMDFuncs::Singleton dmd::DMDFuncs::sSingleton; + +nsresult nsMemoryInfoDumper::OpenDMDFile(const nsAString& aIdentifier, int aPid, + FILE** aOutFile) { + if (!dmd::IsRunning()) { + *aOutFile = nullptr; + return NS_OK; + } + + // Create a filename like dmd--.json.gz, which will be used + // if DMD is enabled. + nsCString dmdFilename; + MakeFilename("dmd", aIdentifier, aPid, "json.gz", dmdFilename); + + // Open a new DMD file named |dmdFilename| in NS_OS_TEMP_DIR for writing, + // and dump DMD output to it. This must occur after the memory reporters + // have been run (above), but before the memory-reports file has been + // renamed (so scripts can detect the DMD file, if present). + + nsresult rv; + nsCOMPtr dmdFile; + rv = nsDumpUtils::OpenTempFile(dmdFilename, getter_AddRefs(dmdFile), + "memory-reports"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = dmdFile->OpenANSIFileDesc("wb", aOutFile); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "OpenANSIFileDesc failed"); + + // Print the path, because on some platforms (e.g. Mac) it's not obvious. + dmd::StatusMsg("opened %s for writing\n", dmdFile->HumanReadablePath().get()); + + return rv; +} + +nsresult nsMemoryInfoDumper::DumpDMDToFile(FILE* aFile) { + RefPtr gzWriter = new nsGZFileWriter(); + nsresult rv = gzWriter->InitANSIFileDesc(aFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Dump DMD's memory reports analysis to the file. + dmd::Analyze(MakeUnique(gzWriter)); + + rv = gzWriter->Finish(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Finish failed"); + return rv; +} +#endif // MOZ_DMD diff --git a/xpcom/base/nsMemoryInfoDumper.h b/xpcom/base/nsMemoryInfoDumper.h new file mode 100644 index 0000000000..68f6d46de7 --- /dev/null +++ b/xpcom/base/nsMemoryInfoDumper.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +#ifndef mozilla_nsMemoryInfoDumper_h +#define mozilla_nsMemoryInfoDumper_h + +#include "nsIMemoryInfoDumper.h" +#include + +/** + * This class facilitates dumping information about our memory usage to disk. + * + * Its cpp file also has Linux-only code which watches various OS signals and + * dumps memory info upon receiving a signal. You can activate these listeners + * by calling Initialize(). + */ +class nsMemoryInfoDumper : public nsIMemoryInfoDumper { + virtual ~nsMemoryInfoDumper(); + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYINFODUMPER + + nsMemoryInfoDumper(); + + static void Initialize(); + +#ifdef MOZ_DMD + // Open an appropriately named file for a DMD report. If DMD is + // disabled, return a null FILE* instead. + static nsresult OpenDMDFile(const nsAString& aIdentifier, int aPid, + FILE** aOutFile); + // Write a DMD report to the given file and close it. + static nsresult DumpDMDToFile(FILE* aFile); +#endif +}; + +#define NS_MEMORY_INFO_DUMPER_CID \ + { \ + 0x00bd71fb, 0x7f09, 0x4ec3, { \ + 0x96, 0xaf, 0xa0, 0xb5, 0x22, 0xb7, 0x79, 0x69 \ + } \ + } + +#endif diff --git a/xpcom/base/nsMemoryReporterManager.cpp b/xpcom/base/nsMemoryReporterManager.cpp new file mode 100644 index 0000000000..b3763d8f01 --- /dev/null +++ b/xpcom/base/nsMemoryReporterManager.cpp @@ -0,0 +1,2916 @@ +/* -*- 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 "nsMemoryReporterManager.h" + +#include "nsAtomTable.h" +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "nsPrintfCString.h" +#include "nsProxyRelease.h" +#include "nsServiceManagerUtils.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" +#include "nsPIDOMWindow.h" +#include "nsIObserverService.h" +#include "nsIOService.h" +#include "nsIGlobalObject.h" +#include "nsIXPConnect.h" +#ifdef MOZ_GECKO_PROFILER +# include "GeckoProfilerReporter.h" +#endif +#if defined(XP_UNIX) || defined(MOZ_DMD) +# include "nsMemoryInfoDumper.h" +#endif +#include "nsNetCID.h" +#include "nsThread.h" +#include "VRProcessManager.h" +#include "mozilla/Attributes.h" +#include "mozilla/MemoryReportingProcess.h" +#include "mozilla/PodOperations.h" +#include "mozilla/Preferences.h" +#include "mozilla/RDDProcessManager.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/dom/MemoryReportTypes.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/ipc/UtilityProcessManager.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#ifdef MOZ_PHC +# include "replace_malloc_bridge.h" +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/java/GeckoAppShellWrappers.h" +# include "mozilla/jni/Utils.h" +#endif + +#ifdef XP_WIN +# include "mozilla/MemoryInfo.h" + +# include +# ifndef getpid +# define getpid _getpid +# endif +#else +# include +#endif + +using namespace mozilla; +using namespace mozilla::ipc; +using namespace dom; + +#if defined(XP_LINUX) + +# include "mozilla/MemoryMapping.h" + +# include +# include +# include + +[[nodiscard]] static nsresult GetProcSelfStatmField(int aField, int64_t* aN) { + // There are more than two fields, but we're only interested in the first + // two. + static const int MAX_FIELD = 2; + size_t fields[MAX_FIELD]; + MOZ_ASSERT(aField < MAX_FIELD, "bad field number"); + FILE* f = fopen("/proc/self/statm", "r"); + if (f) { + int nread = fscanf(f, "%zu %zu", &fields[0], &fields[1]); + fclose(f); + if (nread == MAX_FIELD) { + *aN = fields[aField] * getpagesize(); + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +[[nodiscard]] static nsresult GetProcSelfSmapsPrivate(int64_t* aN, pid_t aPid) { + // You might be tempted to calculate USS by subtracting the "shared" value + // from the "resident" value in /proc//statm. But at least on Linux, + // statm's "shared" value actually counts pages backed by files, which has + // little to do with whether the pages are actually shared. /proc/self/smaps + // on the other hand appears to give us the correct information. + + nsTArray mappings(1024); + MOZ_TRY(GetMemoryMappings(mappings, aPid)); + + int64_t amount = 0; + for (auto& mapping : mappings) { + amount += mapping.Private_Clean(); + amount += mapping.Private_Dirty(); + } + *aN = amount; + return NS_OK; +} + +# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) { + return GetProcSelfStatmField(0, aN); +} + +[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) { + return GetProcSelfStatmField(1, aN); +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmount(aN); +} + +# define HAVE_RESIDENT_UNIQUE_REPORTER 1 +[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount( + int64_t* aN, pid_t aPid = 0) { + return GetProcSelfSmapsPrivate(aN, aPid); +} + +# ifdef HAVE_MALLINFO +# define HAVE_SYSTEM_HEAP_REPORTER 1 +[[nodiscard]] static nsresult SystemHeapSize(int64_t* aSizeOut) { + struct mallinfo info = mallinfo(); + + // The documentation in the glibc man page makes it sound like |uordblks| + // would suffice, but that only gets the small allocations that are put in + // the brk heap. We need |hblkhd| as well to get the larger allocations + // that are mmapped. + // + // The fields in |struct mallinfo| are all |int|, , so it is + // unreliable if memory usage gets high. However, the system heap size on + // Linux should usually be zero (so long as jemalloc is enabled) so that + // shouldn't be a problem. Nonetheless, cast the |int|s to |size_t| before + // adding them to provide a small amount of extra overflow protection. + *aSizeOut = size_t(info.hblkhd) + size_t(info.uordblks); + return NS_OK; +} +# endif + +#elif defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || \ + defined(__OpenBSD__) || defined(__FreeBSD_kernel__) + +# include +# include +# if defined(__DragonFly__) || defined(__FreeBSD__) || \ + defined(__FreeBSD_kernel__) +# include +# endif + +# include + +# if defined(__NetBSD__) +# undef KERN_PROC +# define KERN_PROC KERN_PROC2 +# define KINFO_PROC struct kinfo_proc2 +# else +# define KINFO_PROC struct kinfo_proc +# endif + +# if defined(__DragonFly__) +# define KP_SIZE(kp) (kp.kp_vm_map_size) +# define KP_RSS(kp) (kp.kp_vm_rssize * getpagesize()) +# elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) +# define KP_SIZE(kp) (kp.ki_size) +# define KP_RSS(kp) (kp.ki_rssize * getpagesize()) +# elif defined(__NetBSD__) +# define KP_SIZE(kp) (kp.p_vm_msize * getpagesize()) +# define KP_RSS(kp) (kp.p_vm_rssize * getpagesize()) +# elif defined(__OpenBSD__) +# define KP_SIZE(kp) \ + ((kp.p_vm_dsize + kp.p_vm_ssize + kp.p_vm_tsize) * getpagesize()) +# define KP_RSS(kp) (kp.p_vm_rssize * getpagesize()) +# endif + +[[nodiscard]] static nsresult GetKinfoProcSelf(KINFO_PROC* aProc) { +# if defined(__OpenBSD__) && defined(MOZ_SANDBOX) + static LazyLogModule sPledgeLog("SandboxPledge"); + MOZ_LOG(sPledgeLog, LogLevel::Debug, + ("%s called when pledged, returning NS_ERROR_FAILURE\n", __func__)); + return NS_ERROR_FAILURE; +# endif + int mib[] = { + CTL_KERN, + KERN_PROC, + KERN_PROC_PID, + getpid(), +# if defined(__NetBSD__) || defined(__OpenBSD__) + sizeof(KINFO_PROC), + 1, +# endif + }; + u_int miblen = sizeof(mib) / sizeof(mib[0]); + size_t size = sizeof(KINFO_PROC); + if (sysctl(mib, miblen, aProc, &size, nullptr, 0)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) { + KINFO_PROC proc; + nsresult rv = GetKinfoProcSelf(&proc); + if (NS_SUCCEEDED(rv)) { + *aN = KP_SIZE(proc); + } + return rv; +} + +[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) { + KINFO_PROC proc; + nsresult rv = GetKinfoProcSelf(&proc); + if (NS_SUCCEEDED(rv)) { + *aN = KP_RSS(proc); + } + return rv; +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmount(aN); +} + +# ifdef __FreeBSD__ +# include +# include + +[[nodiscard]] static nsresult GetKinfoVmentrySelf(int64_t* aPrss, + uint64_t* aMaxreg) { + int cnt; + struct kinfo_vmentry* vmmap; + struct kinfo_vmentry* kve; + if (!(vmmap = kinfo_getvmmap(getpid(), &cnt))) { + return NS_ERROR_FAILURE; + } + if (aPrss) { + *aPrss = 0; + } + if (aMaxreg) { + *aMaxreg = 0; + } + + for (int i = 0; i < cnt; i++) { + kve = &vmmap[i]; + if (aPrss) { + *aPrss += kve->kve_private_resident; + } + if (aMaxreg) { + *aMaxreg = std::max(*aMaxreg, kve->kve_end - kve->kve_start); + } + } + + free(vmmap); + return NS_OK; +} + +# define HAVE_PRIVATE_REPORTER 1 +[[nodiscard]] static nsresult PrivateDistinguishedAmount(int64_t* aN) { + int64_t priv; + nsresult rv = GetKinfoVmentrySelf(&priv, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + *aN = priv * getpagesize(); + return NS_OK; +} + +# define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1 +[[nodiscard]] static nsresult VsizeMaxContiguousDistinguishedAmount( + int64_t* aN) { + uint64_t biggestRegion; + nsresult rv = GetKinfoVmentrySelf(nullptr, &biggestRegion); + if (NS_SUCCEEDED(rv)) { + *aN = biggestRegion; + } + return NS_OK; +} +# endif // FreeBSD + +#elif defined(SOLARIS) + +# include +# include +# include + +static void XMappingIter(int64_t& aVsize, int64_t& aResident, + int64_t& aShared) { + aVsize = -1; + aResident = -1; + aShared = -1; + int mapfd = open("/proc/self/xmap", O_RDONLY); + struct stat st; + prxmap_t* prmapp = nullptr; + if (mapfd >= 0) { + if (!fstat(mapfd, &st)) { + int nmap = st.st_size / sizeof(prxmap_t); + while (1) { + // stat(2) on /proc//xmap returns an incorrect value, + // prior to the release of Solaris 11. + // Here is a workaround for it. + nmap *= 2; + prmapp = (prxmap_t*)malloc((nmap + 1) * sizeof(prxmap_t)); + if (!prmapp) { + // out of memory + break; + } + int n = pread(mapfd, prmapp, (nmap + 1) * sizeof(prxmap_t), 0); + if (n < 0) { + break; + } + if (nmap >= n / sizeof(prxmap_t)) { + aVsize = 0; + aResident = 0; + aShared = 0; + for (int i = 0; i < n / sizeof(prxmap_t); i++) { + aVsize += prmapp[i].pr_size; + aResident += prmapp[i].pr_rss * prmapp[i].pr_pagesize; + if (prmapp[i].pr_mflags & MA_SHARED) { + aShared += prmapp[i].pr_rss * prmapp[i].pr_pagesize; + } + } + break; + } + free(prmapp); + } + free(prmapp); + } + close(mapfd); + } +} + +# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) { + int64_t vsize, resident, shared; + XMappingIter(vsize, resident, shared); + if (vsize == -1) { + return NS_ERROR_FAILURE; + } + *aN = vsize; + return NS_OK; +} + +[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) { + int64_t vsize, resident, shared; + XMappingIter(vsize, resident, shared); + if (resident == -1) { + return NS_ERROR_FAILURE; + } + *aN = resident; + return NS_OK; +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmount(aN); +} + +# define HAVE_RESIDENT_UNIQUE_REPORTER 1 +[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount(int64_t* aN) { + int64_t vsize, resident, shared; + XMappingIter(vsize, resident, shared); + if (resident == -1) { + return NS_ERROR_FAILURE; + } + *aN = resident - shared; + return NS_OK; +} + +#elif defined(XP_MACOSX) + +# include +# include +# include +# include +# include + +[[nodiscard]] static bool GetTaskBasicInfo(struct task_basic_info* aTi) { + mach_msg_type_number_t count = TASK_BASIC_INFO_COUNT; + kern_return_t kr = + task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t)aTi, &count); + return kr == KERN_SUCCESS; +} + +// The VSIZE figure on Mac includes huge amounts of shared memory and is always +// absurdly high, eg. 2GB+ even at start-up. But both 'top' and 'ps' report +// it, so we might as well too. +# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) { + task_basic_info ti; + if (!GetTaskBasicInfo(&ti)) { + return NS_ERROR_FAILURE; + } + *aN = ti.virtual_size; + return NS_OK; +} + +// If we're using jemalloc on Mac, we need to instruct jemalloc to purge the +// pages it has madvise(MADV_FREE)'d before we read our RSS in order to get +// an accurate result. The OS will take away MADV_FREE'd pages when there's +// memory pressure, so ideally, they shouldn't count against our RSS. +// +// Purging these pages can take a long time for some users (see bug 789975), +// so we provide the option to get the RSS without purging first. +[[nodiscard]] static nsresult ResidentDistinguishedAmountHelper(int64_t* aN, + bool aDoPurge) { +# ifdef HAVE_JEMALLOC_STATS + if (aDoPurge) { + Telemetry::AutoTimer timer; + jemalloc_purge_freed_pages(); + } +# endif + + task_basic_info ti; + if (!GetTaskBasicInfo(&ti)) { + return NS_ERROR_FAILURE; + } + *aN = ti.resident_size; + return NS_OK; +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ false); +} + +[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmountHelper(aN, /* doPurge = */ true); +} + +# define HAVE_RESIDENT_UNIQUE_REPORTER 1 + +static bool InSharedRegion(mach_vm_address_t aAddr, cpu_type_t aType) { + mach_vm_address_t base; + mach_vm_address_t size; + + switch (aType) { + case CPU_TYPE_ARM: + base = SHARED_REGION_BASE_ARM; + size = SHARED_REGION_SIZE_ARM; + break; + case CPU_TYPE_ARM64: + base = SHARED_REGION_BASE_ARM64; + size = SHARED_REGION_SIZE_ARM64; + break; + case CPU_TYPE_I386: + base = SHARED_REGION_BASE_I386; + size = SHARED_REGION_SIZE_I386; + break; + case CPU_TYPE_X86_64: + base = SHARED_REGION_BASE_X86_64; + size = SHARED_REGION_SIZE_X86_64; + break; + default: + return false; + } + + return base <= aAddr && aAddr < (base + size); +} + +[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount( + int64_t* aN, mach_port_t aPort = 0) { + if (!aN) { + return NS_ERROR_FAILURE; + } + + cpu_type_t cpu_type; + size_t len = sizeof(cpu_type); + if (sysctlbyname("sysctl.proc_cputype", &cpu_type, &len, NULL, 0) != 0) { + return NS_ERROR_FAILURE; + } + + // Roughly based on libtop_update_vm_regions in + // http://www.opensource.apple.com/source/top/top-100.1.2/libtop.c + size_t privatePages = 0; + mach_vm_size_t topSize = 0; + for (mach_vm_address_t addr = MACH_VM_MIN_ADDRESS;; addr += topSize) { + vm_region_top_info_data_t topInfo; + mach_msg_type_number_t topInfoCount = VM_REGION_TOP_INFO_COUNT; + mach_port_t topObjectName; + + kern_return_t kr = mach_vm_region( + aPort ? aPort : mach_task_self(), &addr, &topSize, VM_REGION_TOP_INFO, + reinterpret_cast(&topInfo), &topInfoCount, + &topObjectName); + if (kr == KERN_INVALID_ADDRESS) { + // Done iterating VM regions. + break; + } else if (kr != KERN_SUCCESS) { + return NS_ERROR_FAILURE; + } + + if (InSharedRegion(addr, cpu_type) && topInfo.share_mode != SM_PRIVATE) { + continue; + } + + switch (topInfo.share_mode) { + case SM_LARGE_PAGE: + // NB: Large pages are not shareable and always resident. + case SM_PRIVATE: + privatePages += topInfo.private_pages_resident; + privatePages += topInfo.shared_pages_resident; + break; + case SM_COW: + privatePages += topInfo.private_pages_resident; + if (topInfo.ref_count == 1) { + // Treat copy-on-write pages as private if they only have one + // reference. + privatePages += topInfo.shared_pages_resident; + } + break; + case SM_SHARED: { + // Using mprotect() or similar to protect a page in the middle of a + // mapping can create aliased mappings. They look like shared mappings + // to the VM_REGION_TOP_INFO interface, so re-check with + // VM_REGION_EXTENDED_INFO. + + mach_vm_size_t exSize = 0; + vm_region_extended_info_data_t exInfo; + mach_msg_type_number_t exInfoCount = VM_REGION_EXTENDED_INFO_COUNT; + mach_port_t exObjectName; + kr = mach_vm_region(aPort ? aPort : mach_task_self(), &addr, &exSize, + VM_REGION_EXTENDED_INFO, + reinterpret_cast(&exInfo), + &exInfoCount, &exObjectName); + if (kr == KERN_INVALID_ADDRESS) { + // Done iterating VM regions. + break; + } else if (kr != KERN_SUCCESS) { + return NS_ERROR_FAILURE; + } + + if (exInfo.share_mode == SM_PRIVATE_ALIASED) { + privatePages += exInfo.pages_resident; + } + break; + } + default: + break; + } + } + + vm_size_t pageSize; + if (host_page_size(aPort ? aPort : mach_task_self(), &pageSize) != + KERN_SUCCESS) { + pageSize = PAGE_SIZE; + } + + *aN = privatePages * pageSize; + return NS_OK; +} + +[[nodiscard]] static nsresult PhysicalFootprintAmount(int64_t* aN, + mach_port_t aPort = 0) { + MOZ_ASSERT(aN); + + // The phys_footprint value (introduced in 10.11) of the TASK_VM_INFO data + // matches the value in the 'Memory' column of the Activity Monitor. + task_vm_info_data_t task_vm_info; + mach_msg_type_number_t count = TASK_VM_INFO_COUNT; + kern_return_t kr = task_info(aPort ? aPort : mach_task_self(), TASK_VM_INFO, + (task_info_t)&task_vm_info, &count); + if (kr != KERN_SUCCESS) { + return NS_ERROR_FAILURE; + } + + *aN = task_vm_info.phys_footprint; + return NS_OK; +} + +#elif defined(XP_WIN) + +# include +# include +# include + +# define HAVE_VSIZE_AND_RESIDENT_REPORTERS 1 +[[nodiscard]] static nsresult VsizeDistinguishedAmount(int64_t* aN) { + MEMORYSTATUSEX s; + s.dwLength = sizeof(s); + + if (!GlobalMemoryStatusEx(&s)) { + return NS_ERROR_FAILURE; + } + + *aN = s.ullTotalVirtual - s.ullAvailVirtual; + return NS_OK; +} + +[[nodiscard]] static nsresult ResidentDistinguishedAmount(int64_t* aN) { + PROCESS_MEMORY_COUNTERS pmc; + pmc.cb = sizeof(PROCESS_MEMORY_COUNTERS); + + if (!GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) { + return NS_ERROR_FAILURE; + } + + *aN = pmc.WorkingSetSize; + return NS_OK; +} + +[[nodiscard]] static nsresult ResidentFastDistinguishedAmount(int64_t* aN) { + return ResidentDistinguishedAmount(aN); +} + +# define HAVE_RESIDENT_UNIQUE_REPORTER 1 + +[[nodiscard]] static nsresult ResidentUniqueDistinguishedAmount( + int64_t* aN, HANDLE aProcess = nullptr) { + // Determine how many entries we need. + PSAPI_WORKING_SET_INFORMATION tmp; + DWORD tmpSize = sizeof(tmp); + memset(&tmp, 0, tmpSize); + + HANDLE proc = aProcess ? aProcess : GetCurrentProcess(); + QueryWorkingSet(proc, &tmp, tmpSize); + + // Fudge the size in case new entries are added between calls. + size_t entries = tmp.NumberOfEntries * 2; + + if (!entries) { + return NS_ERROR_FAILURE; + } + + DWORD infoArraySize = tmpSize + (entries * sizeof(PSAPI_WORKING_SET_BLOCK)); + UniqueFreePtr infoArray( + static_cast(malloc(infoArraySize))); + + if (!infoArray) { + return NS_ERROR_FAILURE; + } + + if (!QueryWorkingSet(proc, infoArray.get(), infoArraySize)) { + return NS_ERROR_FAILURE; + } + + entries = static_cast(infoArray->NumberOfEntries); + size_t privatePages = 0; + for (size_t i = 0; i < entries; i++) { + // Count shared pages that only one process is using as private. + if (!infoArray->WorkingSetInfo[i].Shared || + infoArray->WorkingSetInfo[i].ShareCount <= 1) { + privatePages++; + } + } + + SYSTEM_INFO si; + GetSystemInfo(&si); + + *aN = privatePages * si.dwPageSize; + return NS_OK; +} + +# define HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER 1 +[[nodiscard]] static nsresult VsizeMaxContiguousDistinguishedAmount( + int64_t* aN) { + SIZE_T biggestRegion = 0; + MEMORY_BASIC_INFORMATION vmemInfo = {0}; + for (size_t currentAddress = 0;;) { + if (!VirtualQuery((LPCVOID)currentAddress, &vmemInfo, sizeof(vmemInfo))) { + // Something went wrong, just return whatever we've got already. + break; + } + + if (vmemInfo.State == MEM_FREE) { + biggestRegion = std::max(biggestRegion, vmemInfo.RegionSize); + } + + SIZE_T lastAddress = currentAddress; + currentAddress += vmemInfo.RegionSize; + + // If we overflow, we've examined all of the address space. + if (currentAddress < lastAddress) { + break; + } + } + + *aN = biggestRegion; + return NS_OK; +} + +# define HAVE_PRIVATE_REPORTER 1 +[[nodiscard]] static nsresult PrivateDistinguishedAmount(int64_t* aN) { + PROCESS_MEMORY_COUNTERS_EX pmcex; + pmcex.cb = sizeof(PROCESS_MEMORY_COUNTERS_EX); + + if (!GetProcessMemoryInfo(GetCurrentProcess(), + (PPROCESS_MEMORY_COUNTERS)&pmcex, sizeof(pmcex))) { + return NS_ERROR_FAILURE; + } + + *aN = pmcex.PrivateUsage; + return NS_OK; +} + +# define HAVE_SYSTEM_HEAP_REPORTER 1 +// Windows can have multiple separate heaps, but we should not touch non-default +// heaps because they may be destroyed at anytime while we hold a handle. So we +// count only the default heap. +[[nodiscard]] static nsresult SystemHeapSize(int64_t* aSizeOut) { + HANDLE heap = GetProcessHeap(); + + NS_ENSURE_TRUE(HeapLock(heap), NS_ERROR_FAILURE); + + int64_t heapSize = 0; + PROCESS_HEAP_ENTRY entry; + entry.lpData = nullptr; + while (HeapWalk(heap, &entry)) { + // We don't count entry.cbOverhead, because we just want to measure the + // space available to the program. + if (entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) { + heapSize += entry.cbData; + } + } + + // Check this result only after unlocking the heap, so that we don't leave + // the heap locked if there was an error. + DWORD lastError = GetLastError(); + + // I have no idea how things would proceed if unlocking this heap failed... + NS_ENSURE_TRUE(HeapUnlock(heap), NS_ERROR_FAILURE); + + NS_ENSURE_TRUE(lastError == ERROR_NO_MORE_ITEMS, NS_ERROR_FAILURE); + + *aSizeOut = heapSize; + return NS_OK; +} + +struct SegmentKind { + DWORD mState; + DWORD mType; + DWORD mProtect; + int mIsStack; +}; + +struct SegmentEntry : public PLDHashEntryHdr { + static PLDHashNumber HashKey(const void* aKey) { + auto kind = static_cast(aKey); + return mozilla::HashGeneric(kind->mState, kind->mType, kind->mProtect, + kind->mIsStack); + } + + static bool MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey) { + auto kind = static_cast(aKey); + auto entry = static_cast(aEntry); + return kind->mState == entry->mKind.mState && + kind->mType == entry->mKind.mType && + kind->mProtect == entry->mKind.mProtect && + kind->mIsStack == entry->mKind.mIsStack; + } + + static void InitEntry(PLDHashEntryHdr* aEntry, const void* aKey) { + auto kind = static_cast(aKey); + auto entry = static_cast(aEntry); + entry->mKind = *kind; + entry->mCount = 0; + entry->mSize = 0; + } + + static const PLDHashTableOps Ops; + + SegmentKind mKind; // The segment kind. + uint32_t mCount; // The number of segments of this kind. + size_t mSize; // The combined size of segments of this kind. +}; + +/* static */ const PLDHashTableOps SegmentEntry::Ops = { + SegmentEntry::HashKey, SegmentEntry::MatchEntry, + PLDHashTable::MoveEntryStub, PLDHashTable::ClearEntryStub, + SegmentEntry::InitEntry}; + +class WindowsAddressSpaceReporter final : public nsIMemoryReporter { + ~WindowsAddressSpaceReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + // First iterate over all the segments and record how many of each kind + // there were and their aggregate sizes. We use a hash table for this + // because there are a couple of dozen different kinds possible. + + PLDHashTable table(&SegmentEntry::Ops, sizeof(SegmentEntry)); + MEMORY_BASIC_INFORMATION info = {0}; + bool isPrevSegStackGuard = false; + for (size_t currentAddress = 0;;) { + if (!VirtualQuery((LPCVOID)currentAddress, &info, sizeof(info))) { + // Something went wrong, just return whatever we've got already. + break; + } + + size_t size = info.RegionSize; + + // Note that |type| and |protect| are ignored in some cases. + DWORD state = info.State; + DWORD type = + (state == MEM_RESERVE || state == MEM_COMMIT) ? info.Type : 0; + DWORD protect = (state == MEM_COMMIT) ? info.Protect : 0; + bool isStack = isPrevSegStackGuard && state == MEM_COMMIT && + type == MEM_PRIVATE && protect == PAGE_READWRITE; + + SegmentKind kind = {state, type, protect, isStack ? 1 : 0}; + auto entry = + static_cast(table.Add(&kind, mozilla::fallible)); + if (entry) { + entry->mCount += 1; + entry->mSize += size; + } + + isPrevSegStackGuard = info.State == MEM_COMMIT && + info.Type == MEM_PRIVATE && + info.Protect == (PAGE_READWRITE | PAGE_GUARD); + + size_t lastAddress = currentAddress; + currentAddress += size; + + // If we overflow, we've examined all of the address space. + if (currentAddress < lastAddress) { + break; + } + } + + // Then iterate over the hash table and report the details for each segment + // kind. + + for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // For each range of pages, we consider one or more of its State, Type + // and Protect values. These are documented at + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366775%28v=vs.85%29.aspx + // (for State and Type) and + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa366786%28v=vs.85%29.aspx + // (for Protect). + // + // Not all State values have accompanying Type and Protection values. + bool doType = false; + bool doProtect = false; + + auto entry = static_cast(iter.Get()); + + nsCString path("address-space"); + + switch (entry->mKind.mState) { + case MEM_FREE: + path.AppendLiteral("/free"); + break; + + case MEM_RESERVE: + path.AppendLiteral("/reserved"); + doType = true; + break; + + case MEM_COMMIT: + path.AppendLiteral("/commit"); + doType = true; + doProtect = true; + break; + + default: + // Should be impossible, but handle it just in case. + path.AppendLiteral("/???"); + break; + } + + if (doType) { + switch (entry->mKind.mType) { + case MEM_IMAGE: + path.AppendLiteral("/image"); + break; + + case MEM_MAPPED: + path.AppendLiteral("/mapped"); + break; + + case MEM_PRIVATE: + path.AppendLiteral("/private"); + break; + + default: + // Should be impossible, but handle it just in case. + path.AppendLiteral("/???"); + break; + } + } + + if (doProtect) { + DWORD protect = entry->mKind.mProtect; + // Basic attributes. Exactly one of these should be set. + if (protect & PAGE_EXECUTE) { + path.AppendLiteral("/execute"); + } + if (protect & PAGE_EXECUTE_READ) { + path.AppendLiteral("/execute-read"); + } + if (protect & PAGE_EXECUTE_READWRITE) { + path.AppendLiteral("/execute-readwrite"); + } + if (protect & PAGE_EXECUTE_WRITECOPY) { + path.AppendLiteral("/execute-writecopy"); + } + if (protect & PAGE_NOACCESS) { + path.AppendLiteral("/noaccess"); + } + if (protect & PAGE_READONLY) { + path.AppendLiteral("/readonly"); + } + if (protect & PAGE_READWRITE) { + path.AppendLiteral("/readwrite"); + } + if (protect & PAGE_WRITECOPY) { + path.AppendLiteral("/writecopy"); + } + + // Modifiers. At most one of these should be set. + if (protect & PAGE_GUARD) { + path.AppendLiteral("+guard"); + } + if (protect & PAGE_NOCACHE) { + path.AppendLiteral("+nocache"); + } + if (protect & PAGE_WRITECOMBINE) { + path.AppendLiteral("+writecombine"); + } + + // Annotate likely stack segments, too. + if (entry->mKind.mIsStack) { + path.AppendLiteral("+stack"); + } + } + + // Append the segment count. + path.AppendPrintf("(segments=%u)", entry->mCount); + + aHandleReport->Callback(""_ns, path, KIND_OTHER, UNITS_BYTES, + entry->mSize, "From MEMORY_BASIC_INFORMATION."_ns, + aData); + } + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(WindowsAddressSpaceReporter, nsIMemoryReporter) + +#endif // XP_ + +#ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER +class VsizeMaxContiguousReporter final : public nsIMemoryReporter { + ~VsizeMaxContiguousReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount; + if (NS_SUCCEEDED(VsizeMaxContiguousDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "vsize-max-contiguous", KIND_OTHER, UNITS_BYTES, amount, + "Size of the maximum contiguous block of available virtual memory."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(VsizeMaxContiguousReporter, nsIMemoryReporter) +#endif + +#ifdef HAVE_PRIVATE_REPORTER +class PrivateReporter final : public nsIMemoryReporter { + ~PrivateReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount; + if (NS_SUCCEEDED(PrivateDistinguishedAmount(&amount))) { + // clang-format off + MOZ_COLLECT_REPORT( + "private", KIND_OTHER, UNITS_BYTES, amount, +"Memory that cannot be shared with other processes, including memory that is " +"committed and marked MEM_PRIVATE, data that is not mapped, and executable " +"pages that have been written to."); + // clang-format on + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PrivateReporter, nsIMemoryReporter) +#endif + +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS +class VsizeReporter final : public nsIMemoryReporter { + ~VsizeReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount; + if (NS_SUCCEEDED(VsizeDistinguishedAmount(&amount))) { + // clang-format off + MOZ_COLLECT_REPORT( + "vsize", KIND_OTHER, UNITS_BYTES, amount, +"Memory mapped by the process, including code and data segments, the heap, " +"thread stacks, memory explicitly mapped by the process via mmap and similar " +"operations, and memory shared with other processes. This is the vsize figure " +"as reported by 'top' and 'ps'. This figure is of limited use on Mac, where " +"processes share huge amounts of memory with one another. But even on other " +"operating systems, 'resident' is a much better measure of the memory " +"resources used by the process."); + // clang-format on + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(VsizeReporter, nsIMemoryReporter) + +class ResidentReporter final : public nsIMemoryReporter { + ~ResidentReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount; + if (NS_SUCCEEDED(ResidentDistinguishedAmount(&amount))) { + // clang-format off + MOZ_COLLECT_REPORT( + "resident", KIND_OTHER, UNITS_BYTES, amount, +"Memory mapped by the process that is present in physical memory, also known " +"as the resident set size (RSS). This is the best single figure to use when " +"considering the memory resources used by the process, but it depends both on " +"other processes being run and details of the OS kernel and so is best used " +"for comparing the memory usage of a single process at different points in " +"time."); + // clang-format on + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ResidentReporter, nsIMemoryReporter) + +#endif // HAVE_VSIZE_AND_RESIDENT_REPORTERS + +#ifdef HAVE_RESIDENT_UNIQUE_REPORTER +class ResidentUniqueReporter final : public nsIMemoryReporter { + ~ResidentUniqueReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount = 0; + // clang-format off + if (NS_SUCCEEDED(ResidentUniqueDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "resident-unique", KIND_OTHER, UNITS_BYTES, amount, +"Memory mapped by the process that is present in physical memory and not " +"shared with any other processes. This is also known as the process's unique " +"set size (USS). This is the amount of RAM we'd expect to be freed if we " +"closed this process."); + } +#ifdef XP_MACOSX + if (NS_SUCCEEDED(PhysicalFootprintAmount(&amount))) { + MOZ_COLLECT_REPORT( + "resident-phys-footprint", KIND_OTHER, UNITS_BYTES, amount, +"Memory footprint reported by MacOS's task_info API's phys_footprint field. " +"This matches the memory column in Activity Monitor."); + } +#endif + // clang-format on + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ResidentUniqueReporter, nsIMemoryReporter) + +#endif // HAVE_RESIDENT_UNIQUE_REPORTER + +#ifdef HAVE_SYSTEM_HEAP_REPORTER + +class SystemHeapReporter final : public nsIMemoryReporter { + ~SystemHeapReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount; + if (NS_SUCCEEDED(SystemHeapSize(&amount))) { + // clang-format off + MOZ_COLLECT_REPORT( + "system-heap-allocated", KIND_OTHER, UNITS_BYTES, amount, +"Memory used by the system allocator that is currently allocated to the " +"application. This is distinct from the jemalloc heap that Firefox uses for " +"most or all of its heap allocations. Ideally this number is zero, but " +"on some platforms we cannot force every heap allocation through jemalloc."); + // clang-format on + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(SystemHeapReporter, nsIMemoryReporter) +#endif // HAVE_SYSTEM_HEAP_REPORTER + +#ifdef XP_UNIX + +# include + +# define HAVE_RESIDENT_PEAK_REPORTER 1 + +[[nodiscard]] static nsresult ResidentPeakDistinguishedAmount(int64_t* aN) { + struct rusage usage; + if (0 == getrusage(RUSAGE_SELF, &usage)) { + // The units for ru_maxrrs: + // - Mac: bytes + // - Solaris: pages? But some sources it actually always returns 0, so + // check for that + // - Linux, {Net/Open/Free}BSD, DragonFly: KiB +# ifdef XP_MACOSX + *aN = usage.ru_maxrss; +# elif defined(SOLARIS) + *aN = usage.ru_maxrss * getpagesize(); +# else + *aN = usage.ru_maxrss * 1024; +# endif + if (*aN > 0) { + return NS_OK; + } + } + return NS_ERROR_FAILURE; +} + +class ResidentPeakReporter final : public nsIMemoryReporter { + ~ResidentPeakReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount = 0; + if (NS_SUCCEEDED(ResidentPeakDistinguishedAmount(&amount))) { + MOZ_COLLECT_REPORT( + "resident-peak", KIND_OTHER, UNITS_BYTES, amount, + "The peak 'resident' value for the lifetime of the process."); + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ResidentPeakReporter, nsIMemoryReporter) + +# define HAVE_PAGE_FAULT_REPORTERS 1 + +class PageFaultsSoftReporter final : public nsIMemoryReporter { + ~PageFaultsSoftReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + struct rusage usage; + int err = getrusage(RUSAGE_SELF, &usage); + if (err == 0) { + int64_t amount = usage.ru_minflt; + // clang-format off + MOZ_COLLECT_REPORT( + "page-faults-soft", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount, +"The number of soft page faults (also known as 'minor page faults') that " +"have occurred since the process started. A soft page fault occurs when the " +"process tries to access a page which is present in physical memory but is " +"not mapped into the process's address space. For instance, a process might " +"observe soft page faults when it loads a shared library which is already " +"present in physical memory. A process may experience many thousands of soft " +"page faults even when the machine has plenty of available physical memory, " +"and because the OS services a soft page fault without accessing the disk, " +"they impact performance much less than hard page faults."); + // clang-format on + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PageFaultsSoftReporter, nsIMemoryReporter) + +[[nodiscard]] static nsresult PageFaultsHardDistinguishedAmount( + int64_t* aAmount) { + struct rusage usage; + int err = getrusage(RUSAGE_SELF, &usage); + if (err != 0) { + return NS_ERROR_FAILURE; + } + *aAmount = usage.ru_majflt; + return NS_OK; +} + +class PageFaultsHardReporter final : public nsIMemoryReporter { + ~PageFaultsHardReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + int64_t amount = 0; + if (NS_SUCCEEDED(PageFaultsHardDistinguishedAmount(&amount))) { + // clang-format off + MOZ_COLLECT_REPORT( + "page-faults-hard", KIND_OTHER, UNITS_COUNT_CUMULATIVE, amount, +"The number of hard page faults (also known as 'major page faults') that have " +"occurred since the process started. A hard page fault occurs when a process " +"tries to access a page which is not present in physical memory. The " +"operating system must access the disk in order to fulfill a hard page fault. " +"When memory is plentiful, you should see very few hard page faults. But if " +"the process tries to use more memory than your machine has available, you " +"may see many thousands of hard page faults. Because accessing the disk is up " +"to a million times slower than accessing RAM, the program may run very " +"slowly when it is experiencing more than 100 or so hard page faults a " +"second."); + // clang-format on + } + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(PageFaultsHardReporter, nsIMemoryReporter) + +#endif // XP_UNIX + +/** + ** memory reporter implementation for jemalloc and OSX malloc, + ** to obtain info on total memory in use (that we know about, + ** at least -- on OSX, there are sometimes other zones in use). + **/ + +#ifdef HAVE_JEMALLOC_STATS + +static size_t HeapOverhead(const jemalloc_stats_t& aStats) { + return aStats.waste + aStats.bookkeeping + aStats.page_cache + + aStats.bin_unused; +} + +// This has UNITS_PERCENTAGE, so it is multiplied by 100x *again* on top of the +// 100x for the percentage. + +// static +int64_t nsMemoryReporterManager::HeapOverheadFraction( + const jemalloc_stats_t& aStats) { + size_t heapOverhead = HeapOverhead(aStats); + size_t heapCommitted = aStats.allocated + heapOverhead; + return int64_t(10000 * (heapOverhead / (double)heapCommitted)); +} + +class JemallocHeapReporter final : public nsIMemoryReporter { + ~JemallocHeapReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + jemalloc_stats_t stats; + const size_t num_bins = jemalloc_stats_num_bins(); + nsTArray bin_stats(num_bins); + bin_stats.SetLength(num_bins); + jemalloc_stats(&stats, bin_stats.Elements()); + + // clang-format off + MOZ_COLLECT_REPORT( + "heap-committed/allocated", KIND_OTHER, UNITS_BYTES, stats.allocated, +"Memory mapped by the heap allocator that is currently allocated to the " +"application. This may exceed the amount of memory requested by the " +"application because the allocator regularly rounds up request sizes. (The " +"exact amount requested is not recorded.)"); + + MOZ_COLLECT_REPORT( + "heap-allocated", KIND_OTHER, UNITS_BYTES, stats.allocated, +"The same as 'heap-committed/allocated'."); + + // We mark this and the other heap-overhead reporters as KIND_NONHEAP + // because KIND_HEAP memory means "counted in heap-allocated", which + // this is not. + for (auto& bin : bin_stats) { + MOZ_ASSERT(bin.size); + nsPrintfCString path("explicit/heap-overhead/bin-unused/bin-%zu", + bin.size); + aHandleReport->Callback(EmptyCString(), path, KIND_NONHEAP, UNITS_BYTES, + bin.bytes_unused, + nsLiteralCString( + "Unused bytes in all runs of all bins for this size class"), + aData); + } + + if (stats.waste > 0) { + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/waste", KIND_NONHEAP, UNITS_BYTES, + stats.waste, +"Committed bytes which do not correspond to an active allocation and which the " +"allocator is not intentionally keeping alive (i.e., not " +"'explicit/heap-overhead/{bookkeeping,page-cache,bin-unused}')."); + } + + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/bookkeeping", KIND_NONHEAP, UNITS_BYTES, + stats.bookkeeping, +"Committed bytes which the heap allocator uses for internal data structures."); + + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/page-cache", KIND_NONHEAP, UNITS_BYTES, + stats.page_cache, +"Memory which the allocator could return to the operating system, but hasn't. " +"The allocator keeps this memory around as an optimization, so it doesn't " +"have to ask the OS the next time it needs to fulfill a request. This value " +"is typically not larger than a few megabytes."); + + MOZ_COLLECT_REPORT( + "heap-committed/overhead", KIND_OTHER, UNITS_BYTES, + HeapOverhead(stats), +"The sum of 'explicit/heap-overhead/*'."); + + MOZ_COLLECT_REPORT( + "heap-mapped", KIND_OTHER, UNITS_BYTES, stats.mapped, +"Amount of memory currently mapped. Includes memory that is uncommitted, i.e. " +"neither in physical memory nor paged to disk."); + + MOZ_COLLECT_REPORT( + "heap-chunksize", KIND_OTHER, UNITS_BYTES, stats.chunksize, + "Size of chunks."); + +#ifdef MOZ_PHC + mozilla::phc::MemoryUsage usage; + ReplaceMalloc::PHCMemoryUsage(usage); + + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/phc/metadata", KIND_NONHEAP, UNITS_BYTES, + usage.mMetadataBytes, +"Memory used by PHC to store stacks and other metadata for each allocation"); + MOZ_COLLECT_REPORT( + "explicit/heap-overhead/phc/fragmentation", KIND_NONHEAP, UNITS_BYTES, + usage.mFragmentationBytes, +"The amount of memory lost due to rounding up allocations to the next page " +"size. " +"This is also known as 'internal fragmentation'. " +"Note that all allocators have some internal fragmentation, there may still " +"be some internal fragmentation without PHC."); +#endif + + // clang-format on + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(JemallocHeapReporter, nsIMemoryReporter) + +#endif // HAVE_JEMALLOC_STATS + +// Why is this here? At first glance, you'd think it could be defined and +// registered with nsMemoryReporterManager entirely within nsAtomTable.cpp. +// However, the obvious time to register it is when the table is initialized, +// and that happens before XPCOM components are initialized, which means the +// RegisterStrongMemoryReporter call fails. So instead we do it here. +class AtomTablesReporter final : public nsIMemoryReporter { + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + + ~AtomTablesReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + AtomsSizes sizes; + NS_AddSizeOfAtoms(MallocSizeOf, sizes); + + MOZ_COLLECT_REPORT("explicit/atoms/table", KIND_HEAP, UNITS_BYTES, + sizes.mTable, "Memory used by the atom table."); + + MOZ_COLLECT_REPORT( + "explicit/atoms/dynamic-objects-and-chars", KIND_HEAP, UNITS_BYTES, + sizes.mDynamicAtoms, + "Memory used by dynamic atom objects and chars (which are stored " + "at the end of each atom object)."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(AtomTablesReporter, nsIMemoryReporter) + +class ThreadsReporter final : public nsIMemoryReporter { + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + ~ThreadsReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { +#ifdef XP_LINUX + nsTArray mappings(1024); + MOZ_TRY(GetMemoryMappings(mappings)); +#endif + + // Enumerating over active threads requires holding a lock, so we collect + // info on all threads, and then call our reporter callbacks after releasing + // the lock. + struct ThreadData { + nsCString mName; + uint32_t mThreadId; + size_t mPrivateSize; + }; + AutoTArray threads; + + size_t eventQueueSizes = 0; + size_t wrapperSizes = 0; + size_t threadCount = 0; + + for (auto* thread : nsThread::Enumerate()) { + threadCount++; + eventQueueSizes += thread->SizeOfEventQueues(MallocSizeOf); + wrapperSizes += thread->ShallowSizeOfIncludingThis(MallocSizeOf); + + if (!thread->StackBase()) { + continue; + } + +#if defined(XP_LINUX) + int idx = mappings.BinaryIndexOf(thread->StackBase()); + if (idx < 0) { + continue; + } + // Referenced() is the combined size of all pages in the region which have + // ever been touched, and are therefore consuming memory. For stack + // regions, these pages are guaranteed to be un-shared unless we fork + // after creating threads (which we don't). + size_t privateSize = mappings[idx].Referenced(); + + // On Linux, we have to be very careful matching memory regions to thread + // stacks. + // + // To begin with, the kernel only reports VM stats for regions of all + // adjacent pages with the same flags, protection, and backing file. + // There's no way to get finer-grained usage information for a subset of + // those pages. + // + // Stack segments always have a guard page at the bottom of the stack + // (assuming we only support stacks that grow down), so there's no danger + // of them being merged with other stack regions. At the top, there's no + // protection page, and no way to allocate one without using pthreads + // directly and allocating our own stacks. So we get around the problem by + // adding an extra VM flag (NOHUGEPAGES) to our stack region, which we + // don't expect to be set on any heap regions. But this is not fool-proof. + // + // A second kink is that different C libraries (and different versions + // thereof) report stack base locations and sizes differently with regard + // to the guard page. For the libraries that include the guard page in the + // stack size base pointer, we need to adjust those values to compensate. + // But it's possible that our logic will get out of sync with library + // changes, or someone will compile with an unexpected library. + // + // + // The upshot of all of this is that there may be configurations that our + // special cases don't cover. And if there are, we want to know about it. + // So assert that total size of the memory region we're reporting actually + // matches the allocated size of the thread stack. +# ifndef ANDROID + MOZ_ASSERT(mappings[idx].Size() == thread->StackSize(), + "Mapping region size doesn't match stack allocation size"); +# endif +#elif defined(XP_WIN) + auto memInfo = MemoryInfo::Get(thread->StackBase(), thread->StackSize()); + size_t privateSize = memInfo.Committed(); +#else + size_t privateSize = thread->StackSize(); + MOZ_ASSERT_UNREACHABLE( + "Shouldn't have stack base pointer on this " + "platform"); +#endif + + nsCString threadName; + thread->GetThreadName(threadName); + threads.AppendElement(ThreadData{ + std::move(threadName), + thread->ThreadId(), + // On Linux, it's possible (but unlikely) that our stack region will + // have been merged with adjacent heap regions, in which case we'll + // get combined size information for both. So we take the minimum of + // the reported private size and the requested stack size to avoid the + // possible of majorly over-reporting in that case. + std::min(privateSize, thread->StackSize()), + }); + } + + for (auto& thread : threads) { + nsPrintfCString path("explicit/threads/stacks/%s (tid=%u)", + thread.mName.get(), thread.mThreadId); + + aHandleReport->Callback( + ""_ns, path, KIND_NONHEAP, UNITS_BYTES, thread.mPrivateSize, + nsLiteralCString("The sizes of thread stacks which have been " + "committed to memory."), + aData); + } + + MOZ_COLLECT_REPORT("explicit/threads/overhead/event-queues", KIND_HEAP, + UNITS_BYTES, eventQueueSizes, + "The sizes of nsThread event queues and observers."); + + MOZ_COLLECT_REPORT("explicit/threads/overhead/wrappers", KIND_HEAP, + UNITS_BYTES, wrapperSizes, + "The sizes of nsThread/PRThread wrappers."); + +#if defined(XP_WIN) + // Each thread on Windows has a fixed kernel overhead. For 32 bit Windows, + // that's 12K. For 64 bit, it's 24K. + // + // See + // https://blogs.technet.microsoft.com/markrussinovich/2009/07/05/pushing-the-limits-of-windows-processes-and-threads/ + constexpr size_t kKernelSize = (sizeof(void*) == 8 ? 24 : 12) * 1024; +#elif defined(XP_LINUX) + // On Linux, kernel stacks are usually 8K. However, on x86, they are + // allocated virtually, and start out at 4K. They may grow to 8K, but we + // have no way of knowing which ones do, so all we can do is guess. +# if defined(__x86_64__) || defined(__i386__) + constexpr size_t kKernelSize = 4 * 1024; +# else + constexpr size_t kKernelSize = 8 * 1024; +# endif +#elif defined(XP_MACOSX) + // On Darwin, kernel stacks are 16K: + // + // https://books.google.com/books?id=K8vUkpOXhN4C&lpg=PA513&dq=mach%20kernel%20thread%20stack%20size&pg=PA513#v=onepage&q=mach%20kernel%20thread%20stack%20size&f=false + constexpr size_t kKernelSize = 16 * 1024; +#else + // Elsewhere, just assume that kernel stacks require at least 8K. + constexpr size_t kKernelSize = 8 * 1024; +#endif + + MOZ_COLLECT_REPORT("explicit/threads/overhead/kernel", KIND_NONHEAP, + UNITS_BYTES, threadCount * kKernelSize, + "The total kernel overhead for all active threads."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(ThreadsReporter, nsIMemoryReporter) + +#ifdef DEBUG + +// Ideally, this would be implemented in BlockingResourceBase.cpp. +// However, this ends up breaking the linking step of various unit tests due +// to adding a new dependency to libdmd for a commonly used feature (mutexes) +// in DMD builds. So instead we do it here. +class DeadlockDetectorReporter final : public nsIMemoryReporter { + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + + ~DeadlockDetectorReporter() = default; + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "explicit/deadlock-detector", KIND_HEAP, UNITS_BYTES, + BlockingResourceBase::SizeOfDeadlockDetector(MallocSizeOf), + "Memory used by the deadlock detector."); + + return NS_OK; + } +}; +NS_IMPL_ISUPPORTS(DeadlockDetectorReporter, nsIMemoryReporter) + +#endif + +#ifdef MOZ_DMD + +namespace mozilla { +namespace dmd { + +class DMDReporter final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override { + dmd::Sizes sizes; + dmd::SizeOf(&sizes); + + MOZ_COLLECT_REPORT( + "explicit/dmd/stack-traces/used", KIND_HEAP, UNITS_BYTES, + sizes.mStackTracesUsed, + "Memory used by stack traces which correspond to at least " + "one heap block DMD is tracking."); + + MOZ_COLLECT_REPORT( + "explicit/dmd/stack-traces/unused", KIND_HEAP, UNITS_BYTES, + sizes.mStackTracesUnused, + "Memory used by stack traces which don't correspond to any heap " + "blocks DMD is currently tracking."); + + MOZ_COLLECT_REPORT("explicit/dmd/stack-traces/table", KIND_HEAP, + UNITS_BYTES, sizes.mStackTraceTable, + "Memory used by DMD's stack trace table."); + + MOZ_COLLECT_REPORT("explicit/dmd/live-block-table", KIND_HEAP, UNITS_BYTES, + sizes.mLiveBlockTable, + "Memory used by DMD's live block table."); + + MOZ_COLLECT_REPORT("explicit/dmd/dead-block-list", KIND_HEAP, UNITS_BYTES, + sizes.mDeadBlockTable, + "Memory used by DMD's dead block list."); + + return NS_OK; + } + + private: + ~DMDReporter() = default; +}; +NS_IMPL_ISUPPORTS(DMDReporter, nsIMemoryReporter) + +} // namespace dmd +} // namespace mozilla + +#endif // MOZ_DMD + +#ifdef MOZ_WIDGET_ANDROID +class AndroidMemoryReporter final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + + AndroidMemoryReporter() = default; + + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + if (!jni::IsAvailable() || jni::GetAPIVersion() < 23) { + return NS_OK; + } + + int32_t heap = java::GeckoAppShell::GetMemoryUsage("summary.java-heap"_ns); + if (heap > 0) { + MOZ_COLLECT_REPORT("java-heap", KIND_OTHER, UNITS_BYTES, heap * 1024, + "The private Java Heap usage"); + } + return NS_OK; + } + + private: + ~AndroidMemoryReporter() = default; +}; + +NS_IMPL_ISUPPORTS(AndroidMemoryReporter, nsIMemoryReporter) +#endif + +/** + ** nsMemoryReporterManager implementation + **/ + +NS_IMPL_ISUPPORTS(nsMemoryReporterManager, nsIMemoryReporterManager, + nsIMemoryReporter) + +NS_IMETHODIMP +nsMemoryReporterManager::Init() { + if (!NS_IsMainThread()) { + MOZ_CRASH(); + } + + // Under normal circumstances this function is only called once. However, + // we've (infrequently) seen memory report dumps in crash reports that + // suggest that this function is sometimes called multiple times. That in + // turn means that multiple reporters of each kind are registered, which + // leads to duplicated reports of individual measurements such as "resident", + // "vsize", etc. + // + // It's unclear how these multiple calls can occur. The only plausible theory + // so far is badly-written extensions, because this function is callable from + // JS code via nsIMemoryReporter.idl. + // + // Whatever the cause, it's a bad thing. So we protect against it with the + // following check. + static bool isInited = false; + if (isInited) { + NS_WARNING("nsMemoryReporterManager::Init() has already been called!"); + return NS_OK; + } + isInited = true; + +#ifdef HAVE_JEMALLOC_STATS + RegisterStrongReporter(new JemallocHeapReporter()); +#endif + +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + RegisterStrongReporter(new VsizeReporter()); + RegisterStrongReporter(new ResidentReporter()); +#endif + +#ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER + RegisterStrongReporter(new VsizeMaxContiguousReporter()); +#endif + +#ifdef HAVE_RESIDENT_PEAK_REPORTER + RegisterStrongReporter(new ResidentPeakReporter()); +#endif + +#ifdef HAVE_RESIDENT_UNIQUE_REPORTER + RegisterStrongReporter(new ResidentUniqueReporter()); +#endif + +#ifdef HAVE_PAGE_FAULT_REPORTERS + RegisterStrongReporter(new PageFaultsSoftReporter()); + RegisterStrongReporter(new PageFaultsHardReporter()); +#endif + +#ifdef HAVE_PRIVATE_REPORTER + RegisterStrongReporter(new PrivateReporter()); +#endif + +#ifdef HAVE_SYSTEM_HEAP_REPORTER + RegisterStrongReporter(new SystemHeapReporter()); +#endif + + RegisterStrongReporter(new AtomTablesReporter()); + + RegisterStrongReporter(new ThreadsReporter()); + +#ifdef DEBUG + RegisterStrongReporter(new DeadlockDetectorReporter()); +#endif + +#ifdef MOZ_GECKO_PROFILER + // We have to register this here rather than in profiler_init() because + // profiler_init() runs prior to nsMemoryReporterManager's creation. + RegisterStrongReporter(new GeckoProfilerReporter()); +#endif + +#ifdef MOZ_DMD + RegisterStrongReporter(new mozilla::dmd::DMDReporter()); +#endif + +#ifdef XP_WIN + RegisterStrongReporter(new WindowsAddressSpaceReporter()); +#endif + +#ifdef MOZ_WIDGET_ANDROID + RegisterStrongReporter(new AndroidMemoryReporter()); +#endif + +#ifdef XP_UNIX + nsMemoryInfoDumper::Initialize(); +#endif + + // Report our own memory usage as well. + RegisterWeakReporter(this); + + return NS_OK; +} + +nsMemoryReporterManager::nsMemoryReporterManager() + : mMutex("nsMemoryReporterManager::mMutex"), + mIsRegistrationBlocked(false), + mStrongReporters(new StrongReportersTable()), + mWeakReporters(new WeakReportersTable()), + mSavedStrongReporters(nullptr), + mSavedWeakReporters(nullptr), + mNextGeneration(1), + mPendingProcessesState(nullptr), + mPendingReportersState(nullptr) +#ifdef HAVE_JEMALLOC_STATS + , + mThreadPool(do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID)) +#endif +{ +} + +nsMemoryReporterManager::~nsMemoryReporterManager() { + delete mStrongReporters; + delete mWeakReporters; + NS_ASSERTION(!mSavedStrongReporters, "failed to restore strong reporters"); + NS_ASSERTION(!mSavedWeakReporters, "failed to restore weak reporters"); +} + +NS_IMETHODIMP +nsMemoryReporterManager::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + size_t n = MallocSizeOf(this); + { + mozilla::MutexAutoLock autoLock(mMutex); + n += mStrongReporters->ShallowSizeOfIncludingThis(MallocSizeOf); + n += mWeakReporters->ShallowSizeOfIncludingThis(MallocSizeOf); + } + + MOZ_COLLECT_REPORT("explicit/memory-reporter-manager", KIND_HEAP, UNITS_BYTES, + n, "Memory used by the memory reporter infrastructure."); + + return NS_OK; +} + +#ifdef DEBUG_CHILD_PROCESS_MEMORY_REPORTING +# define MEMORY_REPORTING_LOG(format, ...) \ + printf_stderr("++++ MEMORY REPORTING: " format, ##__VA_ARGS__); +#else +# define MEMORY_REPORTING_LOG(...) +#endif + +NS_IMETHODIMP +nsMemoryReporterManager::GetReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, bool aAnonymize) { + return GetReportsExtended(aHandleReport, aHandleReportData, aFinishReporting, + aFinishReportingData, aAnonymize, + /* minimize = */ false, + /* DMDident = */ u""_ns); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetReportsExtended( + nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, bool aAnonymize, bool aMinimize, + const nsAString& aDMDDumpIdent) { + nsresult rv; + + // Memory reporters are not necessarily threadsafe, so this function must + // be called from the main thread. + if (!NS_IsMainThread()) { + MOZ_CRASH(); + } + + uint32_t generation = mNextGeneration++; + + if (mPendingProcessesState) { + // A request is in flight. Don't start another one. And don't report + // an error; just ignore it, and let the in-flight request finish. + MEMORY_REPORTING_LOG("GetReports (gen=%u, s->gen=%u): abort\n", generation, + mPendingProcessesState->mGeneration); + return NS_OK; + } + + MEMORY_REPORTING_LOG("GetReports (gen=%u)\n", generation); + + uint32_t concurrency = Preferences::GetUint("memory.report_concurrency", 1); + MOZ_ASSERT(concurrency >= 1); + if (concurrency < 1) { + concurrency = 1; + } + mPendingProcessesState = new PendingProcessesState( + generation, aAnonymize, aMinimize, concurrency, aHandleReport, + aHandleReportData, aFinishReporting, aFinishReportingData, aDMDDumpIdent); + + if (aMinimize) { + nsCOMPtr callback = + NewRunnableMethod("nsMemoryReporterManager::StartGettingReports", this, + &nsMemoryReporterManager::StartGettingReports); + rv = MinimizeMemoryUsage(callback); + } else { + rv = StartGettingReports(); + } + return rv; +} + +// MainThread only +nsresult nsMemoryReporterManager::StartGettingReports() { + PendingProcessesState* s = mPendingProcessesState; + nsresult rv; + + // Get reports for this process. + FILE* parentDMDFile = nullptr; +#ifdef MOZ_DMD + if (!s->mDMDDumpIdent.IsEmpty()) { + rv = nsMemoryInfoDumper::OpenDMDFile(s->mDMDDumpIdent, getpid(), + &parentDMDFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Proceed with the memory report as if DMD were disabled. + parentDMDFile = nullptr; + } + } +#endif + + // This is async. + GetReportsForThisProcessExtended( + s->mHandleReport, s->mHandleReportData, s->mAnonymize, parentDMDFile, + s->mFinishReporting, s->mFinishReportingData); + + nsTArray childWeakRefs; + dom::ContentParent::GetAll(childWeakRefs); + if (!childWeakRefs.IsEmpty()) { + // Request memory reports from child processes. This happens + // after the parent report so that the parent's main thread will + // be free to process the child reports, instead of causing them + // to be buffered and consume (possibly scarce) memory. + + for (size_t i = 0; i < childWeakRefs.Length(); ++i) { + s->mChildrenPending.AppendElement(childWeakRefs[i]); + } + } + + if (gfx::GPUProcessManager* gpu = gfx::GPUProcessManager::Get()) { + if (RefPtr proc = gpu->GetProcessMemoryReporter()) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + + if (RDDProcessManager* rdd = RDDProcessManager::Get()) { + if (RefPtr proc = rdd->GetProcessMemoryReporter()) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + + if (gfx::VRProcessManager* vr = gfx::VRProcessManager::Get()) { + if (RefPtr proc = vr->GetProcessMemoryReporter()) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + + if (!IsRegistrationBlocked() && net::gIOService) { + if (RefPtr proc = + net::gIOService->GetSocketProcessMemoryReporter()) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + + if (!IsRegistrationBlocked()) { + if (RefPtr utility = + UtilityProcessManager::GetIfExists()) { + for (RefPtr& parent : + utility->GetAllProcessesProcessParent()) { + if (RefPtr proc = + utility->GetProcessMemoryReporter(parent)) { + s->mChildrenPending.AppendElement(proc.forget()); + } + } + } + } + + if (!s->mChildrenPending.IsEmpty()) { + nsCOMPtr timer; + rv = NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), TimeoutCallback, this, kTimeoutLengthMS, + nsITimer::TYPE_ONE_SHOT, + "nsMemoryReporterManager::StartGettingReports"); + if (NS_WARN_IF(NS_FAILED(rv))) { + FinishReporting(); + return rv; + } + + MOZ_ASSERT(!s->mTimer); + s->mTimer.swap(timer); + } + + return NS_OK; +} + +void nsMemoryReporterManager::DispatchReporter( + nsIMemoryReporter* aReporter, bool aIsAsync, + nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, + bool aAnonymize) { + MOZ_ASSERT(mPendingReportersState); + + // Grab refs to everything used in the lambda function. + RefPtr self = this; + nsCOMPtr reporter = aReporter; + nsCOMPtr handleReport = aHandleReport; + nsCOMPtr handleReportData = aHandleReportData; + + nsCOMPtr event = NS_NewRunnableFunction( + "nsMemoryReporterManager::DispatchReporter", + [self, reporter, aIsAsync, handleReport, handleReportData, aAnonymize]() { + reporter->CollectReports(handleReport, handleReportData, aAnonymize); + if (!aIsAsync) { + self->EndReport(); + } + }); + + NS_DispatchToMainThread(event); + mPendingReportersState->mReportsPending++; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetReportsForThisProcessExtended( + nsIHandleReportCallback* aHandleReport, nsISupports* aHandleReportData, + bool aAnonymize, FILE* aDMDFile, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData) { + // Memory reporters are not necessarily threadsafe, so this function must + // be called from the main thread. + if (!NS_IsMainThread()) { + MOZ_CRASH(); + } + + if (NS_WARN_IF(mPendingReportersState)) { + // Report is already in progress. + return NS_ERROR_IN_PROGRESS; + } + +#ifdef MOZ_DMD + if (aDMDFile) { + // Clear DMD's reportedness state before running the memory + // reporters, to avoid spurious twice-reported warnings. + dmd::ClearReports(); + } +#else + MOZ_ASSERT(!aDMDFile); +#endif + + mPendingReportersState = new PendingReportersState( + aFinishReporting, aFinishReportingData, aDMDFile); + + { + mozilla::MutexAutoLock autoLock(mMutex); + + for (const auto& entry : *mStrongReporters) { + DispatchReporter(entry.GetKey(), entry.GetData(), aHandleReport, + aHandleReportData, aAnonymize); + } + + for (const auto& entry : *mWeakReporters) { + nsCOMPtr reporter = entry.GetKey(); + DispatchReporter(reporter, entry.GetData(), aHandleReport, + aHandleReportData, aAnonymize); + } + } + + return NS_OK; +} + +// MainThread only +NS_IMETHODIMP +nsMemoryReporterManager::EndReport() { + if (--mPendingReportersState->mReportsPending == 0) { +#ifdef MOZ_DMD + if (mPendingReportersState->mDMDFile) { + nsMemoryInfoDumper::DumpDMDToFile(mPendingReportersState->mDMDFile); + } +#endif + if (mPendingProcessesState) { + // This is the parent process. + EndProcessReport(mPendingProcessesState->mGeneration, true); + } else { + mPendingReportersState->mFinishReporting->Callback( + mPendingReportersState->mFinishReportingData); + } + + delete mPendingReportersState; + mPendingReportersState = nullptr; + } + + return NS_OK; +} + +nsMemoryReporterManager::PendingProcessesState* +nsMemoryReporterManager::GetStateForGeneration(uint32_t aGeneration) { + // Memory reporting only happens on the main thread. + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + PendingProcessesState* s = mPendingProcessesState; + + if (!s) { + // If we reach here, then: + // + // - A child process reported back too late, and no subsequent request + // is in flight. + // + // So there's nothing to be done. Just ignore it. + MEMORY_REPORTING_LOG("HandleChildReports: no request in flight (aGen=%u)\n", + aGeneration); + return nullptr; + } + + if (aGeneration != s->mGeneration) { + // If we reach here, a child process must have reported back, too late, + // while a subsequent (higher-numbered) request is in flight. Again, + // ignore it. + MOZ_ASSERT(aGeneration < s->mGeneration); + MEMORY_REPORTING_LOG( + "HandleChildReports: gen mismatch (aGen=%u, s->gen=%u)\n", aGeneration, + s->mGeneration); + return nullptr; + } + + return s; +} + +// This function has no return value. If something goes wrong, there's no +// clear place to report the problem to, but that's ok -- we will end up +// hitting the timeout and executing TimeoutCallback(). +void nsMemoryReporterManager::HandleChildReport( + uint32_t aGeneration, const dom::MemoryReport& aChildReport) { + PendingProcessesState* s = GetStateForGeneration(aGeneration); + if (!s) { + return; + } + + // Child reports should have a non-empty process. + MOZ_ASSERT(!aChildReport.process().IsEmpty()); + + // If the call fails, ignore and continue. + s->mHandleReport->Callback(aChildReport.process(), aChildReport.path(), + aChildReport.kind(), aChildReport.units(), + aChildReport.amount(), aChildReport.desc(), + s->mHandleReportData); +} + +/* static */ +bool nsMemoryReporterManager::StartChildReport( + mozilla::MemoryReportingProcess* aChild, + const PendingProcessesState* aState) { + if (!aChild->IsAlive()) { + MEMORY_REPORTING_LOG( + "StartChildReports (gen=%u): child exited before" + " its report was started\n", + aState->mGeneration); + return false; + } + + Maybe dmdFileDesc; +#ifdef MOZ_DMD + if (!aState->mDMDDumpIdent.IsEmpty()) { + FILE* dmdFile = nullptr; + nsresult rv = nsMemoryInfoDumper::OpenDMDFile(aState->mDMDDumpIdent, + aChild->Pid(), &dmdFile); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Proceed with the memory report as if DMD were disabled. + dmdFile = nullptr; + } + if (dmdFile) { + dmdFileDesc = Some(mozilla::ipc::FILEToFileDescriptor(dmdFile)); + fclose(dmdFile); + } + } +#endif + return aChild->SendRequestMemoryReport( + aState->mGeneration, aState->mAnonymize, aState->mMinimize, dmdFileDesc); +} + +void nsMemoryReporterManager::EndProcessReport(uint32_t aGeneration, + bool aSuccess) { + PendingProcessesState* s = GetStateForGeneration(aGeneration); + if (!s) { + return; + } + + MOZ_ASSERT(s->mNumProcessesRunning > 0); + s->mNumProcessesRunning--; + s->mNumProcessesCompleted++; + MEMORY_REPORTING_LOG( + "HandleChildReports (aGen=%u): process %u %s" + " (%u running, %u pending)\n", + aGeneration, s->mNumProcessesCompleted, + aSuccess ? "completed" : "exited during report", s->mNumProcessesRunning, + static_cast(s->mChildrenPending.Length())); + + // Start pending children up to the concurrency limit. + while (s->mNumProcessesRunning < s->mConcurrencyLimit && + !s->mChildrenPending.IsEmpty()) { + // Pop last element from s->mChildrenPending + const RefPtr nextChild = + s->mChildrenPending.PopLastElement(); + // Start report (if the child is still alive). + if (StartChildReport(nextChild, s)) { + ++s->mNumProcessesRunning; + MEMORY_REPORTING_LOG( + "HandleChildReports (aGen=%u): started child report" + " (%u running, %u pending)\n", + aGeneration, s->mNumProcessesRunning, + static_cast(s->mChildrenPending.Length())); + } + } + + // If all the child processes (if any) have reported, we can cancel + // the timer (if started) and finish up. Otherwise, just return. + if (s->mNumProcessesRunning == 0) { + MOZ_ASSERT(s->mChildrenPending.IsEmpty()); + if (s->mTimer) { + s->mTimer->Cancel(); + } + FinishReporting(); + } +} + +/* static */ +void nsMemoryReporterManager::TimeoutCallback(nsITimer* aTimer, void* aData) { + nsMemoryReporterManager* mgr = static_cast(aData); + PendingProcessesState* s = mgr->mPendingProcessesState; + + // Release assert because: if the pointer is null we're about to + // crash regardless of DEBUG, and this way the compiler doesn't + // complain about unused variables. + MOZ_RELEASE_ASSERT(s, "mgr->mPendingProcessesState"); + MEMORY_REPORTING_LOG("TimeoutCallback (s->gen=%u; %u running, %u pending)\n", + s->mGeneration, s->mNumProcessesRunning, + static_cast(s->mChildrenPending.Length())); + + // We don't bother sending any kind of cancellation message to the child + // processes that haven't reported back. + mgr->FinishReporting(); +} + +nsresult nsMemoryReporterManager::FinishReporting() { + // Memory reporting only happens on the main thread. + if (!NS_IsMainThread()) { + MOZ_CRASH(); + } + + MOZ_ASSERT(mPendingProcessesState); + MEMORY_REPORTING_LOG("FinishReporting (s->gen=%u; %u processes reported)\n", + mPendingProcessesState->mGeneration, + mPendingProcessesState->mNumProcessesCompleted); + + // Call this before deleting |mPendingProcessesState|. That way, if + // |mFinishReportData| calls GetReports(), it will silently abort, as + // required. + nsresult rv = mPendingProcessesState->mFinishReporting->Callback( + mPendingProcessesState->mFinishReportingData); + + delete mPendingProcessesState; + mPendingProcessesState = nullptr; + return rv; +} + +nsMemoryReporterManager::PendingProcessesState::PendingProcessesState( + uint32_t aGeneration, bool aAnonymize, bool aMinimize, + uint32_t aConcurrencyLimit, nsIHandleReportCallback* aHandleReport, + nsISupports* aHandleReportData, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, const nsAString& aDMDDumpIdent) + : mGeneration(aGeneration), + mAnonymize(aAnonymize), + mMinimize(aMinimize), + mChildrenPending(), + mNumProcessesRunning(1), // reporting starts with the parent + mNumProcessesCompleted(0), + mConcurrencyLimit(aConcurrencyLimit), + mHandleReport(aHandleReport), + mHandleReportData(aHandleReportData), + mFinishReporting(aFinishReporting), + mFinishReportingData(aFinishReportingData), + mDMDDumpIdent(aDMDDumpIdent) {} + +static void CrashIfRefcountIsZero(nsISupports* aObj) { + // This will probably crash if the object's refcount is 0. + uint32_t refcnt = NS_ADDREF(aObj); + if (refcnt <= 1) { + MOZ_CRASH("CrashIfRefcountIsZero: refcount is zero"); + } + NS_RELEASE(aObj); +} + +nsresult nsMemoryReporterManager::RegisterReporterHelper( + nsIMemoryReporter* aReporter, bool aForce, bool aStrong, bool aIsAsync) { + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + + if (mIsRegistrationBlocked && !aForce) { + return NS_ERROR_FAILURE; + } + + if (mStrongReporters->Contains(aReporter) || + mWeakReporters->Contains(aReporter)) { + return NS_ERROR_FAILURE; + } + + // If |aStrong| is true, |aReporter| may have a refcnt of 0, so we take + // a kung fu death grip before calling PutEntry. Otherwise, if PutEntry + // addref'ed and released |aReporter| before finally addref'ing it for + // good, it would free aReporter! The kung fu death grip could itself be + // problematic if PutEntry didn't addref |aReporter| (because then when the + // death grip goes out of scope, we would delete the reporter). In debug + // mode, we check that this doesn't happen. + // + // If |aStrong| is false, we require that |aReporter| have a non-zero + // refcnt. + // + if (aStrong) { + nsCOMPtr kungFuDeathGrip = aReporter; + mStrongReporters->InsertOrUpdate(aReporter, aIsAsync); + CrashIfRefcountIsZero(aReporter); + } else { + CrashIfRefcountIsZero(aReporter); + nsCOMPtr jsComponent = do_QueryInterface(aReporter); + if (jsComponent) { + // We cannot allow non-native reporters (WrappedJS), since we'll be + // holding onto a raw pointer, which would point to the wrapper, + // and that wrapper is likely to go away as soon as this register + // call finishes. This would then lead to subsequent crashes in + // CollectReports(). + return NS_ERROR_XPC_BAD_CONVERT_JS; + } + mWeakReporters->InsertOrUpdate(aReporter, aIsAsync); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterStrongReporter(nsIMemoryReporter* aReporter) { + return RegisterReporterHelper(aReporter, /* force = */ false, + /* strong = */ true, + /* async = */ false); +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterStrongAsyncReporter( + nsIMemoryReporter* aReporter) { + return RegisterReporterHelper(aReporter, /* force = */ false, + /* strong = */ true, + /* async = */ true); +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterWeakReporter(nsIMemoryReporter* aReporter) { + return RegisterReporterHelper(aReporter, /* force = */ false, + /* strong = */ false, + /* async = */ false); +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterWeakAsyncReporter( + nsIMemoryReporter* aReporter) { + return RegisterReporterHelper(aReporter, /* force = */ false, + /* strong = */ false, + /* async = */ true); +} + +NS_IMETHODIMP +nsMemoryReporterManager::RegisterStrongReporterEvenIfBlocked( + nsIMemoryReporter* aReporter) { + return RegisterReporterHelper(aReporter, /* force = */ true, + /* strong = */ true, + /* async = */ false); +} + +NS_IMETHODIMP +nsMemoryReporterManager::UnregisterStrongReporter( + nsIMemoryReporter* aReporter) { + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + + MOZ_ASSERT(!mWeakReporters->Contains(aReporter)); + + if (mStrongReporters->Contains(aReporter)) { + mStrongReporters->Remove(aReporter); + return NS_OK; + } + + // We don't register new reporters when the block is in place, but we do + // unregister existing reporters. This is so we don't keep holding strong + // references that these reporters aren't expecting (which can keep them + // alive longer than intended). + if (mSavedStrongReporters && mSavedStrongReporters->Contains(aReporter)) { + mSavedStrongReporters->Remove(aReporter); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMemoryReporterManager::UnregisterWeakReporter(nsIMemoryReporter* aReporter) { + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + + MOZ_ASSERT(!mStrongReporters->Contains(aReporter)); + + if (mWeakReporters->Contains(aReporter)) { + mWeakReporters->Remove(aReporter); + return NS_OK; + } + + // We don't register new reporters when the block is in place, but we do + // unregister existing reporters. This is so we don't keep holding weak + // references that the old reporters aren't expecting (which can end up as + // dangling pointers that lead to use-after-frees). + if (mSavedWeakReporters && mSavedWeakReporters->Contains(aReporter)) { + mSavedWeakReporters->Remove(aReporter); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsMemoryReporterManager::BlockRegistrationAndHideExistingReporters() { + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + if (mIsRegistrationBlocked) { + return NS_ERROR_FAILURE; + } + mIsRegistrationBlocked = true; + + // Hide the existing reporters, saving them for later restoration. + MOZ_ASSERT(!mSavedStrongReporters); + MOZ_ASSERT(!mSavedWeakReporters); + mSavedStrongReporters = mStrongReporters; + mSavedWeakReporters = mWeakReporters; + mStrongReporters = new StrongReportersTable(); + mWeakReporters = new WeakReportersTable(); + + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::UnblockRegistrationAndRestoreOriginalReporters() { + // This method is thread-safe. + mozilla::MutexAutoLock autoLock(mMutex); + if (!mIsRegistrationBlocked) { + return NS_ERROR_FAILURE; + } + + // Banish the current reporters, and restore the hidden ones. + delete mStrongReporters; + delete mWeakReporters; + mStrongReporters = mSavedStrongReporters; + mWeakReporters = mSavedWeakReporters; + mSavedStrongReporters = nullptr; + mSavedWeakReporters = nullptr; + + mIsRegistrationBlocked = false; + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetVsize(int64_t* aVsize) { +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + return VsizeDistinguishedAmount(aVsize); +#else + *aVsize = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetVsizeMaxContiguous(int64_t* aAmount) { +#ifdef HAVE_VSIZE_MAX_CONTIGUOUS_REPORTER + return VsizeMaxContiguousDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetResident(int64_t* aAmount) { +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + return ResidentDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetResidentFast(int64_t* aAmount) { +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + return ResidentFastDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +/*static*/ +int64_t nsMemoryReporterManager::ResidentFast() { +#ifdef HAVE_VSIZE_AND_RESIDENT_REPORTERS + int64_t amount; + nsresult rv = ResidentFastDistinguishedAmount(&amount); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +#else + return 0; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetResidentPeak(int64_t* aAmount) { +#ifdef HAVE_RESIDENT_PEAK_REPORTER + return ResidentPeakDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +/*static*/ +int64_t nsMemoryReporterManager::ResidentPeak() { +#ifdef HAVE_RESIDENT_PEAK_REPORTER + int64_t amount = 0; + nsresult rv = ResidentPeakDistinguishedAmount(&amount); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +#else + return 0; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetResidentUnique(int64_t* aAmount) { +#ifdef HAVE_RESIDENT_UNIQUE_REPORTER + return ResidentUniqueDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +#ifdef XP_MACOSX +/*static*/ +int64_t nsMemoryReporterManager::PhysicalFootprint(mach_port_t aPort) { + int64_t amount = 0; + nsresult rv = PhysicalFootprintAmount(&amount, aPort); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +} +#endif + +typedef +#ifdef XP_WIN + HANDLE +#elif XP_MACOSX + mach_port_t +#elif XP_LINUX + pid_t +#else + int /*dummy type */ +#endif + ResidentUniqueArg; + +#if defined(XP_WIN) || defined(XP_MACOSX) || defined(XP_LINUX) + +/*static*/ +int64_t nsMemoryReporterManager::ResidentUnique(ResidentUniqueArg aProcess) { + int64_t amount = 0; + nsresult rv = ResidentUniqueDistinguishedAmount(&amount, aProcess); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +} + +#else + +/*static*/ +int64_t nsMemoryReporterManager::ResidentUnique(ResidentUniqueArg) { +# ifdef HAVE_RESIDENT_UNIQUE_REPORTER + int64_t amount = 0; + nsresult rv = ResidentUniqueDistinguishedAmount(&amount); + NS_ENSURE_SUCCESS(rv, 0); + return amount; +# else + return 0; +# endif +} + +#endif // XP_{WIN, MACOSX, LINUX, *} + +#ifdef HAVE_JEMALLOC_STATS +// static +size_t nsMemoryReporterManager::HeapAllocated(const jemalloc_stats_t& aStats) { + return aStats.allocated; +} +#endif + +NS_IMETHODIMP +nsMemoryReporterManager::GetHeapAllocated(int64_t* aAmount) { +#ifdef HAVE_JEMALLOC_STATS + jemalloc_stats_t stats; + jemalloc_stats(&stats); + *aAmount = HeapAllocated(stats); + return NS_OK; +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +// This has UNITS_PERCENTAGE, so it is multiplied by 100x. +NS_IMETHODIMP +nsMemoryReporterManager::GetHeapOverheadFraction(int64_t* aAmount) { +#ifdef HAVE_JEMALLOC_STATS + jemalloc_stats_t stats; + jemalloc_stats(&stats); + *aAmount = HeapOverheadFraction(stats); + return NS_OK; +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +[[nodiscard]] static nsresult GetInfallibleAmount(InfallibleAmountFn aAmountFn, + int64_t* aAmount) { + if (aAmountFn) { + *aAmount = aAmountFn(); + return NS_OK; + } + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeGCHeap(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeGCHeap, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeTemporaryPeak(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeTemporaryPeak, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeCompartmentsSystem(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsSystem, + aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeCompartmentsUser(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeCompartmentsUser, + aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeRealmsSystem(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeRealmsSystem, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetJSMainRuntimeRealmsUser(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mJSMainRuntimeRealmsUser, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetImagesContentUsedUncompressed(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mImagesContentUsedUncompressed, + aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetStorageSQLite(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mStorageSQLite, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetLowMemoryEventsPhysical(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mLowMemoryEventsPhysical, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetGhostWindows(int64_t* aAmount) { + return GetInfallibleAmount(mAmountFns.mGhostWindows, aAmount); +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetPageFaultsHard(int64_t* aAmount) { +#ifdef HAVE_PAGE_FAULT_REPORTERS + return PageFaultsHardDistinguishedAmount(aAmount); +#else + *aAmount = 0; + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetHasMozMallocUsableSize(bool* aHas) { + void* p = malloc(16); + if (!p) { + return NS_ERROR_OUT_OF_MEMORY; + } + size_t usable = moz_malloc_usable_size(p); + free(p); + *aHas = !!(usable > 0); + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetIsDMDEnabled(bool* aIsEnabled) { +#ifdef MOZ_DMD + *aIsEnabled = true; +#else + *aIsEnabled = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsMemoryReporterManager::GetIsDMDRunning(bool* aIsRunning) { +#ifdef MOZ_DMD + *aIsRunning = dmd::IsRunning(); +#else + *aIsRunning = false; +#endif + return NS_OK; +} + +namespace { + +/** + * This runnable lets us implement + * nsIMemoryReporterManager::MinimizeMemoryUsage(). We fire a heap-minimize + * notification, spin the event loop, and repeat this process a few times. + * + * When this sequence finishes, we invoke the callback function passed to the + * runnable's constructor. + */ +class MinimizeMemoryUsageRunnable : public Runnable { + public: + explicit MinimizeMemoryUsageRunnable(nsIRunnable* aCallback) + : mozilla::Runnable("MinimizeMemoryUsageRunnable"), + mCallback(aCallback), + mRemainingIters(sNumIters) {} + + NS_IMETHOD Run() override { + nsCOMPtr os = services::GetObserverService(); + if (!os) { + return NS_ERROR_FAILURE; + } + + if (mRemainingIters == 0) { + os->NotifyObservers(nullptr, "after-minimize-memory-usage", + u"MinimizeMemoryUsageRunnable"); + if (mCallback) { + mCallback->Run(); + } + return NS_OK; + } + + os->NotifyObservers(nullptr, "memory-pressure", u"heap-minimize"); + mRemainingIters--; + NS_DispatchToMainThread(this); + + return NS_OK; + } + + private: + // Send sNumIters heap-minimize notifications, spinning the event + // loop after each notification (see bug 610166 comment 12 for an + // explanation), because one notification doesn't cut it. + static const uint32_t sNumIters = 3; + + nsCOMPtr mCallback; + uint32_t mRemainingIters; +}; + +} // namespace + +NS_IMETHODIMP +nsMemoryReporterManager::MinimizeMemoryUsage(nsIRunnable* aCallback) { + RefPtr runnable = + new MinimizeMemoryUsageRunnable(aCallback); + + return NS_DispatchToMainThread(runnable); +} + +NS_IMETHODIMP +nsMemoryReporterManager::SizeOfTab(mozIDOMWindowProxy* aTopWindow, + int64_t* aJSObjectsSize, + int64_t* aJSStringsSize, + int64_t* aJSOtherSize, int64_t* aDomSize, + int64_t* aStyleSize, int64_t* aOtherSize, + int64_t* aTotalSize, double* aJSMilliseconds, + double* aNonJSMilliseconds) { + nsCOMPtr global = do_QueryInterface(aTopWindow); + auto* piWindow = nsPIDOMWindowOuter::From(aTopWindow); + if (NS_WARN_IF(!global) || NS_WARN_IF(!piWindow)) { + return NS_ERROR_FAILURE; + } + + TimeStamp t1 = TimeStamp::Now(); + + // Measure JS memory consumption (and possibly some non-JS consumption, via + // |jsPrivateSize|). + size_t jsObjectsSize, jsStringsSize, jsPrivateSize, jsOtherSize; + nsresult rv = mSizeOfTabFns.mJS(global->GetGlobalJSObject(), &jsObjectsSize, + &jsStringsSize, &jsPrivateSize, &jsOtherSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + TimeStamp t2 = TimeStamp::Now(); + + // Measure non-JS memory consumption. + size_t domSize, styleSize, otherSize; + rv = mSizeOfTabFns.mNonJS(piWindow, &domSize, &styleSize, &otherSize); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + TimeStamp t3 = TimeStamp::Now(); + + *aTotalSize = 0; +#define DO(aN, n) \ + { \ + *aN = (n); \ + *aTotalSize += (n); \ + } + DO(aJSObjectsSize, jsObjectsSize); + DO(aJSStringsSize, jsStringsSize); + DO(aJSOtherSize, jsOtherSize); + DO(aDomSize, jsPrivateSize + domSize); + DO(aStyleSize, styleSize); + DO(aOtherSize, otherSize); +#undef DO + + *aJSMilliseconds = (t2 - t1).ToMilliseconds(); + *aNonJSMilliseconds = (t3 - t2).ToMilliseconds(); + + return NS_OK; +} + +namespace mozilla { + +#define GET_MEMORY_REPORTER_MANAGER(mgr) \ + RefPtr mgr = \ + nsMemoryReporterManager::GetOrCreate(); \ + if (!mgr) { \ + return NS_ERROR_FAILURE; \ + } + +nsresult RegisterStrongMemoryReporter(nsIMemoryReporter* aReporter) { + // Hold a strong reference to the argument to make sure it gets released if + // we return early below. + nsCOMPtr reporter = aReporter; + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->RegisterStrongReporter(reporter); +} + +nsresult RegisterStrongAsyncMemoryReporter(nsIMemoryReporter* aReporter) { + // Hold a strong reference to the argument to make sure it gets released if + // we return early below. + nsCOMPtr reporter = aReporter; + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->RegisterStrongAsyncReporter(reporter); +} + +nsresult RegisterWeakMemoryReporter(nsIMemoryReporter* aReporter) { + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->RegisterWeakReporter(aReporter); +} + +nsresult RegisterWeakAsyncMemoryReporter(nsIMemoryReporter* aReporter) { + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->RegisterWeakAsyncReporter(aReporter); +} + +nsresult UnregisterStrongMemoryReporter(nsIMemoryReporter* aReporter) { + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->UnregisterStrongReporter(aReporter); +} + +nsresult UnregisterWeakMemoryReporter(nsIMemoryReporter* aReporter) { + GET_MEMORY_REPORTER_MANAGER(mgr) + return mgr->UnregisterWeakReporter(aReporter); +} + +// Macro for generating functions that register distinguished amount functions +// with the memory reporter manager. +#define DEFINE_REGISTER_DISTINGUISHED_AMOUNT(kind, name) \ + nsresult Register##name##DistinguishedAmount(kind##AmountFn aAmountFn) { \ + GET_MEMORY_REPORTER_MANAGER(mgr) \ + mgr->mAmountFns.m##name = aAmountFn; \ + return NS_OK; \ + } + +// Macro for generating functions that unregister distinguished amount +// functions with the memory reporter manager. +#define DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(name) \ + nsresult Unregister##name##DistinguishedAmount() { \ + GET_MEMORY_REPORTER_MANAGER(mgr) \ + mgr->mAmountFns.m##name = nullptr; \ + return NS_OK; \ + } + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeGCHeap) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeTemporaryPeak) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, + JSMainRuntimeCompartmentsSystem) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeCompartmentsUser) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsSystem) +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, JSMainRuntimeRealmsUser) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, ImagesContentUsedUncompressed) +DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(ImagesContentUsedUncompressed) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, StorageSQLite) +DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT(StorageSQLite) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, LowMemoryEventsPhysical) + +DEFINE_REGISTER_DISTINGUISHED_AMOUNT(Infallible, GhostWindows) + +#undef DEFINE_REGISTER_DISTINGUISHED_AMOUNT +#undef DEFINE_UNREGISTER_DISTINGUISHED_AMOUNT + +#define DEFINE_REGISTER_SIZE_OF_TAB(name) \ + nsresult Register##name##SizeOfTab(name##SizeOfTabFn aSizeOfTabFn) { \ + GET_MEMORY_REPORTER_MANAGER(mgr) \ + mgr->mSizeOfTabFns.m##name = aSizeOfTabFn; \ + return NS_OK; \ + } + +DEFINE_REGISTER_SIZE_OF_TAB(JS); +DEFINE_REGISTER_SIZE_OF_TAB(NonJS); + +#undef DEFINE_REGISTER_SIZE_OF_TAB + +#undef GET_MEMORY_REPORTER_MANAGER + +} // namespace mozilla diff --git a/xpcom/base/nsMemoryReporterManager.h b/xpcom/base/nsMemoryReporterManager.h new file mode 100644 index 0000000000..7064524521 --- /dev/null +++ b/xpcom/base/nsMemoryReporterManager.h @@ -0,0 +1,321 @@ +/* -*- 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/. */ + +#ifndef nsMemoryReporterManager_h__ +#define nsMemoryReporterManager_h__ + +#include "mozilla/Mutex.h" +#include "nsTHashMap.h" +#include "nsHashKeys.h" +#include "nsIMemoryReporter.h" +#include "nsISupports.h" +#include "nsServiceManagerUtils.h" + +#ifdef XP_WIN +# include +#endif // XP_WIN + +#if defined(MOZ_MEMORY) +# define HAVE_JEMALLOC_STATS 1 +# include "mozmemory.h" +#endif // MOZ_MEMORY + +namespace mozilla { +class MemoryReportingProcess; +namespace dom { +class MemoryReport; +} // namespace dom +} // namespace mozilla + +class mozIDOMWindowProxy; +class nsIEventTarget; +class nsIRunnable; +class nsITimer; + +class nsMemoryReporterManager final : public nsIMemoryReporterManager, + public nsIMemoryReporter { + virtual ~nsMemoryReporterManager(); + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIMEMORYREPORTERMANAGER + NS_DECL_NSIMEMORYREPORTER + + MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) + + nsMemoryReporterManager(); + + // Gets the memory reporter manager service. + static already_AddRefed GetOrCreate() { + nsCOMPtr imgr = + do_GetService("@mozilla.org/memory-reporter-manager;1"); + return imgr.forget().downcast(); + } + + typedef nsTHashMap, bool> + StrongReportersTable; + typedef nsTHashMap, bool> WeakReportersTable; + + // Inter-process memory reporting proceeds as follows. + // + // - GetReports() (declared within NS_DECL_NSIMEMORYREPORTERMANAGER) + // synchronously gets memory reports for the current process, sets up some + // state (mPendingProcessesState) for when child processes report back -- + // including a timer -- and starts telling child processes to get memory + // reports. Control then returns to the main event loop. + // + // The number of concurrent child process reports is limited by the pref + // "memory.report_concurrency" in order to prevent the memory overhead of + // memory reporting from causing problems, especially on B2G when swapping + // to compressed RAM; see bug 1154053. + // + // - HandleChildReport() is called (asynchronously) once per child process + // reporter callback. + // + // - EndProcessReport() is called (asynchronously) once per process that + // finishes reporting back, including the parent. If all processes do so + // before time-out, the timer is cancelled. If there are child processes + // whose requests have not yet been sent, they will be started until the + // concurrency limit is (again) reached. + // + // - TimeoutCallback() is called (asynchronously) if all the child processes + // don't respond within the time threshold. + // + // - FinishReporting() finishes things off. It is *always* called -- either + // from EndChildReport() (if all child processes have reported back) or + // from TimeoutCallback() (if time-out occurs). + // + // All operations occur on the main thread. + // + // The above sequence of steps is a "request". A partially-completed request + // is described as "in flight". + // + // Each request has a "generation", a unique number that identifies it. This + // is used to ensure that each reports from a child process corresponds to + // the appropriate request from the parent process. (It's easier to + // implement a generation system than to implement a child report request + // cancellation mechanism.) + // + // Failures are mostly ignored, because it's (a) typically the most sensible + // thing to do, and (b) often hard to do anything else. The following are + // the failure cases of note. + // + // - If a request is made while the previous request is in flight, the new + // request is ignored, as per getReports()'s specification. No error is + // reported, because the previous request will complete soon enough. + // + // - If one or more child processes fail to respond within the time limit, + // things will proceed as if they don't exist. No error is reported, + // because partial information is better than nothing. + // + // - If a child process reports after the time-out occurs, it is ignored. + // (Generation checking will ensure it is ignored even if a subsequent + // request is in flight; this is the main use of generations.) No error + // is reported, because there's nothing sensible to be done about it at + // this late stage. + // + // - If the time-out occurs after a child process has sent some reports but + // before it has signaled completion (see bug 1151597), then what it + // successfully sent will be included, with no explicit indication that it + // is incomplete. + // + // Now, what what happens if a child process is created/destroyed in the + // middle of a request? Well, PendingProcessesState is initialized with an + // array of child process actors as of when the report started. So... + // + // - If a process is created after reporting starts, it won't be sent a + // request for reports. So the reported data will reflect how things were + // when the request began. + // + // - If a process is destroyed before it starts reporting back, the reported + // data will reflect how things are when the request ends. + // + // - If a process is destroyed after it starts reporting back but before it + // finishes, the reported data will contain a partial report for it. + // + // - If a process is destroyed after reporting back, but before all other + // child processes have reported back, it will be included in the reported + // data. So the reported data will reflect how things were when the + // request began. + // + // The inconsistencies between these cases are unfortunate but difficult to + // avoid. It's enough of an edge case to not be worth doing more. + // + void HandleChildReport(uint32_t aGeneration, + const mozilla::dom::MemoryReport& aChildReport); + void EndProcessReport(uint32_t aGeneration, bool aSuccess); + + // Functions that (a) implement distinguished amounts, and (b) are outside of + // this module. + struct AmountFns { + mozilla::InfallibleAmountFn mJSMainRuntimeGCHeap = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeTemporaryPeak = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsSystem = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeCompartmentsUser = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeRealmsSystem = nullptr; + mozilla::InfallibleAmountFn mJSMainRuntimeRealmsUser = nullptr; + + mozilla::InfallibleAmountFn mImagesContentUsedUncompressed = nullptr; + + mozilla::InfallibleAmountFn mStorageSQLite = nullptr; + + mozilla::InfallibleAmountFn mLowMemoryEventsPhysical = nullptr; + + mozilla::InfallibleAmountFn mGhostWindows = nullptr; + }; + AmountFns mAmountFns; + + // Convenience function to get RSS easily from other code. This is useful + // when debugging transient memory spikes with printf instrumentation. + static int64_t ResidentFast(); + + // Convenience function to get peak RSS easily from other code. + static int64_t ResidentPeak(); + + // Convenience function to get USS easily from other code. This is useful + // when debugging unshared memory pages for forked processes. + // + // Returns 0 if, for some reason, the resident unique memory cannot be + // determined - typically if there is a race between us and someone else + // closing the process and we lost that race. +#ifdef XP_WIN + static int64_t ResidentUnique(HANDLE aProcess = nullptr); +#elif XP_MACOSX + // On MacOS this can sometimes be significantly slow. It should not be used + // except in debugging or at the request of a user (eg about:memory). + static int64_t ResidentUnique(mach_port_t aPort = 0); +#else + static int64_t ResidentUnique(pid_t aPid = 0); +#endif // XP_{WIN, MACOSX, LINUX, *} + +#ifdef XP_MACOSX + // Retrive the "phys_footprint" memory statistic on MacOS. + static int64_t PhysicalFootprint(mach_port_t aPort = 0); +#endif + + // Functions that measure per-tab memory consumption. + struct SizeOfTabFns { + mozilla::JSSizeOfTabFn mJS = nullptr; + mozilla::NonJSSizeOfTabFn mNonJS = nullptr; + }; + SizeOfTabFns mSizeOfTabFns; + +#ifdef HAVE_JEMALLOC_STATS + // These C++ only versions of HeapAllocated and HeapOverheadFraction avoid + // extra calls to jemalloc_stats; + static size_t HeapAllocated(const jemalloc_stats_t& stats); + static int64_t HeapOverheadFraction(const jemalloc_stats_t& stats); +#endif + + private: + bool IsRegistrationBlocked() MOZ_EXCLUDES(mMutex) { + mozilla::MutexAutoLock lock(mMutex); + return mIsRegistrationBlocked; + } + + [[nodiscard]] nsresult RegisterReporterHelper(nsIMemoryReporter* aReporter, + bool aForce, bool aStrongRef, + bool aIsAsync); + + [[nodiscard]] nsresult StartGettingReports(); + // No [[nodiscard]] here because ignoring the result is common and reasonable. + nsresult FinishReporting(); + + void DispatchReporter(nsIMemoryReporter* aReporter, bool aIsAsync, + nsIHandleReportCallback* aHandleReport, + nsISupports* aHandleReportData, bool aAnonymize); + + static void TimeoutCallback(nsITimer* aTimer, void* aData); + // Note: this timeout needs to be long enough to allow for the + // possibility of DMD reports and/or running on a low-end phone. + static const uint32_t kTimeoutLengthMS = 180000; + + mozilla::Mutex mMutex; + bool mIsRegistrationBlocked MOZ_GUARDED_BY(mMutex); + + StrongReportersTable* mStrongReporters MOZ_GUARDED_BY(mMutex); + WeakReportersTable* mWeakReporters MOZ_GUARDED_BY(mMutex); + + // These two are only used for testing purposes. + StrongReportersTable* mSavedStrongReporters MOZ_GUARDED_BY(mMutex); + WeakReportersTable* mSavedWeakReporters MOZ_GUARDED_BY(mMutex); + + uint32_t mNextGeneration; // MainThread only + + // Used to keep track of state of which processes are currently running and + // waiting to run memory reports. Holds references to parameters needed when + // requesting a memory report and finishing reporting. + struct PendingProcessesState { + uint32_t mGeneration; + bool mAnonymize; + bool mMinimize; + nsCOMPtr mTimer; + nsTArray> mChildrenPending; + uint32_t mNumProcessesRunning; + uint32_t mNumProcessesCompleted; + uint32_t mConcurrencyLimit; + nsCOMPtr mHandleReport; + nsCOMPtr mHandleReportData; + nsCOMPtr mFinishReporting; + nsCOMPtr mFinishReportingData; + nsString mDMDDumpIdent; + + PendingProcessesState(uint32_t aGeneration, bool aAnonymize, bool aMinimize, + uint32_t aConcurrencyLimit, + nsIHandleReportCallback* aHandleReport, + nsISupports* aHandleReportData, + nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, + const nsAString& aDMDDumpIdent); + }; + + // Used to keep track of the state of the asynchronously run memory + // reporters. The callback and file handle used when all memory reporters + // have finished are also stored here. + struct PendingReportersState { + // Number of memory reporters currently running. + uint32_t mReportsPending; + + // Callback for when all memory reporters have completed. + nsCOMPtr mFinishReporting; + nsCOMPtr mFinishReportingData; + + // File handle to write a DMD report to if requested. + FILE* mDMDFile; + + PendingReportersState(nsIFinishReportingCallback* aFinishReporting, + nsISupports* aFinishReportingData, FILE* aDMDFile) + : mReportsPending(0), + mFinishReporting(aFinishReporting), + mFinishReportingData(aFinishReportingData), + mDMDFile(aDMDFile) {} + }; + + // When this is non-null, a request is in flight. Note: We use manual + // new/delete for this because its lifetime doesn't match block scope or + // anything like that. + PendingProcessesState* mPendingProcessesState; // MainThread only + + // This is reinitialized each time a call to GetReports is initiated. + PendingReportersState* mPendingReportersState; // MainThread only + + // Used in GetHeapAllocatedAsync() to run jemalloc_stats async. + nsCOMPtr mThreadPool MOZ_GUARDED_BY(mMutex); + + PendingProcessesState* GetStateForGeneration(uint32_t aGeneration); + [[nodiscard]] static bool StartChildReport( + mozilla::MemoryReportingProcess* aChild, + const PendingProcessesState* aState); +}; + +#define NS_MEMORY_REPORTER_MANAGER_CID \ + { \ + 0xfb97e4f5, 0x32dd, 0x497a, { \ + 0xba, 0xa2, 0x7d, 0x1e, 0x55, 0x7, 0x99, 0x10 \ + } \ + } + +#endif // nsMemoryReporterManager_h__ diff --git a/xpcom/base/nsMessageLoop.cpp b/xpcom/base/nsMessageLoop.cpp new file mode 100644 index 0000000000..08f73cae37 --- /dev/null +++ b/xpcom/base/nsMessageLoop.cpp @@ -0,0 +1,151 @@ +/* -*- 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 "nsMessageLoop.h" +#include "mozilla/WeakPtr.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "nsINamed.h" +#include "nsIRunnable.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +namespace { + +/** + * This Task runs its nsIRunnable when Run() is called, or after + * aEnsureRunsAfterMS milliseconds have elapsed since the object was + * constructed. + * + * Note that the MessageLoop owns this object and will delete it after it calls + * Run(). Tread lightly. + */ +class MessageLoopIdleTask : public Runnable, public SupportsWeakPtr { + public: + MessageLoopIdleTask(nsIRunnable* aTask, uint32_t aEnsureRunsAfterMS); + NS_IMETHOD Run() override; + + private: + nsresult Init(uint32_t aEnsureRunsAfterMS); + + nsCOMPtr mTask; + nsCOMPtr mTimer; + + virtual ~MessageLoopIdleTask() = default; +}; + +/** + * This timer callback calls MessageLoopIdleTask::Run() when its timer fires. + * (The timer can't call back into MessageLoopIdleTask directly since that's + * not a refcounted object; it's owned by the MessageLoop.) + * + * We keep a weak reference to the MessageLoopIdleTask, although a raw pointer + * should in theory suffice: When the MessageLoopIdleTask runs (right before + * the MessageLoop deletes it), it cancels its timer. But the weak pointer + * saves us from worrying about an edge case somehow messing us up here. + */ +class MessageLoopTimerCallback : public nsITimerCallback, public nsINamed { + public: + explicit MessageLoopTimerCallback(MessageLoopIdleTask* aTask); + + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("MessageLoopTimerCallback"); + return NS_OK; + } + + private: + WeakPtr mTask; + + virtual ~MessageLoopTimerCallback() = default; +}; + +MessageLoopIdleTask::MessageLoopIdleTask(nsIRunnable* aTask, + uint32_t aEnsureRunsAfterMS) + : mozilla::Runnable("MessageLoopIdleTask"), mTask(aTask) { + // Init() really shouldn't fail, but if it does, we schedule our runnable + // immediately, because it's more important to guarantee that we run the task + // eventually than it is to run the task when we're idle. + nsresult rv = Init(aEnsureRunsAfterMS); + if (NS_FAILED(rv)) { + NS_WARNING( + "Running idle task early because we couldn't initialize our timer."); + NS_DispatchToCurrentThread(mTask); + + mTask = nullptr; + mTimer = nullptr; + } +} + +nsresult MessageLoopIdleTask::Init(uint32_t aEnsureRunsAfterMS) { + RefPtr callback = + new MessageLoopTimerCallback(this); + return NS_NewTimerWithCallback(getter_AddRefs(mTimer), callback, + aEnsureRunsAfterMS, nsITimer::TYPE_ONE_SHOT); +} + +NS_IMETHODIMP +MessageLoopIdleTask::Run() { + // Null out our pointers because if Run() was called by the timer, this + // object will be kept alive by the MessageLoop until the MessageLoop calls + // Run(). + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + if (mTask) { + mTask->Run(); + mTask = nullptr; + } + + return NS_OK; +} + +MessageLoopTimerCallback::MessageLoopTimerCallback(MessageLoopIdleTask* aTask) + : mTask(aTask) {} + +NS_IMETHODIMP +MessageLoopTimerCallback::Notify(nsITimer* aTimer) { + // We don't expect to hit the case when the timer fires but mTask has been + // deleted, because mTask should cancel the timer before the mTask is + // deleted. But you never know... + NS_WARNING_ASSERTION(mTask, "This timer shouldn't have fired."); + + if (mTask) { + mTask->Run(); + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(MessageLoopTimerCallback, nsITimerCallback, nsINamed) + +} // namespace + +NS_IMPL_ISUPPORTS(nsMessageLoop, nsIMessageLoop) + +NS_IMETHODIMP +nsMessageLoop::PostIdleTask(nsIRunnable* aTask, uint32_t aEnsureRunsAfterMS) { + // The message loop owns MessageLoopIdleTask and deletes it after calling + // Run(). Be careful... + RefPtr idle = + new MessageLoopIdleTask(aTask, aEnsureRunsAfterMS); + MessageLoop::current()->PostIdleTask(idle.forget()); + + return NS_OK; +} + +nsresult nsMessageLoopConstructor(const nsIID& aIID, void** aInstancePtr) { + nsISupports* messageLoop = new nsMessageLoop(); + return messageLoop->QueryInterface(aIID, aInstancePtr); +} diff --git a/xpcom/base/nsMessageLoop.h b/xpcom/base/nsMessageLoop.h new file mode 100644 index 0000000000..d6475cf797 --- /dev/null +++ b/xpcom/base/nsMessageLoop.h @@ -0,0 +1,29 @@ +/* -*- 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 "nsIMessageLoop.h" + +/* + * nsMessageLoop implements nsIMessageLoop, which wraps Chromium's MessageLoop + * class and adds a bit of sugar. + */ +class nsMessageLoop : public nsIMessageLoop { + NS_DECL_ISUPPORTS + NS_DECL_NSIMESSAGELOOP + + private: + virtual ~nsMessageLoop() = default; +}; + +#define NS_MESSAGE_LOOP_CID \ + { \ + 0x67b3ac0c, 0xd806, 0x4d48, { \ + 0x93, 0x9e, 0x6a, 0x81, 0x9e, 0x6c, 0x24, 0x8f \ + } \ + } + +extern nsresult nsMessageLoopConstructor(const nsIID& aIID, + void** aInstancePtr); diff --git a/xpcom/base/nsObjCExceptions.h b/xpcom/base/nsObjCExceptions.h new file mode 100644 index 0000000000..e8c2059d8e --- /dev/null +++ b/xpcom/base/nsObjCExceptions.h @@ -0,0 +1,56 @@ +/* -*- 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/. */ + +#ifndef nsObjCExceptions_h_ +#define nsObjCExceptions_h_ + +// Undo the damage that exception_defines.h does. +#undef try +#undef catch + +@class NSException; + +// See Mozilla bug 163260. +// This file can only be included in an Objective-C context. + +void nsObjCExceptionLog(NSException* aException); + +namespace mozilla { + +// Check if this is an exception that's outside of our control. +// Called when an exception bubbles up to the native event loop. +bool ShouldIgnoreObjCException(NSException* aException); + +} // namespace mozilla + +// For wrapping blocks of Obj-C calls which are not expected to throw exception. +// Causes a MOZ_CRASH if an Obj-C exception is encountered. +#define NS_OBJC_BEGIN_TRY_ABORT_BLOCK @try { +#define NS_OBJC_END_TRY_ABORT_BLOCK \ + } \ + @catch (NSException * _exn) { \ + nsObjCExceptionLog(_exn); \ + MOZ_CRASH("Encountered unexpected Objective C exception"); \ + } + +// For wrapping blocks of Obj-C calls. Logs the exception and moves on. +#define NS_OBJC_BEGIN_TRY_IGNORE_BLOCK @try { +#define NS_OBJC_END_TRY_IGNORE_BLOCK \ + } \ + @catch (NSException * _exn) { \ + nsObjCExceptionLog(_exn); \ + } + +// Same as above IGNORE_BLOCK but returns a value after the try/catch block. +#define NS_OBJC_BEGIN_TRY_BLOCK_RETURN @try { +#define NS_OBJC_END_TRY_BLOCK_RETURN(_rv) \ + } \ + @catch (NSException * _exn) { \ + nsObjCExceptionLog(_exn); \ + } \ + return _rv; + +#endif // nsObjCExceptions_h_ diff --git a/xpcom/base/nsObjCExceptions.mm b/xpcom/base/nsObjCExceptions.mm new file mode 100644 index 0000000000..9921c746a1 --- /dev/null +++ b/xpcom/base/nsObjCExceptions.mm @@ -0,0 +1,48 @@ +/* -*- 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 "nsObjCExceptions.h" + +#import + +#include +#include + +#include "nsICrashReporter.h" +#include "nsCOMPtr.h" +#include "nsServiceManagerUtils.h" + +#include "nsError.h" + +void nsObjCExceptionLog(NSException* aException) { + NSLog(@"Mozilla has caught an Obj-C exception [%@: %@]", [aException name], [aException reason]); + + // Attach exception info to the crash report. + nsCOMPtr crashReporter = do_GetService("@mozilla.org/toolkit/crash-reporter;1"); + if (crashReporter) { + crashReporter->AppendObjCExceptionInfoToAppNotes(static_cast(aException)); + } + +#ifdef DEBUG + NSLog(@"Stack trace:\n%@", [aException callStackSymbols]); +#endif +} + +namespace mozilla { + +bool ShouldIgnoreObjCException(NSException* aException) { + // Ignore known exceptions that we've seen in crash reports, which shouldn't cause a MOZ_CRASH in + // Nightly. + if (aException.name == NSInternalInconsistencyException) { + if ([aException.reason containsString:@"Missing Touches."]) { + // Seen in bug 1694000. + return true; + } + } + return false; +} + +} // namespace mozilla diff --git a/xpcom/base/nsQueryObject.h b/xpcom/base/nsQueryObject.h new file mode 100644 index 0000000000..7cfbe954ce --- /dev/null +++ b/xpcom/base/nsQueryObject.h @@ -0,0 +1,93 @@ +/* -*- 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/. */ + +#ifndef nsQueryObject_h +#define nsQueryObject_h + +#include "mozilla/Attributes.h" + +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +/*****************************************************************************/ + +template +class MOZ_STACK_CLASS nsQueryObject final : public nsCOMPtr_helper { + public: + explicit nsQueryObject(T* aRawPtr) : mRawPtr(aRawPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override { + nsresult status = mRawPtr ? mRawPtr->QueryInterface(aIID, aResult) + : NS_ERROR_NULL_POINTER; + return status; + } + + private: + T* MOZ_NON_OWNING_REF mRawPtr; +}; + +template +class MOZ_STACK_CLASS nsQueryObjectWithError final : public nsCOMPtr_helper { + public: + nsQueryObjectWithError(T* aRawPtr, nsresult* aErrorPtr) + : mRawPtr(aRawPtr), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override { + nsresult status = mRawPtr ? mRawPtr->QueryInterface(aIID, aResult) + : NS_ERROR_NULL_POINTER; + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; + } + + private: + T* MOZ_NON_OWNING_REF mRawPtr; + nsresult* mErrorPtr; +}; + +/*****************************************************************************/ + +/*****************************************************************************/ + +template +inline nsQueryObject do_QueryObject(T* aRawPtr) { + return nsQueryObject(aRawPtr); +} + +template +inline nsQueryObject do_QueryObject(const nsCOMPtr& aRawPtr) { + return nsQueryObject(aRawPtr); +} + +template +inline nsQueryObject do_QueryObject(const RefPtr& aRawPtr) { + return nsQueryObject(aRawPtr); +} + +template +inline nsQueryObjectWithError do_QueryObject(T* aRawPtr, + nsresult* aErrorPtr) { + return nsQueryObjectWithError(aRawPtr, aErrorPtr); +} + +template +inline nsQueryObjectWithError do_QueryObject(const nsCOMPtr& aRawPtr, + nsresult* aErrorPtr) { + return nsQueryObjectWithError(aRawPtr, aErrorPtr); +} + +template +inline nsQueryObjectWithError do_QueryObject(const RefPtr& aRawPtr, + nsresult* aErrorPtr) { + return nsQueryObjectWithError(aRawPtr, aErrorPtr); +} + +/*****************************************************************************/ + +#endif // !defined(nsQueryObject_h) diff --git a/xpcom/base/nsSecurityConsoleMessage.cpp b/xpcom/base/nsSecurityConsoleMessage.cpp new file mode 100644 index 0000000000..84074b57bc --- /dev/null +++ b/xpcom/base/nsSecurityConsoleMessage.cpp @@ -0,0 +1,37 @@ +/* -*- 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 "nsSecurityConsoleMessage.h" + +NS_IMPL_ISUPPORTS(nsSecurityConsoleMessage, nsISecurityConsoleMessage) + +nsSecurityConsoleMessage::nsSecurityConsoleMessage() = default; + +nsSecurityConsoleMessage::~nsSecurityConsoleMessage() = default; + +NS_IMETHODIMP +nsSecurityConsoleMessage::GetTag(nsAString& aTag) { + aTag = mTag; + return NS_OK; +} + +NS_IMETHODIMP +nsSecurityConsoleMessage::SetTag(const nsAString& aTag) { + mTag = aTag; + return NS_OK; +} + +NS_IMETHODIMP +nsSecurityConsoleMessage::GetCategory(nsAString& aCategory) { + aCategory = mCategory; + return NS_OK; +} + +NS_IMETHODIMP +nsSecurityConsoleMessage::SetCategory(const nsAString& aCategory) { + mCategory = aCategory; + return NS_OK; +} diff --git a/xpcom/base/nsSecurityConsoleMessage.h b/xpcom/base/nsSecurityConsoleMessage.h new file mode 100644 index 0000000000..9a74d3e38b --- /dev/null +++ b/xpcom/base/nsSecurityConsoleMessage.h @@ -0,0 +1,33 @@ +/* -*- 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/. */ + +#ifndef nsSecurityConsoleMessage_h__ +#define nsSecurityConsoleMessage_h__ +#include "nsISecurityConsoleMessage.h" +#include "nsString.h" + +class nsSecurityConsoleMessage final : public nsISecurityConsoleMessage { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISECURITYCONSOLEMESSAGE + + nsSecurityConsoleMessage(); + + private: + ~nsSecurityConsoleMessage(); + + protected: + nsString mTag; + nsString mCategory; +}; + +#define NS_SECURITY_CONSOLE_MESSAGE_CID \ + { \ + 0x43ebf210, 0x8a7b, 0x4ddb, { \ + 0xa8, 0x3d, 0xb8, 0x7c, 0x51, 0xa0, 0x58, 0xdb \ + } \ + } +#endif // nsSecurityConsoleMessage_h__ diff --git a/xpcom/base/nsSystemInfo.cpp b/xpcom/base/nsSystemInfo.cpp new file mode 100644 index 0000000000..9febabaad7 --- /dev/null +++ b/xpcom/base/nsSystemInfo.cpp @@ -0,0 +1,1660 @@ +/* -*- 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/ArrayUtils.h" + +#include "nsAppRunner.h" +#include "nsSystemInfo.h" +#include "prsystem.h" +#include "prio.h" +#include "mozilla/SSE.h" +#include "mozilla/arm.h" +#include "mozilla/LazyIdleThread.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/Sprintf.h" +#include "jsapi.h" +#include "js/PropertyAndElement.h" // JS_SetProperty +#include "mozilla/dom/Promise.h" + +#ifdef XP_WIN +# include +# include +# ifndef __MINGW32__ +# include +# endif // __MINGW32__ +# include +# include +# ifndef __MINGW32__ +# include +# include +# endif // __MINGW32__ +# include "base/scoped_handle_win.h" +# include "mozilla/DynamicallyLinkedFunctionPtr.h" +# include "mozilla/WindowsVersion.h" +# include "nsAppDirectoryServiceDefs.h" +# include "nsDirectoryServiceDefs.h" +# include "nsDirectoryServiceUtils.h" +# include "nsWindowsHelpers.h" +# include "WinUtils.h" +# include "mozilla/NotNull.h" + +#endif + +#ifdef XP_MACOSX +# include "MacHelpers.h" +#endif + +#ifdef MOZ_WIDGET_GTK +# include +# include +# include "mozilla/WidgetUtilsGtk.h" +#endif + +#if defined(XP_LINUX) && !defined(ANDROID) +# include +# include +# include "mozilla/Tokenizer.h" +# include "nsCharSeparatedTokenizer.h" + +# include +# include +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBuild.h" +# include "mozilla/java/GeckoAppShellWrappers.h" +# include "mozilla/jni/Utils.h" +#endif + +#ifdef XP_MACOSX +# include +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/SandboxInfo.h" +#endif + +// Slot for NS_InitXPCOM to pass information to nsSystemInfo::Init. +// Only set to nonzero (potentially) if XP_UNIX. On such systems, the +// system call to discover the appropriate value is not thread-safe, +// so we must call it before going multithreaded, but nsSystemInfo::Init +// only happens well after that point. +uint32_t nsSystemInfo::gUserUmask = 0; + +using namespace mozilla::dom; + +#if defined(XP_WIN) +# define RuntimeClass_Windows_System_Profile_WindowsIntegrityPolicy \ + L"Windows.System.Profile.WindowsIntegrityPolicy" +# ifndef __MINGW32__ +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows::Foundation; +# endif // __MINGW32__ +#endif + +#if defined(XP_LINUX) && !defined(ANDROID) +static void SimpleParseKeyValuePairs( + const std::string& aFilename, + std::map& aKeyValuePairs) { + std::ifstream input(aFilename.c_str()); + for (std::string line; std::getline(input, line);) { + nsAutoCString key, value; + + nsCCharSeparatedTokenizer tokens(nsDependentCString(line.c_str()), ':'); + if (tokens.hasMoreTokens()) { + key = tokens.nextToken(); + if (tokens.hasMoreTokens()) { + value = tokens.nextToken(); + } + // We want the value even if there was just one token, to cover the + // case where we had the key, and the value was blank (seems to be + // a valid scenario some files.) + aKeyValuePairs[key] = value; + } + } +} +#endif + +#ifdef XP_WIN +// Lifted from media/webrtc/trunk/webrtc/base/systeminfo.cc, +// so keeping the _ instead of switching to camel case for now. +static void GetProcessorInformation(int* physical_cpus, int* cache_size_L2, + int* cache_size_L3) { + MOZ_ASSERT(physical_cpus && cache_size_L2 && cache_size_L3); + + *physical_cpus = 0; + *cache_size_L2 = 0; // This will be in kbytes + *cache_size_L3 = 0; // This will be in kbytes + + // Determine buffer size, allocate and get processor information. + // Size can change between calls (unlikely), so a loop is done. + SYSTEM_LOGICAL_PROCESSOR_INFORMATION info_buffer[32]; + SYSTEM_LOGICAL_PROCESSOR_INFORMATION* infos = &info_buffer[0]; + DWORD return_length = sizeof(info_buffer); + while (!::GetLogicalProcessorInformation(infos, &return_length)) { + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER && + infos == &info_buffer[0]) { + infos = new SYSTEM_LOGICAL_PROCESSOR_INFORMATION + [return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION)]; + } else { + return; + } + } + + for (size_t i = 0; + i < return_length / sizeof(SYSTEM_LOGICAL_PROCESSOR_INFORMATION); ++i) { + if (infos[i].Relationship == RelationProcessorCore) { + ++*physical_cpus; + } else if (infos[i].Relationship == RelationCache) { + // Only care about L2 and L3 cache + switch (infos[i].Cache.Level) { + case 2: + *cache_size_L2 = static_cast(infos[i].Cache.Size / 1024); + break; + case 3: + *cache_size_L3 = static_cast(infos[i].Cache.Size / 1024); + break; + default: + break; + } + } + } + if (infos != &info_buffer[0]) { + delete[] infos; + } + return; +} +#endif + +#if defined(XP_WIN) +namespace { +static nsresult GetFolderDiskInfo(nsIFile* file, FolderDiskInfo& info) { + info.model.Truncate(); + info.revision.Truncate(); + info.isSSD = false; + + nsAutoString filePath; + nsresult rv = file->GetPath(filePath); + NS_ENSURE_SUCCESS(rv, rv); + wchar_t volumeMountPoint[MAX_PATH] = {L'\\', L'\\', L'.', L'\\'}; + const size_t PREFIX_LEN = 4; + if (!::GetVolumePathNameW( + filePath.get(), volumeMountPoint + PREFIX_LEN, + mozilla::ArrayLength(volumeMountPoint) - PREFIX_LEN)) { + return NS_ERROR_UNEXPECTED; + } + size_t volumeMountPointLen = wcslen(volumeMountPoint); + // Since we would like to open a drive and not a directory, we need to + // remove any trailing backslash. A drive handle is valid for + // DeviceIoControl calls, a directory handle is not. + if (volumeMountPoint[volumeMountPointLen - 1] == L'\\') { + volumeMountPoint[volumeMountPointLen - 1] = L'\0'; + } + ScopedHandle handle(::CreateFileW(volumeMountPoint, 0, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, + OPEN_EXISTING, 0, nullptr)); + if (!handle.IsValid()) { + return NS_ERROR_UNEXPECTED; + } + STORAGE_PROPERTY_QUERY queryParameters = {StorageDeviceProperty, + PropertyStandardQuery}; + STORAGE_DEVICE_DESCRIPTOR outputHeader = {sizeof(STORAGE_DEVICE_DESCRIPTOR)}; + DWORD bytesRead = 0; + if (!::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, &queryParameters, + sizeof(queryParameters), &outputHeader, + sizeof(outputHeader), &bytesRead, nullptr)) { + return NS_ERROR_FAILURE; + } + PSTORAGE_DEVICE_DESCRIPTOR deviceOutput = + (PSTORAGE_DEVICE_DESCRIPTOR)malloc(outputHeader.Size); + if (!::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, &queryParameters, + sizeof(queryParameters), deviceOutput, + outputHeader.Size, &bytesRead, nullptr)) { + free(deviceOutput); + return NS_ERROR_FAILURE; + } + + queryParameters.PropertyId = StorageDeviceTrimProperty; + bytesRead = 0; + bool isSSD = false; + DEVICE_TRIM_DESCRIPTOR trimDescriptor = {sizeof(DEVICE_TRIM_DESCRIPTOR)}; + if (::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, &queryParameters, + sizeof(queryParameters), &trimDescriptor, + sizeof(trimDescriptor), &bytesRead, nullptr)) { + if (trimDescriptor.TrimEnabled) { + isSSD = true; + } + } + + if (isSSD) { + // Get Seek Penalty + queryParameters.PropertyId = StorageDeviceSeekPenaltyProperty; + bytesRead = 0; + DEVICE_SEEK_PENALTY_DESCRIPTOR seekPenaltyDescriptor = { + sizeof(DEVICE_SEEK_PENALTY_DESCRIPTOR)}; + if (::DeviceIoControl(handle, IOCTL_STORAGE_QUERY_PROPERTY, + &queryParameters, sizeof(queryParameters), + &seekPenaltyDescriptor, sizeof(seekPenaltyDescriptor), + &bytesRead, nullptr)) { + // It is possible that the disk has TrimEnabled, but also + // IncursSeekPenalty; In this case, this is an HDD + if (seekPenaltyDescriptor.IncursSeekPenalty) { + isSSD = false; + } + } + } + + // Some HDDs are including product ID info in the vendor field. Since PNP + // IDs include vendor info and product ID concatenated together, we'll do + // that here and interpret the result as a unique ID for the HDD model. + if (deviceOutput->VendorIdOffset) { + info.model = + reinterpret_cast(deviceOutput) + deviceOutput->VendorIdOffset; + } + if (deviceOutput->ProductIdOffset) { + info.model += + reinterpret_cast(deviceOutput) + deviceOutput->ProductIdOffset; + } + info.model.CompressWhitespace(); + if (deviceOutput->ProductRevisionOffset) { + info.revision = reinterpret_cast(deviceOutput) + + deviceOutput->ProductRevisionOffset; + info.revision.CompressWhitespace(); + } + info.isSSD = isSSD; + free(deviceOutput); + return NS_OK; +} + +static nsresult CollectDiskInfo(nsIFile* greDir, nsIFile* winDir, + nsIFile* profDir, DiskInfo& info) { + nsresult rv = GetFolderDiskInfo(greDir, info.binary); + if (NS_FAILED(rv)) { + return rv; + } + rv = GetFolderDiskInfo(winDir, info.system); + if (NS_FAILED(rv)) { + return rv; + } + return GetFolderDiskInfo(profDir, info.profile); +} + +static nsresult CollectOSInfo(OSInfo& info) { + HKEY installYearHKey; + LONG status = RegOpenKeyExW( + HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", 0, + KEY_READ | KEY_WOW64_64KEY, &installYearHKey); + + if (status != ERROR_SUCCESS) { + return NS_ERROR_UNEXPECTED; + } + + nsAutoRegKey installYearKey(installYearHKey); + + DWORD type = 0; + time_t raw_time = 0; + DWORD time_size = sizeof(time_t); + + status = RegQueryValueExW(installYearHKey, L"InstallDate", nullptr, &type, + (LPBYTE)&raw_time, &time_size); + + if (status != ERROR_SUCCESS) { + return NS_ERROR_UNEXPECTED; + } + + if (type != REG_DWORD) { + return NS_ERROR_UNEXPECTED; + } + + tm time; + if (localtime_s(&time, &raw_time) != 0) { + return NS_ERROR_UNEXPECTED; + } + + info.installYear = 1900UL + time.tm_year; + + nsAutoServiceHandle scm( + OpenSCManager(nullptr, SERVICES_ACTIVE_DATABASE, SC_MANAGER_CONNECT)); + + if (!scm) { + return NS_ERROR_UNEXPECTED; + } + + bool superfetchServiceRunning = false; + + // Superfetch was introduced in Windows Vista as a service with the name + // SysMain. The service display name was also renamed to SysMain after Windows + // 10 build 1809. + nsAutoServiceHandle hService(OpenService(scm, L"SysMain", GENERIC_READ)); + + if (hService) { + SERVICE_STATUS superfetchStatus; + LPSERVICE_STATUS pSuperfetchStatus = &superfetchStatus; + + if (!QueryServiceStatus(hService, pSuperfetchStatus)) { + return NS_ERROR_UNEXPECTED; + } + + superfetchServiceRunning = + superfetchStatus.dwCurrentState == SERVICE_RUNNING; + } + + // If the SysMain (Superfetch) service is available, but not configured using + // the defaults, then it's disabled for our purposes, since it's not going to + // be operating as expected. + bool superfetchUsingDefaultParams = true; + bool prefetchUsingDefaultParams = true; + + static const WCHAR prefetchParamsKeyName[] = + L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory " + L"Management\\PrefetchParameters"; + static const DWORD SUPERFETCH_DEFAULT_PARAM = 3; + static const DWORD PREFETCH_DEFAULT_PARAM = 3; + + HKEY prefetchParamsHKey; + + LONG prefetchParamsStatus = + RegOpenKeyExW(HKEY_LOCAL_MACHINE, prefetchParamsKeyName, 0, + KEY_READ | KEY_WOW64_64KEY, &prefetchParamsHKey); + + if (prefetchParamsStatus == ERROR_SUCCESS) { + DWORD valueSize = sizeof(DWORD); + DWORD superfetchValue = 0; + nsAutoRegKey prefetchParamsKey(prefetchParamsHKey); + LONG superfetchParamStatus = RegQueryValueExW( + prefetchParamsHKey, L"EnableSuperfetch", nullptr, &type, + reinterpret_cast(&superfetchValue), &valueSize); + + // If the EnableSuperfetch registry key doesn't exist, then it's using the + // default configuration. + if (superfetchParamStatus == ERROR_SUCCESS && + superfetchValue != SUPERFETCH_DEFAULT_PARAM) { + superfetchUsingDefaultParams = false; + } + + DWORD prefetchValue = 0; + + LONG prefetchParamStatus = RegQueryValueExW( + prefetchParamsHKey, L"EnablePrefetcher", nullptr, &type, + reinterpret_cast(&prefetchValue), &valueSize); + + // If the EnablePrefetcher registry key doesn't exist, then we interpret + // that as the Prefetcher being disabled (since Prefetch behaviour when + // the key is not available appears to be undefined). + if (prefetchParamStatus != ERROR_SUCCESS || + prefetchValue != PREFETCH_DEFAULT_PARAM) { + prefetchUsingDefaultParams = false; + } + } + + info.hasSuperfetch = superfetchServiceRunning && superfetchUsingDefaultParams; + info.hasPrefetch = prefetchUsingDefaultParams; + + return NS_OK; +} + +nsresult CollectCountryCode(nsAString& aCountryCode) { + GEOID geoid = GetUserGeoID(GEOCLASS_NATION); + if (geoid == GEOID_NOT_AVAILABLE) { + return NS_ERROR_NOT_AVAILABLE; + } + // Get required length + int numChars = GetGeoInfoW(geoid, GEO_ISO2, nullptr, 0, 0); + if (!numChars) { + return NS_ERROR_FAILURE; + } + // Now get the string for real + aCountryCode.SetLength(numChars); + numChars = + GetGeoInfoW(geoid, GEO_ISO2, char16ptr_t(aCountryCode.BeginWriting()), + aCountryCode.Length(), 0); + if (!numChars) { + return NS_ERROR_FAILURE; + } + + // numChars includes null terminator + aCountryCode.Truncate(numChars - 1); + return NS_OK; +} + +} // namespace + +# ifndef __MINGW32__ + +static HRESULT EnumWSCProductList( + nsAString& aOutput, mozilla::NotNull aProdList) { + MOZ_ASSERT(aOutput.IsEmpty()); + + LONG count; + HRESULT hr = aProdList->get_Count(&count); + if (FAILED(hr)) { + return hr; + } + + for (LONG index = 0; index < count; ++index) { + RefPtr product; + hr = aProdList->get_Item(index, getter_AddRefs(product)); + if (FAILED(hr)) { + return hr; + } + + WSC_SECURITY_PRODUCT_STATE state; + hr = product->get_ProductState(&state); + if (FAILED(hr)) { + return hr; + } + + // We only care about products that are active + if (state == WSC_SECURITY_PRODUCT_STATE_OFF || + state == WSC_SECURITY_PRODUCT_STATE_SNOOZED) { + continue; + } + + _bstr_t bName; + hr = product->get_ProductName(bName.GetAddress()); + if (FAILED(hr)) { + return hr; + } + + if (!aOutput.IsEmpty()) { + aOutput.AppendLiteral(u";"); + } + + aOutput.Append((wchar_t*)bName, bName.length()); + } + + return S_OK; +} + +static nsresult GetWindowsSecurityCenterInfo(nsAString& aAVInfo, + nsAString& aAntiSpyInfo, + nsAString& aFirewallInfo) { + aAVInfo.Truncate(); + aAntiSpyInfo.Truncate(); + aFirewallInfo.Truncate(); + + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + const CLSID clsid = __uuidof(WSCProductList); + const IID iid = __uuidof(IWSCProductList); + + // NB: A separate instance of IWSCProductList is needed for each distinct + // security provider type; MSDN says that we cannot reuse the same object + // and call Initialize() to pave over the previous data. + + WSC_SECURITY_PROVIDER providerTypes[] = {WSC_SECURITY_PROVIDER_ANTIVIRUS, + WSC_SECURITY_PROVIDER_ANTISPYWARE, + WSC_SECURITY_PROVIDER_FIREWALL}; + + // Each output must match the corresponding entry in providerTypes. + nsAString* outputs[] = {&aAVInfo, &aAntiSpyInfo, &aFirewallInfo}; + + static_assert( + mozilla::ArrayLength(providerTypes) == mozilla::ArrayLength(outputs), + "Length of providerTypes and outputs arrays must match"); + + for (uint32_t index = 0; index < mozilla::ArrayLength(providerTypes); + ++index) { + RefPtr prodList; + HRESULT hr = ::CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, iid, + getter_AddRefs(prodList)); + if (FAILED(hr)) { + return NS_ERROR_NOT_AVAILABLE; + } + + hr = prodList->Initialize(providerTypes[index]); + if (FAILED(hr)) { + return NS_ERROR_UNEXPECTED; + } + + hr = EnumWSCProductList(*outputs[index], + mozilla::WrapNotNull(prodList.get())); + if (FAILED(hr)) { + return NS_ERROR_UNEXPECTED; + } + } + + return NS_OK; +} + +# endif // __MINGW32__ + +#endif // defined(XP_WIN) + +#ifdef XP_MACOSX +static nsresult GetAppleModelId(nsAutoCString& aModelId) { + size_t numChars = 0; + size_t result = sysctlbyname("hw.model", nullptr, &numChars, nullptr, 0); + if (result != 0 || !numChars) { + return NS_ERROR_FAILURE; + } + aModelId.SetLength(numChars); + result = + sysctlbyname("hw.model", aModelId.BeginWriting(), &numChars, nullptr, 0); + if (result != 0) { + return NS_ERROR_FAILURE; + } + // numChars includes null terminator + aModelId.Truncate(numChars - 1); + return NS_OK; +} + +static nsresult ProcessIsRosettaTranslated(bool& isRosetta) { +# if defined(__aarch64__) + // There is no need to call sysctlbyname() if we are running as arm64. + isRosetta = false; +# else + int ret = 0; + size_t size = sizeof(ret); + if (sysctlbyname("sysctl.proc_translated", &ret, &size, NULL, 0) == -1) { + if (errno != ENOENT) { + fprintf(stderr, "Failed to check for translation environment\n"); + } + isRosetta = false; + } else { + isRosetta = (ret == 1); + } +# endif + return NS_OK; +} +#endif + +using namespace mozilla; + +nsSystemInfo::nsSystemInfo() = default; + +nsSystemInfo::~nsSystemInfo() = default; + +// CPU-specific information. +static const struct PropItems { + const char* name; + bool (*propfun)(void); +} cpuPropItems[] = { + // x86-specific bits. + {"hasMMX", mozilla::supports_mmx}, + {"hasSSE", mozilla::supports_sse}, + {"hasSSE2", mozilla::supports_sse2}, + {"hasSSE3", mozilla::supports_sse3}, + {"hasSSSE3", mozilla::supports_ssse3}, + {"hasSSE4A", mozilla::supports_sse4a}, + {"hasSSE4_1", mozilla::supports_sse4_1}, + {"hasSSE4_2", mozilla::supports_sse4_2}, + {"hasAVX", mozilla::supports_avx}, + {"hasAVX2", mozilla::supports_avx2}, + {"hasAES", mozilla::supports_aes}, + // ARM-specific bits. + {"hasEDSP", mozilla::supports_edsp}, + {"hasARMv6", mozilla::supports_armv6}, + {"hasARMv7", mozilla::supports_armv7}, + {"hasNEON", mozilla::supports_neon}}; + +nsresult CollectProcessInfo(ProcessInfo& info) { + nsAutoCString cpuVendor; + nsAutoCString cpuName; + int cpuSpeed = -1; + int cpuFamily = -1; + int cpuModel = -1; + int cpuStepping = -1; + int logicalCPUs = -1; + int physicalCPUs = -1; + int cacheSizeL2 = -1; + int cacheSizeL3 = -1; + +#if defined(XP_WIN) + // IsWow64Process2 is only available on Windows 10+, so we have to dynamically + // check for its existence. + typedef BOOL(WINAPI * LPFN_IWP2)(HANDLE, USHORT*, USHORT*); + LPFN_IWP2 iwp2 = reinterpret_cast( + GetProcAddress(GetModuleHandle(L"kernel32"), "IsWow64Process2")); + BOOL isWow64 = FALSE; + USHORT processMachine = IMAGE_FILE_MACHINE_UNKNOWN; + USHORT nativeMachine = IMAGE_FILE_MACHINE_UNKNOWN; + BOOL gotWow64Value; + if (iwp2) { + gotWow64Value = iwp2(GetCurrentProcess(), &processMachine, &nativeMachine); + if (gotWow64Value) { + isWow64 = (processMachine != IMAGE_FILE_MACHINE_UNKNOWN); + } + } else { + gotWow64Value = IsWow64Process(GetCurrentProcess(), &isWow64); + // The function only indicates a WOW64 environment if it's 32-bit x86 + // running on x86-64, so emulate what IsWow64Process2 would have given. + if (gotWow64Value && isWow64) { + processMachine = IMAGE_FILE_MACHINE_I386; + nativeMachine = IMAGE_FILE_MACHINE_AMD64; + } + } + NS_WARNING_ASSERTION(gotWow64Value, "IsWow64Process failed"); + if (gotWow64Value) { + // Set this always, even for the x86-on-arm64 case. + info.isWow64 = !!isWow64; + // Additional information if we're running x86-on-arm64 + info.isWowARM64 = (processMachine == IMAGE_FILE_MACHINE_I386 && + nativeMachine == IMAGE_FILE_MACHINE_ARM64); + } + + // S Mode + +# ifndef __MINGW32__ + // WindowsIntegrityPolicy is only available on newer versions + // of Windows 10, so there's no point in trying to check this + // on earlier versions. We know GetActivationFactory crashes on + // Windows 7 when trying to retrieve this class, and may also + // crash on very old versions of Windows 10. + if (IsWin10Sep2018UpdateOrLater()) { + ComPtr wip; + HRESULT hr = GetActivationFactory( + HStringReference( + RuntimeClass_Windows_System_Profile_WindowsIntegrityPolicy) + .Get(), + &wip); + if (SUCCEEDED(hr)) { + // info.isWindowsSMode ends up true if Windows is in S mode, otherwise + // false + // https://docs.microsoft.com/en-us/uwp/api/windows.system.profile.windowsintegritypolicy.isenabled?view=winrt-22000 + hr = wip->get_IsEnabled(&info.isWindowsSMode); + NS_WARNING_ASSERTION(SUCCEEDED(hr), + "WindowsIntegrityPolicy.IsEnabled failed"); + } + } +# endif // __MINGW32__ + + // CPU speed + HKEY key; + static const WCHAR keyName[] = + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_QUERY_VALUE, &key) == + ERROR_SUCCESS) { + DWORD data, len, vtype; + len = sizeof(data); + + if (RegQueryValueEx(key, L"~Mhz", 0, 0, reinterpret_cast(&data), + &len) == ERROR_SUCCESS) { + cpuSpeed = static_cast(data); + } + + // Limit to 64 double byte characters, should be plenty, but create + // a buffer one larger as the result may not be null terminated. If + // it is more than 64, we will not get the value. + wchar_t cpuVendorStr[64 + 1]; + len = sizeof(cpuVendorStr) - 2; + if (RegQueryValueExW(key, L"VendorIdentifier", 0, &vtype, + reinterpret_cast(cpuVendorStr), + &len) == ERROR_SUCCESS && + vtype == REG_SZ && len % 2 == 0 && len > 1) { + cpuVendorStr[len / 2] = 0; // In case it isn't null terminated + CopyUTF16toUTF8(nsDependentString(cpuVendorStr), cpuVendor); + } + + // Limit to 64 double byte characters, should be plenty, but create + // a buffer one larger as the result may not be null terminated. If + // it is more than 64, we will not get the value. + // The expected string size is 48 characters or less. + wchar_t cpuNameStr[64 + 1]; + len = sizeof(cpuNameStr) - 2; + if (RegQueryValueExW(key, L"ProcessorNameString", 0, &vtype, + reinterpret_cast(cpuNameStr), + &len) == ERROR_SUCCESS && + vtype == REG_SZ && len % 2 == 0 && len > 1) { + cpuNameStr[len / 2] = 0; // In case it isn't null terminated + CopyUTF16toUTF8(nsDependentString(cpuNameStr), cpuName); + } + + RegCloseKey(key); + } + + // Other CPU attributes: + SYSTEM_INFO si; + GetNativeSystemInfo(&si); + logicalCPUs = si.dwNumberOfProcessors; + GetProcessorInformation(&physicalCPUs, &cacheSizeL2, &cacheSizeL3); + if (physicalCPUs <= 0) { + physicalCPUs = logicalCPUs; + } + cpuFamily = si.wProcessorLevel; + cpuModel = si.wProcessorRevision >> 8; + cpuStepping = si.wProcessorRevision & 0xFF; +#elif defined(XP_MACOSX) + // CPU speed + uint64_t sysctlValue64 = 0; + uint32_t sysctlValue32 = 0; + size_t len = 0; + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.cpufrequency_max", &sysctlValue64, &len, NULL, 0)) { + cpuSpeed = static_cast(sysctlValue64 / 1000000); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("hw.physicalcpu_max", &sysctlValue32, &len, NULL, 0)) { + physicalCPUs = static_cast(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("hw.logicalcpu_max", &sysctlValue32, &len, NULL, 0)) { + logicalCPUs = static_cast(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.l2cachesize", &sysctlValue64, &len, NULL, 0)) { + cacheSizeL2 = static_cast(sysctlValue64 / 1024); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + len = sizeof(sysctlValue64); + if (!sysctlbyname("hw.l3cachesize", &sysctlValue64, &len, NULL, 0)) { + cacheSizeL3 = static_cast(sysctlValue64 / 1024); + } + MOZ_ASSERT(sizeof(sysctlValue64) == len); + + if (!sysctlbyname("machdep.cpu.vendor", NULL, &len, NULL, 0)) { + char* cpuVendorStr = new char[len]; + if (!sysctlbyname("machdep.cpu.vendor", cpuVendorStr, &len, NULL, 0)) { + cpuVendor = cpuVendorStr; + } + delete[] cpuVendorStr; + } + + if (!sysctlbyname("machdep.cpu.brand_string", NULL, &len, NULL, 0)) { + char* cpuNameStr = new char[len]; + if (!sysctlbyname("machdep.cpu.brand_string", cpuNameStr, &len, NULL, 0)) { + cpuName = cpuNameStr; + } + delete[] cpuNameStr; + } + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.family", &sysctlValue32, &len, NULL, 0)) { + cpuFamily = static_cast(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.model", &sysctlValue32, &len, NULL, 0)) { + cpuModel = static_cast(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + + len = sizeof(sysctlValue32); + if (!sysctlbyname("machdep.cpu.stepping", &sysctlValue32, &len, NULL, 0)) { + cpuStepping = static_cast(sysctlValue32); + } + MOZ_ASSERT(sizeof(sysctlValue32) == len); + +#elif defined(XP_LINUX) && !defined(ANDROID) + // Get vendor, family, model, stepping, physical cores + // from /proc/cpuinfo file + { + std::map keyValuePairs; + SimpleParseKeyValuePairs("/proc/cpuinfo", keyValuePairs); + + // cpuVendor from "vendor_id" + info.cpuVendor.Assign(keyValuePairs["vendor_id"_ns]); + + // cpuName from "model name" + info.cpuName.Assign(keyValuePairs["model name"_ns]); + + { + // cpuFamily from "cpu family" + Tokenizer::Token t; + Tokenizer p(keyValuePairs["cpu family"_ns]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuFamily = static_cast(t.AsInteger()); + } + } + + { + // cpuModel from "model" + Tokenizer::Token t; + Tokenizer p(keyValuePairs["model"_ns]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuModel = static_cast(t.AsInteger()); + } + } + + { + // cpuStepping from "stepping" + Tokenizer::Token t; + Tokenizer p(keyValuePairs["stepping"_ns]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuStepping = static_cast(t.AsInteger()); + } + } + + { + // physicalCPUs from "cpu cores" + Tokenizer::Token t; + Tokenizer p(keyValuePairs["cpu cores"_ns]); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + physicalCPUs = static_cast(t.AsInteger()); + } + } + } + + { + // Get cpuSpeed from another file. + std::ifstream input( + "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"); + std::string line; + if (getline(input, line)) { + Tokenizer::Token t; + Tokenizer p(line.c_str()); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cpuSpeed = static_cast(t.AsInteger() / 1000); + } + } + } + + { + // Get cacheSizeL2 from yet another file + std::ifstream input("/sys/devices/system/cpu/cpu0/cache/index2/size"); + std::string line; + if (getline(input, line)) { + Tokenizer::Token t; + Tokenizer p(line.c_str(), nullptr, "K"); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cacheSizeL2 = static_cast(t.AsInteger()); + } + } + } + + { + // Get cacheSizeL3 from yet another file + std::ifstream input("/sys/devices/system/cpu/cpu0/cache/index3/size"); + std::string line; + if (getline(input, line)) { + Tokenizer::Token t; + Tokenizer p(line.c_str(), nullptr, "K"); + if (p.Next(t) && t.Type() == Tokenizer::TOKEN_INTEGER && + t.AsInteger() <= INT32_MAX) { + cacheSizeL3 = static_cast(t.AsInteger()); + } + } + } + + info.cpuCount = PR_GetNumberOfProcessors(); +#else + info.cpuCount = PR_GetNumberOfProcessors(); +#endif + + if (cpuSpeed >= 0) { + info.cpuSpeed = cpuSpeed; + } else { + info.cpuSpeed = 0; + } + if (!cpuVendor.IsEmpty()) { + info.cpuVendor = cpuVendor; + } + if (!cpuName.IsEmpty()) { + info.cpuName = cpuName; + } + if (cpuFamily >= 0) { + info.cpuFamily = cpuFamily; + } + if (cpuModel >= 0) { + info.cpuModel = cpuModel; + } + if (cpuStepping >= 0) { + info.cpuStepping = cpuStepping; + } + + if (logicalCPUs >= 0) { + info.cpuCount = logicalCPUs; + } + if (physicalCPUs >= 0) { + info.cpuCores = physicalCPUs; + } + + if (cacheSizeL2 >= 0) { + info.l2cacheKB = cacheSizeL2; + } + if (cacheSizeL3 >= 0) { + info.l3cacheKB = cacheSizeL3; + } + + return NS_OK; +} + +#if defined(XP_WIN) && (_WIN32_WINNT < 0x0A00) +WINBASEAPI +BOOL WINAPI IsUserCetAvailableInEnvironment(_In_ DWORD UserCetEnvironment); + +# define USER_CET_ENVIRONMENT_WIN32_PROCESS 0x00000000 +#endif + +nsresult nsSystemInfo::Init() { + // check that it is called from the main thread on all platforms. + MOZ_ASSERT(NS_IsMainThread()); + + nsresult rv; + + static const struct { + PRSysInfo cmd; + const char* name; + } items[] = {{PR_SI_SYSNAME, "name"}, + {PR_SI_ARCHITECTURE, "arch"}, + {PR_SI_RELEASE, "version"}, + {PR_SI_RELEASE_BUILD, "build"}}; + + for (uint32_t i = 0; i < (sizeof(items) / sizeof(items[0])); i++) { + char buf[SYS_INFO_BUFFER_LENGTH]; + if (PR_GetSystemInfo(items[i].cmd, buf, sizeof(buf)) == PR_SUCCESS) { + rv = SetPropertyAsACString(NS_ConvertASCIItoUTF16(items[i].name), + nsDependentCString(buf)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } else { + NS_WARNING("PR_GetSystemInfo failed"); + } + } + + SetPropertyAsBool(u"isPackagedApp"_ns, false); + + // Additional informations not available through PR_GetSystemInfo. + SetInt32Property(u"pagesize"_ns, PR_GetPageSize()); + SetInt32Property(u"pageshift"_ns, PR_GetPageShift()); + SetInt32Property(u"memmapalign"_ns, PR_GetMemMapAlignment()); + SetUint64Property(u"memsize"_ns, PR_GetPhysicalMemorySize()); + SetUint32Property(u"umask"_ns, nsSystemInfo::gUserUmask); + + uint64_t virtualMem = 0; + +#if defined(XP_WIN) + // Virtual memory: + MEMORYSTATUSEX memStat; + memStat.dwLength = sizeof(memStat); + if (GlobalMemoryStatusEx(&memStat)) { + virtualMem = memStat.ullTotalVirtual; + } +#endif + if (virtualMem) SetUint64Property(u"virtualmemsize"_ns, virtualMem); + + for (uint32_t i = 0; i < ArrayLength(cpuPropItems); i++) { + rv = SetPropertyAsBool(NS_ConvertASCIItoUTF16(cpuPropItems[i].name), + cpuPropItems[i].propfun()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + +#ifdef XP_WIN + bool isMinGW = +# ifdef __MINGW32__ + true; +# else + false; +# endif + rv = SetPropertyAsBool(u"isMinGW"_ns, !!isMinGW); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + boolean hasPackageIdentity = widget::WinUtils::HasPackageIdentity(); + + rv = SetPropertyAsBool(u"hasWinPackageId"_ns, hasPackageIdentity); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = SetPropertyAsAString(u"winPackageFamilyName"_ns, + widget::WinUtils::GetPackageFamilyName()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = SetPropertyAsBool(u"isPackagedApp"_ns, hasPackageIdentity); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +# ifndef __MINGW32__ + nsAutoString avInfo, antiSpyInfo, firewallInfo; + if (NS_SUCCEEDED( + GetWindowsSecurityCenterInfo(avInfo, antiSpyInfo, firewallInfo))) { + if (!avInfo.IsEmpty()) { + rv = SetPropertyAsAString(u"registeredAntiVirus"_ns, avInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + if (!antiSpyInfo.IsEmpty()) { + rv = SetPropertyAsAString(u"registeredAntiSpyware"_ns, antiSpyInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + if (!firewallInfo.IsEmpty()) { + rv = SetPropertyAsAString(u"registeredFirewall"_ns, firewallInfo); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + } +# endif // __MINGW32__ + + mozilla::DynamicallyLinkedFunctionPtr< + decltype(&IsUserCetAvailableInEnvironment)> + isUserCetAvailable(L"api-ms-win-core-sysinfo-l1-2-6.dll", + "IsUserCetAvailableInEnvironment"); + bool hasUserCET = isUserCetAvailable && + isUserCetAvailable(USER_CET_ENVIRONMENT_WIN32_PROCESS); + rv = SetPropertyAsBool(u"hasUserCET"_ns, hasUserCET); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#endif + +#if defined(XP_MACOSX) + nsAutoCString modelId; + if (NS_SUCCEEDED(GetAppleModelId(modelId))) { + rv = SetPropertyAsACString(u"appleModelId"_ns, modelId); + NS_ENSURE_SUCCESS(rv, rv); + } + bool isRosetta; + if (NS_SUCCEEDED(ProcessIsRosettaTranslated(isRosetta))) { + rv = SetPropertyAsBool(u"rosettaStatus"_ns, isRosetta); + NS_ENSURE_SUCCESS(rv, rv); + } +#endif + + { + nsAutoCString themeInfo; + LookAndFeel::GetThemeInfo(themeInfo); + MOZ_TRY(SetPropertyAsACString(u"osThemeInfo"_ns, themeInfo)); + } + +#if defined(MOZ_WIDGET_GTK) + // This must be done here because NSPR can only separate OS's when compiled, + // not libraries. 64 bytes is going to be well enough for "GTK " followed by 3 + // integers separated with dots. + char gtkver[64]; + ssize_t gtkver_len = 0; + + if (gtkver_len <= 0) { + gtkver_len = SprintfLiteral(gtkver, "GTK %u.%u.%u", gtk_major_version, + gtk_minor_version, gtk_micro_version); + } + + nsAutoCString secondaryLibrary; + if (gtkver_len > 0 && gtkver_len < int(sizeof(gtkver))) { + secondaryLibrary.Append(nsDependentCSubstring(gtkver, gtkver_len)); + } + +# ifndef MOZ_TSAN + // With TSan, avoid loading libpulse here because we cannot unload it + // afterwards due to restrictions from TSan about unloading libraries + // matched by the suppression list. + void* libpulse = dlopen("libpulse.so.0", RTLD_LAZY); + const char* libpulseVersion = "not-available"; + if (libpulse) { + auto pa_get_library_version = reinterpret_cast( + dlsym(libpulse, "pa_get_library_version")); + + if (pa_get_library_version) { + libpulseVersion = pa_get_library_version(); + } + } + + secondaryLibrary.AppendPrintf(",libpulse %s", libpulseVersion); + + if (libpulse) { + dlclose(libpulse); + } +# endif + + rv = SetPropertyAsACString(u"secondaryLibrary"_ns, secondaryLibrary); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = SetPropertyAsBool(u"isPackagedApp"_ns, + widget::IsRunningUnderFlatpakOrSnap() || + widget::IsPackagedAppFileExists()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } +#endif + +#ifdef MOZ_WIDGET_ANDROID + AndroidSystemInfo info; + GetAndroidSystemInfo(&info); + SetupAndroidInfo(info); +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + SandboxInfo sandInfo = SandboxInfo::Get(); + + SetPropertyAsBool(u"hasSeccompBPF"_ns, + sandInfo.Test(SandboxInfo::kHasSeccompBPF)); + SetPropertyAsBool(u"hasSeccompTSync"_ns, + sandInfo.Test(SandboxInfo::kHasSeccompTSync)); + SetPropertyAsBool(u"hasUserNamespaces"_ns, + sandInfo.Test(SandboxInfo::kHasUserNamespaces)); + SetPropertyAsBool(u"hasPrivilegedUserNamespaces"_ns, + sandInfo.Test(SandboxInfo::kHasPrivilegedUserNamespaces)); + + if (sandInfo.Test(SandboxInfo::kEnabledForContent)) { + SetPropertyAsBool(u"canSandboxContent"_ns, sandInfo.CanSandboxContent()); + } + + if (sandInfo.Test(SandboxInfo::kEnabledForMedia)) { + SetPropertyAsBool(u"canSandboxMedia"_ns, sandInfo.CanSandboxMedia()); + } +#endif // XP_LINUX && MOZ_SANDBOX + + return NS_OK; +} + +#ifdef MOZ_WIDGET_ANDROID +// Prerelease versions of Android use a letter instead of version numbers. +// Unfortunately this breaks websites due to the user agent. +// Chrome works around this by hardcoding an Android version when a +// numeric version can't be obtained. We're doing the same. +// This version will need to be updated whenever there is a new official +// Android release. Search for "kDefaultAndroidMajorVersion" in: +// https://source.chromium.org/chromium/chromium/src/+/master:base/system/sys_info_android.cc +# define DEFAULT_ANDROID_VERSION u"10.0.99" + +/* static */ +void nsSystemInfo::GetAndroidSystemInfo(AndroidSystemInfo* aInfo) { + if (!jni::IsAvailable()) { + // called from xpcshell etc. + aInfo->sdk_version() = 0; + return; + } + + jni::String::LocalRef model = java::sdk::Build::MODEL(); + aInfo->device() = model->ToString(); + + jni::String::LocalRef manufacturer = + mozilla::java::sdk::Build::MANUFACTURER(); + aInfo->manufacturer() = manufacturer->ToString(); + + jni::String::LocalRef hardware = java::sdk::Build::HARDWARE(); + aInfo->hardware() = hardware->ToString(); + + jni::String::LocalRef release = java::sdk::Build::VERSION::RELEASE(); + nsString str(release->ToString()); + int major_version; + int minor_version; + int bugfix_version; + int num_read = sscanf(NS_ConvertUTF16toUTF8(str).get(), "%d.%d.%d", + &major_version, &minor_version, &bugfix_version); + if (num_read == 0) { + aInfo->release_version() = nsLiteralString(DEFAULT_ANDROID_VERSION); + } else { + aInfo->release_version() = str; + } + + aInfo->sdk_version() = jni::GetAPIVersion(); + aInfo->isTablet() = java::GeckoAppShell::IsTablet(); +} + +void nsSystemInfo::SetupAndroidInfo(const AndroidSystemInfo& aInfo) { + if (!aInfo.device().IsEmpty()) { + SetPropertyAsAString(u"device"_ns, aInfo.device()); + } + if (!aInfo.manufacturer().IsEmpty()) { + SetPropertyAsAString(u"manufacturer"_ns, aInfo.manufacturer()); + } + if (!aInfo.release_version().IsEmpty()) { + SetPropertyAsAString(u"release_version"_ns, aInfo.release_version()); + } + SetPropertyAsBool(u"tablet"_ns, aInfo.isTablet()); + // NSPR "version" is the kernel version. For Android we want the Android + // version. Rename SDK version to version and put the kernel version into + // kernel_version. + nsAutoString str; + nsresult rv = GetPropertyAsAString(u"version"_ns, str); + if (NS_SUCCEEDED(rv)) { + SetPropertyAsAString(u"kernel_version"_ns, str); + } + // When JNI is not available (eg. in xpcshell tests), sdk_version is 0. + if (aInfo.sdk_version() != 0) { + if (!aInfo.hardware().IsEmpty()) { + SetPropertyAsAString(u"hardware"_ns, aInfo.hardware()); + } + SetPropertyAsInt32(u"version"_ns, aInfo.sdk_version()); + } +} +#endif // MOZ_WIDGET_ANDROID + +void nsSystemInfo::SetInt32Property(const nsAString& aPropertyName, + const int32_t aValue) { + NS_WARNING_ASSERTION(aValue > 0, "Unable to read system value"); + if (aValue > 0) { +#ifdef DEBUG + nsresult rv = +#endif + SetPropertyAsInt32(aPropertyName, aValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to set property"); + } +} + +void nsSystemInfo::SetUint32Property(const nsAString& aPropertyName, + const uint32_t aValue) { + // Only one property is currently set via this function. + // It may legitimately be zero. +#ifdef DEBUG + nsresult rv = +#endif + SetPropertyAsUint32(aPropertyName, aValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to set property"); +} + +void nsSystemInfo::SetUint64Property(const nsAString& aPropertyName, + const uint64_t aValue) { + NS_WARNING_ASSERTION(aValue > 0, "Unable to read system value"); + if (aValue > 0) { +#ifdef DEBUG + nsresult rv = +#endif + SetPropertyAsUint64(aPropertyName, aValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Unable to set property"); + } +} + +#ifdef XP_WIN + +static bool GetJSObjForDiskInfo(JSContext* aCx, JS::Handle aParent, + const FolderDiskInfo& info, + const char* propName) { + JS::Rooted jsInfo(aCx, JS_NewPlainObject(aCx)); + if (!jsInfo) { + return false; + } + + JSString* strModel = + JS_NewStringCopyN(aCx, info.model.get(), info.model.Length()); + if (!strModel) { + return false; + } + JS::Rooted valModel(aCx, JS::StringValue(strModel)); + if (!JS_SetProperty(aCx, jsInfo, "model", valModel)) { + return false; + } + + JSString* strRevision = + JS_NewStringCopyN(aCx, info.revision.get(), info.revision.Length()); + if (!strRevision) { + return false; + } + JS::Rooted valRevision(aCx, JS::StringValue(strRevision)); + if (!JS_SetProperty(aCx, jsInfo, "revision", valRevision)) { + return false; + } + + JSString* strSSD = JS_NewStringCopyZ(aCx, info.isSSD ? "SSD" : "HDD"); + if (!strSSD) { + return false; + } + JS::Rooted valSSD(aCx, JS::StringValue(strSSD)); + if (!JS_SetProperty(aCx, jsInfo, "type", valSSD)) { + return false; + } + + JS::Rooted val(aCx, JS::ObjectValue(*jsInfo)); + return JS_SetProperty(aCx, aParent, propName, val); +} + +JSObject* GetJSObjForOSInfo(JSContext* aCx, const OSInfo& info) { + JS::Rooted jsInfo(aCx, JS_NewPlainObject(aCx)); + + JS::Rooted valInstallYear(aCx, JS::Int32Value(info.installYear)); + JS_SetProperty(aCx, jsInfo, "installYear", valInstallYear); + + JS::Rooted valHasSuperfetch(aCx, + JS::BooleanValue(info.hasSuperfetch)); + JS_SetProperty(aCx, jsInfo, "hasSuperfetch", valHasSuperfetch); + + JS::Rooted valHasPrefetch(aCx, JS::BooleanValue(info.hasPrefetch)); + JS_SetProperty(aCx, jsInfo, "hasPrefetch", valHasPrefetch); + + return jsInfo; +} + +#endif + +JSObject* GetJSObjForProcessInfo(JSContext* aCx, const ProcessInfo& info) { + JS::Rooted jsInfo(aCx, JS_NewPlainObject(aCx)); + +#if defined(XP_WIN) + JS::Rooted valisWow64(aCx, JS::BooleanValue(info.isWow64)); + JS_SetProperty(aCx, jsInfo, "isWow64", valisWow64); + + JS::Rooted valisWowARM64(aCx, JS::BooleanValue(info.isWowARM64)); + JS_SetProperty(aCx, jsInfo, "isWowARM64", valisWowARM64); + + JS::Rooted valisWindowsSMode( + aCx, JS::BooleanValue(info.isWindowsSMode)); + JS_SetProperty(aCx, jsInfo, "isWindowsSMode", valisWindowsSMode); +#endif + + JS::Rooted valCountInfo(aCx, JS::Int32Value(info.cpuCount)); + JS_SetProperty(aCx, jsInfo, "count", valCountInfo); + + JS::Rooted valCoreInfo(aCx, JS::Int32Value(info.cpuCores)); + JS_SetProperty(aCx, jsInfo, "cores", valCoreInfo); + + JSString* strVendor = + JS_NewStringCopyN(aCx, info.cpuVendor.get(), info.cpuVendor.Length()); + JS::Rooted valVendor(aCx, JS::StringValue(strVendor)); + JS_SetProperty(aCx, jsInfo, "vendor", valVendor); + + JSString* strName = + JS_NewStringCopyN(aCx, info.cpuName.get(), info.cpuName.Length()); + JS::Rooted valName(aCx, JS::StringValue(strName)); + JS_SetProperty(aCx, jsInfo, "name", valName); + + JS::Rooted valFamilyInfo(aCx, JS::Int32Value(info.cpuFamily)); + JS_SetProperty(aCx, jsInfo, "family", valFamilyInfo); + + JS::Rooted valModelInfo(aCx, JS::Int32Value(info.cpuModel)); + JS_SetProperty(aCx, jsInfo, "model", valModelInfo); + + JS::Rooted valSteppingInfo(aCx, JS::Int32Value(info.cpuStepping)); + JS_SetProperty(aCx, jsInfo, "stepping", valSteppingInfo); + + JS::Rooted valL2CacheInfo(aCx, JS::Int32Value(info.l2cacheKB)); + JS_SetProperty(aCx, jsInfo, "l2cacheKB", valL2CacheInfo); + + JS::Rooted valL3CacheInfo(aCx, JS::Int32Value(info.l3cacheKB)); + JS_SetProperty(aCx, jsInfo, "l3cacheKB", valL3CacheInfo); + + JS::Rooted valSpeedInfo(aCx, JS::Int32Value(info.cpuSpeed)); + JS_SetProperty(aCx, jsInfo, "speedMHz", valSpeedInfo); + + return jsInfo; +} + +RefPtr nsSystemInfo::GetBackgroundTarget() { + if (!mBackgroundET) { + MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue( + "SystemInfoThread", getter_AddRefs(mBackgroundET))); + } + return mBackgroundET; +} + +NS_IMETHODIMP +nsSystemInfo::GetOsInfo(JSContext* aCx, Promise** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } +#if defined(XP_WIN) + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr promise = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + if (!mOSInfoPromise) { + RefPtr backgroundET = GetBackgroundTarget(); + + mOSInfoPromise = InvokeAsync(backgroundET, __func__, []() { + OSInfo info; + nsresult rv = CollectOSInfo(info); + if (NS_SUCCEEDED(rv)) { + return OSInfoPromise::CreateAndResolve(info, __func__); + } + return OSInfoPromise::CreateAndReject(rv, __func__); + }); + }; + + // Chain the new promise to the extant mozpromise + RefPtr capturedPromise = promise; + mOSInfoPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [capturedPromise](const OSInfo& info) { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) { + capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted val( + cx, JS::ObjectValue(*GetJSObjForOSInfo(cx, info))); + capturedPromise->MaybeResolve(val); + }, + [capturedPromise](const nsresult rv) { + // Resolve with null when installYear is not available from the system + capturedPromise->MaybeResolve(JS::NullHandleValue); + }); + + promise.forget(aResult); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsSystemInfo::GetDiskInfo(JSContext* aCx, Promise** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } +#ifdef XP_WIN + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + ErrorResult erv; + RefPtr promise = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + if (!mDiskInfoPromise) { + RefPtr backgroundET = GetBackgroundTarget(); + nsCOMPtr greDir; + nsCOMPtr winDir; + nsCOMPtr profDir; + nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greDir)); + if (NS_FAILED(rv)) { + return rv; + } + rv = NS_GetSpecialDirectory(NS_WIN_WINDOWS_DIR, getter_AddRefs(winDir)); + if (NS_FAILED(rv)) { + return rv; + } + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profDir)); + if (NS_FAILED(rv)) { + return rv; + } + + mDiskInfoPromise = + InvokeAsync(backgroundET, __func__, [greDir, winDir, profDir]() { + DiskInfo info; + nsresult rv = CollectDiskInfo(greDir, winDir, profDir, info); + if (NS_SUCCEEDED(rv)) { + return DiskInfoPromise::CreateAndResolve(info, __func__); + } + return DiskInfoPromise::CreateAndReject(rv, __func__); + }); + } + + // Chain the new promise to the extant mozpromise. + RefPtr capturedPromise = promise; + mDiskInfoPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [capturedPromise](const DiskInfo& info) { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) { + capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted jsInfo(cx, JS_NewPlainObject(cx)); + // Store data in the rv: + bool succeededSettingAllObjects = + jsInfo && GetJSObjForDiskInfo(cx, jsInfo, info.binary, "binary") && + GetJSObjForDiskInfo(cx, jsInfo, info.profile, "profile") && + GetJSObjForDiskInfo(cx, jsInfo, info.system, "system"); + // The above can fail due to OOM + if (!succeededSettingAllObjects) { + JS_ClearPendingException(cx); + capturedPromise->MaybeReject(NS_ERROR_FAILURE); + return; + } + + JS::Rooted val(cx, JS::ObjectValue(*jsInfo)); + capturedPromise->MaybeResolve(val); + }, + [capturedPromise](const nsresult rv) { + capturedPromise->MaybeReject(rv); + }); + + promise.forget(aResult); +#endif + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(nsSystemInfo, nsHashPropertyBag, nsISystemInfo) + +NS_IMETHODIMP +nsSystemInfo::GetCountryCode(JSContext* aCx, Promise** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } +#if defined(XP_MACOSX) || defined(XP_WIN) + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr promise = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + if (!mCountryCodePromise) { + RefPtr backgroundET = GetBackgroundTarget(); + + mCountryCodePromise = InvokeAsync(backgroundET, __func__, []() { + nsAutoString countryCode; +# ifdef XP_MACOSX + nsresult rv = GetSelectedCityInfo(countryCode); +# endif +# ifdef XP_WIN + nsresult rv = CollectCountryCode(countryCode); +# endif + + if (NS_SUCCEEDED(rv)) { + return CountryCodePromise::CreateAndResolve(countryCode, __func__); + } + return CountryCodePromise::CreateAndReject(rv, __func__); + }); + } + + RefPtr capturedPromise = promise; + mCountryCodePromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [capturedPromise](const nsString& countryCode) { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) { + capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted jsCountryCode( + cx, JS_NewUCStringCopyZ(cx, countryCode.get())); + + JS::Rooted val(cx, JS::StringValue(jsCountryCode)); + capturedPromise->MaybeResolve(val); + }, + [capturedPromise](const nsresult rv) { + // Resolve with null when countryCode is not available from the system + capturedPromise->MaybeResolve(JS::NullHandleValue); + }); + + promise.forget(aResult); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsSystemInfo::GetProcessInfo(JSContext* aCx, Promise** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + if (!XRE_IsParentProcess()) { + return NS_ERROR_FAILURE; + } + + nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx); + if (NS_WARN_IF(!global)) { + return NS_ERROR_FAILURE; + } + + ErrorResult erv; + RefPtr promise = Promise::Create(global, erv); + if (NS_WARN_IF(erv.Failed())) { + return erv.StealNSResult(); + } + + if (!mProcessInfoPromise) { + RefPtr backgroundET = GetBackgroundTarget(); + + mProcessInfoPromise = InvokeAsync(backgroundET, __func__, []() { + ProcessInfo info; + nsresult rv = CollectProcessInfo(info); + if (NS_SUCCEEDED(rv)) { + return ProcessInfoPromise::CreateAndResolve(info, __func__); + } + return ProcessInfoPromise::CreateAndReject(rv, __func__); + }); + }; + + // Chain the new promise to the extant mozpromise + RefPtr capturedPromise = promise; + mProcessInfoPromise->Then( + GetMainThreadSerialEventTarget(), __func__, + [capturedPromise](const ProcessInfo& info) { + AutoJSAPI jsapi; + if (NS_WARN_IF(!jsapi.Init(capturedPromise->GetGlobalObject()))) { + capturedPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return; + } + JSContext* cx = jsapi.cx(); + JS::Rooted val( + cx, JS::ObjectValue(*GetJSObjForProcessInfo(cx, info))); + capturedPromise->MaybeResolve(val); + }, + [capturedPromise](const nsresult rv) { + // Resolve with null when installYear is not available from the system + capturedPromise->MaybeResolve(JS::NullHandleValue); + }); + + promise.forget(aResult); + + return NS_OK; +} diff --git a/xpcom/base/nsSystemInfo.h b/xpcom/base/nsSystemInfo.h new file mode 100644 index 0000000000..9d47b5d4f6 --- /dev/null +++ b/xpcom/base/nsSystemInfo.h @@ -0,0 +1,130 @@ +/* -*- 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/. */ + +#ifndef _NSSYSTEMINFO_H_ +#define _NSSYSTEMINFO_H_ + +#include "nsHashPropertyBag.h" +#include "nsISystemInfo.h" +#include "mozilla/MozPromise.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/dom/PContent.h" +#endif // MOZ_WIDGET_ANDROID + +#if defined(XP_WIN) +# include + +// The UUID comes from winrt/windows.system.profile.idl +// in the Windows SDK +MIDL_INTERFACE("7D1D81DB-8D63-4789-9EA5-DDCF65A94F3C") +IWindowsIntegrityPolicyStatics : public IInspectable { + public: + virtual HRESULT STDMETHODCALLTYPE get_IsEnabled(bool* value) = 0; +}; +#endif + +class nsISerialEventTarget; + +struct FolderDiskInfo { + nsCString model; + nsCString revision; + bool isSSD; +}; + +struct DiskInfo { + FolderDiskInfo binary; + FolderDiskInfo profile; + FolderDiskInfo system; +}; + +struct OSInfo { + uint32_t installYear; + bool hasSuperfetch; + bool hasPrefetch; +}; + +struct ProcessInfo { + bool isWow64 = false; + bool isWowARM64 = false; + // Whether or not the system is Windows 10 or 11 in S Mode. + // S Mode existed prior to us being able to query it, so this + // is unreliable on Windows versions prior to 1810. + bool isWindowsSMode = false; + int32_t cpuCount = 0; + int32_t cpuCores = 0; + nsCString cpuVendor; + nsCString cpuName; + int32_t cpuFamily = 0; + int32_t cpuModel = 0; + int32_t cpuStepping = 0; + int32_t l2cacheKB = 0; + int32_t l3cacheKB = 0; + int32_t cpuSpeed = 0; +}; + +typedef mozilla::MozPromise + DiskInfoPromise; + +typedef mozilla::MozPromise + CountryCodePromise; + +typedef mozilla::MozPromise + OSInfoPromise; + +typedef mozilla::MozPromise + ProcessInfoPromise; + +// Synchronous info collection, avoid calling it from the main thread, consider +// using the promise-based `nsISystemInfo::GetProcessInfo()` instead. +// Note that only known fields will be written. +nsresult CollectProcessInfo(ProcessInfo& info); + +class nsSystemInfo final : public nsISystemInfo, public nsHashPropertyBag { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISYSTEMINFO + + nsSystemInfo(); + + nsresult Init(); + + // Slot for NS_InitXPCOM to pass information to nsSystemInfo::Init. + // See comments above the variable definition and in NS_InitXPCOM. + static uint32_t gUserUmask; + +#ifdef MOZ_WIDGET_ANDROID + static void GetAndroidSystemInfo(mozilla::dom::AndroidSystemInfo* aInfo); + + protected: + void SetupAndroidInfo(const mozilla::dom::AndroidSystemInfo&); +#endif + + protected: + void SetInt32Property(const nsAString& aPropertyName, const int32_t aValue); + void SetUint32Property(const nsAString& aPropertyName, const uint32_t aValue); + void SetUint64Property(const nsAString& aPropertyName, const uint64_t aValue); + + private: + ~nsSystemInfo(); + + RefPtr mDiskInfoPromise; + RefPtr mCountryCodePromise; + RefPtr mOSInfoPromise; + RefPtr mProcessInfoPromise; + RefPtr mBackgroundET; + RefPtr GetBackgroundTarget(); +}; + +#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1" +#define NS_SYSTEMINFO_CID \ + { \ + 0xd962398a, 0x99e5, 0x49b2, { \ + 0x85, 0x7a, 0xc1, 0x59, 0x04, 0x9c, 0x7f, 0x6c \ + } \ + } + +#endif /* _NSSYSTEMINFO_H_ */ diff --git a/xpcom/base/nsTraceRefcnt.cpp b/xpcom/base/nsTraceRefcnt.cpp new file mode 100644 index 0000000000..1da0f6b647 --- /dev/null +++ b/xpcom/base/nsTraceRefcnt.cpp @@ -0,0 +1,1219 @@ +/* -*- 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 "nsTraceRefcnt.h" + +#include "base/process_util.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Path.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticPtr.h" +#include "nsXPCOMPrivate.h" +#include "nscore.h" +#include "nsClassHashtable.h" +#include "nsContentUtils.h" +#include "nsISupports.h" +#include "nsHashKeys.h" +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "nsTHashtable.h" +#include "prenv.h" +#include "prlink.h" +#include "nsCRT.h" +#include +#include "nsHashKeys.h" +#include "mozilla/StackWalk.h" +#include "nsThreadUtils.h" +#include "CodeAddressService.h" + +#include "nsXULAppAPI.h" +#ifdef XP_WIN +# include +# include +# define getpid _getpid +#else +# include +#endif + +#include "mozilla/Atomics.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/UniquePtr.h" + +#include +#include + +#ifdef HAVE_DLOPEN +# include +#endif + +#ifdef MOZ_DMD +# include "nsMemoryInfoDumper.h" +#endif + +// dynamic_cast is not supported on Windows without RTTI. +#ifndef _WIN32 +# define HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +#endif + +//////////////////////////////////////////////////////////////////////////////// + +#include "prthread.h" + +class MOZ_CAPABILITY("mutex") TraceLogMutex + : private mozilla::detail::MutexImpl { + public: + explicit TraceLogMutex() : ::mozilla::detail::MutexImpl(){}; + + private: + friend class AutoTraceLogLock; + + void Lock() MOZ_CAPABILITY_ACQUIRE() { ::mozilla::detail::MutexImpl::lock(); } + void Unlock() MOZ_CAPABILITY_RELEASE() { + ::mozilla::detail::MutexImpl::unlock(); + } +}; + +static TraceLogMutex gTraceLog; + +class MOZ_RAII AutoTraceLogLock { + public: + explicit AutoTraceLogLock(TraceLogMutex& aMutex) : mMutex(aMutex) { + mMutex.Lock(); + } + + AutoTraceLogLock(const AutoTraceLogLock&) = delete; + AutoTraceLogLock& operator=(const AutoTraceLogLock&) = delete; + AutoTraceLogLock(AutoTraceLogLock&&) = delete; + AutoTraceLogLock& operator=(AutoTraceLogLock&&) = delete; + + ~AutoTraceLogLock() { mMutex.Unlock(); } + + private: + TraceLogMutex& mMutex; +}; + +class BloatEntry; +struct SerialNumberRecord; + +using mozilla::AutoRestore; +using mozilla::CodeAddressService; +using mozilla::CycleCollectedJSContext; +using mozilla::StaticAutoPtr; + +using BloatHash = nsClassHashtable; +using CharPtrSet = nsTHashtable; +using IntPtrSet = nsTHashtable; +using SerialHash = nsClassHashtable; + +static StaticAutoPtr gBloatView; +static StaticAutoPtr gTypesToLog; +static StaticAutoPtr gObjectsToLog; +static StaticAutoPtr gSerialNumbers; + +static intptr_t gNextSerialNumber; +static bool gDumpedStatistics = false; +static bool gLogJSStacks = false; + +// By default, debug builds only do bloat logging. Bloat logging +// only tries to record when an object is created or destroyed, so we +// optimize the common case in NS_LogAddRef and NS_LogRelease where +// only bloat logging is enabled and no logging needs to be done. +enum LoggingType { NoLogging, OnlyBloatLogging, FullLogging }; + +static LoggingType gLogging; + +static bool gLogLeaksOnly; + +#define BAD_TLS_INDEX ((unsigned)-1) + +// if gActivityTLS == BAD_TLS_INDEX, then we're +// unitialized... otherwise this points to a NSPR TLS thread index +// indicating whether addref activity is legal. If the PTR_TO_INT32 is 0 then +// activity is ok, otherwise not! +static unsigned gActivityTLS = BAD_TLS_INDEX; + +static bool gInitialized; +static nsrefcnt gInitCount; + +static FILE* gBloatLog = nullptr; +static FILE* gRefcntsLog = nullptr; +static FILE* gAllocLog = nullptr; +static FILE* gCOMPtrLog = nullptr; + +static void WalkTheStackSavingLocations(std::vector& aLocations, + const void* aFirstFramePC); + +struct SerialNumberRecord { + SerialNumberRecord() + : serialNumber(++gNextSerialNumber), refCount(0), COMPtrCount(0) {} + + intptr_t serialNumber; + int32_t refCount; + int32_t COMPtrCount; + // We use std:: classes here rather than the XPCOM equivalents because the + // XPCOM equivalents do leak-checking, and if you try to leak-check while + // leak-checking, you're gonna have a bad time. + std::vector allocationStack; + mozilla::UniquePtr jsStack; + + void SaveJSStack() { + // If this thread isn't running JS, there's nothing to do. + if (!CycleCollectedJSContext::Get()) { + return; + } + + if (!nsContentUtils::IsInitialized()) { + return; + } + + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + if (!cx) { + return; + } + + JS::UniqueChars chars = xpc_PrintJSStack(cx, + /*showArgs=*/false, + /*showLocals=*/false, + /*showThisProps=*/false); + size_t len = strlen(chars.get()); + jsStack = mozilla::MakeUnique(len + 1); + memcpy(jsStack.get(), chars.get(), len + 1); + } +}; + +struct nsTraceRefcntStats { + uint64_t mCreates; + uint64_t mDestroys; + + bool HaveLeaks() const { return mCreates != mDestroys; } + + void Clear() { + mCreates = 0; + mDestroys = 0; + } + + int64_t NumLeaked() const { return (int64_t)(mCreates - mDestroys); } +}; + +#ifdef DEBUG +static void AssertActivityIsLegal(const char* aType, const char* aAction) { + if (gActivityTLS == BAD_TLS_INDEX || PR_GetThreadPrivate(gActivityTLS)) { + char buf[1024]; + SprintfLiteral(buf, "XPCOM object %s %s from static ctor/dtor", aType, + aAction); + + if (PR_GetEnv("MOZ_FATAL_STATIC_XPCOM_CTORS_DTORS")) { + MOZ_CRASH_UNSAFE_PRINTF("%s", buf); + } else { + NS_WARNING(buf); + } + } +} +# define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \ + do { \ + AssertActivityIsLegal(type_, action_); \ + } while (0) +#else +# define ASSERT_ACTIVITY_IS_LEGAL(type_, action_) \ + do { \ + } while (0) +#endif // DEBUG + +//////////////////////////////////////////////////////////////////////////////// + +mozilla::StaticAutoPtr> gCodeAddressService; + +//////////////////////////////////////////////////////////////////////////////// + +class BloatEntry { + public: + BloatEntry(const char* aClassName, uint32_t aClassSize) + : mClassSize(aClassSize), mStats() { + MOZ_ASSERT(strlen(aClassName) > 0, "BloatEntry name must be non-empty"); + mClassName = aClassName; + mStats.Clear(); + mTotalLeaked = 0; + } + + ~BloatEntry() = default; + + uint32_t GetClassSize() { return (uint32_t)mClassSize; } + const char* GetClassName() { return mClassName; } + + void Ctor() { mStats.mCreates++; } + + void Dtor() { mStats.mDestroys++; } + + void Total(BloatEntry* aTotal) { + aTotal->mStats.mCreates += mStats.mCreates; + aTotal->mStats.mDestroys += mStats.mDestroys; + aTotal->mClassSize += + mClassSize * mStats.mCreates; // adjust for average in DumpTotal + aTotal->mTotalLeaked += mClassSize * mStats.NumLeaked(); + } + + void DumpTotal(FILE* aOut) { + mClassSize /= mStats.mCreates; + Dump(-1, aOut); + } + + bool PrintDumpHeader(FILE* aOut, const char* aMsg) { + fprintf(aOut, "\n== BloatView: %s, %s process %d\n", aMsg, + XRE_GetProcessTypeString(), getpid()); + if (gLogLeaksOnly && !mStats.HaveLeaks()) { + return false; + } + + // clang-format off + fprintf(aOut, + "\n" \ + " |<----------------Class--------------->|<-----Bytes------>|<----Objects---->|\n" \ + " | | Per-Inst Leaked| Total Rem|\n"); + // clang-format on + + this->DumpTotal(aOut); + + return true; + } + + void Dump(int aIndex, FILE* aOut) { + if (gLogLeaksOnly && !mStats.HaveLeaks()) { + return; + } + + if (mStats.HaveLeaks() || mStats.mCreates != 0) { + fprintf(aOut, + "%4d |%-38.38s| %8d %8" PRId64 "|%8" PRIu64 " %8" PRId64 "|\n", + aIndex + 1, mClassName, GetClassSize(), + nsCRT::strcmp(mClassName, "TOTAL") + ? (mStats.NumLeaked() * GetClassSize()) + : mTotalLeaked, + mStats.mCreates, mStats.NumLeaked()); + } + } + + protected: + const char* mClassName; + // mClassSize is stored as a double because of the way we compute the avg + // class size for total bloat. + double mClassSize; + // mTotalLeaked is only used for the TOTAL entry. + int64_t mTotalLeaked; + nsTraceRefcntStats mStats; +}; + +static void EnsureBloatView() { + if (!gBloatView) { + gBloatView = new BloatHash(256); + } +} + +static BloatEntry* GetBloatEntry(const char* aTypeName, + uint32_t aInstanceSize) { + EnsureBloatView(); + BloatEntry* entry = gBloatView->Get(aTypeName); + if (!entry && aInstanceSize > 0) { + entry = gBloatView + ->InsertOrUpdate(aTypeName, mozilla::MakeUnique( + aTypeName, aInstanceSize)) + .get(); + } else { + MOZ_ASSERT( + aInstanceSize == 0 || entry->GetClassSize() == aInstanceSize, + "Mismatched sizes were recorded in the memory leak logging table. " + "The usual cause of this is having a templated class that uses " + "MOZ_COUNT_{C,D}TOR in the constructor or destructor, respectively. " + "As a workaround, the MOZ_COUNT_{C,D}TOR calls can be moved to a " + "non-templated base class. Another possible cause is a runnable with " + "an mName that matches another refcounted class, or two refcounted " + "classes with the same class name in different C++ namespaces."); + } + return entry; +} + +static void DumpSerialNumbers(const SerialHash::ConstIterator& aHashEntry, + FILE* aFd, bool aDumpAsStringBuffer) { + SerialNumberRecord* record = aHashEntry.UserData(); + auto* outputFile = aFd; +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + fprintf(outputFile, "%" PRIdPTR " @%p (%d references; %d from COMPtrs)\n", + record->serialNumber, aHashEntry.Key(), record->refCount, + record->COMPtrCount); +#else + fprintf(outputFile, "%" PRIdPTR " @%p (%d references)\n", + record->serialNumber, aHashEntry.Key(), record->refCount); +#endif + + if (aDumpAsStringBuffer) { + // This output will be wrong if the nsStringBuffer was used to + // store a char16_t string. + auto* buffer = static_cast(aHashEntry.Key()); + nsDependentCString bufferString(static_cast(buffer->Data())); + fprintf(outputFile, + "Contents of leaked nsStringBuffer with storage size %d as a " + "char*: %s\n", + buffer->StorageSize(), bufferString.get()); + } + + if (!record->allocationStack.empty()) { + static const size_t bufLen = 1024; + char buf[bufLen]; + fprintf(outputFile, "allocation stack:\n"); + for (size_t i = 0, length = record->allocationStack.size(); i < length; + ++i) { + gCodeAddressService->GetLocation(i, record->allocationStack[i], buf, + bufLen); + fprintf(outputFile, "%s\n", buf); + } + } + + if (gLogJSStacks) { + if (record->jsStack) { + fprintf(outputFile, "JS allocation stack:\n%s\n", record->jsStack.get()); + } else { + fprintf(outputFile, "There is no JS context on the stack.\n"); + } + } +} + +template <> +class nsDefaultComparator { + public: + bool Equals(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const { + return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) == 0; + } + bool LessThan(BloatEntry* const& aEntry1, BloatEntry* const& aEntry2) const { + return strcmp(aEntry1->GetClassName(), aEntry2->GetClassName()) < 0; + } +}; + +nsresult nsTraceRefcnt::DumpStatistics() { + if (!gBloatLog || !gBloatView) { + return NS_ERROR_FAILURE; + } + + AutoTraceLogLock lock(gTraceLog); + + MOZ_ASSERT(!gDumpedStatistics, + "Calling DumpStatistics more than once may result in " + "bogus positive or negative leaks being reported"); + gDumpedStatistics = true; + + // Don't try to log while we hold the lock, we'd deadlock. + AutoRestore saveLogging(gLogging); + gLogging = NoLogging; + + BloatEntry total("TOTAL", 0); + for (const auto& data : gBloatView->Values()) { + if (nsCRT::strcmp(data->GetClassName(), "TOTAL") != 0) { + data->Total(&total); + } + } + + const char* msg; + if (gLogLeaksOnly) { + msg = "ALL (cumulative) LEAK STATISTICS"; + } else { + msg = "ALL (cumulative) LEAK AND BLOAT STATISTICS"; + } + const bool leaked = total.PrintDumpHeader(gBloatLog, msg); + + nsTArray entries(gBloatView->Count()); + for (const auto& data : gBloatView->Values()) { + entries.AppendElement(data.get()); + } + + const uint32_t count = entries.Length(); + + if (!gLogLeaksOnly || leaked) { + // Sort the entries alphabetically by classname. + entries.Sort(); + + for (uint32_t i = 0; i < count; ++i) { + BloatEntry* entry = entries[i]; + entry->Dump(i, gBloatLog); + } + + fprintf(gBloatLog, "\n"); + } + + fprintf(gBloatLog, "nsTraceRefcnt::DumpStatistics: %d entries\n", count); + + if (gSerialNumbers) { + bool onlyLoggingStringBuffers = gTypesToLog && gTypesToLog->Count() == 1 && + gTypesToLog->Contains("nsStringBuffer"); + + fprintf(gBloatLog, "\nSerial Numbers of Leaked Objects:\n"); + for (auto iter = gSerialNumbers->ConstIter(); !iter.Done(); iter.Next()) { + DumpSerialNumbers(iter, gBloatLog, onlyLoggingStringBuffers); + } + } + + return NS_OK; +} + +void nsTraceRefcnt::ResetStatistics() { + AutoTraceLogLock lock(gTraceLog); + gBloatView = nullptr; +} + +static intptr_t GetSerialNumber(void* aPtr, bool aCreate, void* aFirstFramePC) { + if (!aCreate) { + auto record = gSerialNumbers->Get(aPtr); + return record ? record->serialNumber : 0; + } + + gSerialNumbers->WithEntryHandle(aPtr, [aFirstFramePC](auto&& entry) { + if (entry) { + MOZ_CRASH( + "If an object already has a serial number, we should be destroying " + "it."); + } + + auto& record = entry.Insert(mozilla::MakeUnique()); + WalkTheStackSavingLocations(record->allocationStack, aFirstFramePC); + if (gLogJSStacks) { + record->SaveJSStack(); + } + }); + return gNextSerialNumber; +} + +static void RecycleSerialNumberPtr(void* aPtr) { gSerialNumbers->Remove(aPtr); } + +static bool LogThisObj(intptr_t aSerialNumber) { + return gObjectsToLog->Contains(aSerialNumber); +} + +using EnvCharType = mozilla::filesystem::Path::value_type; + +static bool InitLog(const EnvCharType* aEnvVar, const char* aMsg, + FILE** aResult, const char* aProcType) { +#ifdef XP_WIN + // This is gross, I know. + const wchar_t* envvar = reinterpret_cast(aEnvVar); + const char16_t* value = reinterpret_cast(::_wgetenv(envvar)); +# define ENVVAR_PRINTF "%S" +#else + const char* envvar = aEnvVar; + const char* value = ::getenv(aEnvVar); +# define ENVVAR_PRINTF "%s" +#endif + + if (value) { + nsTDependentString fname(value); + if (fname.EqualsLiteral("1")) { + *aResult = stdout; + fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stdout\n", + envvar, aMsg); + return true; + } + if (fname.EqualsLiteral("2")) { + *aResult = stderr; + fprintf(stdout, "### " ENVVAR_PRINTF " defined -- logging %s to stderr\n", + envvar, aMsg); + return true; + } + if (!XRE_IsParentProcess()) { + nsTString extension; + extension.AssignLiteral(".log"); + bool hasLogExtension = StringEndsWith(fname, extension); + if (hasLogExtension) { + fname.Cut(fname.Length() - 4, 4); + } + fname.Append('_'); + fname.AppendASCII(aProcType); + fname.AppendLiteral("_pid"); + fname.AppendInt((uint32_t)getpid()); + if (hasLogExtension) { + fname.AppendLiteral(".log"); + } + } +#ifdef XP_WIN + FILE* stream = ::_wfopen(fname.get(), L"wN"); + const wchar_t* fp = (const wchar_t*)fname.get(); +#else + FILE* stream = ::fopen(fname.get(), "w"); + const char* fp = fname.get(); +#endif + if (stream) { + MozillaRegisterDebugFD(fileno(stream)); +#ifdef MOZ_ENABLE_FORKSERVER + base::RegisterForkServerNoCloseFD(fileno(stream)); +#endif + *aResult = stream; + fprintf(stderr, + "### " ENVVAR_PRINTF " defined -- logging %s to " ENVVAR_PRINTF + "\n", + envvar, aMsg, fp); + + return true; + } + + fprintf(stderr, + "### " ENVVAR_PRINTF + " defined -- unable to log %s to " ENVVAR_PRINTF "\n", + envvar, aMsg, fp); + MOZ_ASSERT(false, "Tried and failed to create an XPCOM log"); + +#undef ENVVAR_PRINTF + } + return false; +} + +static void maybeUnregisterAndCloseFile(FILE*& aFile) { + if (!aFile) { + return; + } + + MozillaUnRegisterDebugFILE(aFile); + fclose(aFile); + aFile = nullptr; +} + +static void DoInitTraceLog(const char* aProcType) { +#ifdef XP_WIN +# define ENVVAR(x) u"" x +#else +# define ENVVAR(x) x +#endif + + bool defined = InitLog(ENVVAR("XPCOM_MEM_BLOAT_LOG"), "bloat/leaks", + &gBloatLog, aProcType); + if (!defined) { + gLogLeaksOnly = + InitLog(ENVVAR("XPCOM_MEM_LEAK_LOG"), "leaks", &gBloatLog, aProcType); + } + if (defined || gLogLeaksOnly) { + // Use the same bloat view, if there is one, to keep it consistent + // between the fork server and content processes. + EnsureBloatView(); + } else if (gBloatView) { + nsTraceRefcnt::ResetStatistics(); + } + + InitLog(ENVVAR("XPCOM_MEM_REFCNT_LOG"), "refcounts", &gRefcntsLog, aProcType); + + InitLog(ENVVAR("XPCOM_MEM_ALLOC_LOG"), "new/delete", &gAllocLog, aProcType); + + const char* classes = getenv("XPCOM_MEM_LOG_CLASSES"); + +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + if (classes) { + InitLog(ENVVAR("XPCOM_MEM_COMPTR_LOG"), "nsCOMPtr", &gCOMPtrLog, aProcType); + } else { + if (getenv("XPCOM_MEM_COMPTR_LOG")) { + fprintf(stdout, + "### XPCOM_MEM_COMPTR_LOG defined -- " + "but XPCOM_MEM_LOG_CLASSES is not defined\n"); + } + } +#else + const char* comptr_log = getenv("XPCOM_MEM_COMPTR_LOG"); + if (comptr_log) { + fprintf(stdout, + "### XPCOM_MEM_COMPTR_LOG defined -- " + "but it will not work without dynamic_cast\n"); + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + +#undef ENVVAR + + if (classes) { + // if XPCOM_MEM_LOG_CLASSES was set to some value, the value is interpreted + // as a list of class names to track + // + // Use the same |gTypesToLog| and |gSerialNumbers| to keep them + // consistent through the fork server and content processes. + // Without this, counters will be incorrect. + if (!gTypesToLog) { + gTypesToLog = new CharPtrSet(256); + } + + fprintf(stdout, + "### XPCOM_MEM_LOG_CLASSES defined -- " + "only logging these classes: "); + const char* cp = classes; + for (;;) { + char* cm = (char*)strchr(cp, ','); + if (cm) { + *cm = '\0'; + } + if (!gTypesToLog->Contains(cp)) { + gTypesToLog->PutEntry(cp); + } + fprintf(stdout, "%s ", cp); + if (!cm) { + break; + } + *cm = ','; + cp = cm + 1; + } + fprintf(stdout, "\n"); + + if (!gSerialNumbers) { + gSerialNumbers = new SerialHash(256); + } + } else { + gTypesToLog = nullptr; + gSerialNumbers = nullptr; + } + + const char* objects = getenv("XPCOM_MEM_LOG_OBJECTS"); + if (objects) { + gObjectsToLog = new IntPtrSet(256); + + if (!(gRefcntsLog || gAllocLog || gCOMPtrLog)) { + fprintf(stdout, + "### XPCOM_MEM_LOG_OBJECTS defined -- " + "but none of XPCOM_MEM_(REFCNT|ALLOC|COMPTR)_LOG is defined\n"); + } else { + fprintf(stdout, + "### XPCOM_MEM_LOG_OBJECTS defined -- " + "only logging these objects: "); + const char* cp = objects; + for (;;) { + char* cm = (char*)strchr(cp, ','); + if (cm) { + *cm = '\0'; + } + intptr_t top = 0; + intptr_t bottom = 0; + while (*cp) { + if (*cp == '-') { + bottom = top; + top = 0; + ++cp; + } + top *= 10; + top += *cp - '0'; + ++cp; + } + if (!bottom) { + bottom = top; + } + for (intptr_t serialno = bottom; serialno <= top; serialno++) { + gObjectsToLog->PutEntry(serialno); + fprintf(stdout, "%" PRIdPTR " ", serialno); + } + if (!cm) { + break; + } + *cm = ','; + cp = cm + 1; + } + fprintf(stdout, "\n"); + } + } + + if (getenv("XPCOM_MEM_LOG_JS_STACK")) { + fprintf(stdout, "### XPCOM_MEM_LOG_JS_STACK defined\n"); + gLogJSStacks = true; + } + + if (gBloatLog) { + gLogging = OnlyBloatLogging; + } + + if (gRefcntsLog || gAllocLog || gCOMPtrLog) { + gLogging = FullLogging; + } +} + +static void InitTraceLog() { + if (gInitialized) { + return; + } + gInitialized = true; + + DoInitTraceLog(XRE_GetProcessTypeString()); +} + +extern "C" { + +static void EnsureWrite(FILE* aStream, const char* aBuf, size_t aLen) { +#ifdef XP_WIN + int fd = _fileno(aStream); +#else + int fd = fileno(aStream); +#endif + while (aLen > 0) { +#ifdef XP_WIN + auto written = _write(fd, aBuf, aLen); +#else + auto written = write(fd, aBuf, aLen); +#endif + if (written <= 0 || size_t(written) > aLen) { + break; + } + aBuf += written; + aLen -= written; + } +} + +static void PrintStackFrameCached(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) { + auto stream = static_cast(aClosure); + static const int buflen = 1024; + char buf[buflen + 5] = " "; // 5 for leading " " and trailing '\n' + int len = + gCodeAddressService->GetLocation(aFrameNumber, aPC, buf + 4, buflen); + len = std::min(len, buflen + 1 - 2) + 4; + buf[len++] = '\n'; + buf[len] = '\0'; + fflush(stream); + EnsureWrite(stream, buf, len); +} + +static void RecordStackFrame(uint32_t /*aFrameNumber*/, void* aPC, + void* /*aSP*/, void* aClosure) { + auto locations = static_cast*>(aClosure); + locations->push_back(aPC); +} +} + +/** + * This is a variant of |MozWalkTheStack| that uses |CodeAddressService| to + * cache the results of |NS_DescribeCodeAddress|. If |WalkTheStackCached| is + * being called frequently, it will be a few orders of magnitude faster than + * |MozWalkTheStack|. However, the cache uses a lot of memory, which can cause + * OOM crashes. Therefore, this should only be used for things like refcount + * logging which walk the stack extremely frequently. + */ +static void WalkTheStackCached(FILE* aStream, const void* aFirstFramePC) { + if (!gCodeAddressService) { + gCodeAddressService = new CodeAddressService<>(); + } + MozStackWalk(PrintStackFrameCached, aFirstFramePC, /* maxFrames */ 0, + aStream); +} + +static void WalkTheStackSavingLocations(std::vector& aLocations, + const void* aFirstFramePC) { + if (!gCodeAddressService) { + gCodeAddressService = new CodeAddressService<>(); + } + MozStackWalk(RecordStackFrame, aFirstFramePC, /* maxFrames */ 0, &aLocations); +} + +//---------------------------------------------------------------------- + +EXPORT_XPCOM_API(void) +NS_LogInit() { + NS_SetMainThread(); + + // FIXME: This is called multiple times, we should probably not allow that. + if (++gInitCount) { + nsTraceRefcnt::SetActivityIsLegal(true); + } +} + +EXPORT_XPCOM_API(void) +NS_LogTerm() { mozilla::LogTerm(); } + +#ifdef MOZ_DMD +// If MOZ_DMD_SHUTDOWN_LOG is set, dump a DMD report to a file. +// The value of this environment variable is used as the prefix +// of the file name, so you probably want something like "/tmp/". +// By default, this is run in all processes, but you can record a +// log only for a specific process type by setting MOZ_DMD_LOG_PROCESS +// to the process type you want to log, such as "default" or "tab". +// This method can't use the higher level XPCOM file utilities +// because it is run very late in shutdown to avoid recording +// information about refcount logging entries. +static void LogDMDFile() { + const char* dmdFilePrefix = PR_GetEnv("MOZ_DMD_SHUTDOWN_LOG"); + if (!dmdFilePrefix) { + return; + } + + const char* logProcessEnv = PR_GetEnv("MOZ_DMD_LOG_PROCESS"); + if (logProcessEnv && !!strcmp(logProcessEnv, XRE_GetProcessTypeString())) { + return; + } + + nsPrintfCString fileName("%sdmd-%" PRIPID ".log.gz", dmdFilePrefix, + base::GetCurrentProcId()); + FILE* logFile = fopen(fileName.get(), "w"); + if (NS_WARN_IF(!logFile)) { + return; + } + + nsMemoryInfoDumper::DumpDMDToFile(logFile); +} +#endif // MOZ_DMD + +namespace mozilla { +void LogTerm() { + NS_ASSERTION(gInitCount > 0, "NS_LogTerm without matching NS_LogInit"); + + if (--gInitCount == 0) { +#ifdef DEBUG + /* FIXME bug 491977: This is only going to operate on the + * BlockingResourceBase which is compiled into + * libxul/libxpcom_core.so. Anyone using external linkage will + * have their own copy of BlockingResourceBase statics which will + * not be freed by this method. + * + * It sounds like what we really want is to be able to register a + * callback function to call at XPCOM shutdown. Note that with + * this solution, however, we need to guarantee that + * BlockingResourceBase::Shutdown() runs after all other shutdown + * functions. + */ + BlockingResourceBase::Shutdown(); +#endif + + if (gInitialized) { + nsTraceRefcnt::DumpStatistics(); + nsTraceRefcnt::ResetStatistics(); + } + nsTraceRefcnt::Shutdown(); + nsTraceRefcnt::SetActivityIsLegal(false); + gActivityTLS = BAD_TLS_INDEX; + +#ifdef MOZ_DMD + LogDMDFile(); +#endif + } +} + +} // namespace mozilla + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE void) +NS_LogAddRef(void* aPtr, nsrefcnt aRefcnt, const char* aClass, + uint32_t aClassSize) { + ASSERT_ACTIVITY_IS_LEGAL(aClass, "addrefed"); + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == NoLogging) { + return; + } + if (aRefcnt == 1 || gLogging == FullLogging) { + AutoTraceLogLock lock(gTraceLog); + + if (aRefcnt == 1 && gBloatLog) { + BloatEntry* entry = GetBloatEntry(aClass, aClassSize); + if (entry) { + entry->Ctor(); + } + } + + // Here's the case where MOZ_COUNT_CTOR was not used, + // yet we still want to see creation information: + + bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, aRefcnt == 1, CallerPC()); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a refcounted object?"); + auto record = gSerialNumbers->Get(aPtr); + if (record) { + ++record->refCount; + } + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + if (aRefcnt == 1 && gAllocLog && loggingThisType && loggingThisObject) { + fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Create [thread %p]\n", aClass, + aPtr, serialno, PR_GetCurrentThread()); + WalkTheStackCached(gAllocLog, CallerPC()); + } + + if (gRefcntsLog && loggingThisType && loggingThisObject) { + // Can't use MOZ_LOG(), b/c it truncates the line + fprintf(gRefcntsLog, + "\n<%s> %p %" PRIuPTR " AddRef %" PRIuPTR " [thread %p]\n", + aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread()); + WalkTheStackCached(gRefcntsLog, CallerPC()); + fflush(gRefcntsLog); + } + } +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE void) +NS_LogRelease(void* aPtr, nsrefcnt aRefcnt, const char* aClass) { + ASSERT_ACTIVITY_IS_LEGAL(aClass, "released"); + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == NoLogging) { + return; + } + if (aRefcnt == 0 || gLogging == FullLogging) { + AutoTraceLogLock lock(gTraceLog); + + if (aRefcnt == 0 && gBloatLog) { + BloatEntry* entry = GetBloatEntry(aClass, 0); + if (entry) { + entry->Dtor(); + } + } + + bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aClass)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, false, CallerPC()); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a refcounted object?"); + auto record = gSerialNumbers->Get(aPtr); + if (record) { + --record->refCount; + } + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + if (gRefcntsLog && loggingThisType && loggingThisObject) { + // Can't use MOZ_LOG(), b/c it truncates the line + fprintf(gRefcntsLog, + "\n<%s> %p %" PRIuPTR " Release %" PRIuPTR " [thread %p]\n", + aClass, aPtr, serialno, aRefcnt, PR_GetCurrentThread()); + WalkTheStackCached(gRefcntsLog, CallerPC()); + fflush(gRefcntsLog); + } + + // Here's the case where MOZ_COUNT_DTOR was not used, + // yet we still want to see deletion information: + + if (aRefcnt == 0 && gAllocLog && loggingThisType && loggingThisObject) { + fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Destroy [thread %p]\n", aClass, + aPtr, serialno, PR_GetCurrentThread()); + WalkTheStackCached(gAllocLog, CallerPC()); + } + + if (aRefcnt == 0 && gSerialNumbers && loggingThisType) { + RecycleSerialNumberPtr(aPtr); + } + } +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE void) +NS_LogCtor(void* aPtr, const char* aType, uint32_t aInstanceSize) { + ASSERT_ACTIVITY_IS_LEGAL(aType, "constructed"); + if (!gInitialized) { + InitTraceLog(); + } + + if (gLogging == NoLogging) { + return; + } + + AutoTraceLogLock lock(gTraceLog); + + if (gBloatLog) { + BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); + if (entry) { + entry->Ctor(); + } + } + + bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, true, CallerPC()); + MOZ_ASSERT(serialno != 0, + "GetSerialNumber should never return 0 when passed true"); + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + if (gAllocLog && loggingThisType && loggingThisObject) { + fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Ctor (%d)\n", aType, aPtr, + serialno, aInstanceSize); + WalkTheStackCached(gAllocLog, CallerPC()); + } +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE void) +NS_LogDtor(void* aPtr, const char* aType, uint32_t aInstanceSize) { + ASSERT_ACTIVITY_IS_LEGAL(aType, "destroyed"); + if (!gInitialized) { + InitTraceLog(); + } + + if (gLogging == NoLogging) { + return; + } + + AutoTraceLogLock lock(gTraceLog); + + if (gBloatLog) { + BloatEntry* entry = GetBloatEntry(aType, aInstanceSize); + if (entry) { + entry->Dtor(); + } + } + + bool loggingThisType = (!gTypesToLog || gTypesToLog->Contains(aType)); + intptr_t serialno = 0; + if (gSerialNumbers && loggingThisType) { + serialno = GetSerialNumber(aPtr, false, CallerPC()); + MOZ_ASSERT(serialno != 0, + "Serial number requested for unrecognized pointer! " + "Are you memmoving a MOZ_COUNT_CTOR-tracked object?"); + RecycleSerialNumberPtr(aPtr); + } + + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + // (If we're on a losing architecture, don't do this because we'll be + // using LogDeleteXPCOM instead to get file and line numbers.) + if (gAllocLog && loggingThisType && loggingThisObject) { + fprintf(gAllocLog, "\n<%s> %p %" PRIdPTR " Dtor (%d)\n", aType, aPtr, + serialno, aInstanceSize); + WalkTheStackCached(gAllocLog, CallerPC()); + } +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE void) +NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject) { +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + // Get the most-derived object. + void* object = dynamic_cast(aObject); + + // This is a very indirect way of finding out what the class is + // of the object being logged. If we're logging a specific type, + // then + if (!gTypesToLog || !gSerialNumbers) { + return; + } + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == FullLogging) { + AutoTraceLogLock lock(gTraceLog); + + intptr_t serialno = GetSerialNumber(object, false, CallerPC()); + if (serialno == 0) { + return; + } + + auto record = gSerialNumbers->Get(object); + int32_t count = record ? ++record->COMPtrCount : -1; + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + if (gCOMPtrLog && loggingThisObject) { + fprintf(gCOMPtrLog, "\n %p %" PRIdPTR " nsCOMPtrAddRef %d %p\n", + object, serialno, count, aCOMPtr); + WalkTheStackCached(gCOMPtrLog, CallerPC()); + } + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +} + +EXPORT_XPCOM_API(MOZ_NEVER_INLINE void) +NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject) { +#ifdef HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR + // Get the most-derived object. + void* object = dynamic_cast(aObject); + + // This is a very indirect way of finding out what the class is + // of the object being logged. If we're logging a specific type, + // then + if (!gTypesToLog || !gSerialNumbers) { + return; + } + if (!gInitialized) { + InitTraceLog(); + } + if (gLogging == FullLogging) { + AutoTraceLogLock lock(gTraceLog); + + intptr_t serialno = GetSerialNumber(object, false, CallerPC()); + if (serialno == 0) { + return; + } + + auto record = gSerialNumbers->Get(object); + int32_t count = record ? --record->COMPtrCount : -1; + bool loggingThisObject = (!gObjectsToLog || LogThisObj(serialno)); + + if (gCOMPtrLog && loggingThisObject) { + fprintf(gCOMPtrLog, "\n %p %" PRIdPTR " nsCOMPtrRelease %d %p\n", + object, serialno, count, aCOMPtr); + WalkTheStackCached(gCOMPtrLog, CallerPC()); + } + } +#endif // HAVE_CPP_DYNAMIC_CAST_TO_VOID_PTR +} + +static void ClearLogs(bool aKeepCounters) { + gCodeAddressService = nullptr; + // These counters from the fork server process will be preserved + // for the content processes to keep them consistent. + if (!aKeepCounters) { + gBloatView = nullptr; + gTypesToLog = nullptr; + gSerialNumbers = nullptr; + } + gObjectsToLog = nullptr; + gLogJSStacks = false; + gLogLeaksOnly = false; + maybeUnregisterAndCloseFile(gBloatLog); + maybeUnregisterAndCloseFile(gRefcntsLog); + maybeUnregisterAndCloseFile(gAllocLog); + maybeUnregisterAndCloseFile(gCOMPtrLog); +} + +void nsTraceRefcnt::Shutdown() { ClearLogs(false); } + +void nsTraceRefcnt::SetActivityIsLegal(bool aLegal) { + if (gActivityTLS == BAD_TLS_INDEX) { + PR_NewThreadPrivateIndex(&gActivityTLS, nullptr); + } + + PR_SetThreadPrivate(gActivityTLS, reinterpret_cast(!aLegal)); +} + +void nsTraceRefcnt::StartLoggingClass(const char* aClass) { + gLogging = FullLogging; + + if (!gTypesToLog) { + gTypesToLog = new CharPtrSet(256); + } + + fprintf(stdout, "### StartLoggingClass %s\n", aClass); + if (!gTypesToLog->Contains(aClass)) { + gTypesToLog->PutEntry(aClass); + } + + // We are deliberately not initializing gSerialNumbers here, because + // it would cause assertions. gObjectsToLog can't be used because it + // relies on serial numbers. + +#ifdef XP_WIN +# define ENVVAR(x) u"" x +#else +# define ENVVAR(x) x +#endif + + if (!gRefcntsLog) { + InitLog(ENVVAR("XPCOM_MEM_LATE_REFCNT_LOG"), "refcounts", &gRefcntsLog, + XRE_GetProcessTypeString()); + } + +#undef ENVVAR +} + +#ifdef MOZ_ENABLE_FORKSERVER +void nsTraceRefcnt::ResetLogFiles(const char* aProcType) { + AutoRestore saveLogging(gLogging); + gLogging = NoLogging; + + ClearLogs(true); + + // Create log files with the correct process type in the name. + DoInitTraceLog(aProcType); +} +#endif diff --git a/xpcom/base/nsTraceRefcnt.h b/xpcom/base/nsTraceRefcnt.h new file mode 100644 index 0000000000..23eeec0bdf --- /dev/null +++ b/xpcom/base/nsTraceRefcnt.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ +#ifndef nsTraceRefcnt_h +#define nsTraceRefcnt_h + +#include "nscore.h" + +class nsTraceRefcnt { + public: + static void Shutdown(); + + static nsresult DumpStatistics(); + + static void ResetStatistics(); + + /** + * Tell nsTraceRefcnt whether refcounting, allocation, and destruction + * activity is legal. This is used to trigger assertions for any such + * activity that occurs because of static constructors or destructors. + */ + static void SetActivityIsLegal(bool aLegal); + + /** + * Start refcount logging aClass. If refcount logging has not already begun, + * it will use the environment variable XPCOM_MEM_LATE_REFCNT_LOG to decide + * where to make the log, in a similar way as the other nsTraceRefcnt logs. + */ + static void StartLoggingClass(const char* aClass); + +#ifdef MOZ_ENABLE_FORKSERVER + static void ResetLogFiles(const char* aProcType = nullptr); +#endif +}; + +#endif // nsTraceRefcnt_h diff --git a/xpcom/base/nsUUIDGenerator.cpp b/xpcom/base/nsUUIDGenerator.cpp new file mode 100644 index 0000000000..f00437ada7 --- /dev/null +++ b/xpcom/base/nsUUIDGenerator.cpp @@ -0,0 +1,30 @@ +/* -*- 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 "nsUUIDGenerator.h" + +NS_IMPL_ISUPPORTS(nsUUIDGenerator, nsIUUIDGenerator) + +nsUUIDGenerator::~nsUUIDGenerator() = default; + +NS_IMETHODIMP +nsUUIDGenerator::GenerateUUID(nsID** aRet) { + nsID* id = static_cast(moz_xmalloc(sizeof(nsID))); + + nsresult rv = GenerateUUIDInPlace(id); + if (NS_FAILED(rv)) { + free(id); + return rv; + } + + *aRet = id; + return rv; +} + +NS_IMETHODIMP +nsUUIDGenerator::GenerateUUIDInPlace(nsID* aId) { + return nsID::GenerateUUIDInPlace(*aId); +} diff --git a/xpcom/base/nsUUIDGenerator.h b/xpcom/base/nsUUIDGenerator.h new file mode 100644 index 0000000000..b1b2921a3b --- /dev/null +++ b/xpcom/base/nsUUIDGenerator.h @@ -0,0 +1,30 @@ +/* -*- 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/. */ + +#ifndef _NSUUIDGENERATOR_H_ +#define _NSUUIDGENERATOR_H_ + +#include "nsIUUIDGenerator.h" + +class nsUUIDGenerator final : public nsIUUIDGenerator { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIUUIDGENERATOR + + private: + ~nsUUIDGenerator(); +}; + +#define NS_UUID_GENERATOR_CONTRACTID "@mozilla.org/uuid-generator;1" +#define NS_UUID_GENERATOR_CID \ + { \ + 0x706d36bb, 0xbf79, 0x4293, { \ + 0x81, 0xf2, 0x8f, 0x68, 0x28, 0xc1, 0x8f, 0x9d \ + } \ + } + +#endif /* _NSUUIDGENERATOR_H_ */ diff --git a/xpcom/base/nsVersionComparator.cpp b/xpcom/base/nsVersionComparator.cpp new file mode 100644 index 0000000000..f12e54272a --- /dev/null +++ b/xpcom/base/nsVersionComparator.cpp @@ -0,0 +1,401 @@ +/* -*- 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 "nsVersionComparator.h" + +#include +#include +#include +#include +#include "mozilla/CheckedInt.h" +#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) +# include +# include "nsString.h" +#endif + +struct VersionPart { + int32_t numA; + + const char* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + char* extraD; // null-terminated +}; + +#ifdef XP_WIN +struct VersionPartW { + int32_t numA; + + wchar_t* strB; // NOT null-terminated, can be a null pointer + uint32_t strBlen; + + int32_t numC; + + wchar_t* extraD; // null-terminated +}; +#endif + +static int32_t ns_strtol(const char* aPart, char** aNext) { + errno = 0; + long result_long = strtol(aPart, aNext, 10); + + // Different platforms seem to disagree on what to return when the value + // is out of range so we ensure that it is always what we want it to be. + // We choose 0 firstly because that is the default when the number doesn't + // exist at all and also because it would be easier to recover from should + // you somehow end up in a situation where an old version is invalid. It is + // much easier to create a version either larger or smaller than 0, much + // harder to do the same with INT_MAX. + if (errno != 0) { + return 0; + } + + mozilla::CheckedInt result = result_long; + if (!result.isValid()) { + return 0; + } + + return result.value(); +} + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +static char* ParseVP(char* aPart, VersionPart& aResult) { + char* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) { + return aPart; + } + + dot = strchr(aPart, '.'); + if (dot) { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') { + aResult.numA = INT32_MAX; + aResult.strB = ""; + } else { + aResult.numA = ns_strtol(aPart, const_cast(&aResult.strB)); + } + + if (!*aResult.strB) { + aResult.strB = nullptr; + aResult.strBlen = 0; + } else { + if (aResult.strB[0] == '+') { + static const char kPre[] = "pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } else { + const char* numstart = strpbrk(aResult.strB, "0123456789+-"); + if (!numstart) { + aResult.strBlen = strlen(aResult.strB); + } else { + aResult.strBlen = numstart - aResult.strB; + aResult.numC = ns_strtol(numstart, const_cast(&aResult.extraD)); + + if (!*aResult.extraD) { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) { + ++dot; + + if (!*dot) { + dot = nullptr; + } + } + + return dot; +} + +/** + * Parse a version part into a number and "extra text". + * + * @returns A pointer to the next versionpart, or null if none. + */ +#ifdef XP_WIN + +static int32_t ns_wcstol(const wchar_t* aPart, wchar_t** aNext) { + errno = 0; + long result_long = wcstol(aPart, aNext, 10); + + // See above for the rationale for using 0 here. + if (errno != 0) { + return 0; + } + + mozilla::CheckedInt result = result_long; + if (!result.isValid()) { + return 0; + } + + return result.value(); +} + +static wchar_t* ParseVP(wchar_t* aPart, VersionPartW& aResult) { + wchar_t* dot; + + aResult.numA = 0; + aResult.strB = nullptr; + aResult.strBlen = 0; + aResult.numC = 0; + aResult.extraD = nullptr; + + if (!aPart) { + return aPart; + } + + dot = wcschr(aPart, '.'); + if (dot) { + *dot = '\0'; + } + + if (aPart[0] == '*' && aPart[1] == '\0') { + static wchar_t kEmpty[] = L""; + + aResult.numA = INT32_MAX; + aResult.strB = kEmpty; + } else { + aResult.numA = ns_wcstol(aPart, const_cast(&aResult.strB)); + } + + if (!*aResult.strB) { + aResult.strB = nullptr; + aResult.strBlen = 0; + } else { + if (aResult.strB[0] == '+') { + static wchar_t kPre[] = L"pre"; + + ++aResult.numA; + aResult.strB = kPre; + aResult.strBlen = sizeof(kPre) - 1; + } else { + const wchar_t* numstart = wcspbrk(aResult.strB, L"0123456789+-"); + if (!numstart) { + aResult.strBlen = wcslen(aResult.strB); + } else { + aResult.strBlen = numstart - aResult.strB; + aResult.numC = + ns_wcstol(numstart, const_cast(&aResult.extraD)); + + if (!*aResult.extraD) { + aResult.extraD = nullptr; + } + } + } + } + + if (dot) { + ++dot; + + if (!*dot) { + dot = nullptr; + } + } + + return dot; +} +#endif + +// compare two null-terminated strings, which may be null pointers +static int32_t ns_strcmp(const char* aStr1, const char* aStr2) { + // any string is *before* no string + if (!aStr1) { + return aStr2 != 0; + } + + if (!aStr2) { + return -1; + } + + return strcmp(aStr1, aStr2); +} + +// compare two length-specified string, which may be null pointers +static int32_t ns_strnncmp(const char* aStr1, uint32_t aLen1, const char* aStr2, + uint32_t aLen2) { + // any string is *before* no string + if (!aStr1) { + return aStr2 != 0; + } + + if (!aStr2) { + return -1; + } + + for (; aLen1 && aLen2; --aLen1, --aLen2, ++aStr1, ++aStr2) { + if (*aStr1 < *aStr2) { + return -1; + } + + if (*aStr1 > *aStr2) { + return 1; + } + } + + if (aLen1 == 0) { + return aLen2 == 0 ? 0 : -1; + } + + return 1; +} + +// compare two int32_t +static int32_t ns_cmp(int32_t aNum1, int32_t aNum2) { + if (aNum1 < aNum2) { + return -1; + } + + return aNum1 != aNum2; +} + +/** + * Compares two VersionParts + */ +static int32_t CompareVP(VersionPart& aVer1, VersionPart& aVer2) { + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) { + return r; + } + + r = ns_strnncmp(aVer1.strB, aVer1.strBlen, aVer2.strB, aVer2.strBlen); + if (r) { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) { + return r; + } + + return ns_strcmp(aVer1.extraD, aVer2.extraD); +} + +/** + * Compares two VersionParts + */ +#ifdef XP_WIN +static int32_t CompareVP(VersionPartW& aVer1, VersionPartW& aVer2) { + int32_t r = ns_cmp(aVer1.numA, aVer2.numA); + if (r) { + return r; + } + + r = wcsncmp(aVer1.strB, aVer2.strB, XPCOM_MIN(aVer1.strBlen, aVer2.strBlen)); + if (r) { + return r; + } + + r = ns_cmp(aVer1.numC, aVer2.numC); + if (r) { + return r; + } + + if (!aVer1.extraD) { + return aVer2.extraD != 0; + } + + if (!aVer2.extraD) { + return -1; + } + + return wcscmp(aVer1.extraD, aVer2.extraD); +} +#endif + +namespace mozilla { + +#ifdef XP_WIN +int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB) { + wchar_t* A2 = wcsdup(char16ptr_t(aStrA)); + if (!A2) { + return 1; + } + + wchar_t* B2 = wcsdup(char16ptr_t(aStrB)); + if (!B2) { + free(A2); + return 1; + } + + int32_t result; + wchar_t* a = A2; + wchar_t* b = B2; + + do { + VersionPartW va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} +#endif + +int32_t CompareVersions(const char* aStrA, const char* aStrB) { + char* A2 = strdup(aStrA); + if (!A2) { + return 1; + } + + char* B2 = strdup(aStrB); + if (!B2) { + free(A2); + return 1; + } + + int32_t result; + char* a = A2; + char* b = B2; + + do { + VersionPart va, vb; + + a = ParseVP(a, va); + b = ParseVP(b, vb); + + result = CompareVP(va, vb); + if (result) { + break; + } + + } while (a || b); + + free(A2); + free(B2); + + return result; +} + +} // namespace mozilla diff --git a/xpcom/base/nsVersionComparator.h b/xpcom/base/nsVersionComparator.h new file mode 100644 index 0000000000..32ea620583 --- /dev/null +++ b/xpcom/base/nsVersionComparator.h @@ -0,0 +1,112 @@ +/* -*- 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/. */ + +// NB: This code may be used from non-XPCOM code, in particular, the +// standalone updater executable. + +#ifndef nsVersionComparator_h__ +#define nsVersionComparator_h__ + +#include "mozilla/Char16.h" +#include +#include +#include +#if defined(XP_WIN) && !defined(UPDATER_NO_STRING_GLUE_STL) +# include +# include "nsString.h" +#endif + +/** + * In order to compare version numbers in Mozilla, you need to use the + * mozilla::Version class. You can construct an object of this type by passing + * in a string version number to the constructor. Objects of this type can be + * compared using the standard comparison operators. + * + * For example, let's say that you want to make sure that a given version + * number is not older than 15.a2. Here's how you would write a function to + * do that. + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= mozilla::Version(version); + * } + * + * Or, since Version's constructor is implicit, you can simplify this code: + * + * bool IsVersionValid(const char* version) { + * return mozilla::Version("15.a2") <= version; + * } + */ + +namespace mozilla { + +/** + * Compares the version strings provided. + * + * Returns 0 if the versions match, < 0 if aStrB > aStrA and > 0 if + * aStrA > aStrB. + */ +int32_t CompareVersions(const char* aStrA, const char* aStrB); + +#ifdef XP_WIN +/** + * As above but for wide character strings. + */ +int32_t CompareVersions(const char16_t* aStrA, const char16_t* aStrB); +#endif + +struct Version { + explicit Version(const char* aVersionString) { + versionContent = strdup(aVersionString); + } + + const char* ReadContent() const { return versionContent; } + + ~Version() { free(versionContent); } + + bool operator<(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) < 0; + } + bool operator<=(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) < 1; + } + bool operator>(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) > 0; + } + bool operator>=(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) > -1; + } + bool operator==(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) == 0; + } + bool operator!=(const Version& aRhs) const { + return CompareVersions(versionContent, aRhs.ReadContent()) != 0; + } + bool operator<(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) < 0; + } + bool operator<=(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) < 1; + } + bool operator>(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) > 0; + } + bool operator>=(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) > -1; + } + bool operator==(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) == 0; + } + bool operator!=(const char* aRhs) const { + return CompareVersions(versionContent, aRhs) != 0; + } + + private: + char* versionContent; +}; + +} // namespace mozilla + +#endif // nsVersionComparator_h__ diff --git a/xpcom/base/nsVersionComparatorImpl.cpp b/xpcom/base/nsVersionComparatorImpl.cpp new file mode 100644 index 0000000000..b52395bbac --- /dev/null +++ b/xpcom/base/nsVersionComparatorImpl.cpp @@ -0,0 +1,20 @@ +/* -*- 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 "nsVersionComparatorImpl.h" +#include "nsVersionComparator.h" +#include "nsString.h" + +NS_IMPL_ISUPPORTS(nsVersionComparatorImpl, nsIVersionComparator) + +NS_IMETHODIMP +nsVersionComparatorImpl::Compare(const nsACString& aStr1, + const nsACString& aStr2, int32_t* aResult) { + *aResult = mozilla::CompareVersions(PromiseFlatCString(aStr1).get(), + PromiseFlatCString(aStr2).get()); + + return NS_OK; +} diff --git a/xpcom/base/nsVersionComparatorImpl.h b/xpcom/base/nsVersionComparatorImpl.h new file mode 100644 index 0000000000..49b6f157a1 --- /dev/null +++ b/xpcom/base/nsVersionComparatorImpl.h @@ -0,0 +1,28 @@ +/* -*- 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/Attributes.h" + +#include "nsIVersionComparator.h" + +class nsVersionComparatorImpl final : public nsIVersionComparator { + ~nsVersionComparatorImpl() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIVERSIONCOMPARATOR +}; + +#define NS_VERSIONCOMPARATOR_CONTRACTID \ + "@mozilla.org/xpcom/version-comparator;1" + +// c6e47036-ca94-4be3-963a-9abd8705f7a8 +#define NS_VERSIONCOMPARATOR_CID \ + { \ + 0xc6e47036, 0xca94, 0x4be3, { \ + 0x96, 0x3a, 0x9a, 0xbd, 0x87, 0x05, 0xf7, 0xa8 \ + } \ + } diff --git a/xpcom/base/nsWeakReference.cpp b/xpcom/base/nsWeakReference.cpp new file mode 100644 index 0000000000..958ef08451 --- /dev/null +++ b/xpcom/base/nsWeakReference.cpp @@ -0,0 +1,151 @@ +/* -*- 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/. */ + +// nsWeakReference.cpp + +#include "mozilla/Attributes.h" + +#include "nsWeakReference.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" + +class nsWeakReference final : public nsIWeakReference { + public: + // nsISupports... + NS_DECL_ISUPPORTS + + // nsIWeakReference... + NS_DECL_NSIWEAKREFERENCE + + private: + friend class nsSupportsWeakReference; + + explicit nsWeakReference(nsSupportsWeakReference* aReferent) + : nsIWeakReference(aReferent) + // ...I can only be constructed by an |nsSupportsWeakReference| + {} + + ~nsWeakReference() + // ...I will only be destroyed by calling |delete| myself. + { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + if (mObject) { + static_cast(mObject)->NoticeProxyDestruction(); + } + } + + void NoticeReferentDestruction() + // ...called (only) by an |nsSupportsWeakReference| from _its_ dtor. + { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + mObject = nullptr; + } +}; + +nsresult nsQueryReferent::operator()(const nsIID& aIID, void** aAnswer) const { + nsresult status; + if (mWeakPtr) { + if (NS_FAILED(status = mWeakPtr->QueryReferent(aIID, aAnswer))) { + *aAnswer = 0; + } + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsIWeakReference* NS_GetWeakReference(nsISupportsWeakReference* aInstancePtr, + nsresult* aErrorPtr) { + nsresult status; + + nsIWeakReference* result = nullptr; + + if (aInstancePtr) { + status = aInstancePtr->GetWeakReference(&result); + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (aErrorPtr) { + *aErrorPtr = status; + } + + return result; +} + +nsIWeakReference* // or else |already_AddRefed| +NS_GetWeakReference(nsISupports* aInstancePtr, nsresult* aErrorPtr) { + nsresult status; + + nsIWeakReference* result = nullptr; + + if (aInstancePtr) { + nsCOMPtr factoryPtr = + do_QueryInterface(aInstancePtr, &status); + if (factoryPtr) { + status = factoryPtr->GetWeakReference(&result); + } + // else, |status| has already been set by |do_QueryInterface| + } else { + status = NS_ERROR_NULL_POINTER; + } + + if (aErrorPtr) { + *aErrorPtr = status; + } + return result; +} + +nsresult nsSupportsWeakReference::GetWeakReference( + nsIWeakReference** aInstancePtr) { + if (!aInstancePtr) { + return NS_ERROR_NULL_POINTER; + } + + if (!mProxy) { + mProxy = new nsWeakReference(this); + } else { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD_DELEGATED(mProxy); + } + RefPtr rval = mProxy; + rval.forget(aInstancePtr); + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsWeakReference, nsIWeakReference) + +NS_IMETHODIMP +nsWeakReference::QueryReferentFromScript(const nsIID& aIID, + void** aInstancePtr) { + return QueryReferent(aIID, aInstancePtr); +} + +nsresult nsIWeakReference::QueryReferent(const nsIID& aIID, + void** aInstancePtr) { + MOZ_WEAKREF_ASSERT_OWNINGTHREAD; + + if (!mObject) { + return NS_ERROR_NULL_POINTER; + } + + return mObject->QueryInterface(aIID, aInstancePtr); +} + +size_t nsWeakReference::SizeOfOnlyThis(mozilla::MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this); +} + +void nsSupportsWeakReference::ClearWeakReferences() { + if (mProxy) { + mProxy->NoticeReferentDestruction(); + mProxy = nullptr; + } +} diff --git a/xpcom/base/nsWeakReference.h b/xpcom/base/nsWeakReference.h new file mode 100644 index 0000000000..5303f2d2ea --- /dev/null +++ b/xpcom/base/nsWeakReference.h @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +#ifndef nsWeakReference_h__ +#define nsWeakReference_h__ + +// nsWeakReference.h + +// See mfbt/WeakPtr.h for a more typesafe C++ implementation of weak references + +#include "nsIWeakReferenceUtils.h" + +class nsWeakReference; + +class nsSupportsWeakReference : public nsISupportsWeakReference { + public: + nsSupportsWeakReference() : mProxy(0) {} + + NS_DECL_NSISUPPORTSWEAKREFERENCE + + protected: + inline ~nsSupportsWeakReference(); + + private: + friend class nsWeakReference; + + // Called (only) by an |nsWeakReference| from _its_ dtor. + // The thread safety check is made by the caller. + void NoticeProxyDestruction() { mProxy = nullptr; } + + nsWeakReference* MOZ_NON_OWNING_REF mProxy; + + protected: + void ClearWeakReferences(); + bool HasWeakReferences() const { return !!mProxy; } +}; + +inline nsSupportsWeakReference::~nsSupportsWeakReference() { + ClearWeakReferences(); +} + +#define NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \ + tmp->ClearWeakReferences(); + +#define NS_IMPL_CYCLE_COLLECTION_WEAK(class_, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(class_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#define NS_IMPL_CYCLE_COLLECTION_WEAK_INHERITED(class_, super_, ...) \ + NS_IMPL_CYCLE_COLLECTION_CLASS(class_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(class_, super_) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE \ + NS_IMPL_CYCLE_COLLECTION_UNLINK_END \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(class_, super_) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(__VA_ARGS__) \ + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +#endif diff --git a/xpcom/base/nsWindowsHelpers.h b/xpcom/base/nsWindowsHelpers.h new file mode 100644 index 0000000000..c99c7ed08d --- /dev/null +++ b/xpcom/base/nsWindowsHelpers.h @@ -0,0 +1,348 @@ +/* -*- 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/. */ + +// NB: This code may be used from non-XPCOM code, in particular, the +// Windows Default Browser Agent. + +#ifndef nsWindowsHelpers_h +#define nsWindowsHelpers_h + +#include +#include +#include "nsAutoRef.h" +#include "mozilla/Assertions.h" +#include "mozilla/UniquePtr.h" + +// ---------------------------------------------------------------------------- +// Critical Section helper class +// ---------------------------------------------------------------------------- + +class AutoCriticalSection { + public: + explicit AutoCriticalSection(LPCRITICAL_SECTION aSection) + : mSection(aSection) { + ::EnterCriticalSection(mSection); + } + ~AutoCriticalSection() { ::LeaveCriticalSection(mSection); } + + private: + LPCRITICAL_SECTION mSection; +}; + +template <> +class nsAutoRefTraits { + public: + typedef HKEY RawRef; + static HKEY Void() { return nullptr; } + + static void Release(RawRef aFD) { + if (aFD != Void()) { + RegCloseKey(aFD); + } + } +}; + +template <> +class nsAutoRefTraits { + public: + typedef HDC RawRef; + static HDC Void() { return nullptr; } + + static void Release(RawRef aFD) { + if (aFD != Void()) { + ::DeleteDC(aFD); + } + } +}; + +template <> +class nsAutoRefTraits { + public: + typedef HFONT RawRef; + static HFONT Void() { return nullptr; } + + static void Release(RawRef aFD) { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + +template <> +class nsAutoRefTraits { + public: + typedef HBRUSH RawRef; + static HBRUSH Void() { return nullptr; } + + static void Release(RawRef aFD) { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + +template <> +class nsAutoRefTraits { + public: + typedef HRGN RawRef; + static HRGN Void() { return nullptr; } + + static void Release(RawRef aFD) { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + +template <> +class nsAutoRefTraits { + public: + typedef HBITMAP RawRef; + static HBITMAP Void() { return nullptr; } + + static void Release(RawRef aFD) { + if (aFD != Void()) { + ::DeleteObject(aFD); + } + } +}; + +template <> +class nsAutoRefTraits { + public: + typedef SC_HANDLE RawRef; + static SC_HANDLE Void() { return nullptr; } + + static void Release(RawRef aFD) { + if (aFD != Void()) { + CloseServiceHandle(aFD); + } + } +}; + +template <> +class nsSimpleRef { + protected: + typedef HANDLE RawRef; + + nsSimpleRef() : mRawRef(nullptr) {} + + explicit nsSimpleRef(RawRef aRawRef) : mRawRef(aRawRef) {} + + bool HaveResource() const { + return mRawRef && mRawRef != INVALID_HANDLE_VALUE; + } + + public: + RawRef get() const { return mRawRef; } + + static void Release(RawRef aRawRef) { + if (aRawRef && aRawRef != INVALID_HANDLE_VALUE) { + CloseHandle(aRawRef); + } + } + RawRef mRawRef; +}; + +template <> +class nsAutoRefTraits { + public: + typedef HMODULE RawRef; + static RawRef Void() { return nullptr; } + + static void Release(RawRef aFD) { + if (aFD != Void()) { + FreeLibrary(aFD); + } + } +}; + +template <> +class nsAutoRefTraits { + public: + typedef DEVMODEW* RawRef; + static RawRef Void() { return nullptr; } + + static void Release(RawRef aDevMode) { + if (aDevMode != Void()) { + ::HeapFree(::GetProcessHeap(), 0, aDevMode); + } + } +}; + +template <> +class nsAutoRefTraits { + public: + typedef MSIHANDLE RawRef; + static RawRef Void() { return 0; } + + static void Release(RawRef aHandle) { + if (aHandle != Void()) { + ::MsiCloseHandle(aHandle); + } + } +}; + +// HGLOBAL is just a typedef of HANDLE which nsSimpleRef has a specialization +// of, that means having a nsAutoRefTraits specialization for HGLOBAL is +// useless. Therefore we create a wrapper class for HGLOBAL to make +// nsAutoRefTraits and nsAutoRef work as intention. +class nsHGLOBAL { + public: + MOZ_IMPLICIT nsHGLOBAL(HGLOBAL hGlobal) : m_hGlobal(hGlobal) {} + + operator HGLOBAL() const { return m_hGlobal; } + + private: + HGLOBAL m_hGlobal; +}; + +template <> +class nsAutoRefTraits { + public: + typedef nsHGLOBAL RawRef; + static RawRef Void() { return nullptr; } + + static void Release(RawRef hGlobal) { ::GlobalFree(hGlobal); } +}; + +// Because Printer's HANDLE uses ClosePrinter and we already have +// nsAutoRef which uses CloseHandle so we need to create a wrapper class +// for HANDLE to have another specialization for nsAutoRefTraits. +class nsHPRINTER { + public: + MOZ_IMPLICIT nsHPRINTER(HANDLE hPrinter) : m_hPrinter(hPrinter) {} + + operator HANDLE() const { return m_hPrinter; } + + HANDLE* operator&() { return &m_hPrinter; } + + private: + HANDLE m_hPrinter; +}; + +// winspool.h header has AddMonitor macro, it conflicts with AddMonitor member +// function in TaskbarPreview.cpp and TaskbarTabPreview.cpp. Beside, we only +// need ClosePrinter here for Release function, so having its prototype is +// enough. +extern "C" BOOL WINAPI ClosePrinter(HANDLE hPrinter); + +template <> +class nsAutoRefTraits { + public: + typedef nsHPRINTER RawRef; + static RawRef Void() { return nullptr; } + + static void Release(RawRef hPrinter) { ::ClosePrinter(hPrinter); } +}; + +typedef nsAutoRef nsAutoRegKey; +typedef nsAutoRef nsAutoHDC; +typedef nsAutoRef nsAutoFont; +typedef nsAutoRef nsAutoBrush; +typedef nsAutoRef nsAutoRegion; +typedef nsAutoRef nsAutoBitmap; +typedef nsAutoRef nsAutoServiceHandle; +typedef nsAutoRef nsAutoHandle; +typedef nsAutoRef nsModuleHandle; +typedef nsAutoRef nsAutoDevMode; +typedef nsAutoRef nsAutoGlobalMem; +typedef nsAutoRef nsAutoPrinter; +typedef nsAutoRef nsAutoMsiHandle; + +// Construct a path "\". return false if the output buffer +// is too small. +// Note: If the system path cannot be found, or doesn't fit in the output buffer +// with the module name, we will just ignore the system path and output the +// module name alone; +// this may mean using a normal search path wherever the output is used. +bool inline ConstructSystem32Path(LPCWSTR aModule, WCHAR* aSystemPath, + UINT aSize) { + MOZ_ASSERT(aSystemPath); + + size_t fileLen = wcslen(aModule); + if (fileLen >= aSize) { + // The module name alone cannot even fit! + return false; + } + + size_t systemDirLen = GetSystemDirectoryW(aSystemPath, aSize); + + if (systemDirLen) { + if (systemDirLen < aSize - fileLen) { + // Make the system directory path terminate with a slash. + if (aSystemPath[systemDirLen - 1] != L'\\') { + if (systemDirLen + 1 < aSize - fileLen) { + aSystemPath[systemDirLen] = L'\\'; + ++systemDirLen; + // No need to re-nullptr terminate. + } else { + // Couldn't fit the system path with added slash. + systemDirLen = 0; + } + } + } else { + // Couldn't fit the system path. + systemDirLen = 0; + } + } + + MOZ_ASSERT(systemDirLen + fileLen < aSize); + + wcsncpy(aSystemPath + systemDirLen, aModule, fileLen); + aSystemPath[systemDirLen + fileLen] = L'\0'; + return true; +} + +HMODULE inline LoadLibrarySystem32(LPCWSTR aModule) { + static const auto setDefaultDllDirectories = + GetProcAddress(GetModuleHandleW(L"kernel32"), "SetDefaultDllDirectories"); + if (setDefaultDllDirectories) { + return LoadLibraryExW(aModule, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + } + WCHAR systemPath[MAX_PATH + 1]; + if (!ConstructSystem32Path(aModule, systemPath, MAX_PATH + 1)) { + return NULL; + } + return LoadLibraryExW(systemPath, nullptr, LOAD_WITH_ALTERED_SEARCH_PATH); +} + +// for UniquePtr +struct LocalFreeDeleter { + void operator()(void* aPtr) { ::LocalFree(aPtr); } +}; + +struct VirtualFreeDeleter { + void operator()(void* aPtr) { ::VirtualFree(aPtr, 0, MEM_RELEASE); } +}; + +// for UniquePtr to store a PSID +struct FreeSidDeleter { + void operator()(void* aPtr) { ::FreeSid(aPtr); } +}; +// Unfortunately, although SID is a struct, PSID is a void* +// This typedef will work for storing a PSID in a UniquePtr and should make +// things a bit more readable. +typedef mozilla::UniquePtr UniqueSidPtr; + +struct CloseHandleDeleter { + typedef HANDLE pointer; + void operator()(pointer aHandle) { + if (aHandle != INVALID_HANDLE_VALUE) { + ::CloseHandle(aHandle); + } + } +}; + +// One caller of this function is early in startup and several others are not, +// so they have different ways of determining the two parameters. This function +// exists just so any future code that needs to determine whether the dynamic +// blocklist is disabled remembers to check whether safe mode is active. +inline bool IsDynamicBlocklistDisabled(bool isSafeMode, + bool hasCommandLineDisableArgument) { + return isSafeMode || hasCommandLineDisableArgument; +} +#endif diff --git a/xpcom/base/nscore.h b/xpcom/base/nscore.h new file mode 100644 index 0000000000..498853983e --- /dev/null +++ b/xpcom/base/nscore.h @@ -0,0 +1,265 @@ +/* -*- 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/. */ + +#ifndef nscore_h___ +#define nscore_h___ + +/** + * Make sure that we have the proper platform specific + * c++ definitions needed by nscore.h + */ +#ifndef _XPCOM_CONFIG_H_ +# include "xpcom-config.h" // IWYU pragma: export +#endif + +/* Definitions of functions and operators that allocate memory. */ +#if !defined(NS_NO_XPCOM) && !defined(MOZ_NO_MOZALLOC) +# include "mozilla/mozalloc.h" +#endif + +/** + * Incorporate the integer data types which XPCOM uses. + */ +#include // IWYU pragma: export +#include // IWYU pragma: export + +#include "mozilla/HelperMacros.h" // IWYU pragma: export +#include "mozilla/RefCountType.h" + +/* Core XPCOM declarations. */ + +/*----------------------------------------------------------------------*/ +/* Import/export defines */ + +#ifdef HAVE_VISIBILITY_HIDDEN_ATTRIBUTE +# define NS_VISIBILITY_HIDDEN __attribute__((visibility("hidden"))) +#else +# define NS_VISIBILITY_HIDDEN +#endif + +#if defined(HAVE_VISIBILITY_ATTRIBUTE) +# define NS_VISIBILITY_DEFAULT __attribute__((visibility("default"))) +#elif defined(__SUNPRO_C) || defined(__SUNPRO_CC) +# define NS_VISIBILITY_DEFAULT __global +#else +# define NS_VISIBILITY_DEFAULT +#endif + +#define NS_HIDDEN_(type) NS_VISIBILITY_HIDDEN type +#define NS_EXTERNAL_VIS_(type) NS_VISIBILITY_DEFAULT type + +#define NS_HIDDEN NS_VISIBILITY_HIDDEN +#define NS_EXTERNAL_VIS NS_VISIBILITY_DEFAULT + +/** + * Mark a function as using a potentially non-standard function calling + * convention. This can be used on functions that are called very + * frequently, to reduce the overhead of the function call. It is still worth + * using the macro for C++ functions which take no parameters since it allows + * passing |this| in a register. + * + * - Do not use this on any scriptable interface method since xptcall won't be + * aware of the different calling convention. + * - This must appear on the declaration, not the definition. + * - Adding this to a public function _will_ break binary compatibility. + * - This may be used on virtual functions but you must ensure it is applied + * to all implementations - the compiler will _not_ warn but it will crash. + * - This has no effect for functions which take a variable number of + * arguments. + * - __fastcall on windows should not be applied to class + * constructors/destructors - use the NS_CONSTRUCTOR_FASTCALL macro for + * constructors/destructors. + * + * Examples: int NS_FASTCALL func1(char *foo); + * NS_HIDDEN_(int) NS_FASTCALL func2(char *foo); + */ + +#if defined(__i386__) && defined(__GNUC__) +# define NS_FASTCALL __attribute__((regparm(3), stdcall)) +# define NS_CONSTRUCTOR_FASTCALL __attribute__((regparm(3), stdcall)) +#elif defined(XP_WIN) && !defined(_WIN64) +# define NS_FASTCALL __fastcall +# define NS_CONSTRUCTOR_FASTCALL +#else +# define NS_FASTCALL +# define NS_CONSTRUCTOR_FASTCALL +#endif + +/** + * Various API modifiers. + * + * - NS_IMETHOD/NS_IMETHOD_: use for in-class declarations and definitions. + * - NS_IMETHODIMP/NS_IMETHODIMP_: use for out-of-class definitions. + * - NS_METHOD_: usually used in conjunction with NS_CALLBACK_. Best avoided. + * - NS_CALLBACK_: used in some legacy situations. Best avoided. + */ + +#ifdef XP_WIN + +# define NS_IMPORT __declspec(dllimport) +# define NS_IMPORT_(type) __declspec(dllimport) type __stdcall +# define NS_EXPORT __declspec(dllexport) +# define NS_EXPORT_(type) __declspec(dllexport) type __stdcall +# define NS_IMETHOD_(type) virtual type __stdcall +# define NS_IMETHODIMP_(type) type __stdcall +# define NS_METHOD_(type) type __stdcall +# define NS_CALLBACK_(_type, _name) _type(__stdcall* _name) +# ifndef _WIN64 +// Win64 has only one calling convention. __stdcall will be ignored by the +// compiler. +# define NS_STDCALL __stdcall +# define NS_HAVE_STDCALL +# else +# define NS_STDCALL +# endif +# define NS_FROZENCALL __cdecl + +#else + +# define NS_IMPORT NS_EXTERNAL_VIS +# define NS_IMPORT_(type) NS_EXTERNAL_VIS_(type) +# define NS_EXPORT NS_EXTERNAL_VIS +# define NS_EXPORT_(type) NS_EXTERNAL_VIS_(type) +# define NS_IMETHOD_(type) virtual type +# define NS_IMETHODIMP_(type) type +# define NS_METHOD_(type) type +# define NS_CALLBACK_(_type, _name) _type(*_name) +# define NS_STDCALL +# define NS_FROZENCALL + +#endif + +#define NS_IMETHOD NS_IMETHOD_(nsresult) +#define NS_IMETHODIMP NS_IMETHODIMP_(nsresult) + +/** + * Import/Export macros for XPCOM APIs + */ + +#define EXPORT_XPCOM_API(type) type +#define IMPORT_XPCOM_API(type) type +#define GLUE_XPCOM_API(type) type + +#ifdef __cplusplus +# define NS_EXTERN_C extern "C" +#else +# define NS_EXTERN_C +#endif + +#define XPCOM_API(type) NS_EXTERN_C type + +/* If a program allocates memory for the lifetime of the app, it doesn't make + * sense to touch memory pages and free that memory at shutdown, + * unless we are running leak stats. + * + * Note that we're also setting this for code coverage and pgo profile + * generation, because both of those require atexit hooks, which won't fire + * if we're using _exit. Bug 1555974 covers improving this. + * + */ +#ifndef NS_FREE_PERMANENT_DATA +# if defined(NS_BUILD_REFCNT_LOGGING) || defined(MOZ_VALGRIND) || \ + defined(MOZ_ASAN) || defined(MOZ_TSAN) || defined(MOZ_CODE_COVERAGE) || \ + defined(MOZ_PROFILE_GENERATE) || defined(JS_STRUCTURED_SPEW) +# define NS_FREE_PERMANENT_DATA +# endif +#endif + +/** + * NS_NO_VTABLE is emitted by xpidl in interface declarations whenever + * xpidl can determine that the interface can't contain a constructor. + * This results in some space savings and possible runtime savings - + * see bug 49416. We undefine it first, as xpidl-generated headers + * define it for IDL uses that don't include this file. + */ +#ifdef NS_NO_VTABLE +# undef NS_NO_VTABLE +#endif +#if defined(_MSC_VER) +# define NS_NO_VTABLE __declspec(novtable) +#else +# define NS_NO_VTABLE +#endif + +/** + * Generic XPCOM result data type + */ +#include "nsError.h" // IWYU pragma: export + +typedef MozRefCountType nsrefcnt; + +namespace mozilla { +// Extensions to the mozilla::Result type for handling of nsresult values. +// +// Note that these specializations need to be defined before Result.h is +// included, or we run into explicit specialization after instantiation errors, +// especially if Result.h is used in multiple sources in a unified compile. + +namespace detail { +// When used as an error value, nsresult should never be NS_OK. +// This specialization allows us to pack Result into a +// nsresult-sized value. +template +struct UnusedZero; +template <> +struct UnusedZero { + using StorageType = nsresult; + + static constexpr bool value = true; + static constexpr StorageType nullValue = NS_OK; + + static constexpr void AssertValid(StorageType aValue) {} + static constexpr const nsresult& Inspect(const StorageType& aValue) { + return aValue; + } + static constexpr nsresult Unwrap(StorageType aValue) { return aValue; } + static constexpr StorageType Store(nsresult aValue) { return aValue; } +}; +} // namespace detail + +template +class GenericErrorResult; +template <> +class GenericErrorResult; + +struct Ok; +template +class Result; + +// Allow MOZ_TRY to handle `nsresult` values. +template +inline Result ToResult(nsresult aValue); +} // namespace mozilla + +/* + * Use these macros to do 64bit safe pointer conversions. + */ + +#define NS_PTR_TO_INT32(x) ((int32_t)(intptr_t)(x)) +#define NS_PTR_TO_UINT32(x) ((uint32_t)(intptr_t)(x)) +#define NS_INT32_TO_PTR(x) ((void*)(intptr_t)(x)) + +/* + * If we're being linked as standalone glue, we don't want a dynamic + * dependency on NSPR libs, so we skip the debug thread-safety + * checks, and we cannot use the THREADSAFE_ISUPPORTS macros. + */ +#if defined(XPCOM_GLUE) && !defined(XPCOM_GLUE_USE_NSPR) +# define XPCOM_GLUE_AVOID_NSPR +#endif + +/* + * SEH exception macros. + */ +#ifdef HAVE_SEH_EXCEPTIONS +# define MOZ_SEH_TRY __try +# define MOZ_SEH_EXCEPT(expr) __except (expr) +#else +# define MOZ_SEH_TRY if (true) +# define MOZ_SEH_EXCEPT(expr) else +#endif + +#endif /* nscore_h___ */ diff --git a/xpcom/base/nsrootidl.idl b/xpcom/base/nsrootidl.idl new file mode 100644 index 0000000000..28e643e312 --- /dev/null +++ b/xpcom/base/nsrootidl.idl @@ -0,0 +1,105 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/** + * Root idl declarations to be used by all. + */ + +%{C++ + +#include "nscore.h" +#include "nsID.h" +typedef int64_t PRTime; + +/* + * Forward declarations for new string types + */ +#include "nsStringFwd.h" + +struct JSContext; + +/* + * Forward declaration of mozilla::dom::Promise + */ +namespace mozilla { +namespace dom { +class Promise; +} // namespace dom +} // namespace mozilla + +/* + * Start commenting out the C++ versions of the below in the output header + */ +#if 0 +%} + +typedef boolean bool ; +typedef octet uint8_t ; +typedef unsigned short uint16_t ; +typedef unsigned short char16_t; +typedef unsigned long uint32_t ; +typedef unsigned long long uint64_t ; +typedef long long PRTime ; +typedef short int16_t ; +typedef long int32_t ; +typedef long long int64_t ; + +typedef unsigned long nsresult ; + +// If we ever want to use `size_t` in scriptable interfaces, this will need to +// be built into the xpidl compiler, as the size varies based on platform. + native size_t(size_t); + +[ptr] native voidPtr(void); +[ptr] native charPtr(char); +[ptr] native unicharPtr(char16_t); + +[ref, nsid] native nsIDRef(nsID); +[ref, nsid] native nsIIDRef(nsIID); +[ref, nsid] native nsCIDRef(nsCID); + +[ptr, nsid] native nsIDPtr(nsID); +[ptr, nsid] native nsIIDPtr(nsIID); +[ptr, nsid] native nsCIDPtr(nsCID); + +// NOTE: Be careful in using the following 3 types. The *Ref and *Ptr variants +// are more commonly used (and better supported). Those variants require +// nsMemory alloc'd copies when used as 'out' params while these types do not. +// However, currently these types can not be used for 'in' params. And, methods +// that use them as 'out' params *must* be declared [notxpcom] (with an explicit +// return type of nsresult). This makes such methods implicitly not scriptable. +// Use of these types in methods without a [notxpcom] declaration will cause +// the xpidl compiler to raise an error. +// See: http://bugzilla.mozilla.org/show_bug.cgi?id=93792 + +[nsid] native nsIID(nsIID); +[nsid] native nsID(nsID); +[nsid] native nsCID(nsCID); + +[ptr] native nsQIResult(void); + +[ref, utf8string] native AUTF8String(ignored); +[ref, utf8string] native AUTF8StringRef(ignored); +[ptr, utf8string] native AUTF8StringPtr(ignored); + +[ref, cstring] native ACString(ignored); +[ref, cstring] native ACStringRef(ignored); +[ptr, cstring] native ACStringPtr(ignored); + +[ref, astring] native AString(ignored); +[ref, astring] native AStringRef(ignored); +[ptr, astring] native AStringPtr(ignored); + +[ref, jsval] native jsval(jsval); + native jsid(jsid); + +[ptr, promise] native Promise(ignored); + +%{C++ +/* + * End commenting out the C++ versions of the above in the output header + */ +#endif +%} diff --git a/xpcom/build/BinaryPath.h b/xpcom/build/BinaryPath.h new file mode 100644 index 0000000000..47bd6940cb --- /dev/null +++ b/xpcom/build/BinaryPath.h @@ -0,0 +1,321 @@ +/* -*- 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/. */ + +#ifndef mozilla_BinaryPath_h +#define mozilla_BinaryPath_h + +#include "nsXPCOMPrivate.h" // for MAXPATHLEN +#ifdef XP_WIN +# include +#elif defined(XP_DARWIN) +# include +#elif defined(XP_UNIX) +# include +# include +# include +#endif +#if defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) || defined(__OpenBSD__) +# include +#endif +#if defined(__OpenBSD__) +# include +#endif +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" + +#ifdef MOZILLA_INTERNAL_API +# include "nsCOMPtr.h" +# include "nsIFile.h" +# include "nsString.h" +#endif + +namespace mozilla { + +class BinaryPath { + public: +#ifdef XP_WIN + static nsresult Get(char aResult[MAXPATHLEN]) { + wchar_t wide_path[MAXPATHLEN]; + nsresult rv = GetW(wide_path); + if (NS_FAILED(rv)) { + return rv; + } + WideCharToMultiByte(CP_UTF8, 0, wide_path, -1, aResult, MAXPATHLEN, nullptr, + nullptr); + return NS_OK; + } + + static nsresult GetLong(wchar_t aResult[MAXPATHLEN]) { + static bool cached = false; + static wchar_t exeLongPath[MAXPATHLEN] = L""; + + if (!cached) { + nsresult rv = GetW(exeLongPath); + + if (NS_FAILED(rv)) { + return rv; + } + + if (!::GetLongPathNameW(exeLongPath, exeLongPath, MAXPATHLEN)) { + return NS_ERROR_FAILURE; + } + + cached = true; + } + + if (wcscpy_s(aResult, MAXPATHLEN, exeLongPath)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + + private: + static nsresult GetW(wchar_t aResult[MAXPATHLEN]) { + static bool cached = false; + static wchar_t moduleFileName[MAXPATHLEN] = L""; + + if (!cached) { + if (!::GetModuleFileNameW(0, moduleFileName, MAXPATHLEN)) { + return NS_ERROR_FAILURE; + } + + cached = true; + } + + if (wcscpy_s(aResult, MAXPATHLEN, moduleFileName)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + +#elif defined(XP_DARWIN) + static nsresult Get(char aResult[MAXPATHLEN]) { + // Works even if we're not bundled. + CFBundleRef appBundle = CFBundleGetMainBundle(); + if (!appBundle) { + return NS_ERROR_FAILURE; + } + + CFURLRef executableURL = CFBundleCopyExecutableURL(appBundle); + if (!executableURL) { + return NS_ERROR_FAILURE; + } + + nsresult rv; + if (CFURLGetFileSystemRepresentation(executableURL, false, (UInt8*)aResult, + MAXPATHLEN)) { + // Sanitize path in case the app was launched from Terminal via + // './firefox' for example. + size_t readPos = 0; + size_t writePos = 0; + while (aResult[readPos] != '\0') { + if (aResult[readPos] == '.' && aResult[readPos + 1] == '/') { + readPos += 2; + } else { + aResult[writePos] = aResult[readPos]; + readPos++; + writePos++; + } + } + aResult[writePos] = '\0'; + rv = NS_OK; + } else { + rv = NS_ERROR_FAILURE; + } + + CFRelease(executableURL); + return rv; + } + +#elif defined(ANDROID) + static nsresult Get(char aResult[MAXPATHLEN]) { + // On Android, we use the MOZ_ANDROID_LIBDIR variable that is set by the + // Java bootstrap code. + const char* libDir = getenv("MOZ_ANDROID_LIBDIR"); + if (!libDir) { + return NS_ERROR_FAILURE; + } + + snprintf(aResult, MAXPATHLEN, "%s/%s", libDir, "dummy"); + aResult[MAXPATHLEN - 1] = '\0'; + return NS_OK; + } + +#elif defined(XP_LINUX) || defined(XP_SOLARIS) + static nsresult Get(char aResult[MAXPATHLEN]) { +# if defined(XP_SOLARIS) + const char path[] = "/proc/self/path/a.out"; +# else + const char path[] = "/proc/self/exe"; +# endif + + ssize_t len = readlink(path, aResult, MAXPATHLEN - 1); + if (len < 0) { + return NS_ERROR_FAILURE; + } + aResult[len] = '\0'; +# if defined(XP_LINUX) + // Removing suffix " (deleted)" from the binary path + const char suffix[] = " (deleted)"; + const ssize_t suffix_len = sizeof(suffix); + if (len >= suffix_len) { + char* result_end = &aResult[len - (suffix_len - 1)]; + if (memcmp(result_end, suffix, suffix_len) == 0) { + *result_end = '\0'; + } + } +# endif + return NS_OK; + } + +#elif defined(__FreeBSD__) || defined(__DragonFly__) || \ + defined(__FreeBSD_kernel__) || defined(__NetBSD__) + static nsresult Get(char aResult[MAXPATHLEN]) { + int mib[4]; + mib[0] = CTL_KERN; +# ifdef __NetBSD__ + mib[1] = KERN_PROC_ARGS; + mib[2] = -1; + mib[3] = KERN_PROC_PATHNAME; +# else + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PATHNAME; + mib[3] = -1; +# endif + + size_t len = MAXPATHLEN; + if (sysctl(mib, 4, aResult, &len, nullptr, 0) < 0) { + return NS_ERROR_FAILURE; + } + + return NS_OK; + } + +#elif defined(__OpenBSD__) + static nsresult Get(char aResult[MAXPATHLEN]) { + static bool cached = false; + static char cachedPath[MAXPATHLEN]; + nsresult r; + int mib[4]; + if (cached) { + if (strlcpy(aResult, cachedPath, MAXPATHLEN) >= MAXPATHLEN) { + return NS_ERROR_FAILURE; + } + return NS_OK; + } + mib[0] = CTL_KERN; + mib[1] = KERN_PROC_ARGS; + mib[2] = getpid(); + mib[3] = KERN_PROC_ARGV; + + size_t len = 0; + if (sysctl(mib, 4, nullptr, &len, nullptr, 0) < 0) { + return NS_ERROR_FAILURE; + } + + auto argv = MakeUnique(len / sizeof(const char*)); + if (sysctl(mib, 4, argv.get(), &len, nullptr, 0) < 0) { + return NS_ERROR_FAILURE; + } + + r = GetFromArgv0(argv[0], aResult); + if (NS_SUCCEEDED(r)) { + if (strlcpy(cachedPath, aResult, MAXPATHLEN) >= MAXPATHLEN) { + return NS_ERROR_FAILURE; + } + cached = true; + } + return r; + } + + static nsresult GetFromArgv0(const char* aArgv0, char aResult[MAXPATHLEN]) { + struct stat fileStat; + // 1) use realpath() on argv[0], which works unless we're loaded from the + // PATH. Only do so if argv[0] looks like a path (contains a /). + // 2) manually walk through the PATH and look for ourself + // 3) give up + if (strchr(aArgv0, '/') && realpath(aArgv0, aResult) && + stat(aResult, &fileStat) == 0) { + return NS_OK; + } + + const char* path = getenv("PATH"); + if (!path) { + return NS_ERROR_FAILURE; + } + + char* pathdup = strdup(path); + if (!pathdup) { + return NS_ERROR_OUT_OF_MEMORY; + } + + bool found = false; + char* token = strtok(pathdup, ":"); + while (token) { + char tmpPath[MAXPATHLEN]; + sprintf(tmpPath, "%s/%s", token, aArgv0); + if (realpath(tmpPath, aResult) && stat(aResult, &fileStat) == 0) { + found = true; + break; + } + token = strtok(nullptr, ":"); + } + free(pathdup); + if (found) { + return NS_OK; + } + return NS_ERROR_FAILURE; + } + +#else +# error Oops, you need platform-specific code here +#endif + + public: + static UniqueFreePtr Get() { + char path[MAXPATHLEN]; + if (NS_FAILED(Get(path))) { + return nullptr; + } + UniqueFreePtr result; + result.reset(strdup(path)); + return result; + } + +#ifdef MOZILLA_INTERNAL_API + static nsresult GetFile(nsIFile** aResult) { + nsCOMPtr lf; +# ifdef XP_WIN + wchar_t exePath[MAXPATHLEN]; + nsresult rv = GetW(exePath); +# else + char exePath[MAXPATHLEN]; + nsresult rv = Get(exePath); +# endif + if (NS_FAILED(rv)) { + return rv; + } +# ifdef XP_WIN + rv = NS_NewLocalFile(nsDependentString(exePath), true, getter_AddRefs(lf)); +# else + rv = NS_NewNativeLocalFile(nsDependentCString(exePath), true, + getter_AddRefs(lf)); +# endif + if (NS_FAILED(rv)) { + return rv; + } + NS_ADDREF(*aResult = lf); + return NS_OK; + } +#endif +}; + +} // namespace mozilla + +#endif /* mozilla_BinaryPath_h */ diff --git a/xpcom/build/FileLocation.cpp b/xpcom/build/FileLocation.cpp new file mode 100644 index 0000000000..bb090c2b94 --- /dev/null +++ b/xpcom/build/FileLocation.cpp @@ -0,0 +1,212 @@ +/* -*- 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 "FileLocation.h" +#include "nsZipArchive.h" +#include "nsURLHelper.h" + +namespace mozilla { + +FileLocation::FileLocation() = default; + +FileLocation::~FileLocation() = default; + +FileLocation::FileLocation(nsIFile* aFile) { Init(aFile); } + +FileLocation::FileLocation(nsIFile* aFile, const char* aPath) { + Init(aFile, aPath); +} + +FileLocation::FileLocation(nsZipArchive* aZip, const char* aPath) { + Init(aZip, aPath); +} + +FileLocation::FileLocation(const FileLocation& aOther) + + = default; + +FileLocation::FileLocation(FileLocation&& aOther) + : mBaseFile(std::move(aOther.mBaseFile)), + mBaseZip(std::move(aOther.mBaseZip)), + mPath(std::move(aOther.mPath)) { + aOther.mPath.Truncate(); +} + +FileLocation::FileLocation(const FileLocation& aFile, const char* aPath) { + if (aFile.IsZip()) { + if (aFile.mBaseFile) { + Init(aFile.mBaseFile, aFile.mPath.get()); + } else { + Init(aFile.mBaseZip, aFile.mPath.get()); + } + if (aPath) { + int32_t i = mPath.RFindChar('/'); + if (kNotFound == i) { + mPath.Truncate(0); + } else { + mPath.Truncate(i + 1); + } + mPath += aPath; + } + } else { + if (aPath) { + nsCOMPtr cfile; + aFile.mBaseFile->GetParent(getter_AddRefs(cfile)); + +#if defined(XP_WIN) + nsAutoCString pathStr(aPath); + char* p; + uint32_t len = pathStr.GetMutableData(&p); + for (; len; ++p, --len) { + if ('/' == *p) { + *p = '\\'; + } + } + cfile->AppendRelativeNativePath(pathStr); +#else + cfile->AppendRelativeNativePath(nsDependentCString(aPath)); +#endif + Init(cfile); + } else { + Init(aFile.mBaseFile); + } + } +} + +void FileLocation::Init(nsIFile* aFile) { + mBaseZip = nullptr; + mBaseFile = aFile; + mPath.Truncate(); +} + +void FileLocation::Init(nsIFile* aFile, const char* aPath) { + mBaseZip = nullptr; + mBaseFile = aFile; + mPath = aPath; +} + +void FileLocation::Init(nsZipArchive* aZip, const char* aPath) { + mBaseZip = aZip; + mBaseFile = nullptr; + mPath = aPath; +} + +void FileLocation::GetURIString(nsACString& aResult) const { + if (mBaseFile) { + net_GetURLSpecFromActualFile(mBaseFile, aResult); + } else if (mBaseZip) { + RefPtr handler = mBaseZip->GetFD(); + handler->mFile.GetURIString(aResult); + } + if (IsZip()) { + aResult.InsertLiteral("jar:", 0); + aResult += "!/"; + aResult += mPath; + } +} + +already_AddRefed FileLocation::GetBaseFile() { + if (IsZip() && mBaseZip) { + RefPtr handler = mBaseZip->GetFD(); + if (handler) { + return handler->mFile.GetBaseFile(); + } + return nullptr; + } + + nsCOMPtr file = mBaseFile; + return file.forget(); +} + +bool FileLocation::Equals(const FileLocation& aFile) const { + if (mPath != aFile.mPath) { + return false; + } + + if (mBaseFile && aFile.mBaseFile) { + bool eq; + return NS_SUCCEEDED(mBaseFile->Equals(aFile.mBaseFile, &eq)) && eq; + } + + const FileLocation* a = this; + const FileLocation* b = &aFile; + if (a->mBaseZip) { + RefPtr handler = a->mBaseZip->GetFD(); + a = &handler->mFile; + } + if (b->mBaseZip) { + RefPtr handler = b->mBaseZip->GetFD(); + b = &handler->mFile; + } + + return a->Equals(*b); +} + +nsresult FileLocation::GetData(Data& aData) { + if (!IsZip()) { + return mBaseFile->OpenNSPRFileDesc(PR_RDONLY, 0444, &aData.mFd.rwget()); + } + aData.mZip = mBaseZip; + if (!aData.mZip) { + // this can return nullptr + aData.mZip = nsZipArchive::OpenArchive(mBaseFile); + } + if (aData.mZip) { + aData.mItem = aData.mZip->GetItem(mPath.get()); + if (aData.mItem) { + return NS_OK; + } + } + return NS_ERROR_FILE_UNRECOGNIZED_PATH; +} + +nsresult FileLocation::Data::GetSize(uint32_t* aResult) { + if (mFd) { + PRFileInfo64 fileInfo; + if (PR_SUCCESS != PR_GetOpenFileInfo64(mFd, &fileInfo)) { + return NS_ErrorAccordingToNSPR(); + } + + if (fileInfo.size > int64_t(UINT32_MAX)) { + return NS_ERROR_FILE_TOO_BIG; + } + + *aResult = fileInfo.size; + return NS_OK; + } + if (mItem) { + *aResult = mItem->RealSize(); + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +nsresult FileLocation::Data::Copy(char* aBuf, uint32_t aLen) { + if (mFd) { + for (uint32_t totalRead = 0; totalRead < aLen;) { + int32_t read = PR_Read(mFd, aBuf + totalRead, + XPCOM_MIN(aLen - totalRead, uint32_t(INT32_MAX))); + if (read < 0) { + return NS_ErrorAccordingToNSPR(); + } + totalRead += read; + } + return NS_OK; + } + if (mItem) { + nsZipCursor cursor(mItem, mZip, reinterpret_cast(aBuf), aLen, + true); + uint32_t readLen; + cursor.Copy(&readLen); + if (readLen != aLen) { + return NS_ERROR_FILE_CORRUPTED; + } + return NS_OK; + } + return NS_ERROR_NOT_INITIALIZED; +} + +} /* namespace mozilla */ diff --git a/xpcom/build/FileLocation.h b/xpcom/build/FileLocation.h new file mode 100644 index 0000000000..0988a9595f --- /dev/null +++ b/xpcom/build/FileLocation.h @@ -0,0 +1,140 @@ +/* -*- 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/. */ + +#ifndef mozilla_FileLocation_h +#define mozilla_FileLocation_h + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "FileUtils.h" + +class nsZipArchive; +class nsZipItem; + +namespace mozilla { + +class FileLocation { + public: + /** + * FileLocation is an helper to handle different kind of file locations + * within Gecko: + * - on filesystems + * - in archives + * - in archives within archives + * As such, it stores a path within an archive, as well as the archive + * path itself, or the complete file path alone when on a filesystem. + * When the archive is in an archive, an nsZipArchive is stored instead + * of a file path. + */ + FileLocation(); + ~FileLocation(); + + FileLocation(const FileLocation& aOther); + FileLocation(FileLocation&& aOther); + + FileLocation& operator=(const FileLocation&) = default; + + /** + * Constructor for plain files + */ + explicit FileLocation(nsIFile* aFile); + + /** + * Constructors for path within an archive. The archive can be given either + * as nsIFile or nsZipArchive. + */ + FileLocation(nsIFile* aZip, const char* aPath); + + FileLocation(nsZipArchive* aZip, const char* aPath); + + /** + * Creates a new file location relative to another one. + */ + FileLocation(const FileLocation& aFile, const char* aPath); + + /** + * Initialization functions corresponding to constructors + */ + void Init(nsIFile* aFile); + + void Init(nsIFile* aZip, const char* aPath); + + void Init(nsZipArchive* aZip, const char* aPath); + + /** + * Returns an URI string corresponding to the file location + */ + void GetURIString(nsACString& aResult) const; + + /** + * Returns the base file of the location, where base file is defined as: + * - The file itself when the location is on a filesystem + * - The archive file when the location is in an archive + * - The outer archive file when the location is in an archive in an archive + */ + already_AddRefed GetBaseFile(); + + nsZipArchive* GetBaseZip() { return mBaseZip; } + + /** + * Returns whether the "base file" (see GetBaseFile) is an archive + */ + bool IsZip() const { return !mPath.IsEmpty(); } + + /** + * Returns the path within the archive, when within an archive + */ + void GetPath(nsACString& aResult) const { aResult = mPath; } + + /** + * Boolean value corresponding to whether the file location is initialized + * or not. + */ + explicit operator bool() const { return mBaseFile || mBaseZip; } + + /** + * Returns whether another FileLocation points to the same resource + */ + bool Equals(const FileLocation& aFile) const; + + /** + * Data associated with a FileLocation. + */ + class Data { + public: + /** + * Returns the data size + */ + nsresult GetSize(uint32_t* aResult); + + /** + * Copies the data in the given buffer + */ + nsresult Copy(char* aBuf, uint32_t aLen); + + protected: + friend class FileLocation; + nsZipItem* mItem; + RefPtr mZip; + mozilla::AutoFDClose mFd; + }; + + /** + * Returns the data associated with the resource pointed at by the file + * location. + */ + nsresult GetData(Data& aData); + + private: + nsCOMPtr mBaseFile; + RefPtr mBaseZip; + nsCString mPath; +}; /* class FileLocation */ + +} /* namespace mozilla */ + +#endif /* mozilla_FileLocation_h */ diff --git a/xpcom/build/IOInterposer.cpp b/xpcom/build/IOInterposer.cpp new file mode 100644 index 0000000000..0420965b91 --- /dev/null +++ b/xpcom/build/IOInterposer.cpp @@ -0,0 +1,532 @@ +/* -*- 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 +#include + +#include "IOInterposer.h" + +#include "IOInterposerPrivate.h" +#include "MainThreadIOLogger.h" +#include "mozilla/Atomics.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/ThreadLocal.h" +#include "nscore.h" // for NS_FREE_PERMANENT_DATA +#if !defined(XP_WIN) +# include "NSPRInterposer.h" +#endif // !defined(XP_WIN) +#include "nsXULAppAPI.h" +#include "PoisonIOInterposer.h" +#include "prenv.h" + +namespace { + +/** Find if a vector contains a specific element */ +template +bool VectorContains(const std::vector& aVector, const T& aElement) { + return std::find(aVector.begin(), aVector.end(), aElement) != aVector.end(); +} + +/** Remove element from a vector */ +template +void VectorRemove(std::vector& aVector, const T& aElement) { + typename std::vector::iterator newEnd = + std::remove(aVector.begin(), aVector.end(), aElement); + aVector.erase(newEnd, aVector.end()); +} + +/** Lists of Observers */ +struct ObserverLists { + private: + ~ObserverLists() = default; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ObserverLists) + + ObserverLists() = default; + + ObserverLists(ObserverLists const& aOther) + : mCreateObservers(aOther.mCreateObservers), + mReadObservers(aOther.mReadObservers), + mWriteObservers(aOther.mWriteObservers), + mFSyncObservers(aOther.mFSyncObservers), + mStatObservers(aOther.mStatObservers), + mCloseObservers(aOther.mCloseObservers), + mStageObservers(aOther.mStageObservers) {} + // Lists of observers for I/O events. + // These are implemented as vectors since they are allowed to survive gecko, + // without reporting leaks. This is necessary for the IOInterposer to be used + // for late-write checks. + std::vector mCreateObservers; + std::vector mReadObservers; + std::vector mWriteObservers; + std::vector mFSyncObservers; + std::vector mStatObservers; + std::vector mCloseObservers; + std::vector mStageObservers; +}; + +class PerThreadData { + public: + explicit PerThreadData(bool aIsMainThread = false) + : mIsMainThread(aIsMainThread), + mIsHandlingObservation(false), + mCurrentGeneration(0) { + MOZ_COUNT_CTOR(PerThreadData); + } + + MOZ_COUNTED_DTOR(PerThreadData) + + void CallObservers(mozilla::IOInterposeObserver::Observation& aObservation) { + // Prevent recursive reporting. + if (mIsHandlingObservation) { + return; + } + + mIsHandlingObservation = true; + // Decide which list of observers to inform + const std::vector* observers = nullptr; + switch (aObservation.ObservedOperation()) { + case mozilla::IOInterposeObserver::OpCreateOrOpen: + observers = &mObserverLists->mCreateObservers; + break; + case mozilla::IOInterposeObserver::OpRead: + observers = &mObserverLists->mReadObservers; + break; + case mozilla::IOInterposeObserver::OpWrite: + observers = &mObserverLists->mWriteObservers; + break; + case mozilla::IOInterposeObserver::OpFSync: + observers = &mObserverLists->mFSyncObservers; + break; + case mozilla::IOInterposeObserver::OpStat: + observers = &mObserverLists->mStatObservers; + break; + case mozilla::IOInterposeObserver::OpClose: + observers = &mObserverLists->mCloseObservers; + break; + case mozilla::IOInterposeObserver::OpNextStage: + observers = &mObserverLists->mStageObservers; + break; + default: { + // Invalid IO operation, see documentation comment for + // IOInterposer::Report() + MOZ_ASSERT(false); + // Just ignore it in non-debug builds. + return; + } + } + MOZ_ASSERT(observers); + + // Inform observers + for (auto i = observers->begin(), e = observers->end(); i != e; ++i) { + (*i)->Observe(aObservation); + } + mIsHandlingObservation = false; + } + + inline uint32_t GetCurrentGeneration() const { return mCurrentGeneration; } + + inline bool IsMainThread() const { return mIsMainThread; } + + inline void SetObserverLists(uint32_t aNewGeneration, + RefPtr& aNewLists) { + mCurrentGeneration = aNewGeneration; + mObserverLists = aNewLists; + } + + inline void ClearObserverLists() { + if (mObserverLists) { + mCurrentGeneration = 0; + mObserverLists = nullptr; + } + } + + private: + bool mIsMainThread; + bool mIsHandlingObservation; + uint32_t mCurrentGeneration; + RefPtr mObserverLists; +}; + +// Thread-safe list of observers, from which `PerThreadData` sources its own +// local list when needed. +class SourceList { + public: + SourceList() + : mObservedOperations(mozilla::IOInterposeObserver::OpNone), + mIsEnabled(true) { + MOZ_COUNT_CTOR(SourceList); + } + + MOZ_COUNTED_DTOR(SourceList) + + inline void Disable() { mIsEnabled = false; } + inline void Enable() { mIsEnabled = true; } + + void Register(mozilla::IOInterposeObserver::Operation aOp, + mozilla::IOInterposeObserver* aStaticObserver) { + mozilla::IOInterposer::AutoLock lock(mLock); + + ObserverLists* newLists = nullptr; + if (mObserverLists) { + newLists = new ObserverLists(*mObserverLists); + } else { + newLists = new ObserverLists(); + } + // You can register to observe multiple types of observations + // but you'll never be registered twice for the same observations. + if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen && + !VectorContains(newLists->mCreateObservers, aStaticObserver)) { + newLists->mCreateObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpRead && + !VectorContains(newLists->mReadObservers, aStaticObserver)) { + newLists->mReadObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpWrite && + !VectorContains(newLists->mWriteObservers, aStaticObserver)) { + newLists->mWriteObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpFSync && + !VectorContains(newLists->mFSyncObservers, aStaticObserver)) { + newLists->mFSyncObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpStat && + !VectorContains(newLists->mStatObservers, aStaticObserver)) { + newLists->mStatObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpClose && + !VectorContains(newLists->mCloseObservers, aStaticObserver)) { + newLists->mCloseObservers.push_back(aStaticObserver); + } + if (aOp & mozilla::IOInterposeObserver::OpNextStage && + !VectorContains(newLists->mStageObservers, aStaticObserver)) { + newLists->mStageObservers.push_back(aStaticObserver); + } + mObserverLists = newLists; + mObservedOperations = + (mozilla::IOInterposeObserver::Operation)(mObservedOperations | aOp); + + mCurrentGeneration++; + } + + void Unregister(mozilla::IOInterposeObserver::Operation aOp, + mozilla::IOInterposeObserver* aStaticObserver) { + mozilla::IOInterposer::AutoLock lock(mLock); + + ObserverLists* newLists = nullptr; + if (mObserverLists) { + newLists = new ObserverLists(*mObserverLists); + } else { + newLists = new ObserverLists(); + } + + if (aOp & mozilla::IOInterposeObserver::OpCreateOrOpen) { + VectorRemove(newLists->mCreateObservers, aStaticObserver); + if (newLists->mCreateObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & + ~mozilla::IOInterposeObserver::OpCreateOrOpen); + } + } + if (aOp & mozilla::IOInterposeObserver::OpRead) { + VectorRemove(newLists->mReadObservers, aStaticObserver); + if (newLists->mReadObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpRead); + } + } + if (aOp & mozilla::IOInterposeObserver::OpWrite) { + VectorRemove(newLists->mWriteObservers, aStaticObserver); + if (newLists->mWriteObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpWrite); + } + } + if (aOp & mozilla::IOInterposeObserver::OpFSync) { + VectorRemove(newLists->mFSyncObservers, aStaticObserver); + if (newLists->mFSyncObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpFSync); + } + } + if (aOp & mozilla::IOInterposeObserver::OpStat) { + VectorRemove(newLists->mStatObservers, aStaticObserver); + if (newLists->mStatObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpStat); + } + } + if (aOp & mozilla::IOInterposeObserver::OpClose) { + VectorRemove(newLists->mCloseObservers, aStaticObserver); + if (newLists->mCloseObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpClose); + } + } + if (aOp & mozilla::IOInterposeObserver::OpNextStage) { + VectorRemove(newLists->mStageObservers, aStaticObserver); + if (newLists->mStageObservers.empty()) { + mObservedOperations = (mozilla::IOInterposeObserver::Operation)( + mObservedOperations & ~mozilla::IOInterposeObserver::OpNextStage); + } + } + mObserverLists = newLists; + mCurrentGeneration++; + } + + void Update(PerThreadData& aPtd) { + if (mCurrentGeneration == aPtd.GetCurrentGeneration()) { + return; + } + // If the generation counts don't match then we need to update the current + // thread's observer list with the new source list. + mozilla::IOInterposer::AutoLock lock(mLock); + aPtd.SetObserverLists(mCurrentGeneration, mObserverLists); + } + + inline bool IsObservedOperation(mozilla::IOInterposeObserver::Operation aOp) { + // This does not occur inside of a lock, so this makes no guarantees that + // the observers are in sync with this. That is acceptable; it is not a + // problem if we occasionally report more or less IO than is actually + // occurring. + return mIsEnabled && !!(mObservedOperations & aOp); + } + + private: + RefPtr mObserverLists MOZ_GUARDED_BY(mLock); + // Note, we cannot use mozilla::Mutex here as the ObserverLists may be leaked + // (We want to monitor IO during shutdown). Furthermore, as we may have to + // unregister observers during shutdown an OffTheBooksMutex is not an option + // either, as its base calls into sDeadlockDetector which may be nullptr + // during shutdown. + mozilla::IOInterposer::Mutex mLock; + // Flags tracking which operations are being observed + mozilla::Atomic + mObservedOperations; + // Used for quickly disabling everything by IOInterposer::Disable() + mozilla::Atomic mIsEnabled; + // Used to inform threads that the source observer list has changed + mozilla::Atomic mCurrentGeneration; +}; + +// Special observation used by IOInterposer::EnteringNextStage() +class NextStageObservation : public mozilla::IOInterposeObserver::Observation { + public: + NextStageObservation() + : mozilla::IOInterposeObserver::Observation( + mozilla::IOInterposeObserver::OpNextStage, "IOInterposer", false) { + mStart = mozilla::TimeStamp::Now(); + mEnd = mStart; + } +}; + +// List of observers registered +static mozilla::StaticAutoPtr sSourceList; +static MOZ_THREAD_LOCAL(PerThreadData*) sThreadLocalData; +static bool sThreadLocalDataInitialized; + +} // anonymous namespace + +namespace mozilla { + +IOInterposeObserver::Observation::Observation(Operation aOperation, + const char* aReference, + bool aShouldReport) + : mOperation(aOperation), + mReference(aReference), + mShouldReport(IOInterposer::IsObservedOperation(aOperation) && + aShouldReport) { + if (mShouldReport) { + mStart = TimeStamp::Now(); + } +} + +IOInterposeObserver::Observation::Observation(Operation aOperation, + const TimeStamp& aStart, + const TimeStamp& aEnd, + const char* aReference) + : mOperation(aOperation), + mStart(aStart), + mEnd(aEnd), + mReference(aReference), + mShouldReport(false) {} + +const char* IOInterposeObserver::Observation::ObservedOperationString() const { + switch (mOperation) { + case OpCreateOrOpen: + return "create/open"; + case OpRead: + return "read"; + case OpWrite: + return "write"; + case OpFSync: + return "fsync"; + case OpStat: + return "stat"; + case OpClose: + return "close"; + case OpNextStage: + return "NextStage"; + default: + return "unknown"; + } +} + +void IOInterposeObserver::Observation::Report() { + if (mShouldReport) { + mEnd = TimeStamp::Now(); + IOInterposer::Report(*this); + } +} + +bool IOInterposer::Init() { + // Don't initialize twice... + if (sSourceList) { + return true; + } + if (!sThreadLocalData.init()) { + return false; + } + sThreadLocalDataInitialized = true; + bool isMainThread = true; + RegisterCurrentThread(isMainThread); + sSourceList = new SourceList(); + + MainThreadIOLogger::Init(); + + // Now we initialize the various interposers depending on platform + + // Under certain conditions it may be unsafe to initialize PoisonIOInterposer, + // such as when a background thread is already running. We set this variable + // elsewhere when such a condition applies. + if (!PR_GetEnv("MOZ_DISABLE_POISON_IO_INTERPOSER")) { + InitPoisonIOInterposer(); + } + + // We don't hook NSPR on Windows because PoisonIOInterposer captures a + // superset of the former's events. +#if !defined(XP_WIN) + InitNSPRIOInterposing(); +#endif + return true; +} + +bool IOInterposeObserver::IsMainThread() { + if (!sThreadLocalDataInitialized) { + return false; + } + PerThreadData* ptd = sThreadLocalData.get(); + if (!ptd) { + return false; + } + return ptd->IsMainThread(); +} + +void IOInterposer::Clear() { + /* Clear() is a no-op on release builds so that we may continue to trap I/O + until process termination. In leak-checking builds, we need to shut down + IOInterposer so that all references are properly released. */ +#ifdef NS_FREE_PERMANENT_DATA + UnregisterCurrentThread(); + sSourceList = nullptr; +#endif +} + +void IOInterposer::Disable() { + if (!sSourceList) { + return; + } + sSourceList->Disable(); +} + +void IOInterposer::Enable() { + if (!sSourceList) { + return; + } + sSourceList->Enable(); +} + +void IOInterposer::Report(IOInterposeObserver::Observation& aObservation) { + PerThreadData* ptd = sThreadLocalData.get(); + if (!ptd) { + // In this case the current thread is not registered with IOInterposer. + // Alternatively we could take the slow path and just lock everything if + // we're not registered. That could potentially perform poorly, though. + return; + } + + if (!sSourceList) { + // If there is no longer a source list then we should clear the local one. + ptd->ClearObserverLists(); + return; + } + + sSourceList->Update(*ptd); + + // Don't try to report if there's nobody listening. + if (!IOInterposer::IsObservedOperation(aObservation.ObservedOperation())) { + return; + } + + ptd->CallObservers(aObservation); +} + +bool IOInterposer::IsObservedOperation(IOInterposeObserver::Operation aOp) { + return sSourceList && sSourceList->IsObservedOperation(aOp); +} + +void IOInterposer::Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver) { + MOZ_ASSERT(aStaticObserver); + if (!sSourceList || !aStaticObserver) { + return; + } + + sSourceList->Register(aOp, aStaticObserver); +} + +void IOInterposer::Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver) { + if (!sSourceList) { + return; + } + + sSourceList->Unregister(aOp, aStaticObserver); +} + +void IOInterposer::RegisterCurrentThread(bool aIsMainThread) { + if (!sThreadLocalDataInitialized) { + return; + } + MOZ_ASSERT(!sThreadLocalData.get()); + PerThreadData* curThreadData = new PerThreadData(aIsMainThread); + sThreadLocalData.set(curThreadData); +} + +void IOInterposer::UnregisterCurrentThread() { + if (!sThreadLocalDataInitialized) { + return; + } + if (PerThreadData* curThreadData = sThreadLocalData.get()) { + sThreadLocalData.set(nullptr); + delete curThreadData; + } +} + +void IOInterposer::EnteringNextStage() { + if (!sSourceList) { + return; + } + NextStageObservation observation; + Report(observation); +} + +} // namespace mozilla diff --git a/xpcom/build/IOInterposer.h b/xpcom/build/IOInterposer.h new file mode 100644 index 0000000000..917aa26cd4 --- /dev/null +++ b/xpcom/build/IOInterposer.h @@ -0,0 +1,288 @@ +/* -*- 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/. */ + +#ifndef mozilla_IOInterposer_h +#define mozilla_IOInterposer_h + +#include "mozilla/Attributes.h" +#include "mozilla/TimeStamp.h" +#include "nsString.h" + +namespace mozilla { + +/** + * Interface for I/O interposer observers. This is separate from the + * IOInterposer because we have multiple uses for these observations. + */ +class IOInterposeObserver { + public: + enum Operation { + OpNone = 0, + OpCreateOrOpen = (1 << 0), + OpRead = (1 << 1), + OpWrite = (1 << 2), + OpFSync = (1 << 3), + OpStat = (1 << 4), + OpClose = (1 << 5), + OpNextStage = + (1 << 6), // Meta - used when leaving startup, entering shutdown + OpWriteFSync = (OpWrite | OpFSync), + OpAll = (OpCreateOrOpen | OpRead | OpWrite | OpFSync | OpStat | OpClose), + OpAllWithStaging = (OpAll | OpNextStage) + }; + + /** A representation of an I/O observation */ + class Observation { + protected: + /** + * This constructor is for use by subclasses that are intended to take + * timing measurements via RAII. The |aShouldReport| parameter may be + * used to make the measurement and reporting conditional on the + * satisfaction of an arbitrary predicate that was evaluated + * in the subclass. Note that IOInterposer::IsObservedOperation() is + * always ANDed with aShouldReport, so the subclass does not need to + * include a call to that function explicitly. + */ + Observation(Operation aOperation, const char* aReference, + bool aShouldReport = true); + + public: + /** + * Since this constructor accepts start and end times, it does *not* take + * its own timings, nor does it report itself. + */ + Observation(Operation aOperation, const TimeStamp& aStart, + const TimeStamp& aEnd, const char* aReference); + + /** + * Operation observed, this is one of the individual Operation values. + * Combinations of these flags are only used when registering observers. + */ + Operation ObservedOperation() const { return mOperation; } + + /** + * Return the observed operation as a human-readable string. + */ + const char* ObservedOperationString() const; + + /** Time at which the I/O operation was started */ + TimeStamp Start() const { return mStart; } + + /** + * Time at which the I/O operation ended, for asynchronous methods this is + * the time at which the call initiating the asynchronous request returned. + */ + TimeStamp End() const { return mEnd; } + + /** + * Duration of the operation, for asynchronous I/O methods this is the + * duration of the call initiating the asynchronous request. + */ + TimeDuration Duration() const { return mEnd - mStart; } + + /** + * IO reference, function name or name of component (sqlite) that did IO + * this is in addition the generic operation. This attribute may be platform + * specific, but should only take a finite number of distinct values. + * E.g. sqlite-commit, CreateFile, NtReadFile, fread, fsync, mmap, etc. + * I.e. typically the platform specific function that did the IO. + */ + const char* Reference() const { return mReference; } + + virtual const char* FileType() const { return "File"; } + + /** Request filename associated with the I/O operation, empty if unknown */ + virtual void Filename(nsAString& aString) { aString.Truncate(); } + + virtual ~Observation() = default; + + protected: + void Report(); + + Operation mOperation; + TimeStamp mStart; + TimeStamp mEnd; + const char* mReference; // Identifies the source of the Observation + bool mShouldReport; // Measure and report if true + }; + + /** + * Invoked whenever an implementation of the IOInterposeObserver should + * observe aObservation. Implement this and do your thing... + * But do consider if it is wise to use IO functions in this method, they are + * likely to cause recursion :) + * At least, see PoisonIOInterposer.h and register your handle as a debug file + * even, if you don't initialize the poison IO interposer, someone else might. + * + * Remark: Observations may occur on any thread. + */ + virtual void Observe(Observation& aObservation) = 0; + + virtual ~IOInterposeObserver() = default; + + protected: + /** + * We don't use NS_IsMainThread() because we need to be able to determine the + * main thread outside of XPCOM Initialization. IOInterposer observers should + * call this function instead. + */ + static bool IsMainThread(); +}; + +/** + * These functions are responsible for ensuring that events are routed to the + * appropriate observers. + */ +namespace IOInterposer { + +/** + * This function must be called from the main-thread when no other threads are + * running before any of the other methods on this class may be used. + * + * IO reports can however, safely assume that IsObservedOperation() will + * return false until the IOInterposer is initialized. + * + * Remark, it's safe to call this method multiple times, so just call it when + * you to utilize IO interposing. + * + * Using the IOInterposerInit class is preferred to calling this directly. + */ +bool Init(); + +/** + * This function must be called from the main thread, and furthermore + * it must be called when no other threads are executing. Effectively + * restricting us to calling it only during shutdown. + * + * Callers should take care that no other consumers are subscribed to events, + * as these events will stop when this function is called. + * + * In practice, we don't use this method as the IOInterposer is used for + * late-write checks. + */ +void Clear(); + +/** + * This function immediately disables IOInterposer functionality in a fast, + * thread-safe manner. Primarily for use by the crash reporter. + */ +void Disable(); + +/** + * This function re-enables IOInterposer functionality in a fast, thread-safe + * manner. Primarily for use by the crash reporter. + */ +void Enable(); + +/** + * Report IO to registered observers. + * Notice that the reported operation must be either OpRead, OpWrite or + * OpFSync. You are not allowed to report an observation with OpWriteFSync or + * OpAll, these are just auxiliary values for use with Register(). + * + * If the IO call you're reporting does multiple things, write and fsync, you + * can choose to call Report() twice once with write and once with FSync. You + * may not call Report() with OpWriteFSync! The Observation::mOperation + * attribute is meant to be generic, not perfect. + * + * Notice that there is no reason to report an observation with an operation + * which is not being observed. Use IsObservedOperation() to check if the + * operation you are about to report is being observed. This is especially + * important if you are constructing expensive observations containing + * filename and full-path. + * + * Remark: Init() must be called before any IO is reported. But + * IsObservedOperation() will return false until Init() is called. + */ +void Report(IOInterposeObserver::Observation& aObservation); + +/** + * Return whether or not an operation is observed. Reporters should not + * report operations that are not being observed by anybody. This mechanism + * allows us to avoid reporting I/O when no observers are registered. + */ +bool IsObservedOperation(IOInterposeObserver::Operation aOp); + +/** + * Register IOInterposeObserver, the observer object will receive all + * observations for the given operation aOp. + * + * Remarks: + * - Init() must be called before observers are registered. + * - The IOInterposeObserver object should be static, because it could still be + * used on another thread shortly after Unregister(). + */ +void Register(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver); + +/** + * Unregister an IOInterposeObserver for a given operation + * Remark: It is always safe to unregister for all operations, even if yoú + * didn't register for them all. + * I.e. IOInterposer::Unregister(IOInterposeObserver::OpAll, aObserver) + * + * Remarks: + * - Init() must be called before observers are registered. + * - The IOInterposeObserver object should be static, because it could still be + * used on another thread shortly after this Unregister() call. + */ +void Unregister(IOInterposeObserver::Operation aOp, + IOInterposeObserver* aStaticObserver); + +/** + * Registers the current thread with the IOInterposer. This must be done to + * ensure that per-thread data is created in an orderly fashion. + * We could have written this to initialize that data lazily, however this + * could have unintended consequences if a thread that is not aware of + * IOInterposer was implicitly registered: its per-thread data would never + * be deleted because it would not know to unregister itself. + * + * @param aIsMainThread true if IOInterposer should treat the current thread + * as the main thread. + */ +void RegisterCurrentThread(bool aIsMainThread = false); + +/** + * Unregisters the current thread with the IOInterposer. This is important + * to call when a thread is shutting down because it cleans up data that + * is stored in a TLS slot. + */ +void UnregisterCurrentThread(); + +/** + * Called to inform observers that the process has transitioned out of the + * startup stage or into the shutdown stage. Main thread only. + */ +void EnteringNextStage(); + +} // namespace IOInterposer + +class IOInterposerInit { + public: + IOInterposerInit() { +#if defined(EARLY_BETA_OR_EARLIER) + IOInterposer::Init(); +#endif + } + + ~IOInterposerInit() { +#if defined(EARLY_BETA_OR_EARLIER) + IOInterposer::Clear(); +#endif + } +}; + +class MOZ_RAII AutoIOInterposerDisable final { + public: + explicit AutoIOInterposerDisable() { IOInterposer::Disable(); } + ~AutoIOInterposerDisable() { IOInterposer::Enable(); } + + private: +}; + +} // namespace mozilla + +#endif // mozilla_IOInterposer_h diff --git a/xpcom/build/IOInterposerPrivate.h b/xpcom/build/IOInterposerPrivate.h new file mode 100644 index 0000000000..14f71603b8 --- /dev/null +++ b/xpcom/build/IOInterposerPrivate.h @@ -0,0 +1,117 @@ +/* -*- 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/. */ + +#ifndef xpcom_build_IOInterposerPrivate_h +#define xpcom_build_IOInterposerPrivate_h + +/* This header file contains declarations for helper classes that are + to be used exclusively by IOInterposer and its observers. This header + file is not to be used by anything else and MUST NOT be exported! */ + +#include +#include + +#include "mozilla/ThreadSafety.h" + +namespace mozilla { +namespace IOInterposer { + +/** + * The following classes are simple wrappers for PRLock and PRCondVar. + * IOInterposer and friends use these instead of Mozilla::Mutex et al because + * of the fact that IOInterposer is permitted to run until the process + * terminates; we can't use anything that plugs into leak checkers or deadlock + * detectors because IOInterposer will outlive those and generate false + * positives. + */ + +class MOZ_CAPABILITY("monitor") Monitor { + public: + Monitor() : mLock(PR_NewLock()), mCondVar(PR_NewCondVar(mLock)) {} + + ~Monitor() { + PR_DestroyCondVar(mCondVar); + mCondVar = nullptr; + PR_DestroyLock(mLock); + mLock = nullptr; + } + + void Lock() MOZ_CAPABILITY_ACQUIRE() { PR_Lock(mLock); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { PR_Unlock(mLock); } + + bool Wait(PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT) + MOZ_REQUIRES(this) { + return PR_WaitCondVar(mCondVar, aTimeout) == PR_SUCCESS; + } + + bool Notify() { return PR_NotifyCondVar(mCondVar) == PR_SUCCESS; } + + private: + PRLock* mLock; + PRCondVar* mCondVar; +}; + +class MOZ_SCOPED_CAPABILITY MonitorAutoLock { + public: + explicit MonitorAutoLock(Monitor& aMonitor) MOZ_CAPABILITY_ACQUIRE(aMonitor) + : mMonitor(aMonitor) { + mMonitor.Lock(); + } + + ~MonitorAutoLock() MOZ_CAPABILITY_RELEASE() { mMonitor.Unlock(); } + + private: + Monitor& mMonitor; +}; + +class MOZ_SCOPED_CAPABILITY MonitorAutoUnlock { + public: + explicit MonitorAutoUnlock(Monitor& aMonitor) + MOZ_SCOPED_UNLOCK_RELEASE(aMonitor) + : mMonitor(aMonitor) { + mMonitor.Unlock(); + } + + ~MonitorAutoUnlock() MOZ_SCOPED_UNLOCK_REACQUIRE() { mMonitor.Lock(); } + + private: + Monitor& mMonitor; +}; + +class MOZ_CAPABILITY("mutex") Mutex { + public: + Mutex() : mPRLock(PR_NewLock()) {} + + ~Mutex() { + PR_DestroyLock(mPRLock); + mPRLock = nullptr; + } + + void Lock() MOZ_CAPABILITY_ACQUIRE() { PR_Lock(mPRLock); } + + void Unlock() MOZ_CAPABILITY_RELEASE() { PR_Unlock(mPRLock); } + + private: + PRLock* mPRLock; +}; + +class MOZ_SCOPED_CAPABILITY AutoLock { + public: + explicit AutoLock(Mutex& aLock) MOZ_CAPABILITY_ACQUIRE(aLock) : mLock(aLock) { + mLock.Lock(); + } + + ~AutoLock() MOZ_CAPABILITY_RELEASE() { mLock.Unlock(); } + + private: + Mutex& mLock; +}; + +} // namespace IOInterposer +} // namespace mozilla + +#endif // xpcom_build_IOInterposerPrivate_h diff --git a/xpcom/build/LateWriteChecks.cpp b/xpcom/build/LateWriteChecks.cpp new file mode 100644 index 0000000000..e3bf73d73c --- /dev/null +++ b/xpcom/build/LateWriteChecks.cpp @@ -0,0 +1,263 @@ +/* -*- 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 + +#include "mozilla/IOInterposer.h" +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/ProcessedStack.h" +#include "mozilla/SHA1.h" +#include "mozilla/Scoped.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsLocalFile.h" +#include "nsPrintfCString.h" +#include "mozilla/StackWalk.h" +#include "prio.h" + +#ifdef XP_WIN +# define NS_SLASH "\\" +# include +# include +# include +# include +# include +# include +#else +# define NS_SLASH "/" +#endif + +#include "LateWriteChecks.h" + +/*************************** Auxiliary Declarations ***************************/ + +static MOZ_THREAD_LOCAL(int) tlsSuspendLateWriteChecks; + +bool SuspendingLateWriteChecksForCurrentThread() { + if (!tlsSuspendLateWriteChecks.init()) { + return true; + } + return tlsSuspendLateWriteChecks.get() > 0; +} + +// This a wrapper over a file descriptor that provides a Printf method and +// computes the sha1 of the data that passes through it. +class SHA1Stream { + public: + explicit SHA1Stream(FILE* aStream) : mFile(aStream) { + MozillaRegisterDebugFILE(mFile); + } + + void Printf(const char* aFormat, ...) MOZ_FORMAT_PRINTF(2, 3) { + MOZ_ASSERT(mFile); + va_list list; + va_start(list, aFormat); + nsAutoCString str; + str.AppendVprintf(aFormat, list); + va_end(list); + mSHA1.update(str.get(), str.Length()); + mozilla::Unused << fwrite(str.get(), 1, str.Length(), mFile); + } + void Finish(mozilla::SHA1Sum::Hash& aHash) { + int fd = fileno(mFile); + fflush(mFile); + MozillaUnRegisterDebugFD(fd); + fclose(mFile); + mSHA1.finish(aHash); + mFile = nullptr; + } + + private: + FILE* mFile; + mozilla::SHA1Sum mSHA1; +}; + +static void RecordStackWalker(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) { + std::vector* stack = + static_cast*>(aClosure); + stack->push_back(reinterpret_cast(aPC)); +} + +/**************************** Late-Write Observer ****************************/ + +/** + * An implementation of IOInterposeObserver to be registered with IOInterposer. + * This observer logs all writes as late writes. + */ +class LateWriteObserver final : public mozilla::IOInterposeObserver { + using char_type = mozilla::filesystem::Path::value_type; + + public: + explicit LateWriteObserver(const char_type* aProfileDirectory) + : mProfileDirectory(NS_xstrdup(aProfileDirectory)) {} + ~LateWriteObserver() { + free(mProfileDirectory); + mProfileDirectory = nullptr; + } + + void Observe( + mozilla::IOInterposeObserver::Observation& aObservation) override; + + private: + char_type* mProfileDirectory; +}; + +void LateWriteObserver::Observe( + mozilla::IOInterposeObserver::Observation& aOb) { + if (SuspendingLateWriteChecksForCurrentThread()) { + return; + } + +#ifdef DEBUG + MOZ_CRASH(); +#endif + + // If we can't record then abort + if (!mozilla::Telemetry::CanRecordExtended()) { + return; + } + + // Write the stack and loaded libraries to a file. We can get here + // concurrently from many writes, so we use multiple temporary files. + std::vector rawStack; + + MozStackWalk(RecordStackWalker, nullptr, /* maxFrames */ 0, &rawStack); + mozilla::Telemetry::ProcessedStack stack = + mozilla::Telemetry::GetStackAndModules(rawStack); + + nsTAutoString nameAux(mProfileDirectory); + nameAux.AppendLiteral(NS_SLASH "Telemetry.LateWriteTmpXXXXXX"); + char_type* name = nameAux.BeginWriting(); + + // We want the sha1 of the entire file, so please don't write to fd + // directly; use sha1Stream. + FILE* stream; +#ifdef XP_WIN + HANDLE hFile; + do { + // mkstemp isn't supported so keep trying until we get a file + _wmktemp_s(char16ptr_t(name), NS_strlen(name) + 1); + hFile = CreateFileW(char16ptr_t(name), GENERIC_WRITE, 0, nullptr, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, nullptr); + } while (GetLastError() == ERROR_FILE_EXISTS); + + if (hFile == INVALID_HANDLE_VALUE) { + MOZ_CRASH("Um, how did we get here?"); + } + + // http://support.microsoft.com/kb/139640 + int fd = _open_osfhandle((intptr_t)hFile, _O_APPEND); + if (fd == -1) { + MOZ_CRASH("Um, how did we get here?"); + } + + stream = _fdopen(fd, "w"); +#else + int fd = mkstemp(name); + if (fd == -1) { + MOZ_CRASH("mkstemp failed"); + } + stream = fdopen(fd, "w"); +#endif + + SHA1Stream sha1Stream(stream); + + size_t numModules = stack.GetNumModules(); + sha1Stream.Printf("%u\n", (unsigned)numModules); + for (size_t i = 0; i < numModules; ++i) { + mozilla::Telemetry::ProcessedStack::Module module = stack.GetModule(i); + sha1Stream.Printf("%s %s\n", module.mBreakpadId.get(), + NS_ConvertUTF16toUTF8(module.mName).get()); + } + + size_t numFrames = stack.GetStackSize(); + sha1Stream.Printf("%u\n", (unsigned)numFrames); + for (size_t i = 0; i < numFrames; ++i) { + const mozilla::Telemetry::ProcessedStack::Frame& frame = stack.GetFrame(i); + // NOTE: We write the offsets, while the atos tool expects a value with + // the virtual address added. For example, running otool -l on the the + // firefox binary shows + // cmd LC_SEGMENT_64 + // cmdsize 632 + // segname __TEXT + // vmaddr 0x0000000100000000 + // so to print the line matching the offset 123 one has to run + // atos -o firefox 0x100000123. + sha1Stream.Printf("%d %x\n", frame.mModIndex, (unsigned)frame.mOffset); + } + + mozilla::SHA1Sum::Hash sha1; + sha1Stream.Finish(sha1); + + // Note: These files should be deleted by telemetry once it reads them. If + // there were no telemetry runs by the time we shut down, we just add files + // to the existing ones instead of replacing them. Given that each of these + // files is a bug to be fixed, that is probably the right thing to do. + + // We append the sha1 of the contents to the file name. This provides a simple + // client side deduplication. + nsAutoString finalName(u"Telemetry.LateWriteFinal-"_ns); + for (int i = 0; i < 20; ++i) { + finalName.AppendPrintf("%02x", sha1[i]); + } + RefPtr file = new nsLocalFile(nameAux); + file->RenameTo(nullptr, finalName); +} + +/******************************* Setup/Teardown *******************************/ + +static mozilla::StaticAutoPtr sLateWriteObserver; + +namespace mozilla { + +void InitLateWriteChecks() { + nsCOMPtr mozFile; + NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mozFile)); + if (mozFile) { + PathString nativePath = mozFile->NativePath(); + if (nativePath.get()) { + sLateWriteObserver = new LateWriteObserver(nativePath.get()); + } + } +} + +void BeginLateWriteChecks() { + if (sLateWriteObserver) { + IOInterposer::Register(IOInterposeObserver::OpWriteFSync, + sLateWriteObserver); + } +} + +void StopLateWriteChecks() { + if (sLateWriteObserver) { + IOInterposer::Unregister(IOInterposeObserver::OpAll, sLateWriteObserver); + // Deallocation would not be thread-safe, and StopLateWriteChecks() is + // called at shutdown and only in special cases. + // sLateWriteObserver = nullptr; + } +} + +void PushSuspendLateWriteChecks() { + if (!tlsSuspendLateWriteChecks.init()) { + return; + } + tlsSuspendLateWriteChecks.set(tlsSuspendLateWriteChecks.get() + 1); +} + +void PopSuspendLateWriteChecks() { + if (!tlsSuspendLateWriteChecks.init()) { + return; + } + int current = tlsSuspendLateWriteChecks.get(); + MOZ_ASSERT(current > 0); + tlsSuspendLateWriteChecks.set(current - 1); +} + +} // namespace mozilla diff --git a/xpcom/build/LateWriteChecks.h b/xpcom/build/LateWriteChecks.h new file mode 100644 index 0000000000..6c9ee419f9 --- /dev/null +++ b/xpcom/build/LateWriteChecks.h @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +#ifndef mozilla_LateWriteChecks_h +#define mozilla_LateWriteChecks_h + +// This file, along with LateWriteChecks.cpp, serves to check for and report +// late writes. The idea is discover writes to the file system that happens +// during shutdown such that these maybe be moved forward and the process may be +// killed without waiting for static destructors. + +namespace mozilla { + +/** Different shutdown check modes */ +enum ShutdownChecksMode { + SCM_CRASH, /** Crash on shutdown check failure */ + SCM_RECORD, /** Record shutdown check violations */ + SCM_NOTHING /** Don't attempt any shutdown checks */ +}; + +/** + * Current shutdown check mode. + * This variable is defined and initialized in nsAppRunner.cpp + */ +extern ShutdownChecksMode gShutdownChecks; + +/** + * Allocate structures and acquire information from XPCOM necessary to do late + * write checks. This function must be invoked before BeginLateWriteChecks() + * and before XPCOM has stopped working. + */ +void InitLateWriteChecks(); + +/** + * Begin recording all writes as late-writes. This function should be called + * when all legitimate writes have occurred. This function does not rely on + * XPCOM as it is designed to be invoked during XPCOM shutdown. + * + * For late-write checks to work you must initialize one or more backends that + * reports IO through the IOInterposer API. PoisonIOInterposer would probably + * be the backend of choice in this case. + * + * Note: BeginLateWriteChecks() must have been invoked before this function. + */ +void BeginLateWriteChecks(); + +/** + * Stop recording all writes as late-writes, call this function when you want + * late-write checks to stop. I.e. exception handling, or the special case on + * Mac described in bug 826029. + */ +void StopLateWriteChecks(); + +/** + * Temporarily suspend late write checks for the current thread. This is useful + * if you're about to perform a write, but it would be fine if this write were + * interrupted or skipped during a fast shutdown. + */ +void PushSuspendLateWriteChecks(); + +/** + * Resume late write checks for the current thread, assuming an ancestor in the + * call stack hasn't also pushed a suspension. + */ +void PopSuspendLateWriteChecks(); + +class MOZ_RAII AutoSuspendLateWriteChecks { + public: + AutoSuspendLateWriteChecks() { PushSuspendLateWriteChecks(); } + ~AutoSuspendLateWriteChecks() { PopSuspendLateWriteChecks(); } +}; + +} // namespace mozilla + +#endif // mozilla_LateWriteChecks_h diff --git a/xpcom/build/MainThreadIOLogger.cpp b/xpcom/build/MainThreadIOLogger.cpp new file mode 100644 index 0000000000..8fe2d9eba5 --- /dev/null +++ b/xpcom/build/MainThreadIOLogger.cpp @@ -0,0 +1,206 @@ +/* -*- 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 "MainThreadIOLogger.h" + +#include "GeckoProfiler.h" +#include "IOInterposerPrivate.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsNativeCharsetUtils.h" +#include "nsThreadUtils.h" + +/** + * This code uses NSPR stuff and STL containers because it must be detached + * from leak checking code; this observer runs until the process terminates. + */ + +#include +#include +#include +#include + +namespace { + +struct ObservationWithStack { + explicit ObservationWithStack(mozilla::IOInterposeObserver::Observation& aObs, + ProfilerBacktrace* aStack) + : mObservation(aObs), mStack(aStack) { + aObs.Filename(mFilename); + } + + mozilla::IOInterposeObserver::Observation mObservation; + ProfilerBacktrace* mStack; + nsString mFilename; +}; + +class MainThreadIOLoggerImpl final : public mozilla::IOInterposeObserver { + public: + MainThreadIOLoggerImpl(); + ~MainThreadIOLoggerImpl(); + + bool Init(); + + void Observe(Observation& aObservation) override; + + private: + static void sIOThreadFunc(void* aArg); + void IOThreadFunc(); + + mozilla::TimeStamp mLogStartTime; + const char* mFileName; + PRThread* mIOThread; + mozilla::IOInterposer::Monitor mMonitor; + bool mShutdownRequired MOZ_GUARDED_BY(mMonitor); + std::vector mObservations MOZ_GUARDED_BY(mMonitor); +}; + +static mozilla::StaticAutoPtr sImpl; + +MainThreadIOLoggerImpl::MainThreadIOLoggerImpl() + : mFileName(nullptr), mIOThread(nullptr), mShutdownRequired(false) {} + +MainThreadIOLoggerImpl::~MainThreadIOLoggerImpl() { + if (!mIOThread) { + return; + } + { + // Scope for lock + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + mShutdownRequired = true; + mMonitor.Notify(); + } + PR_JoinThread(mIOThread); + mIOThread = nullptr; +} + +bool MainThreadIOLoggerImpl::Init() { + if (mFileName) { + // Already initialized + return true; + } + mFileName = PR_GetEnv("MOZ_MAIN_THREAD_IO_LOG"); + if (!mFileName) { + // Can't start + return false; + } + mIOThread = + PR_CreateThread(PR_USER_THREAD, &sIOThreadFunc, this, PR_PRIORITY_LOW, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + if (!mIOThread) { + return false; + } + return true; +} + +/* static */ +void MainThreadIOLoggerImpl::sIOThreadFunc(void* aArg) { + AUTO_PROFILER_REGISTER_THREAD("MainThreadIOLogger"); + + NS_SetCurrentThreadName("MainThreadIOLogger"); + MainThreadIOLoggerImpl* obj = static_cast(aArg); + obj->IOThreadFunc(); +} + +void MainThreadIOLoggerImpl::IOThreadFunc() { + PRFileDesc* fd = PR_Open(mFileName, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, + PR_IRUSR | PR_IWUSR | PR_IRGRP); + if (!fd) { + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + mShutdownRequired = true; + std::vector().swap(mObservations); + return; + } + mLogStartTime = mozilla::TimeStamp::Now(); + { + // Scope for lock + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + while (true) { + while (!mShutdownRequired && mObservations.empty()) { + mMonitor.Wait(); + } + if (mShutdownRequired) { + break; + } + // Pull events off the shared array onto a local one + std::vector observationsToWrite; + observationsToWrite.swap(mObservations); + + // Release the lock so that we're not holding anybody up during I/O + mozilla::IOInterposer::MonitorAutoUnlock unlock(mMonitor); + + // Now write the events. + for (auto i = observationsToWrite.begin(), e = observationsToWrite.end(); + i != e; ++i) { + if (i->mObservation.ObservedOperation() == OpNextStage) { + PR_fprintf( + fd, "%f,NEXT-STAGE\n", + (mozilla::TimeStamp::Now() - mLogStartTime).ToMilliseconds()); + continue; + } + double durationMs = i->mObservation.Duration().ToMilliseconds(); + nsAutoCString nativeFilename; + nativeFilename.AssignLiteral("(not available)"); + if (!i->mFilename.IsEmpty()) { + if (NS_FAILED(NS_CopyUnicodeToNative(i->mFilename, nativeFilename))) { + nativeFilename.AssignLiteral("(conversion failed)"); + } + } + // clang-format off + /** + * Format: + * Start Timestamp (Milliseconds), Operation, Duration (Milliseconds), Event Source, Filename + */ + // clang-format on + if (PR_fprintf( + fd, "%f,%s,%f,%s,%s\n", + (i->mObservation.Start() - mLogStartTime).ToMilliseconds(), + i->mObservation.ObservedOperationString(), durationMs, + i->mObservation.Reference(), nativeFilename.get()) > 0) { + // TODO: Write out the callstack + i->mStack = nullptr; + } + } + } + } + PR_Close(fd); +} + +void MainThreadIOLoggerImpl::Observe(Observation& aObservation) { + if (!mFileName || !IsMainThread()) { + return; + } + mozilla::IOInterposer::MonitorAutoLock lock(mMonitor); + if (mShutdownRequired) { + // The writer thread isn't running. Don't enqueue any more data. + return; + } + // Passing nullptr as aStack parameter for now + mObservations.push_back(ObservationWithStack(aObservation, nullptr)); + mMonitor.Notify(); +} + +} // namespace + +namespace mozilla { + +namespace MainThreadIOLogger { + +bool Init() { + auto impl = MakeUnique(); + if (!impl->Init()) { + return false; + } + sImpl = impl.release(); + IOInterposer::Register(IOInterposeObserver::OpAllWithStaging, sImpl); + return true; +} + +} // namespace MainThreadIOLogger + +} // namespace mozilla diff --git a/xpcom/build/MainThreadIOLogger.h b/xpcom/build/MainThreadIOLogger.h new file mode 100644 index 0000000000..cdf3b8728a --- /dev/null +++ b/xpcom/build/MainThreadIOLogger.h @@ -0,0 +1,18 @@ +/* -*- 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/. */ + +#ifndef mozilla_MainThreadIOLogger_h +#define mozilla_MainThreadIOLogger_h + +namespace mozilla { +namespace MainThreadIOLogger { + +bool Init(); + +} // namespace MainThreadIOLogger +} // namespace mozilla + +#endif // mozilla_MainThreadIOLogger_h diff --git a/xpcom/build/NSPRInterposer.cpp b/xpcom/build/NSPRInterposer.cpp new file mode 100644 index 0000000000..184c3793e7 --- /dev/null +++ b/xpcom/build/NSPRInterposer.cpp @@ -0,0 +1,207 @@ +/* -*- 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 "IOInterposer.h" +#include "NSPRInterposer.h" + +#include "prio.h" +#include "private/pprio.h" +#include "nsDebug.h" +#include "nscore.h" + +#include +#ifdef XP_MACOSX +# include +#else +# include "prprf.h" +# include +#endif + +namespace { + +/* Original IO methods */ +PRCloseFN sCloseFn = nullptr; +PRReadFN sReadFn = nullptr; +PRWriteFN sWriteFn = nullptr; +PRFsyncFN sFSyncFn = nullptr; +PRFileInfoFN sFileInfoFn = nullptr; +PRFileInfo64FN sFileInfo64Fn = nullptr; + +static int32_t GetPathFromFd(int32_t aFd, char* aBuf, size_t aBufSize) { +#ifdef XP_MACOSX + NS_ASSERTION(aBufSize >= MAXPATHLEN, + "aBufSize should be a least MAXPATHLEN long"); + + return fcntl(aFd, F_GETPATH, aBuf); +#else + char procPath[32]; + if (PR_snprintf(procPath, sizeof(procPath), "/proc/self/fd/%i", aFd) == + (PRUint32)-1) { + return -1; + } + + int32_t ret = readlink(procPath, aBuf, aBufSize - 1); + if (ret > -1) { + aBuf[ret] = '\0'; + } + + return ret; +#endif +} + +/** + * RAII class for timing the duration of an NSPR I/O call and reporting the + * result to the mozilla::IOInterposeObserver API. + */ +class NSPRIOAutoObservation : public mozilla::IOInterposeObserver::Observation { + public: + explicit NSPRIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, + PRFileDesc* aFd) + : mozilla::IOInterposeObserver::Observation(aOp, "NSPRIOInterposer") { + char filename[MAXPATHLEN]; + if (mShouldReport && aFd && + GetPathFromFd(PR_FileDesc2NativeHandle(aFd), filename, + sizeof(filename)) != -1) { + CopyUTF8toUTF16(mozilla::MakeStringSpan(filename), mFilename); + } else { + mFilename.Truncate(); + } + } + + void Filename(nsAString& aFilename) override { aFilename = mFilename; } + + ~NSPRIOAutoObservation() override { Report(); } + + private: + nsString mFilename; +}; + +PRStatus PR_CALLBACK interposedClose(PRFileDesc* aFd) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sCloseFn, "NSPR IO Interposing: sCloseFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpClose, aFd); + return sCloseFn(aFd); +} + +int32_t PR_CALLBACK interposedRead(PRFileDesc* aFd, void* aBuf, int32_t aAmt) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sReadFn, "NSPR IO Interposing: sReadFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFd); + return sReadFn(aFd, aBuf, aAmt); +} + +int32_t PR_CALLBACK interposedWrite(PRFileDesc* aFd, const void* aBuf, + int32_t aAmt) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sWriteFn, "NSPR IO Interposing: sWriteFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd); + return sWriteFn(aFd, aBuf, aAmt); +} + +PRStatus PR_CALLBACK interposedFSync(PRFileDesc* aFd) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sFSyncFn, "NSPR IO Interposing: sFSyncFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFd); + return sFSyncFn(aFd); +} + +PRStatus PR_CALLBACK interposedFileInfo(PRFileDesc* aFd, PRFileInfo* aInfo) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sFileInfoFn, "NSPR IO Interposing: sFileInfoFn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, aFd); + return sFileInfoFn(aFd, aInfo); +} + +PRStatus PR_CALLBACK interposedFileInfo64(PRFileDesc* aFd, + PRFileInfo64* aInfo) { + // If we don't have a valid original function pointer something is very wrong. + NS_ASSERTION(sFileInfo64Fn, "NSPR IO Interposing: sFileInfo64Fn is NULL"); + + NSPRIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, aFd); + return sFileInfo64Fn(aFd, aInfo); +} + +} // namespace + +namespace mozilla { + +void InitNSPRIOInterposing() { + // Check that we have not interposed any of the IO methods before + MOZ_ASSERT(!sCloseFn && !sReadFn && !sWriteFn && !sFSyncFn && !sFileInfoFn && + !sFileInfo64Fn); + + // We can't actually use this assertion because we initialize this code + // before XPCOM is initialized, so NS_IsMainThread() always returns false. + // MOZ_ASSERT(NS_IsMainThread()); + + // Get IO methods from NSPR and const cast the structure so we can modify it. + PRIOMethods* methods = const_cast(PR_GetFileMethods()); + + // Something is badly wrong if we don't get IO methods... However, we don't + // want to crash over that in non-debug builds. This is unlikely to happen + // so an assert is enough, no need to report it to the caller. + MOZ_ASSERT(methods); + if (!methods) { + return; + } + + // Store original functions + sCloseFn = methods->close; + sReadFn = methods->read; + sWriteFn = methods->write; + sFSyncFn = methods->fsync; + sFileInfoFn = methods->fileInfo; + sFileInfo64Fn = methods->fileInfo64; + + // Overwrite with our interposed functions + methods->close = &interposedClose; + methods->read = &interposedRead; + methods->write = &interposedWrite; + methods->fsync = &interposedFSync; + methods->fileInfo = &interposedFileInfo; + methods->fileInfo64 = &interposedFileInfo64; +} + +void ClearNSPRIOInterposing() { + // If we have already cleared IO interposing, or not initialized it this is + // actually bad. + MOZ_ASSERT(sCloseFn && sReadFn && sWriteFn && sFSyncFn && sFileInfoFn && + sFileInfo64Fn); + + // Get IO methods from NSPR and const cast the structure so we can modify it. + PRIOMethods* methods = const_cast(PR_GetFileMethods()); + + // Something is badly wrong if we don't get IO methods... However, we don't + // want to crash over that in non-debug builds. This is unlikely to happen + // so an assert is enough, no need to report it to the caller. + MOZ_ASSERT(methods); + if (!methods) { + return; + } + + // Restore original functions + methods->close = sCloseFn; + methods->read = sReadFn; + methods->write = sWriteFn; + methods->fsync = sFSyncFn; + methods->fileInfo = sFileInfoFn; + methods->fileInfo64 = sFileInfo64Fn; + + // Forget about original functions + sCloseFn = nullptr; + sReadFn = nullptr; + sWriteFn = nullptr; + sFSyncFn = nullptr; + sFileInfoFn = nullptr; + sFileInfo64Fn = nullptr; +} + +} // namespace mozilla diff --git a/xpcom/build/NSPRInterposer.h b/xpcom/build/NSPRInterposer.h new file mode 100644 index 0000000000..3f811df60c --- /dev/null +++ b/xpcom/build/NSPRInterposer.h @@ -0,0 +1,28 @@ +/* -*- 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/. */ + +#ifndef NSPRINTERPOSER_H_ +#define NSPRINTERPOSER_H_ + +namespace mozilla { + +/** + * Initialize IO interposing for NSPR. This will report NSPR read, writes and + * fsyncs to the IOInterposerObserver. It is only safe to call this from the + * main-thread when no other threads are running. + */ +void InitNSPRIOInterposing(); + +/** + * Removes interception of NSPR IO methods as setup by InitNSPRIOInterposing. + * Note, that it is only safe to call this on the main-thread when all other + * threads have stopped. Which is typically the case at shutdown. + */ +void ClearNSPRIOInterposing(); + +} // namespace mozilla + +#endif // NSPRINTERPOSER_H_ diff --git a/xpcom/build/Omnijar.cpp b/xpcom/build/Omnijar.cpp new file mode 100644 index 0000000000..2c6e3eb478 --- /dev/null +++ b/xpcom/build/Omnijar.cpp @@ -0,0 +1,198 @@ +/* -*- 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 "Omnijar.h" + +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsZipArchive.h" +#include "nsNetUtil.h" + +namespace mozilla { + +StaticRefPtr Omnijar::sPath[2]; +StaticRefPtr Omnijar::sReader[2]; +StaticRefPtr Omnijar::sOuterReader[2]; +bool Omnijar::sInitialized = false; +bool Omnijar::sIsUnified = false; + +static const char* sProp[2] = {NS_GRE_DIR, NS_XPCOM_CURRENT_PROCESS_DIR}; + +#define SPROP(Type) ((Type == mozilla::Omnijar::GRE) ? sProp[GRE] : sProp[APP]) + +void Omnijar::CleanUpOne(Type aType) { + if (sReader[aType]) { + sReader[aType] = nullptr; + } + if (sOuterReader[aType]) { + sOuterReader[aType] = nullptr; + } + sPath[aType] = nullptr; +} + +void Omnijar::InitOne(nsIFile* aPath, Type aType) { + nsCOMPtr file; + if (aPath) { + file = aPath; + } else { + nsCOMPtr dir; + nsDirectoryService::gService->Get(SPROP(aType), NS_GET_IID(nsIFile), + getter_AddRefs(dir)); + constexpr auto kOmnijarName = nsLiteralCString{MOZ_STRINGIFY(OMNIJAR_NAME)}; + if (NS_FAILED(dir->Clone(getter_AddRefs(file))) || + NS_FAILED(file->AppendNative(kOmnijarName))) { + return; + } + } + bool isFile; + if (NS_FAILED(file->IsFile(&isFile)) || !isFile) { + // If we're not using an omni.jar for GRE, and we don't have an + // omni.jar for APP, check if both directories are the same. + if ((aType == APP) && (!sPath[GRE])) { + nsCOMPtr greDir, appDir; + bool equals; + nsDirectoryService::gService->Get(sProp[GRE], NS_GET_IID(nsIFile), + getter_AddRefs(greDir)); + nsDirectoryService::gService->Get(sProp[APP], NS_GET_IID(nsIFile), + getter_AddRefs(appDir)); + if (NS_SUCCEEDED(greDir->Equals(appDir, &equals)) && equals) { + sIsUnified = true; + } + } + return; + } + + bool equals; + if ((aType == APP) && (sPath[GRE]) && + NS_SUCCEEDED(sPath[GRE]->Equals(file, &equals)) && equals) { + // If we're using omni.jar on both GRE and APP and their path + // is the same, we're in the unified case. + sIsUnified = true; + return; + } + + RefPtr zipReader = nsZipArchive::OpenArchive(file); + if (!zipReader) { + return; + } + + RefPtr outerReader; + RefPtr handle; + if (NS_SUCCEEDED(nsZipHandle::Init(zipReader, MOZ_STRINGIFY(OMNIJAR_NAME), + getter_AddRefs(handle)))) { + outerReader = zipReader; + zipReader = nsZipArchive::OpenArchive(handle); + if (!zipReader) { + return; + } + } + + CleanUpOne(aType); + sReader[aType] = zipReader; + sOuterReader[aType] = outerReader; + sPath[aType] = file; +} + +void Omnijar::Init(nsIFile* aGrePath, nsIFile* aAppPath) { + InitOne(aGrePath, GRE); + InitOne(aAppPath, APP); + sInitialized = true; +} + +void Omnijar::CleanUp() { + CleanUpOne(GRE); + CleanUpOne(APP); + sInitialized = false; +} + +already_AddRefed Omnijar::GetReader(nsIFile* aPath) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + + bool equals; + nsresult rv; + + if (sPath[GRE]) { + rv = sPath[GRE]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(GRE) ? GetOuterReader(GRE) : GetReader(GRE); + } + } + if (sPath[APP]) { + rv = sPath[APP]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(APP) ? GetOuterReader(APP) : GetReader(APP); + } + } + return nullptr; +} + +already_AddRefed Omnijar::GetInnerReader( + nsIFile* aPath, const nsACString& aEntry) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + + if (!aEntry.EqualsLiteral(MOZ_STRINGIFY(OMNIJAR_NAME))) { + return nullptr; + } + + bool equals; + nsresult rv; + + if (sPath[GRE]) { + rv = sPath[GRE]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(GRE) ? GetReader(GRE) : nullptr; + } + } + if (sPath[APP]) { + rv = sPath[APP]->Equals(aPath, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return IsNested(APP) ? GetReader(APP) : nullptr; + } + } + return nullptr; +} + +nsresult Omnijar::GetURIString(Type aType, nsACString& aResult) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + + aResult.Truncate(); + + // Return an empty string for APP in the unified case. + if ((aType == APP) && sIsUnified) { + return NS_OK; + } + + nsAutoCString omniJarSpec; + if (sPath[aType]) { + nsresult rv = NS_GetURLSpecFromActualFile(sPath[aType], omniJarSpec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + aResult = "jar:"; + if (IsNested(aType)) { + aResult += "jar:"; + } + aResult += omniJarSpec; + aResult += "!"; + if (IsNested(aType)) { + aResult += "/" MOZ_STRINGIFY(OMNIJAR_NAME) "!"; + } + } else { + nsCOMPtr dir; + nsDirectoryService::gService->Get(SPROP(aType), NS_GET_IID(nsIFile), + getter_AddRefs(dir)); + nsresult rv = NS_GetURLSpecFromActualFile(dir, aResult); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + aResult += "/"; + return NS_OK; +} + +} /* namespace mozilla */ diff --git a/xpcom/build/Omnijar.h b/xpcom/build/Omnijar.h new file mode 100644 index 0000000000..1609a3199b --- /dev/null +++ b/xpcom/build/Omnijar.h @@ -0,0 +1,171 @@ +/* -*- 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/. */ + +#ifndef mozilla_Omnijar_h +#define mozilla_Omnijar_h + +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIFile.h" +#include "nsZipArchive.h" + +#include "mozilla/StaticPtr.h" + +namespace mozilla { + +class Omnijar { + private: + /** + * Store an nsIFile for an omni.jar. We can store two paths here, one + * for GRE (corresponding to resource://gre/) and one for APP + * (corresponding to resource:/// and resource://app/), but only + * store one when both point to the same location (unified). + */ + static StaticRefPtr sPath[2]; + + /** + * Cached nsZipArchives for the corresponding sPath + */ + static StaticRefPtr sReader[2]; + + /** + * Cached nsZipArchives for the outer jar, when using nested jars. + * Otherwise nullptr. + */ + static StaticRefPtr sOuterReader[2]; + + /** + * Has Omnijar::Init() been called? + */ + static bool sInitialized; + + /** + * Is using unified GRE/APP jar? + */ + static bool sIsUnified; + + public: + enum Type { GRE = 0, APP = 1 }; + + private: + /** + * Returns whether we are using nested jars. + */ + static inline bool IsNested(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + return !!sOuterReader[aType]; + } + + /** + * Returns a nsZipArchive pointer for the outer jar file when using nested + * jars. Returns nullptr in the same cases GetPath() would, or if not using + * nested jars. + */ + static inline already_AddRefed GetOuterReader(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + RefPtr reader = sOuterReader[aType].get(); + return reader.forget(); + } + + public: + /** + * Returns whether SetBase has been called at least once with + * a valid nsIFile + */ + static inline bool IsInitialized() { return sInitialized; } + + /** + * Initializes the Omnijar API with the given directory or file for GRE and + * APP. Each of the paths given can be: + * - a file path, pointing to the omnijar file, + * - a directory path, pointing to a directory containing an "omni.jar" file, + * - nullptr for autodetection of an "omni.jar" file. + */ + static void Init(nsIFile* aGrePath = nullptr, nsIFile* aAppPath = nullptr); + + /** + * Cleans up the Omnijar API + */ + static void CleanUp(); + + /** + * Returns an nsIFile pointing to the omni.jar file for GRE or APP. + * Returns nullptr when there is no corresponding omni.jar. + * Also returns nullptr for APP in the unified case. + */ + static inline already_AddRefed GetPath(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + nsCOMPtr path = sPath[aType].get(); + return path.forget(); + } + + /** + * Returns whether GRE or APP use an omni.jar. Returns PR_False for + * APP when using an omni.jar in the unified case. + */ + static inline bool HasOmnijar(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + return !!sPath[aType]; + } + + /** + * Returns a nsZipArchive pointer for the omni.jar file for GRE or + * APP. Returns nullptr in the same cases GetPath() would. + */ + static inline already_AddRefed GetReader(Type aType) { + MOZ_ASSERT(IsInitialized(), "Omnijar not initialized"); + RefPtr reader = sReader[aType].get(); + return reader.forget(); + } + + /** + * Returns a nsZipArchive pointer for the given path IAOI the given + * path is the omni.jar for either GRE or APP. + */ + static already_AddRefed GetReader(nsIFile* aPath); + + /** + * In the case of a nested omnijar, this returns the inner reader for the + * omnijar if aPath points to the outer archive and aEntry is the omnijar + * entry name. Returns null otherwise. + * In concrete terms: On Android the omnijar is nested inside the apk archive. + * GetReader("path/to.apk") returns the outer reader and GetInnerReader( + * "path/to.apk", "assets/omni.ja") returns the inner reader. + */ + static already_AddRefed GetInnerReader( + nsIFile* aPath, const nsACString& aEntry); + + /** + * Returns the URI string corresponding to the omni.jar or directory + * for GRE or APP. i.e. jar:/path/to/omni.jar!/ for omni.jar and + * /path/to/base/dir/ otherwise. Returns an empty string for APP in + * the unified case. + * The returned URI is guaranteed to end with a slash. + */ + static nsresult GetURIString(Type aType, nsACString& aResult); + + private: + /** + * Used internally, respectively by Init() and CleanUp() + */ + static void InitOne(nsIFile* aPath, Type aType); + static void CleanUpOne(Type aType); +}; /* class Omnijar */ + +/** + * Returns whether or not the currently running build is an unpackaged + * developer build. This check is implemented by looking for omni.ja in the + * the obj/dist dir. We use this routine to detect when the build dir will + * use symlinks to the repo and object dir. + */ +inline bool IsPackagedBuild() { + return Omnijar::HasOmnijar(mozilla::Omnijar::GRE); +} + +} /* namespace mozilla */ + +#endif /* mozilla_Omnijar_h */ diff --git a/xpcom/build/PoisonIOInterposer.h b/xpcom/build/PoisonIOInterposer.h new file mode 100644 index 0000000000..20adeb835b --- /dev/null +++ b/xpcom/build/PoisonIOInterposer.h @@ -0,0 +1,93 @@ +/* -*- 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/. */ + +#ifndef mozilla_PoisonIOInterposer_h +#define mozilla_PoisonIOInterposer_h + +#include "mozilla/Types.h" +#include + +MOZ_BEGIN_EXTERN_C + +/** Register file handle to be ignored by poisoning IO interposer. This function + * and the corresponding UnRegister function are necessary for exchange of + * handles between binaries not using the same CRT on Windows (which happens + * when one of them links the static CRT). In such cases, giving file + * descriptors or FILEs + * doesn't work because _get_osfhandle fails with "invalid parameter". */ +void MozillaRegisterDebugHandle(intptr_t aHandle); + +/** Register file descriptor to be ignored by poisoning IO interposer */ +void MozillaRegisterDebugFD(int aFd); + +/** Register file to be ignored by poisoning IO interposer */ +void MozillaRegisterDebugFILE(FILE* aFile); + +/** Unregister file handle from being ignored by poisoning IO interposer */ +void MozillaUnRegisterDebugHandle(intptr_t aHandle); + +/** Unregister file descriptor from being ignored by poisoning IO interposer */ +void MozillaUnRegisterDebugFD(int aFd); + +/** Unregister file from being ignored by poisoning IO interposer */ +void MozillaUnRegisterDebugFILE(FILE* aFile); + +MOZ_END_EXTERN_C + +#if defined(XP_MACOSX) || defined(XP_WIN) + +# ifdef __cplusplus +namespace mozilla { + +/** + * Check if a file is registered as a debug file. + */ +bool IsDebugFile(intptr_t aFileID); + +/** + * Initialize IO poisoning, this is only safe to do on the main-thread when no + * other threads are running. + * + * Please, note that this probably has performance implications as all + */ +void InitPoisonIOInterposer(); + +# ifdef XP_MACOSX +/** + * Check that writes are dirty before reporting I/O (Mac OS X only) + * This is necessary for late-write checks on Mac OS X, but reading the buffer + * from file to see if we're writing dirty bits is expensive, so we don't want + * to do this for everything else that uses + */ +void OnlyReportDirtyWrites(); +# endif /* XP_MACOSX */ + +/** + * Clear IO poisoning, this is only safe to do on the main-thread when no other + * threads are running. + * Never called! See bug 1647107. + */ +void ClearPoisonIOInterposer(); + +} // namespace mozilla +# endif /* __cplusplus */ + +#else /* defined(XP_MACOSX) || defined(XP_WIN) */ + +# ifdef __cplusplus +namespace mozilla { +inline bool IsDebugFile(intptr_t aFileID) { return true; } +inline void InitPoisonIOInterposer() {} +inline void ClearPoisonIOInterposer() {} +# ifdef XP_MACOSX +inline void OnlyReportDirtyWrites() {} +# endif /* XP_MACOSX */ +} // namespace mozilla +# endif /* __cplusplus */ + +#endif /* XP_WIN || XP_MACOSX */ + +#endif // mozilla_PoisonIOInterposer_h diff --git a/xpcom/build/PoisonIOInterposerBase.cpp b/xpcom/build/PoisonIOInterposerBase.cpp new file mode 100644 index 0000000000..8add6740da --- /dev/null +++ b/xpcom/build/PoisonIOInterposerBase.cpp @@ -0,0 +1,268 @@ +/* -*- 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 + +#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 +inline mozilla::Maybe 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 FileDescriptorToHandle(int aFd) { + return mozilla::Some(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 { + 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(getDebugFileIDsLock()) { + PR_Lock(get()); + } +}; + +PRLock* DebugFilesAutoLock::Lock; + +// The ChunkedList 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 +class ChunkedList { + struct ListChunk { + static const size_t kLength = + (chunk_size - sizeof(ListChunk*)) / sizeof(mozilla::Atomic); + + mozilla::Atomic mElements[kLength]; + mozilla::UniquePtr mNext; + + ListChunk() : mNext(nullptr) {} + }; + + ListChunk mList; + mozilla::Atomic 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 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 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 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 diff --git a/xpcom/build/PoisonIOInterposerMac.cpp b/xpcom/build/PoisonIOInterposerMac.cpp new file mode 100644 index 0000000000..21eca58757 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerMac.cpp @@ -0,0 +1,348 @@ +/* -*- 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 "PoisonIOInterposer.h" +// Disabled until bug 1658385 is fixed. +#ifndef __aarch64__ +# include "mach_override.h" +#endif + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Mutex.h" +#include "mozilla/ProcessedStack.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsPrintfCString.h" +#include "mozilla/StackWalk.h" +#include "nsTraceRefcnt.h" +#include "prio.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef MOZ_REPLACE_MALLOC +# include "replace_malloc_bridge.h" +#endif + +namespace { + +// Bit tracking if poisoned writes are enabled +static bool sIsEnabled = false; + +// Check if writes are dirty before reporting IO +static bool sOnlyReportDirtyWrites = false; + +// Routines for write validation +bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount); +bool IsIPCWrite(int aFd, const struct stat& aBuf); + +/******************************** IO AutoTimer ********************************/ + +/** + * RAII class for timing the duration of an I/O call and reporting the result + * to the mozilla::IOInterposeObserver API. + */ +class MacIOAutoObservation : public mozilla::IOInterposeObserver::Observation { + public: + MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd) + : mozilla::IOInterposeObserver::Observation( + aOp, sReference, sIsEnabled && !mozilla::IsDebugFile(aFd)), + mFd(aFd), + mHasQueriedFilename(false) {} + + MacIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, int aFd, + const void* aBuf, size_t aCount) + : mozilla::IOInterposeObserver::Observation( + aOp, sReference, + sIsEnabled && !mozilla::IsDebugFile(aFd) && + IsValidWrite(aFd, aBuf, aCount)), + mFd(aFd), + mHasQueriedFilename(false) {} + + // Custom implementation of + // mozilla::IOInterposeObserver::Observation::Filename + void Filename(nsAString& aFilename) override; + + ~MacIOAutoObservation() { Report(); } + + private: + int mFd; + bool mHasQueriedFilename; + nsString mFilename; + static const char* sReference; +}; + +const char* MacIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +void MacIOAutoObservation::Filename(nsAString& aFilename) { + // If mHasQueriedFilename is true, then we already have it + if (mHasQueriedFilename) { + aFilename = mFilename; + return; + } + + char filename[MAXPATHLEN]; + if (fcntl(mFd, F_GETPATH, filename) != -1) { + CopyUTF8toUTF16(filename, mFilename); + } else { + mFilename.Truncate(); + } + mHasQueriedFilename = true; + + aFilename = mFilename; +} + +/****************************** Write Validation ******************************/ + +// We want to detect "actual" writes, not IPC. Some IPC mechanisms are +// implemented with file descriptors, so filter them out. +bool IsIPCWrite(int aFd, const struct stat& aBuf) { + if ((aBuf.st_mode & S_IFMT) == S_IFIFO) { + return true; + } + + if ((aBuf.st_mode & S_IFMT) != S_IFSOCK) { + return false; + } + + sockaddr_storage address; + socklen_t len = sizeof(address); + if (getsockname(aFd, (sockaddr*)&address, &len) != 0) { + return true; // Ignore the aFd if we can't find what it is. + } + + return address.ss_family == AF_UNIX; +} + +// We want to report actual disk IO not things that don't move bits on the disk +bool IsValidWrite(int aFd, const void* aWbuf, size_t aCount) { + // Ignore writes of zero bytes, Firefox does some during shutdown. + if (aCount == 0) { + return false; + } + + { + struct stat buf; + int rv = fstat(aFd, &buf); + if (rv != 0) { + return true; + } + + if (IsIPCWrite(aFd, buf)) { + return false; + } + } + + // For writev we pass a nullptr aWbuf. We should only get here from + // dbm, and it uses write, so assert that we have aWbuf. + if (!aWbuf) { + return true; + } + + // Break, here if we're allowed to report non-dirty writes + if (!sOnlyReportDirtyWrites) { + return true; + } + + // As a really bad hack, accept writes that don't change the on disk + // content. This is needed because dbm doesn't keep track of dirty bits + // and can end up writing the same data to disk twice. Once when the + // user (nss) asks it to sync and once when closing the database. + auto wbuf2 = mozilla::MakeUniqueFallible(aCount); + if (!wbuf2) { + return true; + } + off_t pos = lseek(aFd, 0, SEEK_CUR); + if (pos == -1) { + return true; + } + ssize_t r = read(aFd, wbuf2.get(), aCount); + if (r < 0 || (size_t)r != aCount) { + return true; + } + int cmp = memcmp(aWbuf, wbuf2.get(), aCount); + if (cmp != 0) { + return true; + } + off_t pos2 = lseek(aFd, pos, SEEK_SET); + if (pos2 != pos) { + return true; + } + + // Otherwise this is not a valid write + return false; +} + +/*************************** Function Interception ***************************/ + +/** Structure for declaration of function override */ +struct FuncData { + const char* Name; // Name of the function for the ones we use dlsym + const void* Wrapper; // The function that we will replace 'Function' with + void* Function; // The function that will be replaced with 'Wrapper' + void* Buffer; // Will point to the jump buffer that lets us call + // 'Function' after it has been replaced. +}; + +// Wrap aio_write. We have not seen it before, so just assert/report it. +typedef ssize_t (*aio_write_t)(struct aiocb* aAioCbp); +ssize_t wrap_aio_write(struct aiocb* aAioCbp); +FuncData aio_write_data = {0, (void*)wrap_aio_write, (void*)aio_write}; +ssize_t wrap_aio_write(struct aiocb* aAioCbp) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, + aAioCbp->aio_fildes); + + aio_write_t old_write = (aio_write_t)aio_write_data.Buffer; + return old_write(aAioCbp); +} + +// Wrap pwrite-like functions. +// We have not seen them before, so just assert/report it. +typedef ssize_t (*pwrite_t)(int aFd, const void* buf, size_t aNumBytes, + off_t aOffset); +template +ssize_t wrap_pwrite_temp(int aFd, const void* aBuf, size_t aNumBytes, + off_t aOffset) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd); + pwrite_t old_write = (pwrite_t)foo.Buffer; + return old_write(aFd, aBuf, aNumBytes, aOffset); +} + +// Define a FuncData for a pwrite-like functions. +#define DEFINE_PWRITE_DATA(X, NAME) \ + FuncData X##_data = {NAME, (void*)wrap_pwrite_temp}; + +// This exists everywhere. +DEFINE_PWRITE_DATA(pwrite, "pwrite") +// These exist on 32 bit OS X +DEFINE_PWRITE_DATA(pwrite_NOCANCEL_UNIX2003, "pwrite$NOCANCEL$UNIX2003"); +DEFINE_PWRITE_DATA(pwrite_UNIX2003, "pwrite$UNIX2003"); +// This exists on 64 bit OS X +DEFINE_PWRITE_DATA(pwrite_NOCANCEL, "pwrite$NOCANCEL"); + +typedef ssize_t (*writev_t)(int aFd, const struct iovec* aIov, int aIovCount); +template +ssize_t wrap_writev_temp(int aFd, const struct iovec* aIov, int aIovCount) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd, + nullptr, aIovCount); + writev_t old_write = (writev_t)foo.Buffer; + return old_write(aFd, aIov, aIovCount); +} + +// Define a FuncData for a writev-like functions. +#define DEFINE_WRITEV_DATA(X, NAME) \ + FuncData X##_data = {NAME, (void*)wrap_writev_temp}; + +// This exists everywhere. +DEFINE_WRITEV_DATA(writev, "writev"); +// These exist on 32 bit OS X +DEFINE_WRITEV_DATA(writev_NOCANCEL_UNIX2003, "writev$NOCANCEL$UNIX2003"); +DEFINE_WRITEV_DATA(writev_UNIX2003, "writev$UNIX2003"); +// This exists on 64 bit OS X +DEFINE_WRITEV_DATA(writev_NOCANCEL, "writev$NOCANCEL"); + +typedef ssize_t (*write_t)(int aFd, const void* aBuf, size_t aCount); +template +ssize_t wrap_write_temp(int aFd, const void* aBuf, size_t aCount) { + MacIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFd, aBuf, + aCount); + write_t old_write = (write_t)foo.Buffer; + return old_write(aFd, aBuf, aCount); +} + +// Define a FuncData for a write-like functions. +#define DEFINE_WRITE_DATA(X, NAME) \ + FuncData X##_data = {NAME, (void*)wrap_write_temp}; + +// This exists everywhere. +DEFINE_WRITE_DATA(write, "write"); +// These exist on 32 bit OS X +DEFINE_WRITE_DATA(write_NOCANCEL_UNIX2003, "write$NOCANCEL$UNIX2003"); +DEFINE_WRITE_DATA(write_UNIX2003, "write$UNIX2003"); +// This exists on 64 bit OS X +DEFINE_WRITE_DATA(write_NOCANCEL, "write$NOCANCEL"); + +FuncData* Functions[] = {&aio_write_data, + + &pwrite_data, &pwrite_NOCANCEL_UNIX2003_data, + &pwrite_UNIX2003_data, &pwrite_NOCANCEL_data, + + &write_data, &write_NOCANCEL_UNIX2003_data, + &write_UNIX2003_data, &write_NOCANCEL_data, + + &writev_data, &writev_NOCANCEL_UNIX2003_data, + &writev_UNIX2003_data, &writev_NOCANCEL_data}; + +const int NumFunctions = mozilla::ArrayLength(Functions); + +} // namespace + +/******************************** IO Poisoning ********************************/ + +namespace mozilla { + +void InitPoisonIOInterposer() { + // Enable reporting from poisoned write methods + sIsEnabled = true; + + // Make sure we only poison writes once! + static bool WritesArePoisoned = false; + if (WritesArePoisoned) { + return; + } + WritesArePoisoned = true; + + // stdout and stderr are OK. + MozillaRegisterDebugFD(1); + MozillaRegisterDebugFD(2); + +#ifdef MOZ_REPLACE_MALLOC + // The contract with InitDebugFd is that the given registry can be used + // at any moment, so the instance needs to persist longer than the scope + // of this functions. + static DebugFdRegistry registry; + ReplaceMalloc::InitDebugFd(registry); +#endif + + for (int i = 0; i < NumFunctions; ++i) { + FuncData* d = Functions[i]; + if (!d->Function) { + d->Function = dlsym(RTLD_DEFAULT, d->Name); + } + if (!d->Function) { + continue; + } +#ifndef __aarch64__ + DebugOnly t = + mach_override_ptr(d->Function, d->Wrapper, &d->Buffer); + MOZ_ASSERT(t == err_none); +#endif + } +} + +void OnlyReportDirtyWrites() { sOnlyReportDirtyWrites = true; } + +// Never called! See bug 1647107. +void ClearPoisonIOInterposer() { + // Not sure how or if we can unpoison the functions. Would be nice, but no + // worries we won't need to do this anyway. + sIsEnabled = false; +} + +} // namespace mozilla diff --git a/xpcom/build/PoisonIOInterposerStub.cpp b/xpcom/build/PoisonIOInterposerStub.cpp new file mode 100644 index 0000000000..1f3daa3539 --- /dev/null +++ b/xpcom/build/PoisonIOInterposerStub.cpp @@ -0,0 +1,16 @@ +/* -*- 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 + +extern "C" { + +void MozillaRegisterDebugFD(int aFd) {} +void MozillaRegisterDebugFILE(FILE* aFile) {} +void MozillaUnRegisterDebugFD(int aFd) {} +void MozillaUnRegisterDebugFILE(FILE* aFile) {} + +} // extern "C" diff --git a/xpcom/build/PoisonIOInterposerWin.cpp b/xpcom/build/PoisonIOInterposerWin.cpp new file mode 100644 index 0000000000..0d78e073ca --- /dev/null +++ b/xpcom/build/PoisonIOInterposerWin.cpp @@ -0,0 +1,508 @@ +/* -*- 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 "PoisonIOInterposer.h" + +#include +#include +#include + +#include +#include +#include + +#include "mozilla/Assertions.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/FileUtilsWin.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Mutex.h" +#include "mozilla/NativeNt.h" +#include "mozilla/SmallArrayLRUCache.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" +#include "nsWindowsDllInterceptor.h" + +#ifdef MOZ_REPLACE_MALLOC +# include "replace_malloc_bridge.h" +#endif + +namespace { + +// Keep track of poisoned state. Notice that there is no reason to lock access +// to this variable as it's only changed in InitPoisonIOInterposer and +// ClearPoisonIOInterposer which may only be called on the main-thread when no +// other threads are running. +static bool sIOPoisoned = false; + +/************************ Internal NT API Declarations ************************/ + +/* + * Function pointer declaration for internal NT routine to create/open files. + * For documentation on the NtCreateFile routine, see MSDN. + */ +typedef NTSTATUS(NTAPI* NtCreateFileFn)( + PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess, + POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock, + PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess, + ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer, + ULONG aEaLength); + +/** + * Function pointer declaration for internal NT routine to read data from file. + * For documentation on the NtReadFile routine, see ZwReadFile on MSDN. + */ +typedef NTSTATUS(NTAPI* NtReadFileFn)(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, PVOID aBuffer, + ULONG aLength, PLARGE_INTEGER aOffset, + PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to read data from file. + * No documentation exists, see wine sources for details. + */ +typedef NTSTATUS(NTAPI* NtReadFileScatterFn)( + HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to write data to file. + * For documentation on the NtWriteFile routine, see ZwWriteFile on MSDN. + */ +typedef NTSTATUS(NTAPI* NtWriteFileFn)(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to write data to file. + * No documentation exists, see wine sources for details. + */ +typedef NTSTATUS(NTAPI* NtWriteFileGatherFn)( + HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey); + +/** + * Function pointer declaration for internal NT routine to flush to disk. + * For documentation on the NtFlushBuffersFile routine, see ZwFlushBuffersFile + * on MSDN. + */ +typedef NTSTATUS(NTAPI* NtFlushBuffersFileFn)(HANDLE aFileHandle, + PIO_STATUS_BLOCK aIoStatusBlock); + +typedef struct _FILE_NETWORK_OPEN_INFORMATION* PFILE_NETWORK_OPEN_INFORMATION; +/** + * Function pointer delaration for internal NT routine to query file attributes. + * (equivalent to stat) + */ +typedef NTSTATUS(NTAPI* NtQueryFullAttributesFileFn)( + POBJECT_ATTRIBUTES aObjectAttributes, + PFILE_NETWORK_OPEN_INFORMATION aFileInformation); + +/*************************** Auxiliary Declarations ***************************/ + +// Cache of filenames associated with handles. +// `static` to be shared between all calls to `Filename()`. +// This assumes handles are not reused, at least within a windows of 32 +// handles. +// Profiling showed that during startup, around half of `Filename()` calls are +// resolved with the first entry (best case), and 32 entries cover >95% of +// cases, reducing the average `Filename()` cost by 5-10x. +using HandleToFilenameCache = mozilla::SmallArrayLRUCache; +static mozilla::UniquePtr sHandleToFilenameCache; + +/** + * RAII class for timing the duration of an I/O call and reporting the result + * to the mozilla::IOInterposeObserver API. + */ +class WinIOAutoObservation : public mozilla::IOInterposeObserver::Observation { + public: + WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, + HANDLE aFileHandle, const LARGE_INTEGER* aOffset) + : mozilla::IOInterposeObserver::Observation( + aOp, sReference, + !mozilla::IsDebugFile(reinterpret_cast(aFileHandle))), + mFileHandle(aFileHandle), + mFileHandleType(GetFileType(aFileHandle)), + mHasQueriedFilename(false) { + if (mShouldReport) { + mOffset.QuadPart = aOffset ? aOffset->QuadPart : 0; + } + } + + WinIOAutoObservation(mozilla::IOInterposeObserver::Operation aOp, + nsAString& aFilename) + : mozilla::IOInterposeObserver::Observation(aOp, sReference), + mFileHandle(nullptr), + mFileHandleType(FILE_TYPE_UNKNOWN), + mHasQueriedFilename(false) { + if (mShouldReport) { + nsAutoString dosPath; + if (mozilla::NtPathToDosPath(aFilename, dosPath)) { + mFilename = dosPath; + } else { + // If we can't get a dosPath, what we have is better than nothing. + mFilename = aFilename; + } + mHasQueriedFilename = true; + mOffset.QuadPart = 0; + } + } + + void SetHandle(HANDLE aFileHandle) { + mFileHandle = aFileHandle; + if (aFileHandle) { + // Note: `GetFileType()` is fast enough that we don't need to cache it. + mFileHandleType = GetFileType(aFileHandle); + + if (mHasQueriedFilename) { + // `mHasQueriedFilename` indicates we already have a filename, add it to + // the cache with the now-known handle. + sHandleToFilenameCache->Add(aFileHandle, mFilename); + } + } + } + + const char* FileType() const override; + + void Filename(nsAString& aFilename) override; + + ~WinIOAutoObservation() { Report(); } + + private: + HANDLE mFileHandle; + DWORD mFileHandleType; + LARGE_INTEGER mOffset; + bool mHasQueriedFilename; + nsString mFilename; + static const char* sReference; +}; + +const char* WinIOAutoObservation::sReference = "PoisonIOInterposer"; + +// Get filename for this observation +void WinIOAutoObservation::Filename(nsAString& aFilename) { + // If mHasQueriedFilename is true, then filename is already stored in + // mFilename + if (mHasQueriedFilename) { + aFilename = mFilename; + return; + } + + if (mFileHandle) { + mFilename = sHandleToFilenameCache->FetchOrAdd(mFileHandle, [&]() { + nsString filename; + if (!mozilla::HandleToFilename(mFileHandle, mOffset, filename)) { + // HandleToFilename could fail (return false) but still have added + // something to `filename`, so it should be cleared in this case. + filename.Truncate(); + } + return filename; + }); + } + mHasQueriedFilename = true; + + aFilename = mFilename; +} + +const char* WinIOAutoObservation::FileType() const { + if (mFileHandle) { + switch (mFileHandleType) { + case FILE_TYPE_CHAR: + return "Char"; + case FILE_TYPE_DISK: + return "File"; + case FILE_TYPE_PIPE: + return "Pipe"; + case FILE_TYPE_REMOTE: + return "Remote"; + case FILE_TYPE_UNKNOWN: + default: + break; + } + } + // Fallback to base class default implementation. + return mozilla::IOInterposeObserver::Observation::FileType(); +} + +/*************************** IO Interposing Methods ***************************/ + +// Function pointers to original functions +static mozilla::WindowsDllInterceptor::FuncHookType + gOriginalNtCreateFile; +static mozilla::WindowsDllInterceptor::FuncHookType + gOriginalNtReadFile; +static mozilla::WindowsDllInterceptor::FuncHookType + gOriginalNtReadFileScatter; +static mozilla::WindowsDllInterceptor::FuncHookType + gOriginalNtWriteFile; +static mozilla::WindowsDllInterceptor::FuncHookType + gOriginalNtWriteFileGather; +static mozilla::WindowsDllInterceptor::FuncHookType + gOriginalNtFlushBuffersFile; +static mozilla::WindowsDllInterceptor::FuncHookType + gOriginalNtQueryFullAttributesFile; + +static NTSTATUS NTAPI InterposedNtCreateFile( + PHANDLE aFileHandle, ACCESS_MASK aDesiredAccess, + POBJECT_ATTRIBUTES aObjectAttributes, PIO_STATUS_BLOCK aIoStatusBlock, + PLARGE_INTEGER aAllocationSize, ULONG aFileAttributes, ULONG aShareAccess, + ULONG aCreateDisposition, ULONG aCreateOptions, PVOID aEaBuffer, + ULONG aEaLength) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtCreateFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtCreateFile( + aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock, + aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition, + aCreateOptions, aEaBuffer, aEaLength); + } + + // Report IO + const wchar_t* buf = + aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L""; + uint32_t len = aObjectAttributes + ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR) + : 0; + nsDependentSubstring filename(buf, len); + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpCreateOrOpen, + filename); + + // Execute original function + NTSTATUS status = gOriginalNtCreateFile( + aFileHandle, aDesiredAccess, aObjectAttributes, aIoStatusBlock, + aAllocationSize, aFileAttributes, aShareAccess, aCreateDisposition, + aCreateOptions, aEaBuffer, aEaLength); + if (NT_SUCCESS(status) && aFileHandle) { + timer.SetHandle(*aFileHandle); + } + return status; +} + +static NTSTATUS NTAPI InterposedNtReadFile(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtReadFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); +} + +static NTSTATUS NTAPI InterposedNtReadFileScatter( + HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtReadFileScatter); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpRead, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtReadFileScatter(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); +} + +// Interposed NtWriteFile function +static NTSTATUS NTAPI InterposedNtWriteFile(HANDLE aFileHandle, HANDLE aEvent, + PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, + PVOID aBuffer, ULONG aLength, + PLARGE_INTEGER aOffset, + PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtWriteFile(aFileHandle, aEvent, aApc, aApcCtx, aIoStatus, + aBuffer, aLength, aOffset, aKey); +} + +// Interposed NtWriteFileGather function +static NTSTATUS NTAPI InterposedNtWriteFileGather( + HANDLE aFileHandle, HANDLE aEvent, PIO_APC_ROUTINE aApc, PVOID aApcCtx, + PIO_STATUS_BLOCK aIoStatus, FILE_SEGMENT_ELEMENT* aSegments, ULONG aLength, + PLARGE_INTEGER aOffset, PULONG aKey) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtWriteFileGather); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpWrite, aFileHandle, + aOffset); + + // Execute original function + return gOriginalNtWriteFileGather(aFileHandle, aEvent, aApc, aApcCtx, + aIoStatus, aSegments, aLength, aOffset, + aKey); +} + +static NTSTATUS NTAPI InterposedNtFlushBuffersFile( + HANDLE aFileHandle, PIO_STATUS_BLOCK aIoStatusBlock) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtFlushBuffersFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock); + } + + // Report IO + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpFSync, aFileHandle, + nullptr); + + // Execute original function + return gOriginalNtFlushBuffersFile(aFileHandle, aIoStatusBlock); +} + +static NTSTATUS NTAPI InterposedNtQueryFullAttributesFile( + POBJECT_ATTRIBUTES aObjectAttributes, + PFILE_NETWORK_OPEN_INFORMATION aFileInformation) { + // Something is badly wrong if this function is undefined + MOZ_ASSERT(gOriginalNtQueryFullAttributesFile); + + if (!mozilla::nt::RtlGetThreadLocalStoragePointer()) { + return gOriginalNtQueryFullAttributesFile(aObjectAttributes, + aFileInformation); + } + + // Report IO + const wchar_t* buf = + aObjectAttributes ? aObjectAttributes->ObjectName->Buffer : L""; + uint32_t len = aObjectAttributes + ? aObjectAttributes->ObjectName->Length / sizeof(WCHAR) + : 0; + nsDependentSubstring filename(buf, len); + WinIOAutoObservation timer(mozilla::IOInterposeObserver::OpStat, filename); + + // Execute original function + return gOriginalNtQueryFullAttributesFile(aObjectAttributes, + aFileInformation); +} + +} // namespace + +/******************************** IO Poisoning ********************************/ + +// Windows DLL interceptor +static mozilla::WindowsDllInterceptor sNtDllInterceptor; + +namespace mozilla { + +void InitPoisonIOInterposer() { + // Currently we hook the functions not early enough to precede third-party + // injections. Until we implement a compatible way e.g. applying a hook + // in the parent process (bug 1646804), we skip interposing functions under + // the known condition(s). + + // Bug 1679741: Kingsoft Internet Security calls NtReadFile in their thread + // simultaneously when we're applying a hook on NtReadFile. + if (::GetModuleHandleW(L"kwsui64.dll")) { + return; + } + + // Don't poison twice... as this function may only be invoked on the main + // thread when no other threads are running, it safe to allow multiple calls + // to InitPoisonIOInterposer() without complaining (ie. failing assertions). + if (sIOPoisoned) { + return; + } + sIOPoisoned = true; + + MOZ_RELEASE_ASSERT(!sHandleToFilenameCache); + sHandleToFilenameCache = mozilla::MakeUnique(); + mozilla::RunOnShutdown([]() { + // The interposer may still be active after the final shutdown phase + // (especially since ClearPoisonIOInterposer() is never called, see bug + // 1647107), so we cannot just reset the pointer. Instead we put the cache + // in shutdown mode, to clear its memory and stop caching operations. + sHandleToFilenameCache->Shutdown(); + }); + + // Stdout and Stderr are OK. + MozillaRegisterDebugFD(1); + if (::GetStdHandle(STD_OUTPUT_HANDLE) != ::GetStdHandle(STD_ERROR_HANDLE)) { + MozillaRegisterDebugFD(2); + } + +#ifdef MOZ_REPLACE_MALLOC + // The contract with InitDebugFd is that the given registry can be used + // at any moment, so the instance needs to persist longer than the scope + // of this functions. + static DebugFdRegistry registry; + ReplaceMalloc::InitDebugFd(registry); +#endif + + // Initialize dll interceptor and add hooks + sNtDllInterceptor.Init("ntdll.dll"); + gOriginalNtCreateFile.Set(sNtDllInterceptor, "NtCreateFile", + &InterposedNtCreateFile); + gOriginalNtReadFile.Set(sNtDllInterceptor, "NtReadFile", + &InterposedNtReadFile); + gOriginalNtReadFileScatter.Set(sNtDllInterceptor, "NtReadFileScatter", + &InterposedNtReadFileScatter); + gOriginalNtWriteFile.Set(sNtDllInterceptor, "NtWriteFile", + &InterposedNtWriteFile); + gOriginalNtWriteFileGather.Set(sNtDllInterceptor, "NtWriteFileGather", + &InterposedNtWriteFileGather); + gOriginalNtFlushBuffersFile.Set(sNtDllInterceptor, "NtFlushBuffersFile", + &InterposedNtFlushBuffersFile); + gOriginalNtQueryFullAttributesFile.Set(sNtDllInterceptor, + "NtQueryFullAttributesFile", + &InterposedNtQueryFullAttributesFile); +} + +void ClearPoisonIOInterposer() { + MOZ_ASSERT(false, "Never called! See bug 1647107"); + if (sIOPoisoned) { + // Destroy the DLL interceptor + sIOPoisoned = false; + sNtDllInterceptor.Clear(); + sHandleToFilenameCache->Clear(); + } +} + +} // namespace mozilla diff --git a/xpcom/build/Services.py b/xpcom/build/Services.py new file mode 100644 index 0000000000..4c6c14807f --- /dev/null +++ b/xpcom/build/Services.py @@ -0,0 +1,170 @@ +# 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/. + +# NOTE: Although this generates headers and code for C++, using Services.h +# is deprecated in favour of Components.h. + + +services = [] + + +def service(name, iface, contractid): + """Define a convenient service getter""" + services.append((name, iface, contractid)) + + +# The `name` parameter is derived from the `iface` by removing the `nsI` +# prefix. (This often matches the `contractid`, but not always.) +service("ChromeRegistry", "nsIChromeRegistry", "@mozilla.org/chrome/chrome-registry;1") +service("IOService", "nsIIOService", "@mozilla.org/network/io-service;1") +service("ObserverService", "nsIObserverService", "@mozilla.org/observer-service;1") +service("PermissionManager", "nsIPermissionManager", "@mozilla.org/permissionmanager;1") +service( + "AsyncShutdownService", + "nsIAsyncShutdownService", + "@mozilla.org/async-shutdown-service;1", +) + +# The definition file needs access to the definitions of the particular +# interfaces. If you add a new interface here, make sure the necessary includes +# are also listed in the following code snippet. +CPP_INCLUDES = """ +#include "mozilla/Likely.h" +#include "mozilla/Services.h" +#include "mozIThirdPartyUtil.h" +#include "nsComponentManager.h" +#include "nsIObserverService.h" +#include "nsNetCID.h" +#include "nsObserverService.h" +#include "nsXPCOMPrivate.h" +#include "nsIIOService.h" +#include "nsIDirectoryService.h" +#include "nsIChromeRegistry.h" +#include "nsIStringBundle.h" +#include "nsIToolkitChromeRegistry.h" +#include "IHistory.h" +#include "nsIXPConnect.h" +#include "nsIPermissionManager.h" +#include "nsIPrefService.h" +#include "nsIServiceWorkerManager.h" +#include "nsICacheStorageService.h" +#include "nsIStreamTransportService.h" +#include "nsISocketTransportService.h" +#include "nsIURIClassifier.h" +#include "nsIHttpActivityObserver.h" +#include "nsIAsyncShutdown.h" +#include "nsIUUIDGenerator.h" +#include "nsIGfxInfo.h" +#include "nsIURIFixup.h" +#include "nsIBits.h" +#include "nsIXULRuntime.h" +""" + + +##### +# Codegen Logic +# +# The following code consumes the data listed above to generate the files +# Services.h, and Services.cpp which provide access to these service getters in +# C++ code. + + +def services_h(output): + output.write( + """\ +/* THIS FILE IS GENERATED BY Services.py - DO NOT EDIT */ + +#ifndef mozilla_Services_h +#define mozilla_Services_h + +#include "nscore.h" +#include "nsCOMPtr.h" +""" + ) + + for (name, iface, contractid) in services: + # Write out a forward declaration for the type in question + segs = iface.split("::") + for namespace in segs[:-1]: + output.write("namespace %s {\n" % namespace) + output.write("class %s;\n" % segs[-1]) + for namespace in reversed(segs[:-1]): + output.write("} // namespace %s\n" % namespace) + + # Write out the C-style function signature, and the C++ wrapper + output.write( + """ +#ifdef MOZILLA_INTERNAL_API +namespace mozilla { +namespace services { +/** + * Fetch a cached instance of the %(name)s. + * This function will return nullptr during XPCOM shutdown. + * Prefer using static components to this method. + * WARNING: This method is _not_ threadsafe! + */ +already_AddRefed<%(type)s> Get%(name)s(); +} // namespace services +} // namespace mozilla +#endif // defined(MOZILLA_INTERNAL_API) +""" + % { + "name": name, + "type": iface, + } + ) + + output.write("#endif // !defined(mozilla_Services_h)\n") + + +def services_cpp(output): + output.write( + """\ +/* THIS FILE IS GENERATED BY Services.py - DO NOT EDIT */ +""" + ) + output.write(CPP_INCLUDES) + + for (name, iface, contractid) in services: + output.write( + """ +static %(type)s* g%(name)s = nullptr; + +namespace mozilla { +namespace services { +already_AddRefed<%(type)s> Get%(name)s() +{ + if (MOZ_UNLIKELY(gXPCOMShuttingDown)) { + return nullptr; + } + if (!g%(name)s) { + nsCOMPtr<%(type)s> os = do_GetService("%(contractid)s"); + os.swap(g%(name)s); + } + return do_AddRef(g%(name)s); +} +} +} +""" + % { + "name": name, + "type": iface, + "contractid": contractid, + } + ) + + output.write( + """ +/** + * Clears service cache, sets gXPCOMShuttingDown + */ +void +mozilla::services::Shutdown() +{ + gXPCOMShuttingDown = true; +""" + ) + for (name, iface, contractid) in services: + output.write(" NS_IF_RELEASE(g%s);\n" % name) + output.write("}\n") diff --git a/xpcom/build/SmallArrayLRUCache.h b/xpcom/build/SmallArrayLRUCache.h new file mode 100644 index 0000000000..e7ac727652 --- /dev/null +++ b/xpcom/build/SmallArrayLRUCache.h @@ -0,0 +1,199 @@ +/* -*- 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/. */ + +#ifndef SmallArrayLRUCache_h +#define SmallArrayLRUCache_h + +#include "mozilla/Mutex.h" + +#include +#include +#include + +// Uncomment the next line to get shutdown stats about cache usage. +// #define SMALLARRAYLRUCACHE_STATS + +#ifdef SMALLARRAYLRUCACHE_STATS +# include +#endif + +namespace mozilla { + +// Array-based LRU cache, thread-safe. +// Optimized for cases where `FetchOrAdd` is used with the same key most +// recently, and assuming the cost of running the value-builder function is much +// more expensive than going through the whole list. +// Note: No time limits on keeping items. +// TODO: Move to more public place, if this could be used elsewhere; make sure +// the cost/benefits are highlighted. +template +class SmallArrayLRUCache { + public: + static_assert(std::is_default_constructible_v); + static_assert(std::is_trivially_constructible_v); + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_default_constructible_v); + static_assert(LRUCapacity >= 2); + static_assert(LRUCapacity <= 1024, + "This seems a bit big, is this the right cache for your use?"); + + // Reset all stored values to their default, and set cache size to zero. + void Clear() { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + if (mSize == ShutdownSize) { + return; + } + Clear(lock); + } + + // Clear the cache, and then prevent further uses (other functions will do + // nothing). + void Shutdown() { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + if (mSize == ShutdownSize) { + return; + } + Clear(lock); + mSize = ShutdownSize; + } + + // Add a key-value. + template + void Add(Key aKey, ToValue&& aValue) { + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + + if (mSize == ShutdownSize) { + return; + } + + // Quick add to the front, don't remove possible duplicate handles later in + // the list, they will eventually drop off the end. + KeyAndValue* const item0 = &mLRUArray[0]; + mSize = std::min(mSize + 1, LRUCapacity); + if (MOZ_LIKELY(mSize != 1)) { + // List is not empty. + // Make a hole at the start. + std::move_backward(item0, item0 + mSize - 1, item0 + mSize); + } + item0->mKey = aKey; + item0->mValue = std::forward(aValue); + return; + } + + // Look for the value associated with `aKey` in the cache. + // If not found, run `aValueFunction()`, add it in the cache before returning. + // After shutdown, just run `aValueFunction()`. + template + Value FetchOrAdd(Key aKey, ValueFunction&& aValueFunction) { + Value value; + mozilla::OffTheBooksMutexAutoLock lock(mMutex); + + if (mSize == ShutdownSize) { + value = std::forward(aValueFunction)(); + return value; + } + + KeyAndValue* const item0 = &mLRUArray[0]; + if (MOZ_UNLIKELY(mSize == 0)) { + // List is empty. + value = std::forward(aValueFunction)(); + item0->mKey = aKey; + item0->mValue = value; + mSize = 1; + return value; + } + + if (MOZ_LIKELY(item0->mKey == aKey)) { + // This is already at the beginning of the list, we're done. +#ifdef SMALLARRAYLRUCACHE_STATS + ++mCacheFoundAt[0]; +#endif // SMALLARRAYLRUCACHE_STATS + value = item0->mValue; + return value; + } + + for (KeyAndValue* item = item0 + 1; item != item0 + mSize; ++item) { + if (item->mKey == aKey) { + // Found handle in the middle. +#ifdef SMALLARRAYLRUCACHE_STATS + ++mCacheFoundAt[unsigned(item - item0)]; +#endif // SMALLARRAYLRUCACHE_STATS + value = item->mValue; + // Move this item to the start of the list. + std::rotate(item0, item, item + 1); + return value; + } + } + + // Handle was not in the list. +#ifdef SMALLARRAYLRUCACHE_STATS + ++mCacheFoundAt[LRUCapacity]; +#endif // SMALLARRAYLRUCACHE_STATS + { + // Don't lock while doing the potentially-expensive ValueFunction(). + // This means that the list could change when we lock again, but + // it's okay because we'll want to add the new entry at the beginning + // anyway, whatever else is in the list then. + // In the worst case, it could be the same handle as another `FetchOrAdd` + // in parallel, it just means it will be duplicated, so it's a little bit + // less efficient (using the extra space), but not wrong (the next + // `FetchOrAdd` will find the first one). + mozilla::OffTheBooksMutexAutoUnlock unlock(mMutex); + value = std::forward(aValueFunction)(); + } + // Make a hole at the start, and put the value there. + mSize = std::min(mSize + 1, LRUCapacity); + std::move_backward(item0, item0 + mSize - 1, item0 + mSize); + item0->mKey = aKey; + item0->mValue = value; + return value; + } + +#ifdef SMALLARRAYLRUCACHE_STATS + ~SmallArrayLRUCache() { + if (mSize != 0 && mSize != ShutdownSize) { + fprintf(stderr, + "***** SmallArrayLRUCache stats: (position -> hit count)\n"); + for (unsigned i = 0; i < mSize; ++i) { + fprintf(stderr, "***** %3u -> %6u\n", i, mCacheFoundAt[i]); + } + fprintf(stderr, "***** not found -> %6u\n", mCacheFoundAt[LRUCapacity]); + } + } +#endif // SMALLARRAYLRUCACHE_STATS + + private: + void Clear(const mozilla::OffTheBooksMutexAutoLock&) MOZ_REQUIRES(mMutex) { + for (KeyAndValue* item = &mLRUArray[0]; item != &mLRUArray[mSize]; ++item) { + item->mValue = Value{}; + } + mSize = 0; + } + + struct KeyAndValue { + Key mKey; + Value mValue; + + KeyAndValue() = default; + KeyAndValue(KeyAndValue&&) = default; + KeyAndValue& operator=(KeyAndValue&&) = default; + }; + + // Special size value that indicates the cache is in shutdown mode. + constexpr static unsigned ShutdownSize = unsigned(-1); + + mozilla::OffTheBooksMutex mMutex{"LRU cache"}; + unsigned mSize MOZ_GUARDED_BY(mMutex) = 0; + KeyAndValue mLRUArray[LRUCapacity] MOZ_GUARDED_BY(mMutex); +#ifdef SMALLARRAYLRUCACHE_STATS + // Hit count for each position in the case. +1 for counting not-found cases. + unsigned mCacheFoundAt[LRUCapacity + 1] MOZ_GUARDED_BY(mMutex) = {0u}; +#endif // SMALLARRAYLRUCACHE_STATS +}; + +} // namespace mozilla + +#endif // SmallArrayLRUCache_h diff --git a/xpcom/build/XPCOM.h b/xpcom/build/XPCOM.h new file mode 100644 index 0000000000..1a04091dc4 --- /dev/null +++ b/xpcom/build/XPCOM.h @@ -0,0 +1,167 @@ +/* -*- 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/. */ + +#ifndef mozilla_XPCOM_h +#define mozilla_XPCOM_h + +// NOTE: the following headers are sorted topologically, not alphabetically. +// Do not reorder them without review from bsmedberg. + +// system headers required by XPCOM headers + +#include + +#ifndef MOZILLA_INTERNAL_API +# error "MOZILLA_INTERNAL_API must be defined" +#endif + +// core headers required by pretty much everything else + +#include "nscore.h" + +#include "nsXPCOMCID.h" +#include "nsXPCOM.h" + +#include "nsError.h" +#include "nsDebug.h" + +#include "nsID.h" + +#include "nsISupports.h" + +#include "nsTArray.h" + +#include "nsCOMPtr.h" +#include "nsCOMArray.h" + +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsNativeCharsetUtils.h" + +#include "nsISupportsUtils.h" +#include "nsISupportsImpl.h" + +// core data structures + +#include "nsTHashtable.h" +#include "nsHashKeys.h" +#include "nsBaseHashtable.h" +#include "nsTHashMap.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsRefPtrHashtable.h" + +// interfaces that inherit directly from nsISupports + +#include "nsIArray.h" +#include "nsAtom.h" +#include "nsICategoryManager.h" +#include "nsIClassInfo.h" +#include "nsIComponentManager.h" +#include "nsIConsoleListener.h" +#include "nsIConsoleMessage.h" +#include "nsIConsoleService.h" +#include "nsIDebug2.h" +#include "nsIDirectoryEnumerator.h" +#include "nsIEnvironment.h" +#include "nsIEventTarget.h" +#include "nsIException.h" +#include "nsIFactory.h" +#include "nsIFile.h" +#include "nsIINIParser.h" +#include "nsIInputStream.h" +#include "nsIInterfaceRequestor.h" +#include "nsILineInputStream.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIOutputStream.h" +#include "nsIProcess.h" +#include "nsIProperties.h" +#include "nsIRunnable.h" +#include "nsISeekableStream.h" +#include "nsISerializable.h" +#include "nsIServiceManager.h" +#include "nsIScriptableInputStream.h" +#include "nsISimpleEnumerator.h" +#include "nsIStreamBufferAccess.h" +#include "nsIStringEnumerator.h" +#include "nsIStorageStream.h" +#include "nsISupportsIterators.h" +#include "nsISupportsPrimitives.h" +#include "nsISupportsPriority.h" +#include "nsIThreadManager.h" +#include "nsITimer.h" +#include "nsIUUIDGenerator.h" +#include "nsIUnicharInputStream.h" +#include "nsIUnicharOutputStream.h" +#include "nsIUnicharLineInputStream.h" +#include "nsIVariant.h" +#include "nsIVersionComparator.h" +#include "nsIWritablePropertyBag2.h" + +// interfaces that include something above + +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBinaryInputStream.h" +#include "nsIBinaryOutputStream.h" +#include "nsIConverterInputStream.h" +#include "nsIConverterOutputStream.h" +#include "nsIInputStreamTee.h" +#include "nsIMultiplexInputStream.h" +#include "nsIMutableArray.h" +#include "nsIPersistentProperties2.h" +#include "nsIStringStream.h" +#include "nsIThread.h" +#include "nsIThreadPool.h" + +// interfaces that include something above + +#include "nsILocalFileWin.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIPipe.h" + +#ifdef MOZ_WIDGET_COCOA +# include "nsILocalFileMac.h" +#endif + +// xpcom/glue utility headers + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsWeakReference.h" + +#include "nsArrayEnumerator.h" +#include "nsArrayUtils.h" +#include "nsCRTGlue.h" +#include "nsCycleCollectionParticipant.h" +#include "nsDeque.h" +#include "nsEnumeratorUtils.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/ModuleUtils.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsINIParser.h" +#include "nsProxyRelease.h" +#include "nsTObserverArray.h" +#include "nsTextFormatter.h" +#include "nsThreadUtils.h" +#include "nsVersionComparator.h" +#include "nsXPTCUtils.h" + +// xpcom/base utility headers + +#include "nsAutoRef.h" +#include "nsInterfaceRequestorAgg.h" + +// xpcom/io utility headers + +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" + +#endif // mozilla_XPCOM_h diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp new file mode 100644 index 0000000000..cd6c7f7052 --- /dev/null +++ b/xpcom/build/XPCOMInit.cpp @@ -0,0 +1,790 @@ +/* -*- 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 "ThreadEventTarget.h" +#include "XPCOMModule.h" + +#include "base/basictypes.h" + +#include "mozilla/AbstractThread.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Poison.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/TaskController.h" +#include "mozilla/Unused.h" +#include "mozilla/XPCOM.h" +#include "mozJSModuleLoader.h" +#include "nsXULAppAPI.h" + +#ifndef ANDROID +# include "nsTerminator.h" +#endif + +#include "nsXPCOMPrivate.h" +#include "nsXPCOMCIDInternal.h" + +#include "mozilla/dom/JSExecutionManager.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/CompositorBridgeParent.h" + +#include "prlink.h" + +#include "nsCycleCollector.h" +#include "nsObserverService.h" + +#include "nsDebugImpl.h" +#include "nsSystemInfo.h" + +#include "nsComponentManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsIServiceManager.h" + +#include "nsThreadManager.h" +#include "nsThreadPool.h" + +#include "nsTimerImpl.h" +#include "TimerThread.h" + +#include "nsThread.h" +#include "nsVersionComparatorImpl.h" + +#include "nsIFile.h" +#include "nsLocalFile.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCategoryManager.h" +#include "nsMultiplexInputStream.h" + +#include "nsAtomTable.h" +#include "nsISupportsImpl.h" + +#include "nsSystemInfo.h" +#include "nsMemoryReporterManager.h" +#include "nsMessageLoop.h" +#include "nss.h" +#include "nsNSSComponent.h" + +#include +#include "mozilla/Services.h" +#include "mozilla/Omnijar.h" +#include "mozilla/ScriptPreloader.h" +#include "mozilla/Telemetry.h" +#include "mozilla/BackgroundHangMonitor.h" + +#include "mozilla/PoisonIOInterposer.h" +#include "mozilla/LateWriteChecks.h" + +#include "mozilla/scache/StartupCache.h" + +#include "base/at_exit.h" +#include "base/command_line.h" +#include "base/message_loop.h" + +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/AvailableMemoryTracker.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CountingAllocatorBase.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/ServoStyleConsts.h" + +#include "mozilla/ipc/GeckoChildProcessHost.h" + +#include "ogg/ogg.h" + +#include "GeckoProfiler.h" +#include "ProfilerControl.h" + +#include "jsapi.h" +#include "js/Initialization.h" +#include "mozilla/StaticPrefs_javascript.h" +#include "XPCSelfHostedShmem.h" + +#include "gfxPlatform.h" + +using base::AtExitManager; +using mozilla::ipc::BrowserProcessSubThread; + +// From toolkit/library/rust/lib.rs +extern "C" void GkRust_Init(); +extern "C" void GkRust_Shutdown(); + +namespace { + +static AtExitManager* sExitManager; +static MessageLoop* sMessageLoop; +static bool sCommandLineWasInitialized; +static BrowserProcessSubThread* sIOThread; +static mozilla::BackgroundHangMonitor* sMainHangMonitor; + +} /* anonymous namespace */ + +// Registry Factory creation function defined in nsRegistry.cpp +// We hook into this function locally to create and register the registry +// Since noone outside xpcom needs to know about this and nsRegistry.cpp +// does not have a local include file, we are putting this definition +// here rather than in nsIRegistry.h +extern nsresult NS_RegistryGetFactory(nsIFactory** aFactory); +extern nsresult NS_CategoryManagerGetFactory(nsIFactory**); + +#ifdef XP_WIN +extern nsresult CreateAnonTempFileRemover(); +#endif + +nsresult nsThreadManagerGetSingleton(const nsIID& aIID, void** aInstancePtr) { + NS_ASSERTION(aInstancePtr, "null outptr"); + return nsThreadManager::get().QueryInterface(aIID, aInstancePtr); +} + +nsresult nsLocalFileConstructor(const nsIID& aIID, void** aInstancePtr) { + return nsLocalFile::nsLocalFileConstructor(aIID, aInstancePtr); +} + +nsComponentManagerImpl* nsComponentManagerImpl::gComponentManager = nullptr; +bool gXPCOMShuttingDown = false; +bool gXPCOMMainThreadEventsAreDoomed = false; +char16_t* gGREBinPath = nullptr; + +// gDebug will be freed during shutdown. +static nsIDebug2* gDebug = nullptr; + +EXPORT_XPCOM_API(nsresult) +NS_GetDebug(nsIDebug2** aResult) { + return nsDebugImpl::Create(NS_GET_IID(nsIDebug2), (void**)aResult); +} + +class ICUReporter final : public nsIMemoryReporter, + public mozilla::CountingAllocatorBase { + public: + NS_DECL_ISUPPORTS + + static void* Alloc(const void*, size_t aSize) { + return CountingMalloc(aSize); + } + + static void* Realloc(const void*, void* aPtr, size_t aSize) { + return CountingRealloc(aPtr, aSize); + } + + static void Free(const void*, void* aPtr) { return CountingFree(aPtr); } + + private: + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "explicit/icu", KIND_HEAP, UNITS_BYTES, MemoryAllocated(), + "Memory used by ICU, a Unicode and globalization support library."); + + return NS_OK; + } + + ~ICUReporter() = default; +}; + +NS_IMPL_ISUPPORTS(ICUReporter, nsIMemoryReporter) + +class OggReporter final : public nsIMemoryReporter, + public mozilla::CountingAllocatorBase { + public: + NS_DECL_ISUPPORTS + + private: + NS_IMETHOD + CollectReports(nsIHandleReportCallback* aHandleReport, nsISupports* aData, + bool aAnonymize) override { + MOZ_COLLECT_REPORT( + "explicit/media/libogg", KIND_HEAP, UNITS_BYTES, MemoryAllocated(), + "Memory allocated through libogg for Ogg, Theora, and related media " + "files."); + + return NS_OK; + } + + ~OggReporter() = default; +}; + +NS_IMPL_ISUPPORTS(OggReporter, nsIMemoryReporter) + +static bool sInitializedJS = false; + +static void InitializeJS() { +#if defined(ENABLE_WASM_SIMD) && \ + (defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86)) + // Update static engine preferences, such as AVX, before + // `JS_InitWithFailureDiagnostic` is called. + JS::SetAVXEnabled(mozilla::StaticPrefs::javascript_options_wasm_simd_avx()); +#endif + + const char* jsInitFailureReason = JS_InitWithFailureDiagnostic(); + if (jsInitFailureReason) { + MOZ_CRASH_UNSAFE(jsInitFailureReason); + } +} + +// Note that on OSX, aBinDirectory will point to .app/Contents/Resources/browser +EXPORT_XPCOM_API(nsresult) +NS_InitXPCOM(nsIServiceManager** aResult, nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider, + bool aInitJSContext) { + static bool sInitialized = false; + if (sInitialized) { + return NS_ERROR_FAILURE; + } + + sInitialized = true; + + NS_LogInit(); + + NS_InitAtomTable(); + + // We don't have the arguments by hand here. If logging has already been + // initialized by a previous call to LogModule::Init with the arguments + // passed, passing (0, nullptr) is alright here. + mozilla::LogModule::Init(0, nullptr); + + GkRust_Init(); + + nsresult rv = NS_OK; + + // We are not shutting down + gXPCOMShuttingDown = false; + +#ifdef XP_UNIX + // Discover the current value of the umask, and save it where + // nsSystemInfo::Init can retrieve it when necessary. There is no way + // to read the umask without changing it, and the setting is process- + // global, so this must be done while we are still single-threaded; the + // nsSystemInfo object is typically created much later, when some piece + // of chrome JS wants it. The system call is specified as unable to fail. + nsSystemInfo::gUserUmask = ::umask(0777); + ::umask(nsSystemInfo::gUserUmask); +#endif + + // Set up chromium libs + NS_ASSERTION(!sExitManager && !sMessageLoop, "Bad logic!"); + + if (!AtExitManager::AlreadyRegistered()) { + sExitManager = new AtExitManager(); + } + + MessageLoop* messageLoop = MessageLoop::current(); + if (!messageLoop) { + sMessageLoop = new MessageLoopForUI(MessageLoop::TYPE_MOZILLA_PARENT); + sMessageLoop->set_thread_name("Gecko"); + // Set experimental values for main thread hangs: + // 128ms for transient hangs and 8192ms for permanent hangs + sMessageLoop->set_hang_timeouts(128, 8192); + } else if (messageLoop->type() == MessageLoop::TYPE_MOZILLA_CHILD) { + messageLoop->set_thread_name("Gecko_Child"); + messageLoop->set_hang_timeouts(128, 8192); + } + + if (XRE_IsParentProcess() && + !BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO)) { + mozilla::UniquePtr ioThread = + mozilla::MakeUnique( + BrowserProcessSubThread::IO); + + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + if (NS_WARN_IF(!ioThread->StartWithOptions(options))) { + return NS_ERROR_FAILURE; + } + + sIOThread = ioThread.release(); + } + + // Establish the main thread here. + rv = nsThreadManager::get().Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + AUTO_PROFILER_INIT2; + + // Set up the timer globals/timer thread + rv = nsTimerImpl::Startup(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + +#ifndef ANDROID + // If the locale hasn't already been setup by our embedder, + // get us out of the "C" locale and into the system + if (strcmp(setlocale(LC_ALL, nullptr), "C") == 0) { + setlocale(LC_ALL, ""); + } +#endif + + nsDirectoryService::RealInit(); + + bool value; + + if (aBinDirectory) { + rv = aBinDirectory->IsDirectory(&value); + + if (NS_SUCCEEDED(rv) && value) { + nsDirectoryService::gService->SetCurrentProcessDirectory(aBinDirectory); + } + } + + if (aAppFileLocationProvider) { + rv = nsDirectoryService::gService->RegisterProvider( + aAppFileLocationProvider); + if (NS_FAILED(rv)) { + return rv; + } + } + + nsCOMPtr xpcomLib; + nsDirectoryService::gService->Get(NS_GRE_BIN_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(xpcomLib)); + MOZ_ASSERT(xpcomLib); + + // set gGREBinPath + nsAutoString path; + xpcomLib->GetPath(path); + gGREBinPath = ToNewUnicode(path); + + xpcomLib->AppendNative(nsDependentCString(XPCOM_DLL)); + nsDirectoryService::gService->Set(NS_XPCOM_LIBRARY_FILE, xpcomLib); + + if (!mozilla::Omnijar::IsInitialized()) { + mozilla::Omnijar::Init(); + } + + if ((sCommandLineWasInitialized = !CommandLine::IsInitialized())) { +#ifdef OS_WIN + CommandLine::Init(0, nullptr); +#else + nsCOMPtr binaryFile; + nsDirectoryService::gService->Get(NS_XPCOM_CURRENT_PROCESS_DIR, + NS_GET_IID(nsIFile), + getter_AddRefs(binaryFile)); + if (NS_WARN_IF(!binaryFile)) { + return NS_ERROR_FAILURE; + } + + rv = binaryFile->AppendNative("nonexistent-executable"_ns); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString binaryPath; + rv = binaryFile->GetNativePath(binaryPath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + static char const* const argv = {strdup(binaryPath.get())}; + CommandLine::Init(1, &argv); +#endif + } + + NS_ASSERTION(nsComponentManagerImpl::gComponentManager == nullptr, + "CompMgr not null at init"); + + // Create the Component/Service Manager + nsComponentManagerImpl::gComponentManager = new nsComponentManagerImpl(); + NS_ADDREF(nsComponentManagerImpl::gComponentManager); + + // Global cycle collector initialization. + if (!nsCycleCollector_init()) { + return NS_ERROR_UNEXPECTED; + } + + // And start it up for this thread too. + nsCycleCollector_startup(); + + // Register ICU memory functions. This really shouldn't be necessary: the + // JS engine should do this on its own inside JS_Init, and memory-reporting + // code should call a JSAPI function to observe ICU memory usage. But we + // can't define the alloc/free functions in the JS engine, because it can't + // depend on the XPCOM-based memory reporting goop. So for now, we have + // this oddness. + mozilla::SetICUMemoryFunctions(); + + // Do the same for libogg. + ogg_set_mem_functions( + OggReporter::CountingMalloc, OggReporter::CountingCalloc, + OggReporter::CountingRealloc, OggReporter::CountingFree); + + // Initialize the JS engine. + InitializeJS(); + sInitializedJS = true; + + rv = nsComponentManagerImpl::gComponentManager->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(nsComponentManagerImpl::gComponentManager); + return rv; + } + + if (aResult) { + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + } + + // After autoreg, but before we actually instantiate any components, + // add any services listed in the "xpcom-directory-providers" category + // to the directory service. + nsDirectoryService::gService->RegisterCategoryProviders(); + + // Init mozilla::SharedThreadPool (which needs the service manager). + mozilla::SharedThreadPool::InitStatics(); + + mozilla::scache::StartupCache::GetSingleton(); + mozilla::AvailableMemoryTracker::Init(); + + // Notify observers of xpcom autoregistration start + NS_CreateServicesFromCategory(NS_XPCOM_STARTUP_CATEGORY, nullptr, + NS_XPCOM_STARTUP_OBSERVER_ID); +#ifdef XP_WIN + CreateAnonTempFileRemover(); +#endif + + // The memory reporter manager is up and running -- register our reporters. + RegisterStrongMemoryReporter(new ICUReporter()); + RegisterStrongMemoryReporter(new OggReporter()); + xpc::SelfHostedShmem::GetSingleton().InitMemoryReporter(); + + mozilla::Telemetry::Init(); + + mozilla::BackgroundHangMonitor::Startup(); + + const MessageLoop* const loop = MessageLoop::current(); + sMainHangMonitor = new mozilla::BackgroundHangMonitor( + loop->thread_name().c_str(), loop->transient_hang_timeout(), + loop->permanent_hang_timeout()); + + mozilla::dom::JSExecutionManager::Initialize(); + + if (aInitJSContext) { + xpc::InitializeJSContext(); + } + + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +NS_InitMinimalXPCOM() { + NS_SetMainThread(); + mozilla::TimeStamp::Startup(); + NS_LogInit(); + NS_InitAtomTable(); + + // We don't have the arguments by hand here. If logging has already been + // initialized by a previous call to LogModule::Init with the arguments + // passed, passing (0, nullptr) is alright here. + mozilla::LogModule::Init(0, nullptr); + + GkRust_Init(); + + nsresult rv = nsThreadManager::get().Init(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Set up the timer globals/timer thread. + rv = nsTimerImpl::Startup(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Create the Component/Service Manager + nsComponentManagerImpl::gComponentManager = new nsComponentManagerImpl(); + NS_ADDREF(nsComponentManagerImpl::gComponentManager); + + rv = nsComponentManagerImpl::gComponentManager->Init(); + if (NS_FAILED(rv)) { + NS_RELEASE(nsComponentManagerImpl::gComponentManager); + return rv; + } + + // Global cycle collector initialization. + if (!nsCycleCollector_init()) { + return NS_ERROR_UNEXPECTED; + } + + mozilla::SharedThreadPool::InitStatics(); + mozilla::Telemetry::Init(); + mozilla::BackgroundHangMonitor::Startup(); + + return NS_OK; +} + +// +// NS_ShutdownXPCOM() +// +// The shutdown sequence for xpcom would be +// +// - Notify "xpcom-shutdown" for modules to release primary (root) references +// - Shutdown XPCOM timers +// - Notify "xpcom-shutdown-threads" for thread joins +// - Shutdown the event queues +// - Release the Global Service Manager +// - Release all service instances held by the global service manager +// - Release the Global Service Manager itself +// - Release the Component Manager +// - Release all factories cached by the Component Manager +// - Notify module loaders to shut down +// - Unload Libraries +// - Release Contractid Cache held by Component Manager +// - Release dll abstraction held by Component Manager +// - Release the Registry held by Component Manager +// - Finally, release the component manager itself +// +EXPORT_XPCOM_API(nsresult) +NS_ShutdownXPCOM(nsIServiceManager* aServMgr) { + return mozilla::ShutdownXPCOM(aServMgr); +} + +namespace mozilla { + +void SetICUMemoryFunctions() { + static bool sICUReporterInitialized = false; + if (!sICUReporterInitialized) { + if (!JS_SetICUMemoryFunctions(ICUReporter::Alloc, ICUReporter::Realloc, + ICUReporter::Free)) { + MOZ_CRASH("JS_SetICUMemoryFunctions failed."); + } + sICUReporterInitialized = true; + } +} + +nsresult ShutdownXPCOM(nsIServiceManager* aServMgr) { + // Make sure the hang monitor is enabled for shutdown. + BackgroundHangMonitor().NotifyActivity(); + + if (!NS_IsMainThread()) { + MOZ_CRASH("Shutdown on wrong thread"); + } + + // Notify observers of xpcom shutting down + { + // Block it so that the COMPtr will get deleted before we hit + // servicemanager shutdown + + nsCOMPtr thread = do_GetCurrentThread(); + if (NS_WARN_IF(!thread)) { + return NS_ERROR_UNEXPECTED; + } + + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::XPCOMWillShutdown); + + // We want the service manager to be the subject of notifications + nsCOMPtr mgr; + Unused << NS_GetServiceManager(getter_AddRefs(mgr)); + MOZ_DIAGNOSTIC_ASSERT(mgr != nullptr, "Service manager not present!"); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::XPCOMShutdown, nullptr, do_QueryInterface(mgr)); + + // This must happen after the shutdown of media and widgets, which + // are triggered by the NS_XPCOM_SHUTDOWN_OBSERVER_ID notification. + gfxPlatform::ShutdownLayersIPC(); + + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::XPCOMShutdownThreads); +#ifdef DEBUG + // Prime an assertion at ThreadEventTarget::Dispatch to avoid late + // dispatches to non main-thread threads. + ThreadEventTarget::XPCOMShutdownThreadsNotificationFinished(); +#endif + + // Shutdown the timer thread and all timers that might still be alive + nsTimerImpl::Shutdown(); + + // Have an extra round of processing after the timers went away. + NS_ProcessPendingEvents(thread); + + // Shutdown all remaining threads. This method does not return until + // all threads created using the thread manager (with the exception of + // the main thread) have exited. + nsThreadManager::get().ShutdownNonMainThreads(); + + RefPtr observerService; + CallGetService("@mozilla.org/observer-service;1", + (nsObserverService**)getter_AddRefs(observerService)); + if (observerService) { + observerService->Shutdown(); + } + + // XPCOMShutdownFinal is the default phase for ClearOnShutdown. + // This AdvanceShutdownPhase will thus free most ClearOnShutdown()'ed + // smart pointers. Some destructors may fire extra main thread runnables + // that will be processed inside AdvanceShutdownPhase. + AppShutdown::AdvanceShutdownPhase(ShutdownPhase::XPCOMShutdownFinal); + + // Shutdown the main thread, processing our very last round of events, and + // then mark that we've finished main thread event processing. + nsThreadManager::get().ShutdownMainThread(); + gXPCOMMainThreadEventsAreDoomed = true; + + BackgroundHangMonitor().NotifyActivity(); + + mozilla::dom::JSExecutionManager::Shutdown(); + } + + // XPCOM is officially in shutdown mode NOW + // Set this only after the observers have been notified as this + // will cause servicemanager to become inaccessible. + mozilla::services::Shutdown(); + + // We may have AddRef'd for the caller of NS_InitXPCOM, so release it + // here again: + NS_IF_RELEASE(aServMgr); + + // Shutdown global servicemanager + if (nsComponentManagerImpl::gComponentManager) { + nsComponentManagerImpl::gComponentManager->FreeServices(); + } + + // Remove the remaining main thread representations + nsThreadManager::get().ReleaseMainThread(); + AbstractThread::ShutdownMainThread(); + + // Release the directory service + nsDirectoryService::gService = nullptr; + + free(gGREBinPath); + gGREBinPath = nullptr; + + // FIXME: This can cause harmless writes from sqlite committing + // log files. We have to ignore them before we can move + // the mozilla::PoisonWrite call before this point. See bug + // 834945 for the details. + mozJSModuleLoader::UnloadLoaders(); + + // Clear the profiler's JS context before cycle collection. The profiler will + // notify the JS engine that it can let go of any data it's holding on to for + // profiling purposes. + PROFILER_CLEAR_JS_CONTEXT(); + + bool shutdownCollect; +#ifdef NS_FREE_PERMANENT_DATA + shutdownCollect = true; +#else + shutdownCollect = !!PR_GetEnv("MOZ_CC_RUN_DURING_SHUTDOWN"); +#endif + nsCycleCollector_shutdown(shutdownCollect); + + // There can be code trying to refer to global objects during the final cc + // shutdown. This is the phase for such global objects to correctly release. + AppShutdown::AdvanceShutdownPhase(ShutdownPhase::CCPostLastCycleCollection); + + mozilla::scache::StartupCache::DeleteSingleton(); + mozilla::ScriptPreloader::DeleteSingleton(); + + PROFILER_MARKER_UNTYPED("Shutdown xpcom", OTHER); + + // Shutdown xpcom. This will release all loaders and cause others holding + // a refcount to the component manager to release it. + if (nsComponentManagerImpl::gComponentManager) { + DebugOnly rv = + (nsComponentManagerImpl::gComponentManager)->Shutdown(); + NS_ASSERTION(NS_SUCCEEDED(rv.value), "Component Manager shutdown failed."); + } else { + NS_WARNING("Component Manager was never created ..."); + } + + if (sInitializedJS) { + // Shut down the JS engine. + JS_ShutDown(); + sInitializedJS = false; + } + + mozilla::ScriptPreloader::DeleteCacheDataSingleton(); + + // Release shared memory which might be borrowed by the JS engine. + xpc::SelfHostedShmem::Shutdown(); + + // After all threads have been joined and the component manager has been shut + // down, any remaining objects that could be holding NSS resources (should) + // have been released, so we can safely shut down NSS. + if (NSS_IsInitialized()) { + nsNSSComponent::DoClearSSLExternalAndInternalSessionCache(); + if (NSS_Shutdown() != SECSuccess) { + // If you're seeing this crash and/or warning, some NSS resources are + // still in use (see bugs 1417680 and 1230312). Set the environment + // variable 'MOZ_IGNORE_NSS_SHUTDOWN_LEAKS' to some value to ignore this. + // Also, if leak checking is enabled, report this as a fake leak instead + // of crashing. +#if defined(DEBUG) && !defined(ANDROID) + if (!getenv("MOZ_IGNORE_NSS_SHUTDOWN_LEAKS") && + !getenv("XPCOM_MEM_BLOAT_LOG") && !getenv("XPCOM_MEM_LEAK_LOG") && + !getenv("XPCOM_MEM_REFCNT_LOG") && !getenv("XPCOM_MEM_COMPTR_LOG")) { + MOZ_CRASH("NSS_Shutdown failed"); + } else { +# ifdef NS_BUILD_REFCNT_LOGGING + // Create a fake leak. + NS_LogCtor((void*)0x100, "NSSShutdownFailed", 100); +# endif // NS_BUILD_REFCNT_LOGGING + NS_WARNING("NSS_Shutdown failed"); + } +#else + NS_WARNING("NSS_Shutdown failed"); +#endif // defined(DEBUG) && !defined(ANDROID) + } + } + + // Finally, release the component manager last because it unloads the + // libraries: + if (nsComponentManagerImpl::gComponentManager) { + nsrefcnt cnt; + NS_RELEASE2(nsComponentManagerImpl::gComponentManager, cnt); + NS_ASSERTION(cnt == 0, "Component Manager being held past XPCOM shutdown."); + } + nsComponentManagerImpl::gComponentManager = nullptr; + nsCategoryManager::Destroy(); + + GkRust_Shutdown(); + +#ifdef NS_FREE_PERMANENT_DATA + // By the time we're shutting down, there may still be async parse tasks going + // on in the Servo thread-pool. This is fairly uncommon, though not + // impossible. CSS parsing heavily uses the atom table, so obviously it's not + // fine to get rid of it. + // + // In leak-checking / ASAN / etc. builds, shut down the servo thread-pool, + // which will wait for all the work to be done. For other builds, we don't + // really want to wait on shutdown for possibly slow tasks. So just leak the + // atom table in those. + Servo_ShutdownThreadPool(); + NS_ShutdownAtomTable(); +#endif + + NS_IF_RELEASE(gDebug); + + delete sIOThread; + sIOThread = nullptr; + + delete sMessageLoop; + sMessageLoop = nullptr; + + mozilla::TaskController::Shutdown(); + + if (sCommandLineWasInitialized) { + CommandLine::Terminate(); + sCommandLineWasInitialized = false; + } + + delete sExitManager; + sExitManager = nullptr; + + Omnijar::CleanUp(); + + BackgroundHangMonitor::Shutdown(); + + delete sMainHangMonitor; + sMainHangMonitor = nullptr; + + NS_LogTerm(); + + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/build/XPCOMModule.h b/xpcom/build/XPCOMModule.h new file mode 100644 index 0000000000..e98afc21c0 --- /dev/null +++ b/xpcom/build/XPCOMModule.h @@ -0,0 +1,20 @@ +/* -*- 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/. */ + +#ifndef XPCOMModule_h +#define XPCOMModule_h + +#include "nsID.h" + +class nsISupports; + +nsresult nsThreadManagerGetSingleton(const nsIID& aIID, void** aInstancePtr); + +nsresult nsLocalFileConstructor(const nsIID& aIID, void** aInstancePtr); + +extern nsresult nsStringInputStreamConstructor(const nsID&, void**); + +#endif // XPCOMModule_h diff --git a/xpcom/build/XPCOMModule.inc b/xpcom/build/XPCOMModule.inc new file mode 100644 index 0000000000..4f860fd08e --- /dev/null +++ b/xpcom/build/XPCOMModule.inc @@ -0,0 +1,3 @@ +#if defined(XP_WIN) + COMPONENT(WINDOWSREGKEY, nsWindowsRegKeyConstructor) +#endif diff --git a/xpcom/build/XREAppData.h b/xpcom/build/XREAppData.h new file mode 100644 index 0000000000..61572dfc76 --- /dev/null +++ b/xpcom/build/XREAppData.h @@ -0,0 +1,231 @@ +/* -*- 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/. */ + +#ifndef nsXREAppData_h +#define nsXREAppData_h + +#include +#include "mozilla/Attributes.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsCOMPtr.h" +#include "nsCRTGlue.h" +#include "nsStringFwd.h" +#include "nsIFile.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +namespace sandbox { +class BrokerServices; +} +#endif + +namespace mozilla { + +struct StaticXREAppData; + +/** + * Application-specific data needed to start the apprunner. + */ +class XREAppData { + public: + XREAppData() = default; + ~XREAppData() = default; + XREAppData(const XREAppData& aOther) { *this = aOther; } + + explicit XREAppData(const StaticXREAppData& aOther) { *this = aOther; } + + XREAppData& operator=(const StaticXREAppData& aOther); + XREAppData& operator=(const XREAppData& aOther); + XREAppData& operator=(XREAppData&& aOther) = default; + + // Lots of code reads these fields directly like a struct, so rather + // than using UniquePtr directly, use an auto-converting wrapper. + class CharPtr { + public: + explicit CharPtr() = default; + explicit CharPtr(const char* v) { *this = v; } + CharPtr(CharPtr&&) = default; + ~CharPtr() = default; + + CharPtr& operator=(const char* v) { + if (v) { + mValue.reset(NS_xstrdup(v)); + } else { + mValue = nullptr; + } + return *this; + } + CharPtr& operator=(const CharPtr& v) { + *this = (const char*)v; + return *this; + } + + operator const char*() const { return mValue.get(); } + + private: + UniqueFreePtr mValue; + }; + + /** + * The directory of the application to be run. May be null if the + * xulrunner and the app are installed into the same directory. + */ + nsCOMPtr directory; + + /** + * The name of the application vendor. This must be ASCII, and is normally + * mixed-case, e.g. "Mozilla". Optional (may be null), but highly + * recommended. Must not be the empty string. + */ + CharPtr vendor; + + /** + * The name of the application. This must be ASCII, and is normally + * mixed-case, e.g. "Firefox". Required (must not be null or an empty + * string). + */ + CharPtr name; + + /** + * The internal name of the application for remoting purposes. When left + * unspecified, "name" is used instead. This must be ASCII, and is normally + * lowercase, e.g. "firefox". Optional (may be null but not an empty string). + */ + CharPtr remotingName; + + /** + * The major version, e.g. "0.8.0+". Optional (may be null), but + * required for advanced application features such as the extension + * manager and update service. Must not be the empty string. + */ + CharPtr version; + + /** + * The application's build identifier, e.g. "2004051604" + */ + CharPtr buildID; + + /** + * The application's UUID. Used by the extension manager to determine + * compatible extensions. Optional, but required for advanced application + * features such as the extension manager and update service. + * + * This has traditionally been in the form + * "{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}" but for new applications + * a more readable form is encouraged: "appname@vendor.tld". Only + * the following characters are allowed: a-z A-Z 0-9 - . @ _ { } * + */ + CharPtr ID; + + /** + * The copyright information to print for the -h commandline flag, + * e.g. "Copyright (c) 2003 mozilla.org". + */ + CharPtr copyright; + + /** + * Combination of NS_XRE_ prefixed flags (defined below). + */ + uint32_t flags = 0; + + /** + * The location of the XRE. XRE_main may not be able to figure this out + * programatically. + */ + nsCOMPtr xreDirectory; + + /** + * The minimum/maximum compatible XRE version. + */ + CharPtr minVersion; + CharPtr maxVersion; + + /** + * The server URL to send crash reports to. + */ + CharPtr crashReporterURL; + + /** + * The profile directory that will be used. Optional (may be null). Must not + * be the empty string, must be ASCII. The path is split into components + * along the path separator characters '/' and '\'. + * + * The application data directory ("UAppData", see below) is normally + * composed as follows, where $HOME is platform-specific: + * + * UAppData = $HOME[/$vendor]/$name + * + * If present, the 'profile' string will be used instead of the combination of + * vendor and name as follows: + * + * UAppData = $HOME/$profile + */ + CharPtr profile; + + /** + * The application name to use in the User Agent string. + */ + CharPtr UAName; + + /** + * The URL to the source revision for this build of the application. + */ + CharPtr sourceURL; + + /** + * The URL to use to check for updates. + */ + CharPtr updateURL; + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox BrokerServices. + */ + sandbox::BrokerServices* sandboxBrokerServices = nullptr; +#endif + + // Returns a name suitable for DBUS services. + static void SanitizeNameForDBus(nsACString&); + void GetDBusAppName(nsACString&) const; +}; + +/** + * Indicates whether or not the profile migrator service may be + * invoked at startup when creating a profile. + */ +#define NS_XRE_ENABLE_PROFILE_MIGRATOR (1 << 1) + +/** + * Indicates whether or not to use Breakpad crash reporting. + */ +#define NS_XRE_ENABLE_CRASH_REPORTER (1 << 3) + +/** + * A static version of the XRE app data is compiled into the application + * so that it is not necessary to read application.ini at startup. + * + * This structure is initialized into and matches nsXREAppData + */ +struct StaticXREAppData { + const char* vendor; + const char* name; + const char* remotingName; + const char* version; + const char* buildID; + const char* ID; + const char* copyright; + uint32_t flags; + const char* minVersion; + const char* maxVersion; + const char* crashReporterURL; + const char* profile; + const char* UAName; + const char* sourceURL; + const char* updateURL; +}; + +} // namespace mozilla + +#endif // XREAppData_h diff --git a/xpcom/build/XREChildData.h b/xpcom/build/XREChildData.h new file mode 100644 index 0000000000..8050fb8afc --- /dev/null +++ b/xpcom/build/XREChildData.h @@ -0,0 +1,44 @@ +/* -*- 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/. */ + +#ifndef XREChildData_h +#define XREChildData_h + +#include "mozilla/UniquePtr.h" + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "mozilla/sandboxing/loggingTypes.h" + +namespace sandbox { +class BrokerServices; +class TargetServices; +} // namespace sandbox +#endif + +/** + * Data needed to start a child process. + */ +struct XREChildData { +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox TargetServices. + */ + sandbox::TargetServices* sandboxTargetServices = nullptr; + + /** + * Function to provide a logging function to the chromium sandbox code. + */ + mozilla::sandboxing::ProvideLogFunctionCb ProvideLogFunction = nullptr; + + /** + * Chromium sandbox broker services; needed by the remote sandbox + * launcher process. + */ + sandbox::BrokerServices* sandboxBrokerServices = nullptr; +#endif +}; + +#endif // XREChildData_h diff --git a/xpcom/build/XREShellData.h b/xpcom/build/XREShellData.h new file mode 100644 index 0000000000..a0f736f658 --- /dev/null +++ b/xpcom/build/XREShellData.h @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +#ifndef XREShellData_h +#define XREShellData_h + +#if defined(LIBFUZZER) +# include "FuzzerRegistry.h" // LibFuzzerDriver +#endif + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +namespace sandbox { +class BrokerServices; +} +#endif + +/** + * Data needed by XRE_XPCShellMain. + */ +struct XREShellData { +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /** + * Chromium sandbox BrokerServices. + */ + sandbox::BrokerServices* sandboxBrokerServices; +#endif +#if defined(ANDROID) + FILE* outFile; + FILE* errFile; +#endif +#if defined(LIBFUZZER) + LibFuzzerDriver fuzzerDriver; +#endif +}; + +#endif // XREShellData_h diff --git a/xpcom/build/components.conf b/xpcom/build/components.conf new file mode 100644 index 0000000000..6bf301c26f --- /dev/null +++ b/xpcom/build/components.conf @@ -0,0 +1,266 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{c521a612-2aad-46db-b6ab-3b821fb150b1}', + 'contract_ids': ['@mozilla.org/binaryinputstream;1'], + 'type': 'nsBinaryInputStream', + 'headers': ['/xpcom/io/nsBinaryStream.h'], + }, + { + 'cid': '{86c37b9a-74e7-4672-844e-6e7dd83ba484}', + 'contract_ids': ['@mozilla.org/binaryoutputstream;1'], + 'type': 'nsBinaryOutputStream', + 'headers': ['/xpcom/io/nsBinaryStream.h'], + }, + { + 'cid': '{61ba33c0-3031-11d3-8cd0-0060b0fc14a3}', + 'contract_ids': ['@mozilla.org/network/protocol;1?name=chrome'], + 'type': 'nsChromeProtocolHandler', + 'headers': ['/chrome/nsChromeProtocolHandler.h'], + 'protocol_config': { + 'scheme': 'chrome', + 'flags': [ + 'URI_STD', + 'URI_IS_UI_RESOURCE', + 'URI_IS_LOCAL_RESOURCE', + ], + }, + }, + { + 'name': 'ChromeRegistry', + 'cid': '{47049e42-1d87-482a-984d-56ae185e367a}', + 'contract_ids': ['@mozilla.org/chrome/chrome-registry;1'], + 'singleton': True, + 'type': 'nsChromeRegistry', + 'headers': ['/chrome/nsChromeRegistry.h'], + 'constructor': 'nsChromeRegistry::GetSingleton', + }, + { + 'js_name': 'console', + 'cid': '{7e3ff85c-1dd2-11b2-8d4b-eb452cb0ff40}', + 'contract_ids': ['@mozilla.org/consoleservice;1'], + 'interfaces': ['nsIConsoleService'], + 'type': 'nsConsoleService', + 'headers': ['/xpcom/base/nsConsoleService.h'], + 'init_method': 'Init', + }, + { + 'cid': '{678c50b8-6bcb-4ad0-b9b8-c81175955199}', + 'contract_ids': ['@mozilla.org/hash-property-bag;1'], + 'type': 'nsHashPropertyBagCC', + 'headers': ['nsHashPropertyBag.h'], + }, + { + 'cid': '{eb833911-4f49-4623-845f-e58a8e6de4c2}', + 'contract_ids': ['@mozilla.org/io-util;1'], + 'type': 'nsIOUtil', + 'headers': ['/xpcom/io/nsIOUtil.h'], + }, + { + 'cid': '{2e23e220-60be-11d3-8c4a-000064657374}', + 'contract_ids': ['@mozilla.org/file/local;1'], + 'legacy_constructor': 'nsLocalFileConstructor', + }, + { + 'cid': '{00bd71fb-7f09-4ec3-96af-a0b522b77969}', + 'contract_ids': ['@mozilla.org/memory-info-dumper;1'], + 'type': 'nsMemoryInfoDumper', + 'headers': ['mozilla/nsMemoryInfoDumper.h'], + }, + { + 'cid': '{fb97e4f5-32dd-497a-baa2-7d1e55079910}', + 'contract_ids': ['@mozilla.org/memory-reporter-manager;1'], + 'type': 'nsMemoryReporterManager', + 'headers': ['/xpcom/base/nsMemoryReporterManager.h'], + 'init_method': 'Init', + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{7b4eeb20-d781-11d4-8a83-0010a4e0c9ca}', + 'contract_ids': ['@mozilla.org/process/util;1'], + 'type': 'nsProcess', + 'headers': ['nsProcess.h'], + }, + { + 'cid': '{aaf68860-f849-40ee-bb7a-b229bce036a3}', + 'contract_ids': ['@mozilla.org/scriptablebase64encoder;1'], + 'type': 'nsScriptableBase64Encoder', + 'headers': ['/xpcom/io/nsScriptableBase64Encoder.h'], + }, + { + 'cid': '{43ebf210-8a7b-4ddb-a83d-b87c51a058db}', + 'contract_ids': ['@mozilla.org/securityconsole/message;1'], + 'type': 'nsSecurityConsoleMessage', + 'headers': ['/xpcom/base/nsSecurityConsoleMessage.h'], + }, + { + 'cid': '{669a9795-6ff7-4ed4-9150-c34ce2971b63}', + 'contract_ids': ['@mozilla.org/storagestream;1'], + 'type': 'nsStorageStream', + 'headers': ['nsStorageStream.h'], + }, + { + 'cid': '{acf8dc41-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-cstring;1'], + 'type': 'nsSupportsCString', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4a-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-char;1'], + 'type': 'nsSupportsChar', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{cbf86871-4ac0-11d3-baea-00805f8a5dd7}', + 'contract_ids': ['@mozilla.org/supports-double;1'], + 'type': 'nsSupportsDouble', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{cbf86870-4ac0-11d3-baea-00805f8a5dd7}', + 'contract_ids': ['@mozilla.org/supports-float;1'], + 'type': 'nsSupportsFloat', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{a99febba-1dd1-11b2-a943-b02334a6d083}', + 'contract_ids': ['@mozilla.org/supports-interface-pointer;1'], + 'type': 'nsSupportsInterfacePointer', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc43-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRBool;1'], + 'type': 'nsSupportsPRBool', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4b-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRInt16;1'], + 'type': 'nsSupportsPRInt16', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4c-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRInt32;1'], + 'type': 'nsSupportsPRInt32', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc4d-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRInt64;1'], + 'type': 'nsSupportsPRInt64', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc49-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRTime;1'], + 'type': 'nsSupportsPRTime', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc46-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint16;1'], + 'type': 'nsSupportsPRUint16', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc47-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint32;1'], + 'type': 'nsSupportsPRUint32', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc48-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint64;1'], + 'type': 'nsSupportsPRUint64', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc44-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-PRUint8;1'], + 'type': 'nsSupportsPRUint8', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'cid': '{acf8dc42-4a25-11d3-9890-006008962422}', + 'contract_ids': ['@mozilla.org/supports-string;1'], + 'type': 'nsSupportsString', + 'headers': ['nsSupportsPrimitives.h'], + }, + { + 'js_name': 'sysinfo', + 'cid': '{d962398a-99e5-49b2-857a-c159049c7f6c}', + 'contract_ids': ['@mozilla.org/system-info;1'], + 'interfaces': ['nsIPropertyBag2', 'nsISystemInfo'], + 'type': 'nsSystemInfo', + 'headers': ['nsSystemInfo.h'], + 'init_method': 'Init', + 'overridable': True, + }, + { + 'js_name': 'tm', + 'cid': '{7a4204c6-e45a-4c37-8ebb-6709a22c917c}', + 'contract_ids': ['@mozilla.org/thread-manager;1'], + 'interfaces': ['nsIThreadManager'], + 'legacy_constructor': 'nsThreadManagerGetSingleton', + 'headers': ['/xpcom/build/XPCOMModule.h'], + }, + { + 'js_name': 'uuid', + 'name': 'UUIDGenerator', + 'cid': '{706d36bb-bf79-4293-81f2-8f6828c18f9d}', + 'contract_ids': ['@mozilla.org/uuid-generator;1'], + 'interfaces': ['nsIUUIDGenerator'], + 'type': 'nsUUIDGenerator', + 'headers': ['/xpcom/base/nsUUIDGenerator.h'], + 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS, + }, + { + 'cid': '{0d6ea1d0-879c-11d5-90ef-0010a4e73d9a}', + 'contract_ids': ['@mozilla.org/variant;1'], + 'type': 'nsVariantCC', + 'headers': ['nsVariant.h'], + }, + { + 'js_name': 'vc', + 'cid': '{c6e47036-ca94-4be3-963a-9abd8705f7a8}', + 'contract_ids': ['@mozilla.org/xpcom/version-comparator;1'], + 'interfaces': ['nsIVersionComparator'], + 'type': 'nsVersionComparatorImpl', + 'headers': ['/xpcom/base/nsVersionComparatorImpl.h'], + }, + { + 'cid': '{dfac10a9-dd24-43cf-a095-6ffa2e4b6a6c}', + 'contract_ids': ['@mozilla.org/xpcom/ini-parser-factory;1'], + 'type': 'nsINIParserFactory', + 'headers': ['/xpcom/ds/nsINIParserImpl.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS, + }, +] + +if buildconfig.substs['OS_ARCH'] == 'WINNT': + Classes += [ + { + 'cid': '{a53bc624-d577-4839-b8ec-bb5040a52ff4}', + 'contract_ids': ['@mozilla.org/windows-registry-key;1'], + 'legacy_constructor': 'nsWindowsRegKeyConstructor', + 'headers': ['nsWindowsRegKey.h'], + }, + ] + +if buildconfig.substs['MOZ_WIDGET_TOOLKIT'] == 'cocoa': + Classes += [ + { + 'cid': '{b0f20595-88ce-4738-a1a4-24de78eb8051}', + 'contract_ids': ['@mozilla.org/mac-preferences-reader;1'], + 'type': 'nsMacPreferencesReader', + 'headers': ['mozilla/nsMacPreferencesReader.h'], + }, + ] diff --git a/xpcom/build/gen_process_types.py b/xpcom/build/gen_process_types.py new file mode 100644 index 0000000000..9e16ecfd4c --- /dev/null +++ b/xpcom/build/gen_process_types.py @@ -0,0 +1,34 @@ +# 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/. + +from geckoprocesstypes import process_types + + +def main(output): + output.write( + """\ +/* THIS FILE IS GENERATED BY gen_process_types.py - DO NOT EDIT */ +""" + ) + + for p in process_types: + output.write( + """ +#ifndef SKIP_PROCESS_TYPE_%(allcaps_name)s +GECKO_PROCESS_TYPE(%(enum_value)d, %(enum_name)s, "%(string_name)s", """ + """%(proc_typename)s, %(process_bin_type)s, %(procinfo_typename)s, """ + """%(webidl_typename)s, %(allcaps_name)s) +#endif // SKIP_PROCESS_TYPE_%(allcaps_name)s +""" + % { + "enum_value": p.enum_value, + "enum_name": p.enum_name, + "string_name": p.string_name, + "proc_typename": p.proc_typename, + "process_bin_type": p.process_bin_type, + "procinfo_typename": p.procinfo_typename, + "webidl_typename": p.webidl_typename, + "allcaps_name": p.allcaps_name, + } + ) diff --git a/xpcom/build/mach_override.c b/xpcom/build/mach_override.c new file mode 100644 index 0000000000..1ece045e2c --- /dev/null +++ b/xpcom/build/mach_override.c @@ -0,0 +1,793 @@ +// Copied from upstream at revision 195c13743fe0ebc658714e2a9567d86529f20443. +// mach_override.c semver:1.2.0 +// Copyright (c) 2003-2012 Jonathan 'Wolf' Rentzsch: http://rentzsch.com +// Some rights reserved: http://opensource.org/licenses/mit +// https://github.com/rentzsch/mach_override + +#include "mach_override.h" + +#include +#include +#include +#include +#include + +#include + +/************************** +* +* Constants +* +**************************/ +#pragma mark - +#pragma mark (Constants) + +#define kPageSize 4096 +#if defined(__ppc__) || defined(__POWERPC__) + +long kIslandTemplate[] = { + 0x9001FFFC, // stw r0,-4(SP) + 0x3C00DEAD, // lis r0,0xDEAD + 0x6000BEEF, // ori r0,r0,0xBEEF + 0x7C0903A6, // mtctr r0 + 0x8001FFFC, // lwz r0,-4(SP) + 0x60000000, // nop ; optionally replaced + 0x4E800420 // bctr +}; + +#define kAddressHi 3 +#define kAddressLo 5 +#define kInstructionHi 10 +#define kInstructionLo 11 + +#elif defined(__i386__) + +#define kOriginalInstructionsSize 16 +// On X86 we migh need to instert an add with a 32 bit immediate after the +// original instructions. +#define kMaxFixupSizeIncrease 5 + +unsigned char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xE9, 0xEF, 0xBE, 0xAD, 0xDE +}; + +#define kInstructions 0 +#define kJumpAddress kInstructions + kOriginalInstructionsSize + 1 +#elif defined(__x86_64__) + +#define kOriginalInstructionsSize 32 +// On X86-64 we never need to instert a new instruction. +#define kMaxFixupSizeIncrease 0 + +#define kJumpAddress kOriginalInstructionsSize + 6 + +unsigned char kIslandTemplate[] = { + // kOriginalInstructionsSize nop instructions so that we + // should have enough space to host original instructions + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + // Now the real jump instruction + 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +#endif + +/************************** +* +* Data Types +* +**************************/ +#pragma mark - +#pragma mark (Data Types) + +typedef struct { + char instructions[sizeof(kIslandTemplate)]; +} BranchIsland; + +/************************** +* +* Funky Protos +* +**************************/ +#pragma mark - +#pragma mark (Funky Protos) + +static mach_error_t +allocateBranchIsland( + BranchIsland **island, + void *originalFunctionAddress); + + mach_error_t +freeBranchIsland( + BranchIsland *island ); + +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ); +#endif + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ); +void +atomic_mov64( + uint64_t *targetAddress, + uint64_t value ); + + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ); + + static void +fixupInstructions( + uint32_t offset, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ); +#endif + +/******************************************************************************* +* +* Interface +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Interface) + +#if defined(__i386__) || defined(__x86_64__) +mach_error_t makeIslandExecutable(void *address) { + mach_error_t err = err_none; + uintptr_t page = (uintptr_t)address & ~(uintptr_t)(kPageSize-1); + int e = err_none; + e |= mprotect((void *)page, kPageSize, PROT_EXEC | PROT_READ | PROT_WRITE); + e |= msync((void *)page, kPageSize, MS_INVALIDATE ); + if (e) { + err = err_cannot_override; + } + return err; +} +#endif + + mach_error_t +mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ) +{ + assert( originalFunctionAddress ); + assert( overrideFunctionAddress ); + + // this addresses overriding such functions as AudioOutputUnitStart() + // test with modified DefaultOutputUnit project +#if defined(__x86_64__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp qword near [rip+0x????????] + originalFunctionAddress=*(void**)((char*)originalFunctionAddress+6+*(int32_t *)((uint16_t*)originalFunctionAddress+1)); + else break; + } +#elif defined(__i386__) + for(;;){ + if(*(uint16_t*)originalFunctionAddress==0x25FF) // jmp *0x???????? + originalFunctionAddress=**(void***)((uint16_t*)originalFunctionAddress+1); + else break; + } +#endif + + long *originalFunctionPtr = (long*) originalFunctionAddress; + mach_error_t err = err_none; + +#if defined(__ppc__) || defined(__POWERPC__) + // Ensure first instruction isn't 'mfctr'. + #define kMFCTRMask 0xfc1fffff + #define kMFCTRInstruction 0x7c0903a6 + + long originalInstruction = *originalFunctionPtr; + if( !err && ((originalInstruction & kMFCTRMask) == kMFCTRInstruction) ) + err = err_cannot_override; +#elif defined(__i386__) || defined(__x86_64__) + int eatenCount = 0; + int originalInstructionCount = 0; + char originalInstructions[kOriginalInstructionsSize]; + uint8_t originalInstructionSizes[kOriginalInstructionsSize]; + uint64_t jumpRelativeInstruction = 0; // JMP + + Boolean overridePossible = eatKnownInstructions ((unsigned char *)originalFunctionPtr, + &jumpRelativeInstruction, &eatenCount, + originalInstructions, &originalInstructionCount, + originalInstructionSizes ); + if (eatenCount + kMaxFixupSizeIncrease > kOriginalInstructionsSize) { + //printf ("Too many instructions eaten\n"); + overridePossible = false; + } + if (!overridePossible) err = err_cannot_override; + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); +#endif + + // Make the original function implementation writable. + if( !err ) { + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_ALL | VM_PROT_COPY) ); + if( err ) + err = vm_protect( mach_task_self(), + (vm_address_t) originalFunctionPtr, 8, false, + (VM_PROT_DEFAULT | VM_PROT_COPY) ); + } + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + // Allocate and target the escape island to the overriding function. + BranchIsland *escapeIsland = NULL; + if( !err ) { + err = allocateBranchIsland( &escapeIsland, originalFunctionAddress ); + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + } + +#if defined(__ppc__) || defined(__POWERPC__) + if( !err ) + err = setBranchIslandTarget( escapeIsland, overrideFunctionAddress, 0 ); + + // Build the branch absolute instruction to the escape island. + long branchAbsoluteInstruction = 0; // Set to 0 just to silence warning. + if( !err ) { + long escapeIslandAddress = ((long) escapeIsland) & 0x3FFFFFF; + branchAbsoluteInstruction = 0x48000002 | escapeIslandAddress; + } +#elif defined(__i386__) || defined(__x86_64__) + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + + if( !err ) + err = setBranchIslandTarget_i386( escapeIsland, overrideFunctionAddress, 0 ); + + if (err) fprintf(stderr, "err = %x %s:%d\n", err, __FILE__, __LINE__); + // Build the jump relative instruction to the escape island +#endif + + +#if defined(__i386__) || defined(__x86_64__) + if (!err) { + uint32_t addressOffset = ((char*)escapeIsland - (char*)originalFunctionPtr - 5); + addressOffset = OSSwapInt32(addressOffset); + + jumpRelativeInstruction |= 0xE900000000000000LL; + jumpRelativeInstruction |= ((uint64_t)addressOffset & 0xffffffff) << 24; + jumpRelativeInstruction = OSSwapInt64(jumpRelativeInstruction); + } +#endif + + // Optionally allocate & return the reentry island. This may contain relocated + // jmp instructions and so has all the same addressing reachability requirements + // the escape island has to the original function, except the escape island is + // technically our original function. + BranchIsland *reentryIsland = NULL; + if( !err && originalFunctionReentryIsland ) { + err = allocateBranchIsland( &reentryIsland, escapeIsland); + if( !err ) + *originalFunctionReentryIsland = reentryIsland; + } + +#if defined(__ppc__) || defined(__POWERPC__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instruction into the reentry island. + // o Target the reentry island at the 2nd instruction of the + // original function. + // o Replace the original instruction with the branch absolute. + if( !err ) { + int escapeIslandEngaged = false; + do { + if( reentryIsland ) + err = setBranchIslandTarget( reentryIsland, + (void*) (originalFunctionPtr+1), originalInstruction ); + if( !err ) { + escapeIslandEngaged = CompareAndSwap( originalInstruction, + branchAbsoluteInstruction, + (UInt32*)originalFunctionPtr ); + if( !escapeIslandEngaged ) { + // Someone replaced the instruction out from under us, + // re-read the instruction, make sure it's still not + // 'mfctr' and try again. + originalInstruction = *originalFunctionPtr; + if( (originalInstruction & kMFCTRMask) == kMFCTRInstruction) + err = err_cannot_override; + } + } + } while( !err && !escapeIslandEngaged ); + } +#elif defined(__i386__) || defined(__x86_64__) + // Atomically: + // o If the reentry island was allocated: + // o Insert the original instructions into the reentry island. + // o Target the reentry island at the first non-replaced + // instruction of the original function. + // o Replace the original first instructions with the jump relative. + // + // Note that on i386, we do not support someone else changing the code under our feet + if ( !err ) { + uint32_t offset = (uintptr_t)originalFunctionPtr - (uintptr_t)reentryIsland; + fixupInstructions(offset, originalInstructions, + originalInstructionCount, originalInstructionSizes ); + + if( reentryIsland ) + err = setBranchIslandTarget_i386( reentryIsland, + (void*) ((char *)originalFunctionPtr+eatenCount), originalInstructions ); + // try making islands executable before planting the jmp +#if defined(__x86_64__) || defined(__i386__) + if( !err ) + err = makeIslandExecutable(escapeIsland); + if( !err && reentryIsland ) + err = makeIslandExecutable(reentryIsland); +#endif + if ( !err ) + atomic_mov64((uint64_t *)originalFunctionPtr, jumpRelativeInstruction); + } +#endif + + // Clean up on error. + if( err ) { + if( reentryIsland ) + freeBranchIsland( reentryIsland ); + if( escapeIsland ) + freeBranchIsland( escapeIsland ); + } + + return err; +} + +/******************************************************************************* +* +* Implementation +* +*******************************************************************************/ +#pragma mark - +#pragma mark (Implementation) + +static bool jump_in_range(intptr_t from, intptr_t to) { + intptr_t field_value = to - from - 5; + int32_t field_value_32 = field_value; + return field_value == field_value_32; +} + +/******************************************************************************* + Implementation: Allocates memory for a branch island. + + @param island <- The allocated island. + @result <- mach_error_t + + ***************************************************************************/ + +static mach_error_t +allocateBranchIslandAux( + BranchIsland **island, + void *originalFunctionAddress, + bool forward) +{ + assert( island ); + assert( sizeof( BranchIsland ) <= kPageSize ); + + vm_map_t task_self = mach_task_self(); + vm_address_t original_address = (vm_address_t) originalFunctionAddress; + vm_address_t address = original_address; + + for (;;) { + vm_size_t vmsize = 0; + memory_object_name_t object = 0; + kern_return_t kr = 0; + vm_region_flavor_t flavor = VM_REGION_BASIC_INFO; + // Find the region the address is in. +#if __WORDSIZE == 32 + vm_region_basic_info_data_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT; + kr = vm_region(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); +#else + vm_region_basic_info_data_64_t info; + mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64; + kr = vm_region_64(task_self, &address, &vmsize, flavor, + (vm_region_info_t)&info, &info_count, &object); +#endif + if (kr != KERN_SUCCESS) + return kr; + assert((address & (kPageSize - 1)) == 0); + + // Go to the first page before or after this region + vm_address_t new_address = forward ? address + vmsize : address - kPageSize; +#if __WORDSIZE == 64 + if(!jump_in_range(original_address, new_address)) + break; +#endif + address = new_address; + + // Try to allocate this page. + kr = vm_allocate(task_self, &address, kPageSize, 0); + if (kr == KERN_SUCCESS) { + *island = (BranchIsland*) address; + return err_none; + } + if (kr != KERN_NO_SPACE) + return kr; + } + + return KERN_NO_SPACE; +} + +static mach_error_t +allocateBranchIsland( + BranchIsland **island, + void *originalFunctionAddress) +{ + mach_error_t err = + allocateBranchIslandAux(island, originalFunctionAddress, true); + if (!err) + return err; + return allocateBranchIslandAux(island, originalFunctionAddress, false); +} + + +/******************************************************************************* + Implementation: Deallocates memory for a branch island. + + @param island -> The island to deallocate. + @result <- mach_error_t + + ***************************************************************************/ + + mach_error_t +freeBranchIsland( + BranchIsland *island ) +{ + assert( island ); + assert( (*(long*)&island->instructions[0]) == kIslandTemplate[0] ); + assert( sizeof( BranchIsland ) <= kPageSize ); + return vm_deallocate( mach_task_self(), (vm_address_t) island, + kPageSize ); +} + +/******************************************************************************* + Implementation: Sets the branch island's target, with an optional + instruction. + + @param island -> The branch island to insert target into. + @param branchTo -> The address of the target. + @param instruction -> Optional instruction to execute prior to branch. Set + to zero for nop. + @result <- mach_error_t + + ***************************************************************************/ +#if defined(__ppc__) || defined(__POWERPC__) + mach_error_t +setBranchIslandTarget( + BranchIsland *island, + const void *branchTo, + long instruction ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Fill in the address. + ((short*)island->instructions)[kAddressLo] = ((long) branchTo) & 0x0000FFFF; + ((short*)island->instructions)[kAddressHi] + = (((long) branchTo) >> 16) & 0x0000FFFF; + + // Fill in the (optional) instuction. + if( instruction != 0 ) { + ((short*)island->instructions)[kInstructionLo] + = instruction & 0x0000FFFF; + ((short*)island->instructions)[kInstructionHi] + = (instruction >> 16) & 0x0000FFFF; + } + + //MakeDataExecutable( island->instructions, sizeof( kIslandTemplate ) ); + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + +#if defined(__i386__) + mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // copy original instructions + if (instructions) { + bcopy (instructions, island->instructions + kInstructions, kOriginalInstructionsSize); + } + + // Fill in the address. + int32_t addressOffset = (char *)branchTo - (island->instructions + kJumpAddress + 4); + *((int32_t *)(island->instructions + kJumpAddress)) = addressOffset; + + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + return err_none; +} + +#elif defined(__x86_64__) +mach_error_t +setBranchIslandTarget_i386( + BranchIsland *island, + const void *branchTo, + char* instructions ) +{ + // Copy over the template code. + bcopy( kIslandTemplate, island->instructions, sizeof( kIslandTemplate ) ); + + // Copy original instructions. + if (instructions) { + bcopy (instructions, island->instructions, kOriginalInstructionsSize); + } + + // Fill in the address. + *((uint64_t *)(island->instructions + kJumpAddress)) = (uint64_t)branchTo; + msync( island->instructions, sizeof( kIslandTemplate ), MS_INVALIDATE ); + + return err_none; +} +#endif + + +#if defined(__i386__) || defined(__x86_64__) +// simplistic instruction matching +typedef struct { + unsigned int length; // max 15 + unsigned char mask[15]; // sequence of bytes in memory order + unsigned char constraint[15]; // sequence of bytes in memory order +} AsmInstructionMatch; + +#if defined(__i386__) +static AsmInstructionMatch possibleInstructions[] = { + // clang-format off + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x5, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0x55, 0x89, 0xe5, 0xc9, 0xc3} }, // push %ebp; mov %esp,%ebp; leave; ret + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xFF}, {0x55} }, // push %esp + { 0x2, {0xFF, 0xFF}, {0x89, 0xE5} }, // mov %esp,%ebp + { 0x1, {0xFF}, {0x53} }, // push %ebx + { 0x3, {0xFF, 0xFF, 0x00}, {0x83, 0xEC, 0x00} }, // sub 0x??, %esp + { 0x6, {0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x81, 0xEC, 0x00, 0x00, 0x00, 0x00} }, // sub 0x??, %esp with 32bit immediate + { 0x1, {0xFF}, {0x57} }, // push %edi + { 0x1, {0xFF}, {0x56} }, // push %esi + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x3, {0xFF, 0x4F, 0x00}, {0x8B, 0x45, 0x00} }, // mov $imm(%ebp), %reg + { 0x3, {0xFF, 0x4C, 0x00}, {0x8B, 0x40, 0x00} }, // mov $imm(%eax-%edx), %reg + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x8B, 0x4C, 0x24, 0x00} }, // mov $imm(%esp), %ecx + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %eax + { 0x6, {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, {0xE8, 0x00, 0x00, 0x00, 0x00, 0x58} }, // call $imm; pop %eax + { 0x0 } + // clang-format on +}; +#elif defined(__x86_64__) +static AsmInstructionMatch possibleInstructions[] = { + // clang-format off + { 0x5, {0xFF, 0x00, 0x00, 0x00, 0x00}, {0xE9, 0x00, 0x00, 0x00, 0x00} }, // jmp 0x???????? + { 0x1, {0xFF}, {0x90} }, // nop + { 0x1, {0xF8}, {0x50} }, // push %rX + { 0x3, {0xFF, 0xFF, 0xFF}, {0x48, 0x89, 0xE5} }, // mov %rsp,%rbp + { 0x4, {0xFF, 0xFF, 0xFF, 0x00}, {0x48, 0x83, 0xEC, 0x00} }, // sub 0x??, %rsp + { 0x4, {0xFB, 0xFF, 0x00, 0x00}, {0x48, 0x89, 0x00, 0x00} }, // move onto rbp + { 0x4, {0xFF, 0xFF, 0xFF, 0xFF}, {0x40, 0x0f, 0xbe, 0xce} }, // movsbl %sil, %ecx + { 0x2, {0xFF, 0x00}, {0x41, 0x00} }, // push %rXX + { 0x2, {0xFF, 0x00}, {0x85, 0x00} }, // test %rX,%rX + { 0x5, {0xF8, 0x00, 0x00, 0x00, 0x00}, {0xB8, 0x00, 0x00, 0x00, 0x00} }, // mov $imm, %reg + { 0x3, {0xFF, 0xFF, 0x00}, {0xFF, 0x77, 0x00} }, // pushq $imm(%rdi) + { 0x2, {0xFF, 0xFF}, {0x31, 0xC0} }, // xor %eax, %eax + { 0x2, {0xFF, 0xFF}, {0x89, 0xF8} }, // mov %edi, %eax + + //leaq offset(%rip),%rax + { 0x7, {0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, {0x48, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00} }, + + { 0x0 } + // clang-format on +}; +#endif + +static Boolean codeMatchesInstruction(unsigned char *code, AsmInstructionMatch* instruction) +{ + Boolean match = true; + + size_t i; + for (i=0; ilength; i++) { + unsigned char mask = instruction->mask[i]; + unsigned char constraint = instruction->constraint[i]; + unsigned char codeValue = code[i]; + + match = ((codeValue & mask) == constraint); + if (!match) break; + } + + return match; +} + +#if defined(__i386__) || defined(__x86_64__) + static Boolean +eatKnownInstructions( + unsigned char *code, + uint64_t *newInstruction, + int *howManyEaten, + char *originalInstructions, + int *originalInstructionCount, + uint8_t *originalInstructionSizes ) +{ + Boolean allInstructionsKnown = true; + int totalEaten = 0; + unsigned char* ptr = code; + int remainsToEat = 5; // a JMP instruction takes 5 bytes + int instructionIndex = 0; + + if (howManyEaten) *howManyEaten = 0; + if (originalInstructionCount) *originalInstructionCount = 0; + while (remainsToEat > 0) { + Boolean curInstructionKnown = false; + + // See if instruction matches one we know + AsmInstructionMatch* curInstr = possibleInstructions; + do { + if ((curInstructionKnown = codeMatchesInstruction(ptr, curInstr))) break; + curInstr++; + } while (curInstr->length > 0); + + // if all instruction matches failed, we don't know current instruction then, stop here + if (!curInstructionKnown) { + allInstructionsKnown = false; + fprintf(stderr, "mach_override: some instructions unknown! Need to update mach_override.c\n"); + break; + } + + // At this point, we've matched curInstr + int eaten = curInstr->length; + ptr += eaten; + remainsToEat -= eaten; + totalEaten += eaten; + + if (originalInstructionSizes) originalInstructionSizes[instructionIndex] = eaten; + instructionIndex += 1; + if (originalInstructionCount) *originalInstructionCount = instructionIndex; + } + + + if (howManyEaten) *howManyEaten = totalEaten; + + if (originalInstructions) { + Boolean enoughSpaceForOriginalInstructions = (totalEaten < kOriginalInstructionsSize); + + if (enoughSpaceForOriginalInstructions) { + memset(originalInstructions, 0x90 /* NOP */, kOriginalInstructionsSize); // fill instructions with NOP + bcopy(code, originalInstructions, totalEaten); + } else { + // printf ("Not enough space in island to store original instructions. Adapt the island definition and kOriginalInstructionsSize\n"); + return false; + } + } + + if (allInstructionsKnown) { + // save last 3 bytes of first 64bits of codre we'll replace + uint64_t currentFirst64BitsOfCode = *((uint64_t *)code); + currentFirst64BitsOfCode = OSSwapInt64(currentFirst64BitsOfCode); // back to memory representation + currentFirst64BitsOfCode &= 0x0000000000FFFFFFLL; + + // keep only last 3 instructions bytes, first 5 will be replaced by JMP instr + *newInstruction &= 0xFFFFFFFFFF000000LL; // clear last 3 bytes + *newInstruction |= (currentFirst64BitsOfCode & 0x0000000000FFFFFFLL); // set last 3 bytes + } + + return allInstructionsKnown; +} + + static void +fixupInstructions( + uint32_t offset, + void *instructionsToFix, + int instructionCount, + uint8_t *instructionSizes ) +{ + // The start of "leaq offset(%rip),%rax" + static const uint8_t LeaqHeader[] = {0x48, 0x8d, 0x05}; + + int index; + for (index = 0;index < instructionCount;index += 1) + { + if (*(uint8_t*)instructionsToFix == 0xE9) // 32-bit jump relative + { + uint32_t *jumpOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 1); + *jumpOffsetPtr += offset; + } + + // leaq offset(%rip),%rax + if (memcmp(instructionsToFix, LeaqHeader, 3) == 0) { + uint32_t *LeaqOffsetPtr = (uint32_t*)((uintptr_t)instructionsToFix + 3); + *LeaqOffsetPtr += offset; + } + + // 32-bit call relative to the next addr; pop %eax + if (*(uint8_t*)instructionsToFix == 0xE8) + { + // Just this call is larger than the jump we use, so we + // know this is the last instruction. + assert(index == (instructionCount - 1)); + assert(instructionSizes[index] == 6); + + // Insert "addl $offset, %eax" in the end so that when + // we jump to the rest of the function %eax has the + // value it would have if eip had been pushed by the + // call in its original position. + uint8_t *op = instructionsToFix; + op += 6; + *op = 0x05; // addl + uint32_t *addImmPtr = (uint32_t*)(op + 1); + *addImmPtr = offset; + } + + instructionsToFix = (void*)((uintptr_t)instructionsToFix + instructionSizes[index]); + } +} +#endif + +#if defined(__i386__) +__asm( + ".text;" + ".align 2, 0x90;" + "_atomic_mov64:;" + " pushl %ebp;" + " movl %esp, %ebp;" + " pushl %esi;" + " pushl %ebx;" + " pushl %ecx;" + " pushl %eax;" + " pushl %edx;" + + // atomic push of value to an address + // we use cmpxchg8b, which compares content of an address with + // edx:eax. If they are equal, it atomically puts 64bit value + // ecx:ebx in address. + // We thus put contents of address in edx:eax to force ecx:ebx + // in address + " mov 8(%ebp), %esi;" // esi contains target address + " mov 12(%ebp), %ebx;" + " mov 16(%ebp), %ecx;" // ecx:ebx now contains value to put in target address + " mov (%esi), %eax;" + " mov 4(%esi), %edx;" // edx:eax now contains value currently contained in target address + " lock; cmpxchg8b (%esi);" // atomic move. + + // restore registers + " popl %edx;" + " popl %eax;" + " popl %ecx;" + " popl %ebx;" + " popl %esi;" + " popl %ebp;" + " ret" +); +#elif defined(__x86_64__) +void atomic_mov64( + uint64_t *targetAddress, + uint64_t value ) +{ + *targetAddress = value; +} +#endif +#endif diff --git a/xpcom/build/mach_override.h b/xpcom/build/mach_override.h new file mode 100644 index 0000000000..21fbc7ff4d --- /dev/null +++ b/xpcom/build/mach_override.h @@ -0,0 +1,121 @@ +/******************************************************************************* + mach_override.h + Copyright (c) 2003-2009 Jonathan 'Wolf' Rentzsch: + Some rights reserved: + + ***************************************************************************/ + +/***************************************************************************//** + @mainpage mach_override + @author Jonathan 'Wolf' Rentzsch: + + This package, coded in C to the Mach API, allows you to override ("patch") + program- and system-supplied functions at runtime. You can fully replace + functions with your implementations, or merely head- or tail-patch the + original implementations. + + Use it by #include'ing mach_override.h from your .c, .m or .mm file(s). + + @todo Discontinue use of Carbon's MakeDataExecutable() and + CompareAndSwap() calls and start using the Mach equivalents, if they + exist. If they don't, write them and roll them in. That way, this + code will be pure Mach, which will make it easier to use everywhere. + Update: MakeDataExecutable() has been replaced by + msync(MS_INVALIDATE). There is an OSCompareAndSwap in libkern, but + I'm currently unsure if I can link against it. May have to roll in + my own version... + @todo Stop using an entire 4K high-allocated VM page per 28-byte escape + branch island. Done right, this will dramatically speed up escape + island allocations when they number over 250. Then again, if you're + overriding more than 250 functions, maybe speed isn't your main + concern... + @todo Add detection of: b, bl, bla, bc, bcl, bcla, bcctrl, bclrl + first-instructions. Initially, we should refuse to override + functions beginning with these instructions. Eventually, we should + dynamically rewrite them to make them position-independent. + @todo Write mach_unoverride(), which would remove an override placed on a + function. Must be multiple-override aware, which means an almost + complete rewrite under the covers, because the target address can't + be spread across two load instructions like it is now since it will + need to be atomically updatable. + @todo Add non-rentry variants of overrides to test_mach_override. + + ***************************************************************************/ + +#ifndef _mach_override_ +#define _mach_override_ + +#include +#include + +#ifdef __cplusplus + extern "C" { +#endif + +/** + Returned if the function to be overrided begins with a 'mfctr' instruction. +*/ +#define err_cannot_override (err_local|1) + +/************************************************************************************//** + Dynamically overrides the function implementation referenced by + originalFunctionAddress with the implentation pointed to by overrideFunctionAddress. + Optionally returns a pointer to a "reentry island" which, if jumped to, will resume + the original implementation. + + @param originalFunctionAddress -> Required address of the function to + override (with overrideFunctionAddress). + @param overrideFunctionAddress -> Required address to the overriding + function. + @param originalFunctionReentryIsland <- Optional pointer to pointer to the + reentry island. Can be nullptr. + @result <- err_cannot_override if the original + function's implementation begins with + the 'mfctr' instruction. + + ************************************************************************************/ + + mach_error_t +mach_override_ptr( + void *originalFunctionAddress, + const void *overrideFunctionAddress, + void **originalFunctionReentryIsland ); + +/************************************************************************************//** + + + ************************************************************************************/ + +#ifdef __cplusplus + +#define MACH_OVERRIDE( ORIGINAL_FUNCTION_RETURN_TYPE, ORIGINAL_FUNCTION_NAME, ORIGINAL_FUNCTION_ARGS, ERR ) \ + { \ + static ORIGINAL_FUNCTION_RETURN_TYPE (*ORIGINAL_FUNCTION_NAME##_reenter)ORIGINAL_FUNCTION_ARGS; \ + static bool ORIGINAL_FUNCTION_NAME##_overriden = false; \ + class mach_override_class__##ORIGINAL_FUNCTION_NAME { \ + public: \ + static kern_return_t override(void *originalFunctionPtr) { \ + kern_return_t result = err_none; \ + if (!ORIGINAL_FUNCTION_NAME##_overriden) { \ + ORIGINAL_FUNCTION_NAME##_overriden = true; \ + result = mach_override_ptr( (void*)originalFunctionPtr, \ + (void*)mach_override_class__##ORIGINAL_FUNCTION_NAME::replacement, \ + (void**)&ORIGINAL_FUNCTION_NAME##_reenter ); \ + } \ + return result; \ + } \ + static ORIGINAL_FUNCTION_RETURN_TYPE replacement ORIGINAL_FUNCTION_ARGS { + +#define END_MACH_OVERRIDE( ORIGINAL_FUNCTION_NAME ) \ + } \ + }; \ + \ + err = mach_override_class__##ORIGINAL_FUNCTION_NAME::override((void*)ORIGINAL_FUNCTION_NAME); \ + } + +#endif + +#ifdef __cplusplus + } +#endif +#endif // _mach_override_ diff --git a/xpcom/build/moz.build b/xpcom/build/moz.build new file mode 100644 index 0000000000..8d08e73978 --- /dev/null +++ b/xpcom/build/moz.build @@ -0,0 +1,105 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + "nsXPCOM.h", + "nsXPCOMCID.h", + "nsXPCOMCIDInternal.h", + "nsXULAppAPI.h", + "XREChildData.h", + "XREShellData.h", +] + +EXPORTS.mozilla += [ + "!GeckoProcessTypes.h", + "!Services.h", + "FileLocation.h", + "IOInterposer.h", + "LateWriteChecks.h", + "Omnijar.h", + "PoisonIOInterposer.h", + "SmallArrayLRUCache.h", + "XPCOM.h", + "XREAppData.h", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS.mozilla += [ + "perfprobe.h", + ] + SOURCES += [ + "perfprobe.cpp", + "PoisonIOInterposerBase.cpp", + "PoisonIOInterposerWin.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "PoisonIOInterposerBase.cpp", + "PoisonIOInterposerMac.cpp", + ] + if CONFIG["CPU_ARCH"] != "aarch64": + SOURCES += ["mach_override.c"] + SOURCES["mach_override.c"].flags += ["-Wno-unused-function"] +else: + SOURCES += ["PoisonIOInterposerStub.cpp"] + +include("../glue/objs.mozbuild") + +XPCOM_MANIFESTS += [ + "components.conf", +] + +UNIFIED_SOURCES += xpcom_gluens_src_cppsrcs +UNIFIED_SOURCES += xpcom_glue_src_cppsrcs + +UNIFIED_SOURCES += [ + "FileLocation.cpp", + "IOInterposer.cpp", + "LateWriteChecks.cpp", + "MainThreadIOLogger.cpp", + "Omnijar.cpp", + "XPCOMInit.cpp", +] + +SOURCES += ["!Services.cpp"] + +if CONFIG["OS_ARCH"] != "WINNT": + SOURCES += [ + "NSPRInterposer.cpp", + ] + +GeneratedFile("Services.cpp", script="Services.py", entry_point="services_cpp") +GeneratedFile("Services.h", script="Services.py", entry_point="services_h") +GeneratedFile( + "GeckoProcessTypes.h", + script="gen_process_types.py", + entry_point="main", +) + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +DEFINES["_IMPL_NS_STRINGAPI"] = True +DEFINES["OMNIJAR_NAME"] = CONFIG["OMNIJAR_NAME"] + +LOCAL_INCLUDES += [ + "!..", + "../base", + "../components", + "../ds", + "../glue", + "../io", + "../threads", + "/chrome", + "/docshell/base", + "/js/xpconnect/loader", +] + +if CONFIG["MOZ_VPX"]: + LOCAL_INCLUDES += [ + "/media/libvpx", + ] diff --git a/xpcom/build/nsXPCOM.h b/xpcom/build/nsXPCOM.h new file mode 100644 index 0000000000..a000f7b39c --- /dev/null +++ b/xpcom/build/nsXPCOM.h @@ -0,0 +1,383 @@ +/* -*- 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/. */ + +#ifndef nsXPCOM_h__ +#define nsXPCOM_h__ + +#include "nscore.h" +#include "nsXPCOMCID.h" +#include "mozilla/Attributes.h" +#include "mozilla/Atomics.h" + +#ifdef __cplusplus +# define DECL_CLASS(c) class c +# define DECL_STRUCT(c) struct c +#else +# define DECL_CLASS(c) typedef struct c c +# define DECL_STRUCT(c) typedef struct c c +#endif + +DECL_CLASS(nsISupports); +DECL_CLASS(nsIComponentManager); +DECL_CLASS(nsIComponentRegistrar); +DECL_CLASS(nsIServiceManager); +DECL_CLASS(nsIFile); +DECL_CLASS(nsIDirectoryServiceProvider); +DECL_CLASS(nsIMemory); +DECL_CLASS(nsIDebug2); + +#ifdef __cplusplus +extern bool gXPCOMShuttingDown; +extern bool gXPCOMMainThreadEventsAreDoomed; +#endif + +#ifdef __cplusplus +# include "nsStringFwd.h" +#endif + +/** + * Initialises XPCOM. You must call one of the NS_InitXPCOM methods + * before proceeding to use xpcom. The one exception is that you may + * call NS_NewLocalFile to create a nsIFile. + * + * @note Use NS_NewLocalFile or NS_NewNativeLocalFile + * to create the file object you supply as the bin directory path in this + * call. The function may be safely called before the rest of XPCOM or + * embedding has been initialised. + * + * @param aResult The service manager. You may pass null. + * + * @param aBinDirectory The directory containing the component + * registry and runtime libraries; + * or use nullptr to use the working + * directory. + * + * @param aAppFileLocationProvider The object to be used by Gecko that + * specifies to Gecko where to find profiles, the + * component registry preferences and so on; or use + * nullptr for the default behaviour. + * + * @param aInitJSContext Whether the nsXPCJSContext should be initialized at + * this point. + * + * @see NS_NewLocalFile + * @see nsIFile + * @see nsIDirectoryServiceProvider + * + * @return NS_OK for success; + * NS_ERROR_NOT_INITIALIZED if static globals were not initialized, + * which can happen if XPCOM is reloaded, but did not completly + * shutdown. Other error codes indicate a failure during + * initialisation. + */ +XPCOM_API(nsresult) +NS_InitXPCOM(nsIServiceManager** aResult, nsIFile* aBinDirectory, + nsIDirectoryServiceProvider* aAppFileLocationProvider, + bool aInitJSContext = true); + +/** + * Initialize only minimal components of XPCOM. This ensures nsThreadManager, + * logging, and timers will work. + */ +XPCOM_API(nsresult) +NS_InitMinimalXPCOM(); + +/** + * Shutdown XPCOM. You must call this method after you are finished + * using xpcom. + * + * @param aServMgr The service manager which was returned by + * NS_InitXPCOM. This will release servMgr. You may pass null. + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + * + * MOZ_CAN_RUN_SCRIPT_BOUNDARY for now, but really it should maybe be + * MOZ_CAN_RUN_SCRIPT. + */ +XPCOM_API(MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult) +NS_ShutdownXPCOM(nsIServiceManager* aServMgr); + +/** + * Public Method to access to the service manager. + * + * @param aResult Interface pointer to the service manager + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetServiceManager(nsIServiceManager** aResult); + +/** + * Public Method to access to the component manager. + * + * @param aResult Interface pointer to the service + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetComponentManager(nsIComponentManager** aResult); + +/** + * Public Method to access to the component registration manager. + * + * @param aResult Interface pointer to the service + * + * @return NS_OK for success; + * other error codes indicate a failure during initialisation. + */ +XPCOM_API(nsresult) NS_GetComponentRegistrar(nsIComponentRegistrar** aResult); + +/** + * Public Method to create an instance of a nsIFile. This function + * may be called prior to NS_InitXPCOM. + * + * @param aPath + * A string which specifies a full file path to a + * location. Relative paths will be treated as an + * error (NS_ERROR_FILE_UNRECOGNIZED_PATH). + * |NS_NewNativeLocalFile|'s path must be in the + * filesystem charset. + * @param aFollowLinks + * This attribute will determine if the nsLocalFile will auto + * resolve symbolic links. By default, this value will be false + * on all non unix systems. On unix, this attribute is effectively + * a noop. + * @param aResult Interface pointer to a new instance of an nsIFile + * + * @return NS_OK for success; + * other error codes indicate a failure. + */ + +#ifdef __cplusplus + +XPCOM_API(nsresult) +NS_NewLocalFile(const nsAString& aPath, bool aFollowLinks, nsIFile** aResult); + +XPCOM_API(nsresult) +NS_NewNativeLocalFile(const nsACString& aPath, bool aFollowLinks, + nsIFile** aResult); + +// Use NS_NewLocalFile if you already have a UTF-16 string. +// Otherwise non-ASCII paths will break on some platforms +// including Windows. +class NS_ConvertUTF16toUTF8; +nsresult NS_NewNativeLocalFile(const NS_ConvertUTF16toUTF8& aPath, + bool aFollowLinks, nsIFile** aResult) = delete; + +#endif + +/** + * Support for warnings, assertions, and debugging breaks. + */ + +enum { + NS_DEBUG_WARNING = 0, + NS_DEBUG_ASSERTION = 1, + NS_DEBUG_BREAK = 2, + NS_DEBUG_ABORT = 3 +}; + +/** + * Print a runtime assertion. This function is available in both debug and + * release builds. + * + * @note Based on the value of aSeverity and the XPCOM_DEBUG_BREAK + * environment variable, this function may cause the application to + * print the warning, print a stacktrace, break into a debugger, or abort + * immediately. + * + * @param aSeverity A NS_DEBUG_* value + * @param aStr A readable error message (ASCII, may be null) + * @param aExpr The expression evaluated (may be null) + * @param aFile The source file containing the assertion (may be null) + * @param aLine The source file line number (-1 indicates no line number) + */ +XPCOM_API(void) +NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr, + const char* aFile, int32_t aLine); + +/** + * Perform a stack-walk to a debugging log under various + * circumstances. Used to aid debugging of leaked object graphs. + * + * The NS_Log* functions are available in both debug and release + * builds of XPCOM, but the output will be useless unless binary + * debugging symbols for all modules in the stacktrace are available. + */ + +/** + * By default, refcount logging is enabled at NS_InitXPCOM and + * refcount statistics are printed at NS_ShutdownXPCOM. NS_LogInit and + * NS_LogTerm allow applications to enable logging earlier and delay + * printing of logging statistics. They should always be used as a + * matched pair. + */ +XPCOM_API(void) NS_LogInit(); + +XPCOM_API(void) NS_LogTerm(); + +#ifdef __cplusplus +/** + * A helper class that calls NS_LogInit in its constructor and + * NS_LogTerm in its destructor. + */ + +class ScopedLogging { + public: + ScopedLogging() { NS_LogInit(); } + + ~ScopedLogging() { NS_LogTerm(); } +}; +#endif + +/** + * Log construction and destruction of objects. Processing tools can use the + * stacktraces printed by these functions to identify objects that are being + * leaked. + * + * @param aPtr A pointer to the concrete object. + * @param aTypeName The class name of the type + * @param aInstanceSize The size of the type + */ + +XPCOM_API(void) +NS_LogCtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize); + +XPCOM_API(void) +NS_LogDtor(void* aPtr, const char* aTypeName, uint32_t aInstanceSize); + +/** + * Log a stacktrace when an XPCOM object's refcount is incremented or + * decremented. Processing tools can use the stacktraces printed by these + * functions to identify objects that were leaked due to XPCOM references. + * + * @param aPtr A pointer to the concrete object + * @param aNewRefCnt The new reference count. + * @param aTypeName The class name of the type + * @param aInstanceSize The size of the type + */ +XPCOM_API(void) +NS_LogAddRef(void* aPtr, nsrefcnt aNewRefCnt, const char* aTypeName, + uint32_t aInstanceSize); + +XPCOM_API(void) +NS_LogRelease(void* aPtr, nsrefcnt aNewRefCnt, const char* aTypeName); + +/** + * Log reference counting performed by COMPtrs. Processing tools can + * use the stacktraces printed by these functions to simplify reports + * about leaked objects generated from the data printed by + * NS_LogAddRef/NS_LogRelease. + * + * @param aCOMPtr the address of the COMPtr holding a strong reference + * @param aObject the object being referenced by the COMPtr + */ + +XPCOM_API(void) NS_LogCOMPtrAddRef(void* aCOMPtr, nsISupports* aObject); + +XPCOM_API(void) NS_LogCOMPtrRelease(void* aCOMPtr, nsISupports* aObject); + +/** + * The XPCOM cycle collector analyzes and breaks reference cycles between + * participating XPCOM objects. All objects in the cycle must implement + * nsCycleCollectionParticipant to break cycles correctly. + */ + +#ifdef __cplusplus + +class nsCycleCollectionParticipant; +class nsCycleCollectingAutoRefCnt; + +XPCOM_API(void) +NS_CycleCollectorSuspect3(void* aPtr, nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete); + +XPCOM_API(void) +NS_CycleCollectorSuspectUsingNursery(void* aPtr, + nsCycleCollectionParticipant* aCp, + nsCycleCollectingAutoRefCnt* aRefCnt, + bool* aShouldDelete); + +#endif + +/** + * Categories (in the category manager service) used by XPCOM: + */ + +/** + * A category which is read after component registration but before + * the "xpcom-startup" notifications. Each category entry is treated + * as the contract ID of a service which implements + * nsIDirectoryServiceProvider. Each directory service provider is + * installed in the global directory service. + */ +#define XPCOM_DIRECTORY_PROVIDER_CATEGORY "xpcom-directory-providers" + +/** + * A category which is read after component registration but before + * NS_InitXPCOM returns. Each category entry is treated as the contractID of + * a service: each service is instantiated, and if it implements nsIObserver + * the nsIObserver.observe method is called with the "xpcom-startup" topic. + */ +#define NS_XPCOM_STARTUP_CATEGORY "xpcom-startup" + +/** + * Observer topics (in the observer service) used by XPCOM: + */ + +/** + * At XPCOM startup after component registration is complete, the + * following topic is notified. In order to receive this notification, + * component must register their contract ID in the category manager, + * + * @see NS_XPCOM_STARTUP_CATEGORY + */ +#define NS_XPCOM_STARTUP_OBSERVER_ID "xpcom-startup" + +/** + * At XPCOM shutdown, this topic is notified just before "xpcom-shutdown". + * Components should only use this to mark themselves as 'being destroyed'. + * Nothing should be dispatched to any event loop. + */ +#define NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID "xpcom-will-shutdown" + +/** + * At XPCOM shutdown, this topic is notified. All components must + * release any interface references to objects in other modules when + * this topic is notified. + */ +#define NS_XPCOM_SHUTDOWN_OBSERVER_ID "xpcom-shutdown" + +/** + * This topic is notified when an entry was added to a category in the + * category manager. The subject of the notification will be the name of + * the added entry as an nsISupportsCString, and the data will be the + * name of the category. The notification will occur on the main thread. + */ +#define NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID "xpcom-category-entry-added" + +/** + * This topic is notified when an entry was removed from a category in the + * category manager. The subject of the notification will be the name of + * the removed entry as an nsISupportsCString, and the data will be the + * name of the category. The notification will occur on the main thread. + */ +#define NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID \ + "xpcom-category-entry-removed" + +/** + * This topic is notified when an a category was cleared in the category + * manager. The subject of the notification will be the category manager, + * and the data will be the name of the cleared category. + * The notification will occur on the main thread. + */ +#define NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID "xpcom-category-cleared" + +XPCOM_API(nsresult) NS_GetDebug(nsIDebug2** aResult); + +#endif diff --git a/xpcom/build/nsXPCOMCID.h b/xpcom/build/nsXPCOMCID.h new file mode 100644 index 0000000000..a5df26717a --- /dev/null +++ b/xpcom/build/nsXPCOMCID.h @@ -0,0 +1,220 @@ +/* -*- 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/. */ + +#ifndef nsXPCOMCID_h__ +#define nsXPCOMCID_h__ + +/** + * XPCOM Directory Service Contract ID + * The directory service provides ways to obtain file system locations. The + * directory service is a singleton. + * + * This contract supports the nsIDirectoryService and the nsIProperties + * interfaces. + * + */ +#define NS_DIRECTORY_SERVICE_CONTRACTID "@mozilla.org/file/directory_service;1" + +/** + * XPCOM File + * The file abstraction provides ways to obtain and access files and + * directories located on the local system. + * + * This contract supports the nsIFile interface. + * This contract may also support platform specific interfaces such as + * nsILocalFileMac on platforms where additional interfaces are required. + * + */ +#define NS_LOCAL_FILE_CONTRACTID "@mozilla.org/file/local;1" + +/** + * XPCOM Category Manager Contract ID + * The contract supports the nsICategoryManager interface. The + * category manager is a singleton. + * The "enumerateCategory" method of nsICategoryManager will return an object + * that implements nsIUTF8StringEnumerator. In addition, the enumerator will + * return the entries in sorted order (sorted by byte comparison). + */ +#define NS_CATEGORYMANAGER_CONTRACTID "@mozilla.org/categorymanager;1" + +/** + * XPCOM Properties Object Contract ID + * Simple mapping object which supports the nsIProperties interface. + */ +#define NS_PROPERTIES_CONTRACTID "@mozilla.org/properties;1" + +/** + * XPCOM Array Object ContractID + * Simple array implementation which supports the nsIArray and + * nsIMutableArray interfaces. + */ +#define NS_ARRAY_CONTRACTID "@mozilla.org/array;1" + +/** + * Observer Service ContractID + * The observer service implements the global nsIObserverService object. + * It should be used from the main thread only. + */ +#define NS_OBSERVERSERVICE_CONTRACTID "@mozilla.org/observer-service;1" + +/** + * IO utilities service contract id. + * This guarantees implementation of nsIIOUtil. Usable from any thread. + */ +#define NS_IOUTIL_CONTRACTID "@mozilla.org/io-util;1" + +/** + * Memory reporter service CID + */ +#define NS_MEMORY_REPORTER_MANAGER_CONTRACTID \ + "@mozilla.org/memory-reporter-manager;1" + +/** + * Memory info dumper service CID + */ +#define NS_MEMORY_INFO_DUMPER_CONTRACTID "@mozilla.org/memory-info-dumper;1" + +/** + * nsMessageLoop contract id + */ +#define NS_MESSAGE_LOOP_CONTRACTID "@mozilla.org/message-loop;1" + +#define NS_COMPARTMENT_INFO_CONTRACTID "@mozilla.org/compartment-info;1" + +/** + * The following are the CIDs and Contract IDs of the nsISupports wrappers for + * primative types. + */ +#define NS_SUPPORTS_ID_CID \ + { \ + 0xacf8dc40, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_ID_CONTRACTID "@mozilla.org/supports-id;1" + +#define NS_SUPPORTS_CSTRING_CID \ + { \ + 0xacf8dc41, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_CSTRING_CONTRACTID "@mozilla.org/supports-cstring;1" + +#define NS_SUPPORTS_STRING_CID \ + { \ + 0xacf8dc42, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_STRING_CONTRACTID "@mozilla.org/supports-string;1" + +#define NS_SUPPORTS_PRBOOL_CID \ + { \ + 0xacf8dc43, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRBOOL_CONTRACTID "@mozilla.org/supports-PRBool;1" + +#define NS_SUPPORTS_PRUINT8_CID \ + { \ + 0xacf8dc44, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT8_CONTRACTID "@mozilla.org/supports-PRUint8;1" + +#define NS_SUPPORTS_PRUINT16_CID \ + { \ + 0xacf8dc46, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT16_CONTRACTID "@mozilla.org/supports-PRUint16;1" + +#define NS_SUPPORTS_PRUINT32_CID \ + { \ + 0xacf8dc47, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT32_CONTRACTID "@mozilla.org/supports-PRUint32;1" + +#define NS_SUPPORTS_PRUINT64_CID \ + { \ + 0xacf8dc48, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRUINT64_CONTRACTID "@mozilla.org/supports-PRUint64;1" + +#define NS_SUPPORTS_PRTIME_CID \ + { \ + 0xacf8dc49, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRTIME_CONTRACTID "@mozilla.org/supports-PRTime;1" + +#define NS_SUPPORTS_CHAR_CID \ + { \ + 0xacf8dc4a, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_CHAR_CONTRACTID "@mozilla.org/supports-char;1" + +#define NS_SUPPORTS_PRINT16_CID \ + { \ + 0xacf8dc4b, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRINT16_CONTRACTID "@mozilla.org/supports-PRInt16;1" + +#define NS_SUPPORTS_PRINT32_CID \ + { \ + 0xacf8dc4c, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRINT32_CONTRACTID "@mozilla.org/supports-PRInt32;1" + +#define NS_SUPPORTS_PRINT64_CID \ + { \ + 0xacf8dc4d, 0x4a25, 0x11d3, { \ + 0x98, 0x90, 0x0, 0x60, 0x8, 0x96, 0x24, 0x22 \ + } \ + } +#define NS_SUPPORTS_PRINT64_CONTRACTID "@mozilla.org/supports-PRInt64;1" + +#define NS_SUPPORTS_FLOAT_CID \ + { \ + 0xcbf86870, 0x4ac0, 0x11d3, { \ + 0xba, 0xea, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 \ + } \ + } +#define NS_SUPPORTS_FLOAT_CONTRACTID "@mozilla.org/supports-float;1" + +#define NS_SUPPORTS_DOUBLE_CID \ + { \ + 0xcbf86871, 0x4ac0, 0x11d3, { \ + 0xba, 0xea, 0x0, 0x80, 0x5f, 0x8a, 0x5d, 0xd7 \ + } \ + } +#define NS_SUPPORTS_DOUBLE_CONTRACTID "@mozilla.org/supports-double;1" + +#define NS_SUPPORTS_INTERFACE_POINTER_CID \ + { \ + 0xA99FEBBA, 0x1DD1, 0x11B2, { \ + 0xA9, 0x43, 0xB0, 0x23, 0x34, 0xA6, 0xD0, 0x83 \ + } \ + } +#define NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID \ + "@mozilla.org/supports-interface-pointer;1" + +#endif diff --git a/xpcom/build/nsXPCOMCIDInternal.h b/xpcom/build/nsXPCOMCIDInternal.h new file mode 100644 index 0000000000..d07fcb1c4d --- /dev/null +++ b/xpcom/build/nsXPCOMCIDInternal.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef nsXPCOMCIDInternal_h__ +#define nsXPCOMCIDInternal_h__ + +#include "nsXPCOMCID.h" + +/** + * A hashtable-based property bag component. + * @implements nsIWritablePropertyBag, nsIWritablePropertyBag2 + */ +#define NS_HASH_PROPERTY_BAG_CID \ + { \ + 0x678c50b8, 0x6bcb, 0x4ad0, { \ + 0xb9, 0xb8, 0xc8, 0x11, 0x75, 0x95, 0x51, 0x99 \ + } \ + } +#define NS_HASH_PROPERTY_BAG_CONTRACTID "@mozilla.org/hash-property-bag;1" + +/** + * Factory for creating nsIUnicharInputStream + * @implements nsIUnicharInputStreamFactory + * @note nsIUnicharInputStream instances cannot be created via + * createInstance. Code must use one of the custom factory methods. + */ +#define NS_SIMPLE_UNICHAR_STREAM_FACTORY_CONTRACTID \ + "@mozilla.org/xpcom/simple-unichar-stream-factory;1" + +/** + * The global thread manager service. This component is a singleton. + * @implements nsIThreadManager + */ +#define NS_THREADMANAGER_CONTRACTID "@mozilla.org/thread-manager;1" + +/** + * The contract id for the nsIXULAppInfo service. + */ +#define XULAPPINFO_SERVICE_CONTRACTID "@mozilla.org/xre/app-info;1" + +/** + * The contract id for the nsIXULRuntime service. + */ +#define XULRUNTIME_SERVICE_CONTRACTID "@mozilla.org/xre/runtime;1" + +#endif // nsXPCOMCIDInternal_h__ diff --git a/xpcom/build/nsXPCOMPrivate.h b/xpcom/build/nsXPCOMPrivate.h new file mode 100644 index 0000000000..49f36c709c --- /dev/null +++ b/xpcom/build/nsXPCOMPrivate.h @@ -0,0 +1,126 @@ +/* -*- 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/. */ + +#ifndef nsXPCOMPrivate_h__ +#define nsXPCOMPrivate_h__ + +#include "nscore.h" +#include "nsXPCOM.h" +#include "mozilla/Attributes.h" + +/** + * During this shutdown notification all threads which run XPCOM code must + * be joined. + */ +#define NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID "xpcom-shutdown-threads" + +// PUBLIC +namespace mozilla { + +/** + * Shutdown XPCOM. You must call this method after you are finished + * using xpcom. + * + * @param aServMgr The service manager which was returned by + * NS_InitXPCOM. This will release servMgr. You may pass null. + * + * @return NS_OK for success; + * other error codes indicate a failure during shutdown + * + */ +MOZ_CAN_RUN_SCRIPT +nsresult ShutdownXPCOM(nsIServiceManager* aServMgr); + +void SetICUMemoryFunctions(); + +/** + * C++ namespaced version of NS_LogTerm. + */ +void LogTerm(); + +} // namespace mozilla + +/* XPCOM Specific Defines + * + * XPCOM_DLL - name of the loadable xpcom library on disk. + * XUL_DLL - name of the loadable XUL library on disk + * XPCOM_SEARCH_KEY - name of the environment variable that can be + * modified to include additional search paths. + * GRE_CONF_NAME - Name of the GRE Configuration file + */ + +#if defined(XP_WIN) + +# define XPCOM_SEARCH_KEY "PATH" +# define GRE_CONF_NAME "gre.config" +# define GRE_WIN_REG_LOC L"Software\\mozilla.org\\GRE" +# define XPCOM_DLL XUL_DLL +# define LXPCOM_DLL LXUL_DLL +# define XUL_DLL "xul.dll" +# define LXUL_DLL L"xul.dll" + +#else // Unix +# include // for PATH_MAX + +# define XPCOM_DLL XUL_DLL + +// you have to love apple.. +# ifdef XP_MACOSX +# define XPCOM_SEARCH_KEY "DYLD_LIBRARY_PATH" +# define GRE_FRAMEWORK_NAME "XUL.framework" +# define XUL_DLL "XUL" +# else +# define XPCOM_SEARCH_KEY "LD_LIBRARY_PATH" +# define XUL_DLL "libxul" MOZ_DLL_SUFFIX +# endif + +# define GRE_CONF_NAME ".gre.config" +# define GRE_CONF_PATH "/etc/gre.conf" +# define GRE_CONF_DIR "/etc/gre.d" +# define GRE_USER_CONF_DIR ".gre.d" +#endif + +#if defined(XP_WIN) +# define XPCOM_FILE_PATH_SEPARATOR "\\" +# define XPCOM_ENV_PATH_SEPARATOR ";" +#elif defined(XP_UNIX) +# define XPCOM_FILE_PATH_SEPARATOR "/" +# define XPCOM_ENV_PATH_SEPARATOR ":" +#else +# error need_to_define_your_file_path_separator_and_illegal_characters +#endif + +#ifdef AIX +# include +#endif + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +// Needed by the IPC layer from off the main thread +extern char16_t* gGREBinPath; + +namespace mozilla { +namespace services { + +/** + * Clears service cache, sets gXPCOMShuttingDown + */ +void Shutdown(); + +} // namespace services +} // namespace mozilla + +#endif diff --git a/xpcom/build/nsXULAppAPI.h b/xpcom/build/nsXULAppAPI.h new file mode 100644 index 0000000000..c3fec696fd --- /dev/null +++ b/xpcom/build/nsXULAppAPI.h @@ -0,0 +1,387 @@ +/* -*- 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/. */ + +#ifndef _nsXULAppAPI_h__ +#define _nsXULAppAPI_h__ + +#include "js/TypeDecls.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/ProcessType.h" +#include "mozilla/TimeStamp.h" +#include "nscore.h" + +#if defined(MOZ_WIDGET_ANDROID) +# include +#endif + +class JSString; +class MessageLoop; +class nsIDirectoryServiceProvider; +class nsIFile; +class nsISupports; +struct JSContext; +struct XREChildData; +struct XREShellData; + +namespace mozilla { +class XREAppData; +struct BootstrapConfig; +} // namespace mozilla + +/** + * A directory service key which provides the platform-correct "application + * data" directory as follows, where $name and $vendor are as defined above and + * $vendor is optional: + * + * Windows: + * HOME = Documents and Settings\$USER\Application Data + * UAppData = $HOME[\$vendor]\$name + * + * Unix: + * HOME = ~ + * UAppData = $HOME/.[$vendor/]$name + * + * Mac: + * HOME = ~ + * UAppData = $HOME/Library/Application Support/$name + * + * Note that the "profile" member above will change the value of UAppData as + * follows: + * + * Windows: + * UAppData = $HOME\$profile + * + * Unix: + * UAppData = $HOME/.$profile + * + * Mac: + * UAppData = $HOME/Library/Application Support/$profile + */ +#define XRE_USER_APP_DATA_DIR "UAppData" + +/** + * A directory service key which provides the executable file used to + * launch the current process. This is the same value returned by the + * XRE_GetBinaryPath function defined below. + */ +#define XRE_EXECUTABLE_FILE "XREExeF" + +/** + * A directory service key which specifies the profile + * directory. Unlike the NS_APP_USER_PROFILE_50_DIR key, this key may + * be available when the profile hasn't been "started", or after is + * has been shut down. If the application is running without a + * profile, such as when showing the profile manager UI, this key will + * not be available. This key is provided by the XUL apprunner or by + * the aAppDirProvider object passed to XRE_InitEmbedding. + */ +#define NS_APP_PROFILE_DIR_STARTUP "ProfDS" + +/** + * A directory service key which specifies the profile + * directory. Unlike the NS_APP_USER_PROFILE_LOCAL_50_DIR key, this key may + * be available when the profile hasn't been "started", or after is + * has been shut down. If the application is running without a + * profile, such as when showing the profile manager UI, this key will + * not be available. This key is provided by the XUL apprunner or by + * the aAppDirProvider object passed to XRE_InitEmbedding. + */ +#define NS_APP_PROFILE_LOCAL_DIR_STARTUP "ProfLDS" + +/** + * A directory service key which specifies the system extension + * parent directory containing platform-specific extensions. + * This key may not be available on all platforms. + */ +#define XRE_SYS_LOCAL_EXTENSION_PARENT_DIR "XRESysLExtPD" + +/** + * A directory service key which specifies the system extension + * parent directory containing platform-independent extensions. + * This key may not be available on all platforms. + * Additionally, the directory may be equal to that returned by + * XRE_SYS_LOCAL_EXTENSION_PARENT_DIR on some platforms. + */ +#define XRE_SYS_SHARE_EXTENSION_PARENT_DIR "XRESysSExtPD" + +#if defined(XP_UNIX) || defined(XP_MACOSX) +/** + * Directory service keys for the system-wide and user-specific + * directories where native manifests used by the WebExtensions + * native messaging and managed storage features are found. + */ +# define XRE_SYS_NATIVE_MANIFESTS "XRESysNativeManifests" +# define XRE_USER_NATIVE_MANIFESTS "XREUserNativeManifests" +#endif + +/** + * A directory service key which specifies the user system extension + * parent directory. + */ +#define XRE_USER_SYS_EXTENSION_DIR "XREUSysExt" + +/** + * A directory service key which specifies the distribution specific files for + * the application. + */ +#define XRE_APP_DISTRIBUTION_DIR "XREAppDist" + +/** + * A directory service key which specifies the location for system add-ons. + */ +#define XRE_APP_FEATURES_DIR "XREAppFeat" + +/** + * A directory service key which specifies the location for app dir add-ons. + * Should be a synonym for XCurProcD everywhere except in tests. + */ +#define XRE_ADDON_APP_DIR "XREAddonAppDir" + +/** + * A directory service key which specifies the distribution specific files for + * the application unique for each user. + * It's located at /run/user/$UID// + */ +#define XRE_USER_RUNTIME_DIR "XREUserRunTimeDir" + +/** + * A directory service key which provides the update directory. Callers should + * fall back to appDir. + * Windows: If vendor name exists: + * ProgramData\\updates\ + * + * + * If vendor name doesn't exist, but product name exists: + * ProgramData\\updates\ + * + * + * If neither vendor nor product name exists: + * ProgramData\Mozilla\updates + * + * Mac: ~/Library/Caches/Mozilla/updates/ + * + * All others: Parent directory of XRE_EXECUTABLE_FILE. + */ +#define XRE_UPDATE_ROOT_DIR "UpdRootD" + +/** + * A directory service key which provides the *old* update directory. This + * path should only be used when data needs to be migrated from the old update + * directory. + * Windows: If vendor name exists: + * Documents and Settings\\Local Settings\Application Data\ + * \updates\ + * + * + * If vendor name doesn't exist, but product name exists: + * Documents and Settings\\Local Settings\Application Data\ + * \updates\ + * + * + * If neither vendor nor product name exists: + * Documents and Settings\\Local Settings\Application Data\ + * Mozilla\updates + * + * This path does not exist on other operating systems + */ +#define XRE_OLD_UPDATE_ROOT_DIR "OldUpdRootD" + +/** + * Begin an XUL application. Does not return until the user exits the + * application. + * + * @param argc/argv Command-line parameters to pass to the application. On + * Windows, these should be in UTF8. On unix-like platforms + * these are in the "native" character set. + * + * @param aConfig Information about the application to be run. + * + * @return A native result code suitable for returning from main(). + * + * @note If the binary is linked against the standalone XPCOM glue, + * XPCOMGlueStartup() should be called before this method. + */ +int XRE_main(int argc, char* argv[], const mozilla::BootstrapConfig& aConfig); + +/** + * Given a path relative to the current working directory (or an absolute + * path), return an appropriate nsIFile object. + * + * @note Pass UTF8 strings on Windows... native charset on other platforms. + */ +nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult); + +/** + * Get the path of the running application binary and store it in aResult. + */ +nsresult XRE_GetBinaryPath(nsIFile** aResult); + +/** + * Register XPCOM components found in an array of files/directories. + * This method may be called at any time before or after XRE_main or + * XRE_InitEmbedding. + * + * @param aFiles An array of files or directories. + * @param aFileCount the number of items in the aFiles array. + * @note appdir/components is registered automatically. + * + * NS_APP_LOCATION specifies a location to search for binary XPCOM + * components as well as component/chrome manifest files. + * + * NS_EXTENSION_LOCATION excludes binary XPCOM components but allows other + * manifest instructions. + * + * NS_SKIN_LOCATION specifies a location to search for chrome manifest files + * which are only allowed to register skin packages. + */ +enum NSLocationType { + NS_APP_LOCATION, + NS_EXTENSION_LOCATION, + NS_SKIN_LOCATION, + NS_BOOTSTRAPPED_LOCATION +}; + +nsresult XRE_AddManifestLocation(NSLocationType aType, nsIFile* aLocation); + +/** + * Register XPCOM components found in a JAR. + * This is similar to XRE_AddManifestLocation except the file specified + * must be a zip archive with a manifest named chrome.manifest + * This method may be called at any time before or after XRE_main or + * XRE_InitEmbedding. + * + * @param aFiles An array of files or directories. + * @param aFileCount the number of items in the aFiles array. + * @note appdir/components is registered automatically. + * + * NS_COMPONENT_LOCATION specifies a location to search for binary XPCOM + * components as well as component/chrome manifest files. + * + * NS_SKIN_LOCATION specifies a location to search for chrome manifest files + * which are only allowed to register skin packages. + */ +nsresult XRE_AddJarManifestLocation(NSLocationType aType, nsIFile* aLocation); + +/** + * Parse an INI file (application.ini or override.ini) into an existing + * nsXREAppData structure. + * + * @param aINIFile The INI file to parse + * @param aAppData The nsXREAppData structure to fill. + */ +nsresult XRE_ParseAppData(nsIFile* aINIFile, mozilla::XREAppData& aAppData); + +const char* XRE_GeckoProcessTypeToString(GeckoProcessType aProcessType); +const char* XRE_ChildProcessTypeToAnnotation(GeckoProcessType aProcessType); + +#if defined(MOZ_WIDGET_ANDROID) +struct XRE_AndroidChildFds { + int mPrefsFd; + int mPrefMapFd; + int mIpcFd; + int mCrashFd; + int mCrashAnnotationFd; +}; + +void XRE_SetAndroidChildFds(JNIEnv* env, const XRE_AndroidChildFds& fds); +#endif // defined(MOZ_WIDGET_ANDROID) + +void XRE_SetProcessType(const char* aProcessTypeString); + +nsresult XRE_InitChildProcess(int aArgc, char* aArgv[], + const XREChildData* aChildData); + +/** + * Return the GeckoProcessType of the current process. + */ +GeckoProcessType XRE_GetProcessType(); + +/** + * Return the string representation of the GeckoProcessType of the current + * process. + */ +const char* XRE_GetProcessTypeString(); + +/** + * Returns true when called in the e10s parent process. Does *NOT* return true + * when called in the main process if e10s is disabled. + */ +bool XRE_IsE10sParentProcess(); + +/** + * Defines XRE_IsParentProcess, XRE_IsContentProcess, etc. + * + * XRE_IsParentProcess is unique in that it returns true when called in + * the e10s parent process or called in the main process when e10s is + * disabled. + */ +#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \ + process_bin_type, procinfo_typename, \ + webidl_typename, allcaps_name) \ + bool XRE_Is##proc_typename##Process(); +#include "mozilla/GeckoProcessTypes.h" +#undef GECKO_PROCESS_TYPE + +bool XRE_IsSocketProcess(); + +/** + * Returns true if the appshell should run its own native event loop. Returns + * false if we should rely solely on the Gecko event loop. + */ +bool XRE_UseNativeEventProcessing(); + +typedef void (*MainFunction)(void* aData); + +int XRE_RunIPDLTest(int aArgc, char* aArgv[]); + +nsresult XRE_RunAppShell(); + +nsresult XRE_InitCommandLine(int aArgc, char* aArgv[]); + +nsresult XRE_DeinitCommandLine(); + +void XRE_ShutdownChildProcess(); + +MessageLoop* XRE_GetIOMessageLoop(); + +bool XRE_SendTestShellCommand(JSContext* aCx, JSString* aCommand, + JS::Value* aCallback); +bool XRE_ShutdownTestShell(); + +void XRE_InstallX11ErrorHandler(); +void XRE_CleanupX11ErrorHandler(); + +void XRE_TelemetryAccumulate(int aID, uint32_t aSample); + +void XRE_StartupTimelineRecord(int aEvent, mozilla::TimeStamp aWhen); + +void XRE_InitOmnijar(nsIFile* aGreOmni, nsIFile* aAppOmni); +void XRE_StopLateWriteChecks(void); + +void XRE_EnableSameExecutableForContentProc(); + +namespace mozilla { +enum class BinPathType { Self, PluginContainer }; +} +mozilla::BinPathType XRE_GetChildProcBinPathType(GeckoProcessType aProcessType); + +int XRE_XPCShellMain(int argc, char** argv, char** envp, + const XREShellData* aShellData); + +#ifdef LIBFUZZER +# include "FuzzerRegistry.h" + +void XRE_LibFuzzerSetDriver(LibFuzzerDriver); + +#endif // LIBFUZZER + +#ifdef MOZ_ENABLE_FORKSERVER + +int XRE_ForkServer(int* aArgc, char*** aArgv); + +#endif // MOZ_ENABLE_FORKSERVER + +#endif // _nsXULAppAPI_h__ diff --git a/xpcom/build/perfprobe.cpp b/xpcom/build/perfprobe.cpp new file mode 100644 index 0000000000..d5a82be59d --- /dev/null +++ b/xpcom/build/perfprobe.cpp @@ -0,0 +1,215 @@ +/* -*- 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/. */ + +/***************************** + Windows implementation of probes, using xperf + *****************************/ +#include +#include +#include + +#include "perfprobe.h" + +namespace mozilla { +namespace probes { + +#if defined(MOZ_LOGGING) +static LazyLogModule sProbeLog("SysProbe"); +# define LOG(x) MOZ_LOG(sProbeLog, mozilla::LogLevel::Debug, x) +#else +# define LOG(x) +#endif + +// Utility function +GUID CID_to_GUID(const nsCID& aCID) { + GUID result; + result.Data1 = aCID.m0; + result.Data2 = aCID.m1; + result.Data3 = aCID.m2; + for (int i = 0; i < 8; ++i) { + result.Data4[i] = aCID.m3[i]; + } + return result; +} + +// Implementation of Probe + +Probe::Probe(const nsCID& aGUID, const nsACString& aName, + ProbeManager* aManager) + : mGUID(CID_to_GUID(aGUID)), mName(aName), mManager(aManager) {} + +nsresult Probe::Trigger() { + if (!(mManager->mIsActive)) { + // Do not trigger if there is no session + return NS_OK; + } + + _EVENT_TRACE_HEADER event; + ZeroMemory(&event, sizeof(event)); + event.Size = sizeof(event); + event.Flags = WNODE_FLAG_TRACED_GUID; + event.Guid = (const GUID)mGUID; + event.Class.Type = 1; + event.Class.Version = 0; + event.Class.Level = TRACE_LEVEL_INFORMATION; + + ULONG result = TraceEvent(mManager->mSessionHandle, &event); + + LOG(("Probes: Triggered %s, %s, %ld", mName.Data(), + result == ERROR_SUCCESS ? "success" : "failure", result)); + + nsresult rv; + switch (result) { + case ERROR_SUCCESS: + rv = NS_OK; + break; + case ERROR_INVALID_FLAG_NUMBER: + case ERROR_MORE_DATA: + case ERROR_INVALID_PARAMETER: + rv = NS_ERROR_INVALID_ARG; + break; + case ERROR_INVALID_HANDLE: + rv = NS_ERROR_FAILURE; + break; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + rv = NS_ERROR_OUT_OF_MEMORY; + break; + default: + rv = NS_ERROR_UNEXPECTED; + } + return rv; +} + +// Implementation of ProbeManager + +ProbeManager::~ProbeManager() { + // If the manager goes out of scope, stop the session. + if (mIsActive && mRegistrationHandle) { + StopSession(); + } +} + +ProbeManager::ProbeManager(const nsCID& aApplicationUID, + const nsACString& aApplicationName) + : mIsActive(false), + mApplicationUID(aApplicationUID), + mApplicationName(aApplicationName), + mSessionHandle(0), + mRegistrationHandle(0), + mInitialized(false) { +#if defined(MOZ_LOGGING) + char cidStr[NSID_LENGTH]; + aApplicationUID.ToProvidedString(cidStr); + LOG(("ProbeManager::Init for application %s, %s", aApplicationName.Data(), + cidStr)); +#endif +} + +// Note: The Windows API is just a little bit scary there. +// The only way to obtain the session handle is to +//- ignore the session handle obtained from RegisterTraceGuids +//- pass a callback +//- in that callback, request the session handle through +// GetTraceLoggerHandle and some opaque value received by the callback + +ULONG WINAPI ControlCallback(WMIDPREQUESTCODE aRequestCode, PVOID aContext, + ULONG* aReserved, PVOID aBuffer) { + ProbeManager* context = (ProbeManager*)aContext; + switch (aRequestCode) { + case WMI_ENABLE_EVENTS: { + context->mIsActive = true; + TRACEHANDLE sessionHandle = GetTraceLoggerHandle(aBuffer); + // Note: We only accept one handle + if ((HANDLE)sessionHandle == INVALID_HANDLE_VALUE) { + ULONG result = GetLastError(); + LOG(("Probes: ControlCallback failed, %lu", result)); + return result; + } else if (context->mIsActive && context->mSessionHandle && + context->mSessionHandle != sessionHandle) { + LOG( + ("Probes: Can only handle one context at a time, " + "ignoring activation")); + return ERROR_SUCCESS; + } else { + context->mSessionHandle = sessionHandle; + LOG(("Probes: ControlCallback activated")); + return ERROR_SUCCESS; + } + } + + case WMI_DISABLE_EVENTS: + context->mIsActive = false; + context->mSessionHandle = 0; + LOG(("Probes: ControlCallback deactivated")); + return ERROR_SUCCESS; + + default: + LOG(("Probes: ControlCallback does not know what to do with %d", + aRequestCode)); + return ERROR_INVALID_PARAMETER; + } +} + +already_AddRefed ProbeManager::GetProbe(const nsCID& aEventUID, + const nsACString& aEventName) { + RefPtr result(new Probe(aEventUID, aEventName, this)); + mAllProbes.AppendElement(result); + return result.forget(); +} + +nsresult ProbeManager::StartSession() { return StartSession(mAllProbes); } + +nsresult ProbeManager::StartSession(nsTArray>& aProbes) { + const size_t probesCount = aProbes.Length(); + _TRACE_GUID_REGISTRATION* probes = new _TRACE_GUID_REGISTRATION[probesCount]; + for (unsigned int i = 0; i < probesCount; ++i) { + const Probe* probe = aProbes[i]; + const Probe* probeX = static_cast(probe); + probes[i].Guid = (LPCGUID)&probeX->mGUID; + } + ULONG result = + RegisterTraceGuids(&ControlCallback + /*RequestAddress: Sets mSessions appropriately.*/, + this + /*RequestContext: Passed to ControlCallback*/, + (LPGUID)&mApplicationUID + /*ControlGuid: Tracing GUID + the cast comes from MSDN examples*/ + , + probesCount + /*GuidCount: Number of probes*/, + probes + /*TraceGuidReg: Probes registration*/, + nullptr + /*MofImagePath: Must be nullptr, says MSDN*/, + nullptr + /*MofResourceName:Must be nullptr, says MSDN*/, + &mRegistrationHandle + /*RegistrationHandle: Handler. + used only for unregistration*/ + ); + delete[] probes; + if (NS_WARN_IF(result != ERROR_SUCCESS)) { + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult ProbeManager::StopSession() { + LOG(("Probes: Stopping measures")); + if (mSessionHandle != 0) { + ULONG result = UnregisterTraceGuids(mSessionHandle); + mSessionHandle = 0; + if (result != ERROR_SUCCESS) { + return NS_ERROR_INVALID_ARG; + } + } + return NS_OK; +} + +} // namespace probes +} // namespace mozilla diff --git a/xpcom/build/perfprobe.h b/xpcom/build/perfprobe.h new file mode 100644 index 0000000000..e5a0382322 --- /dev/null +++ b/xpcom/build/perfprobe.h @@ -0,0 +1,198 @@ +/* -*- 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/. */ + +/** + * A mechanism for interacting with operating system-provided + * debugging/profiling tools such as Microsoft EWT/Windows Performance Toolkit. + */ + +#ifndef mozilla_perfprobe_h +#define mozilla_perfprobe_h + +#if !defined(XP_WIN) +# error "For the moment, perfprobe.h is defined only for Windows platforms" +#endif + +#include "nsError.h" +#include "nsString.h" +#include "mozilla/Logging.h" +#include "nsTArray.h" +#include +#undef GetStartupInfo // Prevent Windows from polluting global namespace +#include +#include + +namespace mozilla { +namespace probes { + +class ProbeManager; + +/** + * A data structure supporting a trigger operation that can be used to + * send information to the operating system. + */ + +class Probe { + public: + NS_INLINE_DECL_REFCOUNTING(Probe) + + /** + * Trigger the event. + * + * Note: Can be called from any thread. + */ + nsresult Trigger(); + + protected: + ~Probe(){}; + + Probe(const nsCID& aGUID, const nsACString& aName, ProbeManager* aManager); + friend class ProbeManager; + + protected: + /** + * The system GUID associated to this probe. See the documentation + * of |ProbeManager::Make| for more details. + */ + const GUID mGUID; + + /** + * The name of this probe. See the documentation + * of |ProbeManager::Make| for more details. + */ + const nsCString mName; + + /** + * The ProbeManager managing this probe. + * + * Note: This is a weak reference to avoid a useless cycle. + */ + class ProbeManager* mManager; +}; + +/** + * A manager for a group of probes. + * + * You can have several managers in one application, provided that they all + * have distinct IDs and names. However, having more than 2 is considered a bad + * practice. + */ +class ProbeManager { + public: + NS_INLINE_DECL_REFCOUNTING(ProbeManager) + + /** + * Create a new probe manager. + * + * This constructor should be called from the main thread. + * + * @param aApplicationUID The unique ID of the probe. Under Windows, this + * unique ID must have been previously registered using an external tool. + * See MyCategory on http://msdn.microsoft.com/en-us/library/aa364100.aspx + * @param aApplicationName A name for the probe. Currently used only for + * logging purposes. In the future, may be attached to the data sent to the + * operating system. + * + * Note: If two ProbeManagers are constructed with the same uid and/or name, + * behavior is unspecified. + */ + ProbeManager(const nsCID& aApplicationUID, + const nsACString& aApplicationName); + + /** + * Acquire a probe. + * + * Note: Only probes acquired before the call to SetReady are taken into + * account + * Note: Can be called only from the main thread. + * + * @param aEventUID The unique ID of the probe. Under Windows, this unique + * ID must have been previously registered using an external tool. + * See MyCategory on http://msdn.microsoft.com/en-us/library/aa364100.aspx + * @param aEventName A name for the probe. Currently used only for logging + * purposes. In the + * future, may be attached to the data sent to the operating system. + * @return Either |null| in case of error or a valid |Probe*|. + * + * Note: If this method is called twice with the same uid and/or name, + * behavior is undefined. + */ + already_AddRefed GetProbe(const nsCID& aEventUID, + const nsACString& aEventName); + + /** + * Start/stop the measuring session. + * + * This method should be called from the main thread. + * + * Note that starting an already started probe manager has no effect, + * nor does stopping an already stopped probe manager. + */ + nsresult StartSession(); + nsresult StopSession(); + + /** + * @return true If measures are currently on, i.e. if triggering probes is any + * is useful. You do not have to check this before triggering a probe, unless + * this can avoid complex computations. + */ + bool IsActive(); + + protected: + ~ProbeManager(); + + nsresult StartSession(nsTArray>& aProbes); + nsresult Init(const nsCID& aApplicationUID, + const nsACString& aApplicationName); + + protected: + /** + * `true` if a session is in activity, `false` otherwise. + */ + bool mIsActive; + + /** + * The UID of this manager. + * See documentation above for registration steps that you + * may have to take. + */ + nsCID mApplicationUID; + + /** + * The name of the application. + */ + nsCString mApplicationName; + + /** + * All the probes that have been created for this manager. + */ + nsTArray> mAllProbes; + + /** + * Handle used for triggering events + */ + TRACEHANDLE mSessionHandle; + + /** + * Handle used for registration/unregistration + */ + TRACEHANDLE mRegistrationHandle; + + /** + * `true` if initialization has been performed, `false` until then. + */ + bool mInitialized; + + friend class Probe; // Needs to access |mSessionHandle| + friend ULONG WINAPI ControlCallback(WMIDPREQUESTCODE aRequestCode, + PVOID aContext, ULONG* aReserved, + PVOID aBuffer); // Sets |mSessionHandle| +}; + +} // namespace probes +} // namespace mozilla + +#endif // mozilla_perfprobe_h diff --git a/xpcom/build/xpcom_alpha.def b/xpcom/build/xpcom_alpha.def new file mode 100644 index 0000000000..38fedfa17f --- /dev/null +++ b/xpcom/build/xpcom_alpha.def @@ -0,0 +1,256 @@ +;+# 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/. + +LIBRARY xpcom +DESCRIPTION "xpcom library" + +EXPORTS + ?Stub3@nsXPTCStubBase@@UAAIXZ + ?Stub4@nsXPTCStubBase@@UAAIXZ + ?Stub5@nsXPTCStubBase@@UAAIXZ + ?Stub6@nsXPTCStubBase@@UAAIXZ + ?Stub7@nsXPTCStubBase@@UAAIXZ + ?Stub8@nsXPTCStubBase@@UAAIXZ + ?Stub9@nsXPTCStubBase@@UAAIXZ + ?Stub10@nsXPTCStubBase@@UAAIXZ + ?Stub11@nsXPTCStubBase@@UAAIXZ + ?Stub12@nsXPTCStubBase@@UAAIXZ + ?Stub13@nsXPTCStubBase@@UAAIXZ + ?Stub14@nsXPTCStubBase@@UAAIXZ + ?Stub15@nsXPTCStubBase@@UAAIXZ + ?Stub16@nsXPTCStubBase@@UAAIXZ + ?Stub17@nsXPTCStubBase@@UAAIXZ + ?Stub18@nsXPTCStubBase@@UAAIXZ + ?Stub19@nsXPTCStubBase@@UAAIXZ + ?Stub20@nsXPTCStubBase@@UAAIXZ + ?Stub21@nsXPTCStubBase@@UAAIXZ + ?Stub22@nsXPTCStubBase@@UAAIXZ + ?Stub23@nsXPTCStubBase@@UAAIXZ + ?Stub24@nsXPTCStubBase@@UAAIXZ + ?Stub25@nsXPTCStubBase@@UAAIXZ + ?Stub26@nsXPTCStubBase@@UAAIXZ + ?Stub27@nsXPTCStubBase@@UAAIXZ + ?Stub28@nsXPTCStubBase@@UAAIXZ + ?Stub29@nsXPTCStubBase@@UAAIXZ + ?Stub30@nsXPTCStubBase@@UAAIXZ + ?Stub31@nsXPTCStubBase@@UAAIXZ + ?Stub32@nsXPTCStubBase@@UAAIXZ + ?Stub33@nsXPTCStubBase@@UAAIXZ + ?Stub34@nsXPTCStubBase@@UAAIXZ + ?Stub35@nsXPTCStubBase@@UAAIXZ + ?Stub36@nsXPTCStubBase@@UAAIXZ + ?Stub37@nsXPTCStubBase@@UAAIXZ + ?Stub38@nsXPTCStubBase@@UAAIXZ + ?Stub39@nsXPTCStubBase@@UAAIXZ + ?Stub40@nsXPTCStubBase@@UAAIXZ + ?Stub41@nsXPTCStubBase@@UAAIXZ + ?Stub42@nsXPTCStubBase@@UAAIXZ + ?Stub43@nsXPTCStubBase@@UAAIXZ + ?Stub44@nsXPTCStubBase@@UAAIXZ + ?Stub45@nsXPTCStubBase@@UAAIXZ + ?Stub46@nsXPTCStubBase@@UAAIXZ + ?Stub47@nsXPTCStubBase@@UAAIXZ + ?Stub48@nsXPTCStubBase@@UAAIXZ + ?Stub49@nsXPTCStubBase@@UAAIXZ + ?Stub50@nsXPTCStubBase@@UAAIXZ + ?Stub51@nsXPTCStubBase@@UAAIXZ + ?Stub52@nsXPTCStubBase@@UAAIXZ + ?Stub53@nsXPTCStubBase@@UAAIXZ + ?Stub54@nsXPTCStubBase@@UAAIXZ + ?Stub55@nsXPTCStubBase@@UAAIXZ + ?Stub56@nsXPTCStubBase@@UAAIXZ + ?Stub57@nsXPTCStubBase@@UAAIXZ + ?Stub58@nsXPTCStubBase@@UAAIXZ + ?Stub59@nsXPTCStubBase@@UAAIXZ + ?Stub60@nsXPTCStubBase@@UAAIXZ + ?Stub61@nsXPTCStubBase@@UAAIXZ + ?Stub62@nsXPTCStubBase@@UAAIXZ + ?Stub63@nsXPTCStubBase@@UAAIXZ + ?Stub64@nsXPTCStubBase@@UAAIXZ + ?Stub65@nsXPTCStubBase@@UAAIXZ + ?Stub66@nsXPTCStubBase@@UAAIXZ + ?Stub67@nsXPTCStubBase@@UAAIXZ + ?Stub68@nsXPTCStubBase@@UAAIXZ + ?Stub69@nsXPTCStubBase@@UAAIXZ + ?Stub70@nsXPTCStubBase@@UAAIXZ + ?Stub71@nsXPTCStubBase@@UAAIXZ + ?Stub72@nsXPTCStubBase@@UAAIXZ + ?Stub73@nsXPTCStubBase@@UAAIXZ + ?Stub74@nsXPTCStubBase@@UAAIXZ + ?Stub75@nsXPTCStubBase@@UAAIXZ + ?Stub76@nsXPTCStubBase@@UAAIXZ + ?Stub77@nsXPTCStubBase@@UAAIXZ + ?Stub78@nsXPTCStubBase@@UAAIXZ + ?Stub79@nsXPTCStubBase@@UAAIXZ + ?Stub80@nsXPTCStubBase@@UAAIXZ + ?Stub81@nsXPTCStubBase@@UAAIXZ + ?Stub82@nsXPTCStubBase@@UAAIXZ + ?Stub83@nsXPTCStubBase@@UAAIXZ + ?Stub84@nsXPTCStubBase@@UAAIXZ + ?Stub85@nsXPTCStubBase@@UAAIXZ + ?Stub86@nsXPTCStubBase@@UAAIXZ + ?Stub87@nsXPTCStubBase@@UAAIXZ + ?Stub88@nsXPTCStubBase@@UAAIXZ + ?Stub89@nsXPTCStubBase@@UAAIXZ + ?Stub90@nsXPTCStubBase@@UAAIXZ + ?Stub91@nsXPTCStubBase@@UAAIXZ + ?Stub92@nsXPTCStubBase@@UAAIXZ + ?Stub93@nsXPTCStubBase@@UAAIXZ + ?Stub94@nsXPTCStubBase@@UAAIXZ + ?Stub95@nsXPTCStubBase@@UAAIXZ + ?Stub96@nsXPTCStubBase@@UAAIXZ + ?Stub97@nsXPTCStubBase@@UAAIXZ + ?Stub98@nsXPTCStubBase@@UAAIXZ + ?Stub99@nsXPTCStubBase@@UAAIXZ + ?Stub100@nsXPTCStubBase@@UAAIXZ + ?Stub101@nsXPTCStubBase@@UAAIXZ + ?Stub102@nsXPTCStubBase@@UAAIXZ + ?Stub103@nsXPTCStubBase@@UAAIXZ + ?Stub104@nsXPTCStubBase@@UAAIXZ + ?Stub105@nsXPTCStubBase@@UAAIXZ + ?Stub106@nsXPTCStubBase@@UAAIXZ + ?Stub107@nsXPTCStubBase@@UAAIXZ + ?Stub108@nsXPTCStubBase@@UAAIXZ + ?Stub109@nsXPTCStubBase@@UAAIXZ + ?Stub110@nsXPTCStubBase@@UAAIXZ + ?Stub111@nsXPTCStubBase@@UAAIXZ + ?Stub112@nsXPTCStubBase@@UAAIXZ + ?Stub113@nsXPTCStubBase@@UAAIXZ + ?Stub114@nsXPTCStubBase@@UAAIXZ + ?Stub115@nsXPTCStubBase@@UAAIXZ + ?Stub116@nsXPTCStubBase@@UAAIXZ + ?Stub117@nsXPTCStubBase@@UAAIXZ + ?Stub118@nsXPTCStubBase@@UAAIXZ + ?Stub119@nsXPTCStubBase@@UAAIXZ + ?Stub120@nsXPTCStubBase@@UAAIXZ + ?Stub121@nsXPTCStubBase@@UAAIXZ + ?Stub122@nsXPTCStubBase@@UAAIXZ + ?Stub123@nsXPTCStubBase@@UAAIXZ + ?Stub124@nsXPTCStubBase@@UAAIXZ + ?Stub125@nsXPTCStubBase@@UAAIXZ + ?Stub126@nsXPTCStubBase@@UAAIXZ + ?Stub127@nsXPTCStubBase@@UAAIXZ + ?Stub128@nsXPTCStubBase@@UAAIXZ + ?Stub129@nsXPTCStubBase@@UAAIXZ + ?Stub130@nsXPTCStubBase@@UAAIXZ + ?Stub131@nsXPTCStubBase@@UAAIXZ + ?Stub132@nsXPTCStubBase@@UAAIXZ + ?Stub133@nsXPTCStubBase@@UAAIXZ + ?Stub134@nsXPTCStubBase@@UAAIXZ + ?Stub135@nsXPTCStubBase@@UAAIXZ + ?Stub136@nsXPTCStubBase@@UAAIXZ + ?Stub137@nsXPTCStubBase@@UAAIXZ + ?Stub138@nsXPTCStubBase@@UAAIXZ + ?Stub139@nsXPTCStubBase@@UAAIXZ + ?Stub140@nsXPTCStubBase@@UAAIXZ + ?Stub141@nsXPTCStubBase@@UAAIXZ + ?Stub142@nsXPTCStubBase@@UAAIXZ + ?Stub143@nsXPTCStubBase@@UAAIXZ + ?Stub144@nsXPTCStubBase@@UAAIXZ + ?Stub145@nsXPTCStubBase@@UAAIXZ + ?Stub146@nsXPTCStubBase@@UAAIXZ + ?Stub147@nsXPTCStubBase@@UAAIXZ + ?Stub148@nsXPTCStubBase@@UAAIXZ + ?Stub149@nsXPTCStubBase@@UAAIXZ + ?Stub150@nsXPTCStubBase@@UAAIXZ + ?Stub151@nsXPTCStubBase@@UAAIXZ + ?Stub152@nsXPTCStubBase@@UAAIXZ + ?Stub153@nsXPTCStubBase@@UAAIXZ + ?Stub154@nsXPTCStubBase@@UAAIXZ + ?Stub155@nsXPTCStubBase@@UAAIXZ + ?Stub156@nsXPTCStubBase@@UAAIXZ + ?Stub157@nsXPTCStubBase@@UAAIXZ + ?Stub158@nsXPTCStubBase@@UAAIXZ + ?Stub159@nsXPTCStubBase@@UAAIXZ + ?Stub160@nsXPTCStubBase@@UAAIXZ + ?Stub161@nsXPTCStubBase@@UAAIXZ + ?Stub162@nsXPTCStubBase@@UAAIXZ + ?Stub163@nsXPTCStubBase@@UAAIXZ + ?Stub164@nsXPTCStubBase@@UAAIXZ + ?Stub165@nsXPTCStubBase@@UAAIXZ + ?Stub166@nsXPTCStubBase@@UAAIXZ + ?Stub167@nsXPTCStubBase@@UAAIXZ + ?Stub168@nsXPTCStubBase@@UAAIXZ + ?Stub169@nsXPTCStubBase@@UAAIXZ + ?Stub170@nsXPTCStubBase@@UAAIXZ + ?Stub171@nsXPTCStubBase@@UAAIXZ + ?Stub172@nsXPTCStubBase@@UAAIXZ + ?Stub173@nsXPTCStubBase@@UAAIXZ + ?Stub174@nsXPTCStubBase@@UAAIXZ + ?Stub175@nsXPTCStubBase@@UAAIXZ + ?Stub176@nsXPTCStubBase@@UAAIXZ + ?Stub177@nsXPTCStubBase@@UAAIXZ + ?Stub178@nsXPTCStubBase@@UAAIXZ + ?Stub179@nsXPTCStubBase@@UAAIXZ + ?Stub180@nsXPTCStubBase@@UAAIXZ + ?Stub181@nsXPTCStubBase@@UAAIXZ + ?Stub182@nsXPTCStubBase@@UAAIXZ + ?Stub183@nsXPTCStubBase@@UAAIXZ + ?Stub184@nsXPTCStubBase@@UAAIXZ + ?Stub185@nsXPTCStubBase@@UAAIXZ + ?Stub186@nsXPTCStubBase@@UAAIXZ + ?Stub187@nsXPTCStubBase@@UAAIXZ + ?Stub188@nsXPTCStubBase@@UAAIXZ + ?Stub189@nsXPTCStubBase@@UAAIXZ + ?Stub190@nsXPTCStubBase@@UAAIXZ + ?Stub191@nsXPTCStubBase@@UAAIXZ + ?Stub192@nsXPTCStubBase@@UAAIXZ + ?Stub193@nsXPTCStubBase@@UAAIXZ + ?Stub194@nsXPTCStubBase@@UAAIXZ + ?Stub195@nsXPTCStubBase@@UAAIXZ + ?Stub196@nsXPTCStubBase@@UAAIXZ + ?Stub197@nsXPTCStubBase@@UAAIXZ + ?Stub198@nsXPTCStubBase@@UAAIXZ + ?Stub199@nsXPTCStubBase@@UAAIXZ + ?Stub200@nsXPTCStubBase@@UAAIXZ + ?Stub201@nsXPTCStubBase@@UAAIXZ + ?Stub202@nsXPTCStubBase@@UAAIXZ + ?Stub203@nsXPTCStubBase@@UAAIXZ + ?Stub204@nsXPTCStubBase@@UAAIXZ + ?Stub205@nsXPTCStubBase@@UAAIXZ + ?Stub206@nsXPTCStubBase@@UAAIXZ + ?Stub207@nsXPTCStubBase@@UAAIXZ + ?Stub208@nsXPTCStubBase@@UAAIXZ + ?Stub209@nsXPTCStubBase@@UAAIXZ + ?Stub210@nsXPTCStubBase@@UAAIXZ + ?Stub211@nsXPTCStubBase@@UAAIXZ + ?Stub212@nsXPTCStubBase@@UAAIXZ + ?Stub213@nsXPTCStubBase@@UAAIXZ + ?Stub214@nsXPTCStubBase@@UAAIXZ + ?Stub215@nsXPTCStubBase@@UAAIXZ + ?Stub216@nsXPTCStubBase@@UAAIXZ + ?Stub217@nsXPTCStubBase@@UAAIXZ + ?Stub218@nsXPTCStubBase@@UAAIXZ + ?Stub219@nsXPTCStubBase@@UAAIXZ + ?Stub220@nsXPTCStubBase@@UAAIXZ + ?Stub221@nsXPTCStubBase@@UAAIXZ + ?Stub222@nsXPTCStubBase@@UAAIXZ + ?Stub223@nsXPTCStubBase@@UAAIXZ + ?Stub224@nsXPTCStubBase@@UAAIXZ + ?Stub225@nsXPTCStubBase@@UAAIXZ + ?Stub226@nsXPTCStubBase@@UAAIXZ + ?Stub227@nsXPTCStubBase@@UAAIXZ + ?Stub228@nsXPTCStubBase@@UAAIXZ + ?Stub229@nsXPTCStubBase@@UAAIXZ + ?Stub230@nsXPTCStubBase@@UAAIXZ + ?Stub231@nsXPTCStubBase@@UAAIXZ + ?Stub232@nsXPTCStubBase@@UAAIXZ + ?Stub233@nsXPTCStubBase@@UAAIXZ + ?Stub234@nsXPTCStubBase@@UAAIXZ + ?Stub235@nsXPTCStubBase@@UAAIXZ + ?Stub236@nsXPTCStubBase@@UAAIXZ + ?Stub237@nsXPTCStubBase@@UAAIXZ + ?Stub238@nsXPTCStubBase@@UAAIXZ + ?Stub239@nsXPTCStubBase@@UAAIXZ + ?Stub240@nsXPTCStubBase@@UAAIXZ + ?Stub241@nsXPTCStubBase@@UAAIXZ + ?Stub242@nsXPTCStubBase@@UAAIXZ + ?Stub243@nsXPTCStubBase@@UAAIXZ + ?Stub244@nsXPTCStubBase@@UAAIXZ + ?Stub245@nsXPTCStubBase@@UAAIXZ + ?Stub246@nsXPTCStubBase@@UAAIXZ + ?Stub247@nsXPTCStubBase@@UAAIXZ + ?Stub248@nsXPTCStubBase@@UAAIXZ + ?Stub249@nsXPTCStubBase@@UAAIXZ + diff --git a/xpcom/components/GenericFactory.cpp b/xpcom/components/GenericFactory.cpp new file mode 100644 index 0000000000..5eb88e8ba4 --- /dev/null +++ b/xpcom/components/GenericFactory.cpp @@ -0,0 +1,18 @@ +/* -*- 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/GenericFactory.h" + +namespace mozilla { + +NS_IMPL_ISUPPORTS(GenericFactory, nsIFactory) + +NS_IMETHODIMP +GenericFactory::CreateInstance(REFNSIID aIID, void** aResult) { + return mCtor(aIID, aResult); +} + +} // namespace mozilla diff --git a/xpcom/components/GenericFactory.h b/xpcom/components/GenericFactory.h new file mode 100644 index 0000000000..3fb2186e12 --- /dev/null +++ b/xpcom/components/GenericFactory.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ + +#ifndef mozilla_GenericFactory_h +#define mozilla_GenericFactory_h + +#include "nsIFactory.h" + +namespace mozilla { + +/** + * A generic factory which uses a constructor function to create instances. + * This class is intended for use by the component manager and the generic + * module. + */ +class GenericFactory final : public nsIFactory { + ~GenericFactory() = default; + + public: + typedef nsresult (*ConstructorProcPtr)(const nsIID& aIID, void** aResult); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFACTORY + + explicit GenericFactory(ConstructorProcPtr aCtor) : mCtor(aCtor) { + NS_ASSERTION(mCtor, "GenericFactory with no constructor"); + } + + private: + ConstructorProcPtr mCtor; +}; + +} // namespace mozilla + +#endif // mozilla_GenericFactory_h diff --git a/xpcom/components/ManifestParser.cpp b/xpcom/components/ManifestParser.cpp new file mode 100644 index 0000000000..88ee06d78d --- /dev/null +++ b/xpcom/components/ManifestParser.cpp @@ -0,0 +1,678 @@ +/* -*- 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/ArrayUtils.h" +#include "mozilla/Printf.h" +#include "mozilla/UniquePtr.h" + +#include "ManifestParser.h" + +#include + +#include "prio.h" +#if defined(XP_WIN) +# include +#elif defined(MOZ_WIDGET_COCOA) +# include +# include "nsCocoaFeatures.h" +#elif defined(MOZ_WIDGET_GTK) +# include +#endif + +#ifdef MOZ_WIDGET_ANDROID +# include "AndroidBuild.h" +# include "mozilla/java/GeckoAppShellWrappers.h" +#endif + +#ifdef MOZ_BACKGROUNDTASKS +# include "mozilla/BackgroundTasks.h" +#endif + +#include "mozilla/Services.h" + +#include "nsCRT.h" +#include "nsConsoleMessage.h" +#include "nsTextFormatter.h" +#include "nsVersionComparator.h" +#include "nsXPCOMCIDInternal.h" + +#include "nsIConsoleService.h" +#include "nsIScriptError.h" +#include "nsIXULAppInfo.h" +#include "nsIXULRuntime.h" + +using namespace mozilla; + +struct ManifestDirective { + const char* directive; + int argc; + + bool ischrome; + + // The contentaccessible flags only apply to content/resource directives. + bool contentflags; + + // Function to handle this directive. This isn't a union because C++ still + // hasn't learned how to initialize unions in a sane way. + void (nsComponentManagerImpl::*mgrfunc)( + nsComponentManagerImpl::ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void (nsChromeRegistry::*regfunc)( + nsChromeRegistry::ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv, int aFlags); +}; +static const ManifestDirective kParsingTable[] = { + // clang-format off + { + "manifest", 1, true, false, + &nsComponentManagerImpl::ManifestManifest, nullptr, + }, + { + "category", 3, false, false, + &nsComponentManagerImpl::ManifestCategory, nullptr, + }, + { + "content", 2, true, true, + nullptr, &nsChromeRegistry::ManifestContent, + }, + { + "locale", 3, true, false, + nullptr, &nsChromeRegistry::ManifestLocale, + }, + { + "skin", 3, true, false, + nullptr, &nsChromeRegistry::ManifestSkin, + }, + { + // NB: note that while skin manifests can use this, they are only allowed + // to use it for chrome://../skin/ URLs + "override", 2, true, false, + nullptr, &nsChromeRegistry::ManifestOverride, + }, + { + "resource", 2, false, true, + nullptr, &nsChromeRegistry::ManifestResource, + } + // clang-format on +}; + +static const char kWhitespace[] = "\t "; + +static bool IsNewline(char aChar) { return aChar == '\n' || aChar == '\r'; } + +void LogMessage(const char* aMsg, ...) { + MOZ_ASSERT(nsComponentManagerImpl::gComponentManager); + + nsCOMPtr console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) { + return; + } + + va_list args; + va_start(args, aMsg); + SmprintfPointer formatted(mozilla::Vsmprintf(aMsg, args)); + va_end(args); + + nsCOMPtr error = + new nsConsoleMessage(NS_ConvertUTF8toUTF16(formatted.get())); + console->LogMessage(error); +} + +void LogMessageWithContext(FileLocation& aFile, uint32_t aLineNumber, + const char* aMsg, ...) { + va_list args; + va_start(args, aMsg); + SmprintfPointer formatted(mozilla::Vsmprintf(aMsg, args)); + va_end(args); + if (!formatted) { + return; + } + + MOZ_ASSERT(nsComponentManagerImpl::gComponentManager); + + nsCString file; + aFile.GetURIString(file); + + nsCOMPtr error = do_CreateInstance(NS_SCRIPTERROR_CONTRACTID); + if (!error) { + // This can happen early in component registration. Fall back to a + // generic console message. + LogMessage("Warning: in '%s', line %i: %s", file.get(), aLineNumber, + formatted.get()); + return; + } + + nsCOMPtr console = + do_GetService(NS_CONSOLESERVICE_CONTRACTID); + if (!console) { + return; + } + + nsresult rv = error->Init( + NS_ConvertUTF8toUTF16(formatted.get()), NS_ConvertUTF8toUTF16(file), + u""_ns, aLineNumber, 0, nsIScriptError::warningFlag, + "chrome registration"_ns, false /* from private window */, + true /* from chrome context */); + if (NS_FAILED(rv)) { + return; + } + + console->LogMessage(error); +} + +/** + * Check for a modifier flag of the following forms: + * "flag" (same as "true") + * "flag=yes|true|1" + * "flag="no|false|0" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aResult If the flag is found, the value is assigned here. + * @return Whether the flag was handled. + */ +static bool CheckFlag(const nsAString& aFlag, const nsAString& aData, + bool& aResult) { + if (!StringBeginsWith(aData, aFlag)) { + return false; + } + + if (aFlag.Length() == aData.Length()) { + // the data is simply "flag", which is the same as "flag=yes" + aResult = true; + return true; + } + + if (aData.CharAt(aFlag.Length()) != '=') { + // the data is "flag2=", which is not anything we care about + return false; + } + + if (aData.Length() == aFlag.Length() + 1) { + aResult = false; + return true; + } + + switch (aData.CharAt(aFlag.Length() + 1)) { + case '1': + case 't': // true + case 'y': // yes + aResult = true; + return true; + + case '0': + case 'f': // false + case 'n': // no + aResult = false; + return true; + } + + return false; +} + +enum TriState { eUnspecified, eBad, eOK }; + +/** + * Check for a modifier flag of the following form: + * "flag=string" + * "flag!=string" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aValue The value that is expected. + * @param aResult If this is "ok" when passed in, this is left alone. + * Otherwise if the flag is found it is set to eBad or eOK. + * @return Whether the flag was handled. + */ +static bool CheckStringFlag(const nsAString& aFlag, const nsAString& aData, + const nsAString& aValue, TriState& aResult) { + if (aData.Length() < aFlag.Length() + 1) { + return false; + } + + if (!StringBeginsWith(aData, aFlag)) { + return false; + } + + bool comparison = true; + if (aData[aFlag.Length()] != '=') { + if (aData[aFlag.Length()] == '!' && aData.Length() >= aFlag.Length() + 2 && + aData[aFlag.Length() + 1] == '=') { + comparison = false; + } else { + return false; + } + } + + if (aResult != eOK) { + nsDependentSubstring testdata = + Substring(aData, aFlag.Length() + (comparison ? 1 : 2)); + if (testdata.Equals(aValue)) { + aResult = comparison ? eOK : eBad; + } else { + aResult = comparison ? eBad : eOK; + } + } + + return true; +} + +static bool CheckOsFlag(const nsAString& aFlag, const nsAString& aData, + const nsAString& aValue, TriState& aResult) { + bool result = CheckStringFlag(aFlag, aData, aValue, aResult); +#if defined(XP_UNIX) && !defined(XP_DARWIN) && !defined(ANDROID) + if (result && aResult == eBad) { + result = CheckStringFlag(aFlag, aData, u"likeunix"_ns, aResult); + } +#endif + return result; +} + +/** + * Check for a modifier flag of the following form: + * "flag=version" + * "flag<=version" + * "flag=version" + * "flag>version" + * @param aFlag The flag to compare. + * @param aData The tokenized data to check; this is lowercased + * before being passed in. + * @param aValue The value that is expected. If this is empty then no + * comparison will match. + * @param aResult If this is eOK when passed in, this is left alone. + * Otherwise if the flag is found it is set to eBad or eOK. + * @return Whether the flag was handled. + */ + +#define COMPARE_EQ 1 << 0 +#define COMPARE_LT 1 << 1 +#define COMPARE_GT 1 << 2 + +static bool CheckVersionFlag(const nsString& aFlag, const nsString& aData, + const nsString& aValue, TriState& aResult) { + if (aData.Length() < aFlag.Length() + 2) { + return false; + } + + if (!StringBeginsWith(aData, aFlag)) { + return false; + } + + if (aValue.Length() == 0) { + if (aResult != eOK) { + aResult = eBad; + } + return true; + } + + uint32_t comparison; + nsAutoString testdata; + + switch (aData[aFlag.Length()]) { + case '=': + comparison = COMPARE_EQ; + testdata = Substring(aData, aFlag.Length() + 1); + break; + + case '<': + if (aData[aFlag.Length() + 1] == '=') { + comparison = COMPARE_EQ | COMPARE_LT; + testdata = Substring(aData, aFlag.Length() + 2); + } else { + comparison = COMPARE_LT; + testdata = Substring(aData, aFlag.Length() + 1); + } + break; + + case '>': + if (aData[aFlag.Length() + 1] == '=') { + comparison = COMPARE_EQ | COMPARE_GT; + testdata = Substring(aData, aFlag.Length() + 2); + } else { + comparison = COMPARE_GT; + testdata = Substring(aData, aFlag.Length() + 1); + } + break; + + default: + return false; + } + + if (testdata.Length() == 0) { + return false; + } + + if (aResult != eOK) { + int32_t c = mozilla::CompareVersions(NS_ConvertUTF16toUTF8(aValue).get(), + NS_ConvertUTF16toUTF8(testdata).get()); + if ((c == 0 && comparison & COMPARE_EQ) || + (c < 0 && comparison & COMPARE_LT) || + (c > 0 && comparison & COMPARE_GT)) { + aResult = eOK; + } else { + aResult = eBad; + } + } + + return true; +} + +// In-place conversion of ascii characters to lower case +static void ToLowerCase(char* aToken) { + for (; *aToken; ++aToken) { + *aToken = NS_ToLower(*aToken); + } +} + +namespace { + +struct CachedDirective { + int lineno; + char* argv[4]; +}; + +} // namespace + +void ParseManifest(NSLocationType aType, FileLocation& aFile, char* aBuf, + bool aChromeOnly) { + nsComponentManagerImpl::ManifestProcessingContext mgrcx(aType, aFile, + aChromeOnly); + nsChromeRegistry::ManifestProcessingContext chromecx(aType, aFile); + nsresult rv; + + constexpr auto kContentAccessible = u"contentaccessible"_ns; + constexpr auto kRemoteEnabled = u"remoteenabled"_ns; + constexpr auto kRemoteRequired = u"remoterequired"_ns; + constexpr auto kApplication = u"application"_ns; + constexpr auto kAppVersion = u"appversion"_ns; + constexpr auto kGeckoVersion = u"platformversion"_ns; + constexpr auto kOs = u"os"_ns; + constexpr auto kOsVersion = u"osversion"_ns; + constexpr auto kABI = u"abi"_ns; + constexpr auto kProcess = u"process"_ns; +#if defined(MOZ_WIDGET_ANDROID) + constexpr auto kTablet = u"tablet"_ns; +#endif + // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, but it's not + // possible to have conditional manifest contents, so we need to recognize and + // discard these tokens even when MOZ_BACKGROUNDTASKS is not set. + constexpr auto kBackgroundTask = u"backgroundtask"_ns; + + constexpr auto kMain = u"main"_ns; + constexpr auto kContent = u"content"_ns; + + // Obsolete + constexpr auto kXPCNativeWrappers = u"xpcnativewrappers"_ns; + + nsAutoString appID; + nsAutoString appVersion; + nsAutoString geckoVersion; + nsAutoString osTarget; + nsAutoString abi; + nsAutoString process; + + nsCOMPtr xapp(do_GetService(XULAPPINFO_SERVICE_CONTRACTID)); + if (xapp) { + nsAutoCString s; + rv = xapp->GetID(s); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(s, appID); + } + + rv = xapp->GetVersion(s); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(s, appVersion); + } + + rv = xapp->GetPlatformVersion(s); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(s, geckoVersion); + } + + nsCOMPtr xruntime(do_QueryInterface(xapp)); + if (xruntime) { + rv = xruntime->GetOS(s); + if (NS_SUCCEEDED(rv)) { + ToLowerCase(s); + CopyUTF8toUTF16(s, osTarget); + } + + rv = xruntime->GetXPCOMABI(s); + if (NS_SUCCEEDED(rv) && osTarget.Length()) { + ToLowerCase(s); + CopyUTF8toUTF16(s, abi); + abi.Insert(char16_t('_'), 0); + abi.Insert(osTarget, 0); + } + } + } + + nsAutoString osVersion; +#if defined(XP_WIN) +# pragma warning(push) +# pragma warning(disable : 4996) // VC12+ deprecates GetVersionEx + OSVERSIONINFO info = {sizeof(OSVERSIONINFO)}; + if (GetVersionEx(&info)) { + nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", info.dwMajorVersion, + info.dwMinorVersion); + } +# pragma warning(pop) +#elif defined(MOZ_WIDGET_COCOA) + SInt32 majorVersion = nsCocoaFeatures::macOSVersionMajor(); + SInt32 minorVersion = nsCocoaFeatures::macOSVersionMinor(); + nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", majorVersion, minorVersion); +#elif defined(MOZ_WIDGET_GTK) + nsTextFormatter::ssprintf(osVersion, u"%ld.%ld", gtk_major_version, + gtk_minor_version); +#elif defined(MOZ_WIDGET_ANDROID) + bool isTablet = false; + if (jni::IsAvailable()) { + jni::String::LocalRef release = java::sdk::Build::VERSION::RELEASE(); + osVersion.Assign(release->ToString()); + isTablet = java::GeckoAppShell::IsTablet(); + } +#endif + + if (XRE_IsContentProcess()) { + process = kContent; + } else { + process = kMain; + } + + char* token; + char* newline = aBuf; + uint32_t line = 0; + + // outer loop tokenizes by newline + while (*newline) { + while (*newline && IsNewline(*newline)) { + ++newline; + ++line; + } + if (!*newline) { + break; + } + + token = newline; + while (*newline && !IsNewline(*newline)) { + ++newline; + } + + if (*newline) { + *newline = '\0'; + ++newline; + } + ++line; + + if (*token == '#') { // ignore lines that begin with # as comments + continue; + } + + char* whitespace = token; + token = nsCRT::strtok(whitespace, kWhitespace, &whitespace); + if (!token) { + continue; + } + + const ManifestDirective* directive = nullptr; + for (const ManifestDirective* d = kParsingTable; + d < ArrayEnd(kParsingTable); ++d) { + if (!strcmp(d->directive, token)) { + directive = d; + break; + } + } + + if (!directive) { + LogMessageWithContext( + aFile, line, "Ignoring unrecognized chrome manifest directive '%s'.", + token); + continue; + } + + if (!directive->ischrome && NS_BOOTSTRAPPED_LOCATION == aType) { + LogMessageWithContext( + aFile, line, + "Bootstrapped manifest not allowed to use '%s' directive.", token); + continue; + } + + NS_ASSERTION(directive->argc < 4, "Need to reset argv array length"); + char* argv[4]; + for (int i = 0; i < directive->argc; ++i) { + argv[i] = nsCRT::strtok(whitespace, kWhitespace, &whitespace); + } + + if (!argv[directive->argc - 1]) { + LogMessageWithContext(aFile, line, + "Not enough arguments for chrome manifest " + "directive '%s', expected %i.", + token, directive->argc); + continue; + } + + bool ok = true; + TriState stAppVersion = eUnspecified; + TriState stGeckoVersion = eUnspecified; + TriState stApp = eUnspecified; + TriState stOsVersion = eUnspecified; + TriState stOs = eUnspecified; + TriState stABI = eUnspecified; + TriState stProcess = eUnspecified; +#if defined(MOZ_WIDGET_ANDROID) + TriState stTablet = eUnspecified; +#endif +#ifdef MOZ_BACKGROUNDTASKS + // When in background task mode, default to not registering + // category directivies unless backgroundtask=1 is specified. + TriState stBackgroundTask = (BackgroundTasks::IsBackgroundTaskMode() && + strcmp("category", directive->directive) == 0) + ? eBad + : eUnspecified; +#endif + int flags = 0; + + while ((token = nsCRT::strtok(whitespace, kWhitespace, &whitespace)) && + ok) { + ToLowerCase(token); + NS_ConvertASCIItoUTF16 wtoken(token); + + if (CheckStringFlag(kApplication, wtoken, appID, stApp) || + CheckOsFlag(kOs, wtoken, osTarget, stOs) || + CheckStringFlag(kABI, wtoken, abi, stABI) || + CheckStringFlag(kProcess, wtoken, process, stProcess) || + CheckVersionFlag(kOsVersion, wtoken, osVersion, stOsVersion) || + CheckVersionFlag(kAppVersion, wtoken, appVersion, stAppVersion) || + CheckVersionFlag(kGeckoVersion, wtoken, geckoVersion, + stGeckoVersion)) { + continue; + } + +#if defined(MOZ_WIDGET_ANDROID) + bool tablet = false; + if (CheckFlag(kTablet, wtoken, tablet)) { + stTablet = (tablet == isTablet) ? eOK : eBad; + continue; + } +#endif + + // You might expect this to be guarded by MOZ_BACKGROUNDTASKS, it's not + // possible to have conditional manifest contents. + bool flag; + if (CheckFlag(kBackgroundTask, wtoken, flag)) { +#if defined(MOZ_BACKGROUNDTASKS) + // Background task mode is active: filter. + stBackgroundTask = + (flag == BackgroundTasks::IsBackgroundTaskMode()) ? eOK : eBad; +#endif /* defined(MOZ_BACKGROUNDTASKS) */ + continue; + } + + if (directive->contentflags) { + bool flag; + if (CheckFlag(kContentAccessible, wtoken, flag)) { + if (flag) flags |= nsChromeRegistry::CONTENT_ACCESSIBLE; + continue; + } + if (CheckFlag(kRemoteEnabled, wtoken, flag)) { + if (flag) flags |= nsChromeRegistry::REMOTE_ALLOWED; + continue; + } + if (CheckFlag(kRemoteRequired, wtoken, flag)) { + if (flag) flags |= nsChromeRegistry::REMOTE_REQUIRED; + continue; + } + } + + bool xpcNativeWrappers = true; // Dummy for CheckFlag. + if (CheckFlag(kXPCNativeWrappers, wtoken, xpcNativeWrappers)) { + LogMessageWithContext( + aFile, line, "Ignoring obsolete chrome registration modifier '%s'.", + token); + continue; + } + + LogMessageWithContext( + aFile, line, "Unrecognized chrome manifest modifier '%s'.", token); + ok = false; + } + + if (!ok || stApp == eBad || stAppVersion == eBad || + stGeckoVersion == eBad || stOs == eBad || stOsVersion == eBad || +#ifdef MOZ_WIDGET_ANDROID + stTablet == eBad || +#endif +#ifdef MOZ_BACKGROUNDTASKS + stBackgroundTask == eBad || +#endif + stABI == eBad || stProcess == eBad) { + continue; + } + + if (directive->regfunc) { + if (GeckoProcessType_Default != XRE_GetProcessType()) { + continue; + } + + if (!nsChromeRegistry::gChromeRegistry) { + nsCOMPtr cr = mozilla::services::GetChromeRegistry(); + if (!nsChromeRegistry::gChromeRegistry) { + LogMessageWithContext(aFile, line, + "Chrome registry isn't available yet."); + continue; + } + } + + (nsChromeRegistry::gChromeRegistry->*(directive->regfunc))(chromecx, line, + argv, flags); + } else if (directive->ischrome || !aChromeOnly) { + (nsComponentManagerImpl::gComponentManager->*(directive->mgrfunc))( + mgrcx, line, argv); + } + } +} diff --git a/xpcom/components/ManifestParser.h b/xpcom/components/ManifestParser.h new file mode 100644 index 0000000000..e66cb58bcb --- /dev/null +++ b/xpcom/components/ManifestParser.h @@ -0,0 +1,23 @@ +/* -*- 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/. */ + +#ifndef ManifestParser_h +#define ManifestParser_h + +#include "nsComponentManager.h" +#include "nsChromeRegistry.h" +#include "mozilla/Attributes.h" +#include "mozilla/FileLocation.h" + +void ParseManifest(NSLocationType aType, mozilla::FileLocation& aFile, + char* aBuf, bool aChromeOnly); + +void LogMessage(const char* aMsg, ...) MOZ_FORMAT_PRINTF(1, 2); + +void LogMessageWithContext(mozilla::FileLocation& aFile, uint32_t aLineNumber, + const char* aMsg, ...) MOZ_FORMAT_PRINTF(3, 4); + +#endif // ManifestParser_h diff --git a/xpcom/components/Module.h b/xpcom/components/Module.h new file mode 100644 index 0000000000..044626b1ab --- /dev/null +++ b/xpcom/components/Module.h @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +#ifndef mozilla_Module_h +#define mozilla_Module_h + +#include "nscore.h" + +namespace mozilla { + +namespace Module { +/** + * This selector allows components to be marked so that they're only loaded + * into certain kinds of processes. Selectors can be combined. + */ +// Note: This must be kept in sync with the selector matching in +// nsComponentManager.cpp. +enum ProcessSelector { + ANY_PROCESS = 0x0, + MAIN_PROCESS_ONLY = 0x1, + CONTENT_PROCESS_ONLY = 0x2, + + /** + * By default, modules are not loaded in the GPU, VR, Socket, RDD, Utility + * and IPDLUnitTest processes, even if ANY_PROCESS is specified. This flag + * enables a module in the relevant process. + * + * NOTE: IPDLUnitTest does not have its own flag, and will only load a + * module if it is enabled in all processes. + */ + ALLOW_IN_GPU_PROCESS = 0x4, + ALLOW_IN_VR_PROCESS = 0x8, + ALLOW_IN_SOCKET_PROCESS = 0x10, + ALLOW_IN_RDD_PROCESS = 0x20, + ALLOW_IN_UTILITY_PROCESS = 0x30, + ALLOW_IN_GPU_AND_MAIN_PROCESS = ALLOW_IN_GPU_PROCESS | MAIN_PROCESS_ONLY, + ALLOW_IN_GPU_AND_VR_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS, + ALLOW_IN_GPU_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_RDD_AND_SOCKET_PROCESS = + ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | + ALLOW_IN_SOCKET_PROCESS, + ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS = + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_VR_PROCESS | + ALLOW_IN_SOCKET_PROCESS | ALLOW_IN_UTILITY_PROCESS +}; + +static constexpr size_t kMaxProcessSelector = + size_t(ProcessSelector::ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS); + +/** + * This allows category entries to be marked so that they are or are + * not loaded when in backgroundtask mode. + */ +// Note: This must be kept in sync with the selector matching in +// StaticComponents.cpp.in. +enum BackgroundTasksSelector { + NO_TASKS = 0x0, + ALL_TASKS = 0xFFFF, +}; +}; // namespace Module + +} // namespace mozilla + +#endif // mozilla_Module_h diff --git a/xpcom/components/ModuleUtils.h b/xpcom/components/ModuleUtils.h new file mode 100644 index 0000000000..92ec8aa173 --- /dev/null +++ b/xpcom/components/ModuleUtils.h @@ -0,0 +1,81 @@ +/* -*- 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/. */ + +#ifndef mozilla_GenericModule_h +#define mozilla_GenericModule_h + +#include + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Attributes.h" +#include "mozilla/Module.h" + +#define NS_GENERIC_FACTORY_CONSTRUCTOR(_InstanceClass) \ + static nsresult _InstanceClass##Constructor(REFNSIID aIID, void** aResult) { \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + \ + inst = new _InstanceClass(); \ + return inst->QueryInterface(aIID, aResult); \ + } + +#define NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(_InstanceClass, _InitMethod) \ + static nsresult _InstanceClass##Constructor(REFNSIID aIID, void** aResult) { \ + nsresult rv; \ + \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + \ + inst = new _InstanceClass(); \ + rv = inst->_InitMethod(); \ + if (NS_SUCCEEDED(rv)) { \ + rv = inst->QueryInterface(aIID, aResult); \ + } \ + \ + return rv; \ + } + +namespace mozilla { +namespace detail { + +template +struct RemoveAlreadyAddRefed { + using Type = T; +}; + +template +struct RemoveAlreadyAddRefed> { + using Type = T; +}; + +} // namespace detail +} // namespace mozilla + +// 'Constructor' that uses an existing getter function that gets a singleton. +#define NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(_InstanceClass, _GetterProc) \ + static nsresult _InstanceClass##Constructor(REFNSIID aIID, void** aResult) { \ + RefPtr<_InstanceClass> inst; \ + \ + *aResult = nullptr; \ + \ + using T = \ + mozilla::detail::RemoveAlreadyAddRefed::Type; \ + static_assert( \ + std::is_same_v, decltype(_GetterProc())>, \ + "Singleton constructor must return already_AddRefed"); \ + static_assert( \ + std::is_base_of<_InstanceClass, T>::value, \ + "Singleton constructor must return correct already_AddRefed"); \ + inst = _GetterProc(); \ + if (nullptr == inst) { \ + return NS_ERROR_OUT_OF_MEMORY; \ + } \ + return inst->QueryInterface(aIID, aResult); \ + } + +#endif // mozilla_GenericModule_h diff --git a/xpcom/components/StaticComponents.cpp.in b/xpcom/components/StaticComponents.cpp.in new file mode 100644 index 0000000000..7f3fee6859 --- /dev/null +++ b/xpcom/components/StaticComponents.cpp.in @@ -0,0 +1,410 @@ +/* -*- 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 "StaticComponents.h" + +#include "mozilla/ArrayUtils.h" +#ifdef MOZ_BACKGROUNDTASKS +# include "mozilla/BackgroundTasks.h" +#endif +#include "mozilla/PerfectHash.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozJSModuleLoader.h" +#include "nsCOMPtr.h" +#include "nsComponentManager.h" +#include "nsContentUtils.h" +#include "nsIFactory.h" +#include "nsISupports.h" +#include "nsIXPConnect.h" +#include "nsString.h" +#include "nsStringEnumerator.h" +#include "nsTArray.h" +#include "xptdata.h" +#include "xptinfo.h" +#include "js/PropertyAndElement.h" // JS_GetProperty + +// Cleanup pollution from zipstruct.h +#undef UNSUPPORTED + +// Public includes +//# @includes@ + +// Relative includes +//# @relative_includes@ + +//# @decls@ + +namespace mozilla { + +using dom::AutoJSAPI; + +namespace xpcom { + +static constexpr uint32_t kNoContractID = 0xffffffff; + +namespace { +// Template helpers for constructor function sanity checks. +template +struct RemoveAlreadyAddRefed { + using Type = T; +}; + +template +struct RemoveAlreadyAddRefed> { + using Type = T; +}; +} // anonymous namespace + + +uint8_t gInvalidContracts[kContractCount / 8 + 1]; + +static StaticRefPtr gServiceInstances[kStaticModuleCount]; + +uint8_t gInitCalled[kModuleInitCount / 8 + 1]; + +static const char gStrings[] = +//# @strings@ + ""; + +const StaticCategory gStaticCategories[kStaticCategoryCount] = { +//# @categories@ +}; +const StaticCategoryEntry gStaticCategoryEntries[] = { +//# @category_entries@ +}; + +const nsXPTInterface gInterfaces[] = { +//# @interfaces@ +}; + +const StringOffset gComponentJSMs[] = { +//# @component_jsms@ +}; + +const StringOffset gComponentESModules[] = { +//# @component_esmodules@ +}; + +/** + * Returns a nsCString corresponding to the given entry in the `gStrings` string + * table. The resulting nsCString points directly to static storage, and does + * not incur any memory allocation overhead. + */ +static inline nsCString GetString(const StringOffset& aOffset) { + const char* str = &gStrings[aOffset.mOffset]; + nsCString result; + result.AssignLiteral(str, strlen(str)); + return result; +} + +nsCString ContractEntry::ContractID() const { + return GetString(mContractID); +} + +bool ContractEntry::Matches(const nsACString& aContractID) const { + return aContractID == ContractID() && Module().Active(); +} + +enum class ComponentType { JSM, ESM }; + +template +static nsresult ConstructJSMOrESMComponent(const nsACString& aURI, + const char* aConstructor, + nsISupports** aResult) { + if (!nsComponentManagerImpl::JSLoaderReady()) { + return NS_ERROR_NOT_AVAILABLE; + } + + AutoJSAPI jsapi; + MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope())); + JSContext* cx = jsapi.cx(); + + JS::Rooted exports(cx); + if constexpr (type == ComponentType::JSM) { + JS::Rooted global(cx); + MOZ_TRY(mozJSModuleLoader::Get()->Import(cx, aURI, &global, &exports)); + } else { + MOZ_TRY(mozJSModuleLoader::Get()->ImportESModule(cx, aURI, &exports)); + } + + JS::Rooted ctor(cx); + if (!JS_GetProperty(cx, exports, aConstructor, &ctor) || + !ctor.isObject()) { + return NS_ERROR_XPC_JSOBJECT_HAS_NO_FUNCTION_NAMED; + } + + JS::Rooted inst(cx); + if (!JS::Construct(cx, ctor, JS::HandleValueArray::empty(), &inst)) { + return NS_ERROR_FAILURE; + } + + return nsContentUtils::XPConnect()->WrapJS(cx, inst, NS_GET_IID(nsISupports), + (void**)aResult); +} + +static nsresult ConstructJSMComponent(const nsACString& aURI, + const char* aConstructor, + nsISupports** aResult) { + return ConstructJSMOrESMComponent( + aURI, aConstructor, aResult); +} + +static nsresult ConstructESModuleComponent(const nsACString& aURI, + const char* aConstructor, + nsISupports** aResult) { + return ConstructJSMOrESMComponent( + aURI, aConstructor, aResult); +} + +//# @module_cid_table@ + +//# @module_contract_id_table@ + +//# @js_services_table@ + +//# @protocol_handlers_table@ + +static inline bool CalledInit(size_t aIdx) { + return GetBit(gInitCalled, aIdx); +} + +static nsresult CallInitFunc(size_t aIdx) { + if (CalledInit(aIdx)) { + return NS_OK; + } + + nsresult rv = NS_OK; + switch (aIdx) { +//# @init_funcs@ + } + + SetBit(gInitCalled, aIdx); + + MOZ_ASSERT(NS_SUCCEEDED(rv)); + return rv; +} + +static void CallUnloadFuncs() { +//# @unload_funcs@ +} + +nsresult CreateInstanceImpl(ModuleID aID, const nsIID& aIID, void** aResult) { + // The full set of constructors for all static modules. + // This switch statement will be compiled to a relative address jump table + // with no runtime relocations and a single indirect jump. + switch (aID) { +//# @constructors@ + } + + MOZ_ASSERT_UNREACHABLE("Constructor didn't return"); + return NS_ERROR_FAILURE; +} + + +namespace { + +class StaticModuleFactory final : public nsIFactory { + NS_DECL_ISUPPORTS + NS_DECL_NSIFACTORY + + explicit StaticModuleFactory(ModuleID aID) : mID(aID) {} + +private: + ~StaticModuleFactory() = default; + + const ModuleID mID; +}; + +NS_IMPL_ISUPPORTS(StaticModuleFactory, nsIFactory) + +NS_IMETHODIMP StaticModuleFactory::CreateInstance(const nsIID& aIID, + void** aResult) { + return CreateInstanceImpl(mID, aIID, aResult); +} + +} // anonymous namespace + + +already_AddRefed StaticModule::GetFactory() const { + return do_AddRef(new StaticModuleFactory(ID())); +} + +bool StaticModule::Active() const { + return FastProcessSelectorMatches(mProcessSelector); +} + +bool StaticModule::Overridable() const { + return mContractID.mOffset != kNoContractID; +} + +nsCString StaticModule::ContractID() const { + MOZ_ASSERT(Overridable()); + return GetString(mContractID); +} + +nsresult StaticModule::CreateInstance(const nsIID& aIID, void** aResult) const { + return CreateInstanceImpl(ID(), aIID, aResult); +} + +GetServiceHelper StaticModule::GetService() const { + return { ID(), nullptr }; +} + +GetServiceHelper StaticModule::GetService(nsresult* aRv) const { + return { ID(), aRv }; +} + + +nsISupports* StaticModule::ServiceInstance() const { + return gServiceInstances[Idx()]; +} + +void StaticModule::SetServiceInstance( + already_AddRefed aInst) const { + gServiceInstances[Idx()] = aInst; +} + + +nsCString StaticCategoryEntry::Entry() const { + return GetString(mEntry); +} + +nsCString StaticCategoryEntry::Value() const { + return GetString(mValue); +} + +bool StaticCategoryEntry::Active() const { + if (!FastProcessSelectorMatches(mProcessSelector)) { + return false; + } +#ifdef MOZ_BACKGROUNDTASKS + if (MOZ_UNLIKELY(BackgroundTasks::IsBackgroundTaskMode())) { + return mBackgroundTasksSelector != Module::BackgroundTasksSelector::NO_TASKS; + } +#endif /* MOZ_BACKGROUNDTASKS */ + return true; +} + +nsCString StaticCategory::Name() const { + return GetString(mName); +} + +nsCString JSServiceEntry::Name() const { + return GetString(mName); +} + +JSServiceEntry::InterfaceList JSServiceEntry::Interfaces() const { + InterfaceList iids; + iids.SetCapacity(mInterfaceCount); + + for (size_t i = 0; i < mInterfaceCount; i++) { + nsXPTInterface ifaceID = gInterfaces[mInterfaceOffset.mOffset + i]; + iids.AppendElement(&nsXPTInterfaceInfo::Get(ifaceID)->IID()); + } + return iids; +} + + +/* static */ +const JSServiceEntry* JSServiceEntry::Lookup(const nsACString& aName) { + return LookupJSService(aName); +} + +nsCString StaticProtocolHandler::Scheme() const { + return GetString(mScheme); +} + +/* static */ +const StaticProtocolHandler* StaticProtocolHandler::Lookup(const nsACString& aScheme) { + return LookupProtocolHandler(aScheme); +} + +/* static */ const StaticModule* StaticComponents::LookupByCID( + const nsID& aCID) { + return ModuleByCID(aCID); +} + +/* static */ const StaticModule* StaticComponents::LookupByContractID( + const nsACString& aContractID) { + if (const ContractEntry* entry = LookupContractID(aContractID)) { + if (!entry->Invalid()) { + return &entry->Module(); + } + } + return nullptr; +} + +/* static */ bool StaticComponents::InvalidateContractID( + const nsACString& aContractID, bool aInvalid) { + if (const ContractEntry* entry = LookupContractID(aContractID)) { + entry->SetInvalid(aInvalid); + return true; + } + return false; +} + +/* static */ already_AddRefed +StaticComponents::GetComponentJSMs() { + auto jsms = MakeUnique>(MOZ_ARRAY_LENGTH(gComponentJSMs)); + + for (const auto& entry : gComponentJSMs) { + jsms->AppendElement(GetString(entry)); + } + + nsCOMPtr result; + MOZ_ALWAYS_SUCCEEDS(NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(result), + jsms.release())); + return result.forget(); +} + +/* static */ already_AddRefed +StaticComponents::GetComponentESModules() { + auto esModules = MakeUnique>(MOZ_ARRAY_LENGTH(gComponentESModules)); + + for (const auto& entry : gComponentESModules) { + esModules->AppendElement(GetString(entry)); + } + + nsCOMPtr result; + MOZ_ALWAYS_SUCCEEDS(NS_NewAdoptingUTF8StringEnumerator(getter_AddRefs(result), + esModules.release())); + return result.forget(); +} + +/* static */ Span StaticComponents::GetJSServices() { + return { gJSServices, ArrayLength(gJSServices) }; +} + +/* static */ void StaticComponents::Shutdown() { + CallUnloadFuncs(); +} + +/* static */ const nsID& Components::GetCID(ModuleID aID) { + return gStaticModules[size_t(aID)].CID(); +} + +nsresult GetServiceHelper::operator()(const nsIID& aIID, void** aResult) const { + nsresult rv = + nsComponentManagerImpl::gComponentManager->GetService(mId, aIID, aResult); + return SetResult(rv); +} + +nsresult CreateInstanceHelper::operator()(const nsIID& aIID, + void** aResult) const { + const auto& entry = gStaticModules[size_t(mId)]; + if (!entry.Active()) { + return SetResult(NS_ERROR_FACTORY_NOT_REGISTERED); + } + + nsresult rv = entry.CreateInstance(aIID, aResult); + return SetResult(rv); +} + +} // namespace xpcom +} // namespace mozilla diff --git a/xpcom/components/StaticComponents.h b/xpcom/components/StaticComponents.h new file mode 100644 index 0000000000..ac4095f2f3 --- /dev/null +++ b/xpcom/components/StaticComponents.h @@ -0,0 +1,284 @@ +/* -*- 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/. */ + +#ifndef StaticComponents_h +#define StaticComponents_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Module.h" +#include "mozilla/Span.h" +#include "nsID.h" +#include "nsStringFwd.h" +#include "nscore.h" + +#include "mozilla/Components.h" +#include "StaticComponentData.h" + +class nsIFactory; +class nsIUTF8StringEnumerator; +class nsISupports; +template +class AutoTArray; + +namespace mozilla { +namespace xpcom { + +struct ContractEntry; +struct StaticModule; + +struct StaticCategoryEntry; +struct StaticCategory; + +struct StaticProtocolHandler; + +extern const StaticModule gStaticModules[kStaticModuleCount]; + +extern const ContractEntry gContractEntries[kContractCount]; +extern uint8_t gInvalidContracts[kContractCount / 8 + 1]; + +extern const StaticCategory gStaticCategories[kStaticCategoryCount]; +extern const StaticCategoryEntry gStaticCategoryEntries[]; + +extern const StaticProtocolHandler + gStaticProtocolHandlers[kStaticProtocolHandlerCount]; + +template +static inline bool GetBit(const uint8_t (&aBits)[N], size_t aBit) { + static constexpr size_t width = sizeof(aBits[0]) * 8; + + size_t idx = aBit / width; + MOZ_ASSERT(idx < N); + return aBits[idx] & (1 << (aBit % width)); +} + +template +static inline void SetBit(uint8_t (&aBits)[N], size_t aBit, + bool aValue = true) { + static constexpr size_t width = sizeof(aBits[0]) * 8; + + size_t idx = aBit / width; + MOZ_ASSERT(idx < N); + if (aValue) { + aBits[idx] |= 1 << (aBit % width); + } else { + aBits[idx] &= ~(1 << (aBit % width)); + } +} + +/** + * Represents a string entry in the static string table. Can be converted to a + * nsCString using GetString() in StaticComponents.cpp. + * + * This is a struct rather than a pure offset primarily for the purposes of type + * safety, but also so that it can easily be extended to include a static length + * in the future, if efficiency concerns warrant it. + */ +struct StringOffset final { + uint32_t mOffset; +}; + +/** + * Represents an offset into the interfaces table. + */ +struct InterfaceOffset final { + uint16_t mOffset; +}; + +/** + * Represents a static component entry defined in a `Classes` list in an XPCOM + * manifest. Handles creating instances of and caching service instances for + * that class. + */ +struct StaticModule { + nsID mCID; + StringOffset mContractID; + Module::ProcessSelector mProcessSelector; + + const nsID& CID() const { return mCID; } + + ModuleID ID() const { return ModuleID(this - gStaticModules); } + + /** + * Returns this entry's index in the gStaticModules array. + */ + size_t Idx() const { return size_t(ID()); } + + /** + * Returns true if this component's corresponding contract ID is expected to + * be overridden at runtime. If so, it should always be looked up by its + * ContractID() when retrieving its service instance. + */ + bool Overridable() const; + + /** + * If this entry is overridable, returns its associated contract ID string. + * The component should always be looked up by this contract ID when + * retrieving its service instance. + * + * Note: This may *only* be called if Overridable() returns true. + */ + nsCString ContractID() const; + + /** + * Returns true if this entry is active. Typically this will only return false + * if the entry's process selector does not match this process. + */ + bool Active() const; + + already_AddRefed GetFactory() const; + + nsresult CreateInstance(const nsIID& aIID, void** aResult) const; + + GetServiceHelper GetService() const; + GetServiceHelper GetService(nsresult*) const; + + nsISupports* ServiceInstance() const; + void SetServiceInstance(already_AddRefed aInst) const; +}; + +/** + * Represents a static mapping between a contract ID string and a StaticModule + * entry. + */ +struct ContractEntry final { + StringOffset mContractID; + ModuleID mModuleID; + + size_t Idx() const { return this - gContractEntries; } + + nsCString ContractID() const; + + const StaticModule& Module() const { + return gStaticModules[size_t(mModuleID)]; + } + + /** + * Returns true if this entry's underlying module is active, and its contract + * ID matches the given contract ID string. This is used by the PerfectHash + * function to determine whether to return a result for this entry. + */ + bool Matches(const nsACString& aContractID) const; + + /** + * Returns true if this entry has been invalidated, and should be ignored. + * + * Contract IDs may be overwritten at runtime. When that happens for a static + * contract ID, we mark its entry invalid, and ignore it thereafter. + */ + bool Invalid() const { return GetBit(gInvalidContracts, Idx()); } + + /** + * Marks this entry invalid (or unsets the invalid bit if aInvalid is false), + * after which it will be ignored in contract ID lookup attempts. See + * `Invalid()` above. + */ + void SetInvalid(bool aInvalid = true) const { + return SetBit(gInvalidContracts, Idx(), aInvalid); + } +}; + +/** + * Represents a declared category manager entry declared in an XPCOM manifest. + * + * The entire set of static category entries is read at startup and loaded into + * the category manager's dynamic hash tables, so there is memory and + * initialization overhead for each entry in these tables. This may be further + * optimized in the future to reduce some of that overhead. + */ +struct StaticCategoryEntry final { + StringOffset mEntry; + StringOffset mValue; + Module::BackgroundTasksSelector mBackgroundTasksSelector; + Module::ProcessSelector mProcessSelector; + + nsCString Entry() const; + nsCString Value() const; + bool Active() const; +}; + +struct StaticCategory final { + StringOffset mName; + uint16_t mStart; + uint16_t mCount; + + nsCString Name() const; + + const StaticCategoryEntry* begin() const { + return &gStaticCategoryEntries[mStart]; + } + const StaticCategoryEntry* end() const { + return &gStaticCategoryEntries[mStart + mCount]; + } +}; + +struct JSServiceEntry final { + using InterfaceList = AutoTArray; + + static const JSServiceEntry* Lookup(const nsACString& aName); + + StringOffset mName; + ModuleID mModuleID; + + InterfaceOffset mInterfaceOffset; + + uint8_t mInterfaceCount; + + nsCString Name() const; + + const StaticModule& Module() const { + return gStaticModules[size_t(mModuleID)]; + } + + InterfaceList Interfaces() const; +}; + +struct StaticProtocolHandler final { + static const StaticProtocolHandler* Lookup(const nsACString& aScheme); + static const StaticProtocolHandler& Default() { + return gStaticProtocolHandlers[kDefaultProtocolHandlerIndex]; + } + + StringOffset mScheme; + uint32_t mProtocolFlags; + int32_t mDefaultPort; + ModuleID mModuleID; + bool mHasDynamicFlags; + + nsCString Scheme() const; + + const StaticModule& Module() const { + return gStaticModules[size_t(mModuleID)]; + } +}; + +class StaticComponents final { + public: + static const StaticModule* LookupByCID(const nsID& aCID); + + static const StaticModule* LookupByContractID(const nsACString& aContractID); + + /** + * Marks a static contract ID entry invalid (or unsets the invalid bit if + * aInvalid is false). See `CategoryEntry::Invalid()`. + */ + static bool InvalidateContractID(const nsACString& aContractID, + bool aInvalid = true); + + static already_AddRefed GetComponentJSMs(); + static already_AddRefed GetComponentESModules(); + + static Span GetJSServices(); + + /** + * Calls any module unload from manifests whose components have been loaded. + */ + static void Shutdown(); +}; + +} // namespace xpcom +} // namespace mozilla + +#endif // defined StaticComponents_h diff --git a/xpcom/components/components.conf b/xpcom/components/components.conf new file mode 100644 index 0000000000..73cc177e8d --- /dev/null +++ b/xpcom/components/components.conf @@ -0,0 +1,23 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'js_name': 'catMan', + 'cid': '{16d222a6-1dd2-11b2-b693-f38b02c021b2}', + 'contract_ids': ['@mozilla.org/categorymanager;1'], + 'interfaces': ['nsICategoryManager'], + 'legacy_constructor': 'nsCategoryManager::Create', + 'headers': ['/xpcom/components/nsCategoryManager.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{91775d60-d5dc-11d2-92fb-00e09805570f}', + 'legacy_constructor': 'nsComponentManagerImpl::Create', + 'headers': ['/xpcom/components/nsComponentManager.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS, + }, +] diff --git a/xpcom/components/gen_static_components.py b/xpcom/components/gen_static_components.py new file mode 100644 index 0000000000..3d73eda603 --- /dev/null +++ b/xpcom/components/gen_static_components.py @@ -0,0 +1,1206 @@ +import json +import os +import re +import struct +from collections import defaultdict +from uuid import UUID + +import buildconfig +from mozbuild.util import FileAvoidWrite +from perfecthash import PerfectHash + +NO_CONTRACT_ID = 0xFFFFFFFF + +PHF_SIZE = 512 + +TINY_PHF_SIZE = 16 + +# In tests, we might not have a (complete) buildconfig. +ENDIAN = ( + "<" if buildconfig.substs.get("TARGET_ENDIANNESS", "little") == "little" else ">" +) + + +# Represents a UUID in the format used internally by Gecko, and supports +# serializing it in that format to both C++ source and raw byte arrays. +class UUIDRepr(object): + def __init__(self, uuid): + self.uuid = uuid + + fields = uuid.fields + + self.a = fields[0] + self.b = fields[1] + self.c = fields[2] + + d = list(fields[3:5]) + for i in range(0, 6): + d.append(fields[5] >> (8 * (5 - i)) & 0xFF) + + self.d = tuple(d) + + def __str__(self): + return str(self.uuid) + + @property + def bytes(self): + return struct.pack(ENDIAN + "IHHBBBBBBBB", self.a, self.b, self.c, *self.d) + + def to_cxx(self): + rest = ", ".join("0x%02x" % b for b in self.d) + + return "{ 0x%x, 0x%x, 0x%x, { %s } }" % (self.a, self.b, self.c, rest) + + +# Corresponds to the Module::ProcessSelector enum in Module.h. The actual +# values don't matter, since the code generator emits symbolic constants for +# these values, but we use the same values as the enum constants for clarity. +class ProcessSelector: + ANY_PROCESS = 0x0 + MAIN_PROCESS_ONLY = 0x1 + CONTENT_PROCESS_ONLY = 0x2 + ALLOW_IN_GPU_PROCESS = 0x4 + ALLOW_IN_VR_PROCESS = 0x8 + ALLOW_IN_SOCKET_PROCESS = 0x10 + ALLOW_IN_RDD_PROCESS = 0x20 + ALLOW_IN_UTILITY_PROCESS = 0x30 + ALLOW_IN_GPU_AND_MAIN_PROCESS = ALLOW_IN_GPU_PROCESS | MAIN_PROCESS_ONLY + ALLOW_IN_GPU_AND_SOCKET_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_SOCKET_PROCESS + ALLOW_IN_GPU_AND_VR_PROCESS = ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS + ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS | ALLOW_IN_VR_PROCESS | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_RDD_AND_SOCKET_PROCESS = ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS + ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS | ALLOW_IN_RDD_PROCESS | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + ) + ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS = ( + ALLOW_IN_GPU_PROCESS + | ALLOW_IN_RDD_PROCESS + | ALLOW_IN_VR_PROCESS + | ALLOW_IN_SOCKET_PROCESS + | ALLOW_IN_UTILITY_PROCESS + ) + + +# Maps ProcessSelector constants to the name of the corresponding +# Module::ProcessSelector enum value. +PROCESSES = { + ProcessSelector.ANY_PROCESS: "ANY_PROCESS", + ProcessSelector.MAIN_PROCESS_ONLY: "MAIN_PROCESS_ONLY", + ProcessSelector.CONTENT_PROCESS_ONLY: "CONTENT_PROCESS_ONLY", + ProcessSelector.ALLOW_IN_GPU_PROCESS: "ALLOW_IN_GPU_PROCESS", + ProcessSelector.ALLOW_IN_VR_PROCESS: "ALLOW_IN_VR_PROCESS", + ProcessSelector.ALLOW_IN_SOCKET_PROCESS: "ALLOW_IN_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_RDD_PROCESS: "ALLOW_IN_RDD_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS: "ALLOW_IN_GPU_AND_MAIN_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_AND_VR_PROCESS: "ALLOW_IN_GPU_AND_VR_PROCESS", + ProcessSelector.ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_RDD_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS", + ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS: "ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS", # NOQA: E501 + ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS: "ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS", # NOQA: E501 +} + + +# Emits the C++ symbolic constant corresponding to a ProcessSelector constant. +def lower_processes(processes): + return "Module::ProcessSelector::%s" % PROCESSES[processes] + + +# Emits the C++ symbolic constant for a ModuleEntry's ModuleID enum entry. +def lower_module_id(module): + return "ModuleID::%s" % module.name + + +# Corresponds to the Module::BackgroundTasksSelector enum in Module.h. The +# actual values don't matter, since the code generator emits symbolic constants +# for these values, but we use the same values as the enum constants for +# clarity. +class BackgroundTasksSelector: + NO_TASKS = 0x0 + ALL_TASKS = 0xFFFF + + +# Maps BackgroundTasksSelector constants to the name of the corresponding +# Module::BackgroundTasksSelector enum value. +BACKGROUNDTASKS = { + BackgroundTasksSelector.ALL_TASKS: "ALL_TASKS", + BackgroundTasksSelector.NO_TASKS: "NO_TASKS", +} + + +# Emits the C++ symbolic constant corresponding to a BackgroundTasks constant. +def lower_backgroundtasks(backgroundtasks): + return "Module::BackgroundTasksSelector::%s" % BACKGROUNDTASKS[backgroundtasks] + + +# Represents a static string table, indexed by offset. This allows us to +# reference strings from static data structures without requiring runtime +# relocations. +class StringTable(object): + def __init__(self): + self.entries = {} + self.entry_list = [] + self.size = 0 + + self._serialized = False + + # Returns the index of the given string in the `entry_list` array. If + # no entry for the string exists, it first creates one. + def get_idx(self, string): + idx = self.entries.get(string, None) + if idx is not None: + return idx + + assert not self._serialized + + assert len(string) == len(string.encode("utf-8")) + + idx = self.size + self.size += len(string) + 1 + + self.entries[string] = idx + self.entry_list.append(string) + return idx + + # Returns the C++ code representing string data of this string table, as a + # single string literal. This must only be called after the last call to + # `get_idx()` or `entry_to_cxx()` for this instance. + def to_cxx(self): + self._serialized = True + + lines = [] + + idx = 0 + for entry in self.entry_list: + str_ = entry.replace("\\", "\\\\").replace('"', r"\"").replace("\n", r"\n") + + lines.append(' /* 0x%x */ "%s\\0"\n' % (idx, str_)) + + idx += len(entry) + 1 + + return "".join(lines) + + # Returns a `StringEntry` struct initializer for the string table entry + # corresponding to the given string. If no matching entry exists, it is + # first created. + def entry_to_cxx(self, string): + idx = self.get_idx(string) + return "{ 0x%x } /* %s */" % (idx, pretty_string(string)) + + +strings = StringTable() + +interfaces = [] + + +# Represents a C++ namespace, containing a set of classes and potentially +# sub-namespaces. This is used to generate pre-declarations for incomplete +# types referenced in XPCOM manifests. +class Namespace(object): + def __init__(self, name=None): + self.name = name + self.classes = set() + self.namespaces = {} + + # Returns a Namespace object for the sub-namespace with the given name. + def sub(self, name): + assert name not in self.classes + + if name not in self.namespaces: + self.namespaces[name] = Namespace(name) + return self.namespaces[name] + + # Generates C++ code to pre-declare all classes in this namespace and all + # of its sub-namespaces. + def to_cxx(self): + res = "" + if self.name: + res += "namespace %s {\n" % self.name + + for clas in sorted(self.classes): + res += "class %s;\n" % clas + + for ns in sorted(self.namespaces.keys()): + res += self.namespaces[ns].to_cxx() + + if self.name: + res += "} // namespace %s\n" % self.name + + return res + + +# Represents a component defined in an XPCOM manifest's `Classes` array. +class ModuleEntry(object): + next_anon_id = 0 + + def __init__(self, data, init_idx): + self.cid = UUIDRepr(UUID(data["cid"])) + self.contract_ids = data.get("contract_ids", []) + self.type = data.get("type", "nsISupports") + self.categories = data.get("categories", {}) + self.processes = data.get("processes", 0) + self.headers = data.get("headers", []) + + self.js_name = data.get("js_name", None) + self.interfaces = data.get("interfaces", []) + + if len(self.interfaces) > 255: + raise Exception( + "JS service %s may not have more than 255 " "interfaces" % self.js_name + ) + + self.interfaces_offset = len(interfaces) + for iface in self.interfaces: + interfaces.append(iface) + + # If the manifest declares Init or Unload functions, this contains its + # index, as understood by the `CallInitFunc()` function. + # + # If it contains any value other than `None`, a corresponding + # `CallInitFunc(init_idx)` call will be genrated before calling this + # module's constructor. + self.init_idx = init_idx + + self.constructor = data.get("constructor", None) + self.legacy_constructor = data.get("legacy_constructor", None) + self.init_method = data.get("init_method", []) + + self.jsm = data.get("jsm", None) + self.esModule = data.get("esModule", None) + + self.external = data.get( + "external", not (self.headers or self.legacy_constructor) + ) + self.singleton = data.get("singleton", False) + self.overridable = data.get("overridable", False) + + self.protocol_config = data.get("protocol_config", None) + + if "name" in data: + self.anonymous = False + self.name = data["name"] + else: + self.anonymous = True + self.name = "Anonymous%03d" % ModuleEntry.next_anon_id + ModuleEntry.next_anon_id += 1 + + def error(str_): + raise Exception( + "Error defining component %s (%s): %s" + % (str(self.cid), ", ".join(map(repr, self.contract_ids)), str_) + ) + + if self.jsm: + if not self.constructor: + error("JavaScript components must specify a constructor") + + for prop in ("init_method", "legacy_constructor", "headers"): + if getattr(self, prop): + error( + "JavaScript components may not specify a '%s' " + "property" % prop + ) + elif self.esModule: + if not self.constructor: + error("JavaScript components must specify a constructor") + + for prop in ("init_method", "legacy_constructor", "headers"): + if getattr(self, prop): + error( + "JavaScript components may not specify a '%s' " + "property" % prop + ) + elif self.external: + if self.constructor or self.legacy_constructor: + error( + "Externally-constructed components may not specify " + "'constructor' or 'legacy_constructor' properties" + ) + if self.init_method: + error( + "Externally-constructed components may not specify " + "'init_method' properties" + ) + if self.type == "nsISupports": + error( + "Externally-constructed components must specify a type " + "other than nsISupports" + ) + + if self.constructor and self.legacy_constructor: + error( + "The 'constructor' and 'legacy_constructor' properties " + "are mutually exclusive" + ) + + if self.overridable and not self.contract_ids: + error("Overridable components must specify at least one contract " "ID") + + @property + def contract_id(self): + return self.contract_ids[0] + + # Generates the C++ code for a StaticModule struct initializer + # representing this component. + def to_cxx(self): + contract_id = ( + strings.entry_to_cxx(self.contract_id) + if self.overridable + else "{ 0x%x }" % NO_CONTRACT_ID + ) + + return """ + /* {name} */ {{ + /* {{{cid_string}}} */ + {cid}, + {contract_id}, + {processes}, + }}""".format( + name=self.name, + cid=self.cid.to_cxx(), + cid_string=str(self.cid), + contract_id=contract_id, + processes=lower_processes(self.processes), + ) + + # Generates the C++ code for a JSServiceEntry representing this module. + def lower_js_service(self): + return """ + {{ + {js_name}, + ModuleID::{name}, + {{ {iface_offset} }}, + {iface_count} + }}""".format( + js_name=strings.entry_to_cxx(self.js_name), + name=self.name, + iface_offset=self.interfaces_offset, + iface_count=len(self.interfaces), + ) + + # Generates the C++ code necessary to construct an instance of this + # component. + # + # This code lives in a function with the following arguments: + # + # - aIID: The `const nsIID&` interface ID that the resulting instance + # will be queried to. + # + # - aResult: The `void**` pointer in which to store the result. + # + # And which returns an `nsresult` indicating success or failure. + def lower_constructor(self): + res = "" + + if self.init_idx is not None: + res += " MOZ_TRY(CallInitFunc(%d));\n" % self.init_idx + + if self.legacy_constructor: + res += ( + " return /* legacy */ %s(aIID, aResult);\n" + % self.legacy_constructor + ) + return res + + if self.jsm: + res += ( + " nsCOMPtr inst;\n" + " MOZ_TRY(ConstructJSMComponent(nsLiteralCString(%s),\n" + " %s,\n" + " getter_AddRefs(inst)));" + "\n" % (json.dumps(self.jsm), json.dumps(self.constructor)) + ) + elif self.esModule: + res += ( + " nsCOMPtr inst;\n" + " MOZ_TRY(ConstructESModuleComponent(nsLiteralCString(%s),\n" + " %s,\n" + " getter_AddRefs(inst)));" + "\n" % (json.dumps(self.esModule), json.dumps(self.constructor)) + ) + elif self.external: + res += ( + " nsCOMPtr inst = " + "mozCreateComponent<%s>();\n" % self.type + ) + # The custom constructor may return null, so check before calling + # any methods. + res += " NS_ENSURE_TRUE(inst, NS_ERROR_FAILURE);\n" + else: + res += " RefPtr<%s> inst = " % self.type + + if not self.constructor: + res += "new %s();\n" % self.type + else: + res += "%s();\n" % self.constructor + # The `new` operator is infallible, so we don't need to worry + # about it returning null, but custom constructors may, so + # check before calling any methods. + res += " NS_ENSURE_TRUE(inst, NS_ERROR_OUT_OF_MEMORY);\n" + + # Check that the constructor function returns an appropriate + # `already_AddRefed` value for our declared type. + res += """ + using T = + RemoveAlreadyAddRefed::Type; + static_assert( + std::is_same_v, decltype(%(constructor)s())>, + "Singleton constructor must return already_AddRefed"); + static_assert( + std::is_base_of<%(type)s, T>::value, + "Singleton constructor must return correct already_AddRefed"); + +""" % { + "type": self.type, + "constructor": self.constructor, + } + + if self.init_method: + res += " MOZ_TRY(inst->%s());\n" % self.init_method + + res += " return inst->QueryInterface(aIID, aResult);\n" + + return res + + # Generates the C++ code for the `mozilla::components::` entry + # corresponding to this component. This may not be called for modules + # without an explicit `name` (in which cases, `self.anonymous` will be + # true). + def lower_getters(self): + assert not self.anonymous + + substs = { + "name": self.name, + "id": "::mozilla::xpcom::ModuleID::%s" % self.name, + } + + res = ( + """ +namespace %(name)s { +static inline const nsID& CID() { + return ::mozilla::xpcom::Components::GetCID(%(id)s); +} + +static inline ::mozilla::xpcom::GetServiceHelper Service(nsresult* aRv = nullptr) { + return {%(id)s, aRv}; +} +""" + % substs + ) + + if not self.singleton: + res += ( + """ +static inline ::mozilla::xpcom::CreateInstanceHelper Create(nsresult* aRv = nullptr) { + return {%(id)s, aRv}; +} +""" + % substs + ) + + res += ( + """\ +} // namespace %(name)s +""" + % substs + ) + + return res + + # Generates the rust code for the `xpcom::components::` entry + # corresponding to this component. This may not be called for modules + # without an explicit `name` (in which cases, `self.anonymous` will be + # true). + def lower_getters_rust(self): + assert not self.anonymous + + substs = { + "name": self.name, + "id": "super::ModuleID::%s" % self.name, + } + + res = ( + """ +#[allow(non_snake_case)] +pub mod %(name)s { + /// Get the singleton service instance for this component. + pub fn service() -> Result, nserror::nsresult> { + let mut ga = crate::GetterAddrefs::::new(); + let rv = unsafe { super::Gecko_GetServiceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) + } +""" + % substs + ) + + if not self.singleton: + res += ( + """ + /// Create a new instance of this component. + pub fn create() -> Result, nserror::nsresult> { + let mut ga = crate::GetterAddrefs::::new(); + let rv = unsafe { super::Gecko_CreateInstanceByModuleID(%(id)s, &T::IID, ga.void_ptr()) }; + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(nserror::NS_ERROR_NO_INTERFACE) + } +""" + % substs + ) + + res += """\ +} +""" + + return res + + +# Returns a quoted string literal representing the given raw string, with +# certain special characters replaced so that it can be used in a C++-style +# (/* ... */) comment. +def pretty_string(string): + return json.dumps(string).replace("*/", r"*\/").replace("/*", r"/\*") + + +# Represents a static contract ID entry, corresponding to a C++ ContractEntry +# struct, mapping a contract ID to a static module entry. +class ContractEntry(object): + def __init__(self, contract, module): + self.contract = contract + self.module = module + + def to_cxx(self): + return """ + {{ + {contract}, + {module_id}, + }}""".format( + contract=strings.entry_to_cxx(self.contract), + module_id=lower_module_id(self.module), + ) + + +# Represents a static ProtocolHandler entry, corresponding to a C++ +# ProtocolEntry struct, mapping a scheme to a static module entry and metadata. +class ProtocolHandler(object): + def __init__(self, config, module): + def error(str_): + raise Exception( + "Error defining protocol handler %s (%s): %s" + % (str(module.cid), ", ".join(map(repr, module.contract_ids)), str_) + ) + + self.module = module + self.scheme = config.get("scheme", None) + if self.scheme is None: + error("No scheme defined for protocol component") + self.flags = config.get("flags", None) + if self.flags is None: + error("No flags defined for protocol component") + self.default_port = config.get("default_port", -1) + self.has_dynamic_flags = config.get("has_dynamic_flags", False) + + def to_cxx(self): + return """ + {{ + .mScheme = {scheme}, + .mProtocolFlags = {flags}, + .mDefaultPort = {default_port}, + .mModuleID = {module_id}, + .mHasDynamicFlags = {has_dynamic_flags}, + }} + """.format( + scheme=strings.entry_to_cxx(self.scheme), + module_id=lower_module_id(self.module), + flags=" | ".join("nsIProtocolHandler::%s" % flag for flag in self.flags), + default_port=self.default_port, + has_dynamic_flags="true" if self.has_dynamic_flags else "false", + ) + + +# Generates the C++ code for the StaticCategoryEntry and StaticCategory +# structs for all category entries declared in XPCOM manifests. +def gen_categories(substs, categories): + cats = [] + ents = [] + + count = 0 + for category, entries in sorted(categories.items()): + + def k(entry): + return tuple(entry[0]["name"]) + entry[1:] + + entries.sort(key=k) + + cats.append( + " { %s,\n" + " %d, %d },\n" % (strings.entry_to_cxx(category), count, len(entries)) + ) + count += len(entries) + + ents.append(" /* %s */\n" % pretty_string(category)) + for entry, value, processes in entries: + name = entry["name"] + backgroundtasks = entry.get( + "backgroundtasks", BackgroundTasksSelector.NO_TASKS + ) + + ents.append( + " { %s,\n" + " %s,\n" + " %s,\n" + " %s },\n" + % ( + strings.entry_to_cxx(name), + strings.entry_to_cxx(value), + lower_backgroundtasks(backgroundtasks), + lower_processes(processes), + ) + ) + ents.append("\n") + ents.pop() + + substs["category_count"] = len(cats) + substs["categories"] = "".join(cats) + substs["category_entries"] = "".join(ents) + + +# Generates the C++ code for all Init and Unload functions declared in XPCOM +# manifests. These form the bodies of the `CallInitFunc()` and `CallUnload` +# functions in StaticComponents.cpp. +def gen_module_funcs(substs, funcs): + inits = [] + unloads = [] + + template = """\ + case %d: + %s + break; +""" + + for i, (init, unload) in enumerate(funcs): + init_code = "%s();" % init if init else "/* empty */" + inits.append(template % (i, init_code)) + + if unload: + unloads.append( + """\ + if (CalledInit(%d)) { + %s(); + } +""" + % (i, unload) + ) + + substs["init_funcs"] = "".join(inits) + substs["unload_funcs"] = "".join(unloads) + substs["init_count"] = len(funcs) + + +def gen_interfaces(ifaces): + res = [] + for iface in ifaces: + res.append(" nsXPTInterface::%s,\n" % iface) + return "".join(res) + + +# Generates class pre-declarations for any types referenced in `Classes` array +# entries which do not have corresponding `headers` entries to fully declare +# their types. +def gen_decls(types): + root_ns = Namespace() + + for type_ in sorted(types): + parts = type_.split("::") + + ns = root_ns + for part in parts[:-1]: + ns = ns.sub(part) + ns.classes.add(parts[-1]) + + return root_ns.to_cxx() + + +# Generates the `switch` body for the `CreateInstanceImpl()` function, with a +# `case` for each value in ModuleID to construct an instance of the +# corresponding component. +def gen_constructors(entries): + constructors = [] + for entry in entries: + constructors.append( + """\ + case {id}: {{ +{constructor}\ + }} +""".format( + id=lower_module_id(entry), constructor=entry.lower_constructor() + ) + ) + + return "".join(constructors) + + +# Generates the getter code for each named component entry in the +# `mozilla::components::` namespace. +def gen_getters(entries): + entries = list(entries) + entries.sort(key=lambda e: e.name) + + return "".join(entry.lower_getters() for entry in entries if not entry.anonymous) + + +# Generates the rust getter code for each named component entry in the +# `xpcom::components::` module. +def gen_getters_rust(entries): + entries = list(entries) + entries.sort(key=lambda e: e.name) + + return "".join( + entry.lower_getters_rust() for entry in entries if not entry.anonymous + ) + + +def gen_includes(substs, all_headers): + headers = set() + absolute_headers = set() + + for header in all_headers: + if header.startswith("/"): + absolute_headers.add(header) + else: + headers.add(header) + + includes = ['#include "%s"' % header for header in sorted(headers)] + substs["includes"] = "\n".join(includes) + "\n" + + relative_includes = [ + '#include "../..%s"' % header for header in sorted(absolute_headers) + ] + substs["relative_includes"] = "\n".join(relative_includes) + "\n" + + +def to_category_list(val): + # Entries can be bare strings (like `"m-browser"`), lists of bare strings, + # or dictionaries (like `{"name": "m-browser", "backgroundtasks": + # BackgroundTasksSelector.ALL_TASKS}`), somewhat recursively. + + def ensure_dict(v): + # Turn `v` into `{"name": v}` if it's not already a dict. + if isinstance(v, dict): + return v + return {"name": v} + + if isinstance(val, (list, tuple)): + return tuple(ensure_dict(v) for v in val) + + if isinstance(val, dict): + # Explode `{"name": ["x", "y"], "backgroundtasks": ...}` into + # `[{"name": "x", "backgroundtasks": ...}, {"name": "y", "backgroundtasks": ...}]`. + names = val.pop("name") + + vals = [] + for entry in to_category_list(names): + d = dict(val) + d["name"] = entry["name"] + vals.append(d) + + return tuple(vals) + + return (ensure_dict(val),) + + +def gen_substs(manifests): + module_funcs = [] + + headers = set() + + modules = [] + categories = defaultdict(list) + + for manifest in manifests: + headers |= set(manifest.get("Headers", [])) + + init_idx = None + init = manifest.get("InitFunc") + unload = manifest.get("UnloadFunc") + if init or unload: + init_idx = len(module_funcs) + module_funcs.append((init, unload)) + + for clas in manifest["Classes"]: + modules.append(ModuleEntry(clas, init_idx)) + + for category, entries in manifest.get("Categories", {}).items(): + for key, entry in entries.items(): + if isinstance(entry, tuple): + value, process = entry + else: + value, process = entry, 0 + categories[category].append(({"name": key}, value, process)) + + cids = set() + contracts = [] + contract_map = {} + js_services = {} + protocol_handlers = {} + + jsms = set() + esModules = set() + + types = set() + + for mod in modules: + headers |= set(mod.headers) + + for contract_id in mod.contract_ids: + if contract_id in contract_map: + raise Exception("Duplicate contract ID: %s" % contract_id) + + entry = ContractEntry(contract_id, mod) + contracts.append(entry) + contract_map[contract_id] = entry + + for category, entries in mod.categories.items(): + for entry in to_category_list(entries): + categories[category].append((entry, mod.contract_id, mod.processes)) + + if mod.type and not mod.headers: + types.add(mod.type) + + if mod.jsm: + jsms.add(mod.jsm) + + if mod.esModule: + esModules.add(mod.esModule) + + if mod.js_name: + if mod.js_name in js_services: + raise Exception("Duplicate JS service name: %s" % mod.js_name) + js_services[mod.js_name] = mod + + if mod.protocol_config: + handler = ProtocolHandler(mod.protocol_config, mod) + if handler.scheme in protocol_handlers: + raise Exception("Duplicate protocol handler: %s" % handler.scheme) + protocol_handlers[handler.scheme] = handler + + if str(mod.cid) in cids: + raise Exception("Duplicate cid: %s" % str(mod.cid)) + cids.add(str(mod.cid)) + + cid_phf = PerfectHash(modules, PHF_SIZE, key=lambda module: module.cid.bytes) + + contract_phf = PerfectHash(contracts, PHF_SIZE, key=lambda entry: entry.contract) + + js_services_phf = PerfectHash( + list(js_services.values()), PHF_SIZE, key=lambda entry: entry.js_name + ) + + protocol_handlers_phf = PerfectHash( + list(protocol_handlers.values()), TINY_PHF_SIZE, key=lambda entry: entry.scheme + ) + + js_services_json = {} + for entry in js_services.values(): + for iface in entry.interfaces: + js_services_json[iface] = entry.js_name + + substs = {} + + gen_categories(substs, categories) + + substs["module_ids"] = "".join(" %s,\n" % entry.name for entry in cid_phf.entries) + + substs["module_count"] = len(modules) + substs["contract_count"] = len(contracts) + substs["protocol_handler_count"] = len(protocol_handlers) + + substs["default_protocol_handler_idx"] = protocol_handlers_phf.get_index("default") + + gen_module_funcs(substs, module_funcs) + + gen_includes(substs, headers) + + substs["component_jsms"] = ( + "\n".join(" %s," % strings.entry_to_cxx(jsm) for jsm in sorted(jsms)) + "\n" + ) + substs["component_esmodules"] = ( + "\n".join( + " %s," % strings.entry_to_cxx(esModule) for esModule in sorted(esModules) + ) + + "\n" + ) + + substs["interfaces"] = gen_interfaces(interfaces) + + substs["decls"] = gen_decls(types) + + substs["constructors"] = gen_constructors(cid_phf.entries) + + substs["component_getters"] = gen_getters(cid_phf.entries) + + substs["component_getters_rust"] = gen_getters_rust(cid_phf.entries) + + substs["module_cid_table"] = cid_phf.cxx_codegen( + name="ModuleByCID", + entry_type="StaticModule", + entries_name="gStaticModules", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const StaticModule*", + return_entry=( + "return entry.CID().Equals(aKey) && entry.Active()" " ? &entry : nullptr;" + ), + key_type="const nsID&", + key_bytes="reinterpret_cast(&aKey)", + key_length="sizeof(nsID)", + ) + + substs["module_contract_id_table"] = contract_phf.cxx_codegen( + name="LookupContractID", + entry_type="ContractEntry", + entries_name="gContractEntries", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const ContractEntry*", + return_entry="return entry.Matches(aKey) ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["js_services_table"] = js_services_phf.cxx_codegen( + name="LookupJSService", + entry_type="JSServiceEntry", + entries_name="gJSServices", + lower_entry=lambda entry: entry.lower_js_service(), + return_type="const JSServiceEntry*", + return_entry="return entry.Name() == aKey ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["protocol_handlers_table"] = protocol_handlers_phf.cxx_codegen( + name="LookupProtocolHandler", + entry_type="StaticProtocolHandler", + entries_name="gStaticProtocolHandlers", + lower_entry=lambda entry: entry.to_cxx(), + return_type="const StaticProtocolHandler*", + return_entry="return entry.Scheme() == aKey ? &entry : nullptr;", + key_type="const nsACString&", + key_bytes="aKey.BeginReading()", + key_length="aKey.Length()", + ) + + substs["js_services_json"] = json.dumps(js_services_json, sort_keys=True, indent=4) + + # Do this only after everything else has been emitted so we're sure the + # string table is complete. + substs["strings"] = strings.to_cxx() + return substs + + +# Returns true if the given build config substitution is defined and truthy. +def defined(subst): + return bool(buildconfig.substs.get(subst)) + + +def read_manifest(filename): + glbl = { + "buildconfig": buildconfig, + "defined": defined, + "ProcessSelector": ProcessSelector, + "BackgroundTasksSelector": BackgroundTasksSelector, + } + code = compile(open(filename).read(), filename, "exec") + exec(code, glbl) + return glbl + + +def main(fd, conf_file, template_file): + def open_output(filename): + return FileAvoidWrite(os.path.join(os.path.dirname(fd.name), filename)) + + conf = json.load(open(conf_file, "r")) + + deps = set() + + manifests = [] + for filename in conf["manifests"]: + deps.add(filename) + manifest = read_manifest(filename) + manifests.append(manifest) + manifest.setdefault("Priority", 50) + manifest["__filename__"] = filename + + manifests.sort(key=lambda man: (man["Priority"], man["__filename__"])) + + substs = gen_substs(manifests) + + def replacer(match): + return substs[match.group(1)] + + with open_output("StaticComponents.cpp") as fh: + with open(template_file, "r") as tfh: + template = tfh.read() + + fh.write(re.sub(r"//# @([a-zA-Z_]+)@\n", replacer, template)) + + with open_output("StaticComponentData.h") as fh: + fh.write( + """\ +/* -*- 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/. */ + +#ifndef StaticComponentData_h +#define StaticComponentData_h + +#include + +namespace mozilla { +namespace xpcom { + +static constexpr size_t kStaticModuleCount = %(module_count)d; + +static constexpr size_t kContractCount = %(contract_count)d; + +static constexpr size_t kStaticCategoryCount = %(category_count)d; + +static constexpr size_t kModuleInitCount = %(init_count)d; + +static constexpr size_t kStaticProtocolHandlerCount = %(protocol_handler_count)d; + +static constexpr size_t kDefaultProtocolHandlerIndex = %(default_protocol_handler_idx)d; + +} // namespace xpcom +} // namespace mozilla + +#endif +""" + % substs + ) + + with open_output("components.rs") as fh: + fh.write( + """\ +/// Unique IDs for each statically-registered module. +#[repr(u16)] +pub enum ModuleID { +%(module_ids)s +} + +%(component_getters_rust)s +""" + % substs + ) + + fd.write( + """\ +/* -*- 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/. */ + +#ifndef mozilla_Components_h +#define mozilla_Components_h + +#include "nsCOMPtr.h" + +struct nsID; + +#define NS_IMPL_COMPONENT_FACTORY(iface) \\ + template <> \\ + already_AddRefed mozCreateComponent() + +template +already_AddRefed mozCreateComponent(); + +namespace mozilla { +namespace xpcom { + +enum class ModuleID : uint16_t { +%(module_ids)s +}; + +// May be added as a friend function to allow constructing services via +// private constructors and init methods. +nsresult CreateInstanceImpl(ModuleID aID, const nsIID& aIID, void** aResult); + +class MOZ_STACK_CLASS StaticModuleHelper : public nsCOMPtr_helper { + public: + StaticModuleHelper(ModuleID aId, nsresult* aErrorPtr) + : mId(aId), mErrorPtr(aErrorPtr) {} + + protected: + nsresult SetResult(nsresult aRv) const { + if (mErrorPtr) { + *mErrorPtr = aRv; + } + return aRv; + } + + ModuleID mId; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS GetServiceHelper final : public StaticModuleHelper { + public: + using StaticModuleHelper::StaticModuleHelper; + + nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override; +}; + +class MOZ_STACK_CLASS CreateInstanceHelper final : public StaticModuleHelper { + public: + using StaticModuleHelper::StaticModuleHelper; + + nsresult NS_FASTCALL operator()(const nsIID& aIID, + void** aResult) const override; +}; + +class Components final { + public: + static const nsID& GetCID(ModuleID aID); +}; + +} // namespace xpcom + +namespace components { +%(component_getters)s +} // namespace components + +} // namespace mozilla + +#endif +""" + % substs + ) + + with open_output("services.json") as fh: + fh.write(substs["js_services_json"]) + + return deps diff --git a/xpcom/components/moz.build b/xpcom/components/moz.build new file mode 100644 index 0000000000..95ee64e985 --- /dev/null +++ b/xpcom/components/moz.build @@ -0,0 +1,85 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + "nsICategoryManager.idl", + "nsIClassInfo.idl", + "nsIComponentManager.idl", + "nsIComponentRegistrar.idl", + "nsIFactory.idl", + "nsIServiceManager.idl", +] + +XPIDL_MODULE = "xpcom_components" + +EXPORTS += [ + "nsCategoryCache.h", + "nsCategoryManagerUtils.h", + "nsComponentManagerUtils.h", + "nsServiceManagerUtils.h", +] + +EXPORTS.mozilla += [ + "GenericFactory.h", + "Module.h", + "ModuleUtils.h", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +if CONFIG["COMPILE_ENVIRONMENT"]: + EXPORTS.mozilla += [ + "!Components.h", + ] + + GeneratedFile( + "Components.h", + "StaticComponentData.h", + "StaticComponents.cpp", + "services.json", + "components.rs", + script="gen_static_components.py", + inputs=["!manifest-lists.json", "StaticComponents.cpp.in"], + ) + +UNIFIED_SOURCES += [ + "GenericFactory.cpp", + "ManifestParser.cpp", + "nsCategoryCache.cpp", + "nsCategoryManager.cpp", + "nsComponentManager.cpp", + "nsComponentManagerUtils.cpp", +] + +SOURCES += [ + "!StaticComponents.cpp", +] + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "!..", + "../base", + "../build", + "../ds", + "/chrome", + "/js/xpconnect/loader", + "/layout/build", + "/modules/libjar", +] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"] + if CONFIG["MOZ_ENABLE_DBUS"]: + CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"] + +include("/ipc/chromium/chromium-config.mozbuild") + +PYTHON_UNITTEST_MANIFESTS += [ + "test/python.ini", +] diff --git a/xpcom/components/nsCategoryCache.cpp b/xpcom/components/nsCategoryCache.cpp new file mode 100644 index 0000000000..35f555fa51 --- /dev/null +++ b/xpcom/components/nsCategoryCache.cpp @@ -0,0 +1,162 @@ +/* -*- 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 "nsIObserverService.h" +#include "mozilla/Services.h" +#include "mozilla/SimpleEnumerator.h" + +#include "nsICategoryManager.h" +#include "nsISupportsPrimitives.h" + +#include "nsXPCOMCID.h" + +#include "nsCategoryCache.h" + +using mozilla::SimpleEnumerator; + +nsCategoryObserver::nsCategoryObserver(const nsACString& aCategory) + : mCategory(aCategory), + mCallback(nullptr), + mClosure(nullptr), + mObserversRemoved(false) { + MOZ_ASSERT(NS_IsMainThread()); + // First, enumerate the currently existing entries + nsCOMPtr catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return; + } + + nsCOMPtr enumerator; + nsresult rv = + catMan->EnumerateCategory(aCategory, getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) { + return; + } + + for (auto& categoryEntry : SimpleEnumerator(enumerator)) { + nsAutoCString entryValue; + categoryEntry->GetValue(entryValue); + + if (nsCOMPtr service = do_GetService(entryValue.get())) { + nsAutoCString entryName; + categoryEntry->GetEntry(entryName); + + mHash.InsertOrUpdate(entryName, service); + } + } + + // Now, listen for changes + nsCOMPtr serv = mozilla::services::GetObserverService(); + if (serv) { + serv->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, false); + serv->AddObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, false); + } +} + +nsCategoryObserver::~nsCategoryObserver() = default; + +NS_IMPL_ISUPPORTS(nsCategoryObserver, nsIObserver) + +void nsCategoryObserver::ListenerDied() { + MOZ_ASSERT(NS_IsMainThread()); + RemoveObservers(); + mCallback = nullptr; + mClosure = nullptr; +} + +void nsCategoryObserver::SetListener(void(aCallback)(void*), void* aClosure) { + MOZ_ASSERT(NS_IsMainThread()); + mCallback = aCallback; + mClosure = aClosure; +} + +void nsCategoryObserver::RemoveObservers() { + MOZ_ASSERT(NS_IsMainThread()); + + if (mObserversRemoved) { + return; + } + + if (mCallback) { + mCallback(mClosure); + } + + mObserversRemoved = true; + nsCOMPtr obsSvc = mozilla::services::GetObserverService(); + if (obsSvc) { + obsSvc->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID); + obsSvc->RemoveObserver(this, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID); + } +} + +NS_IMETHODIMP +nsCategoryObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + MOZ_ASSERT(NS_IsMainThread()); + + if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { + mHash.Clear(); + RemoveObservers(); + + return NS_OK; + } + + if (!aData || + !nsDependentString(aData).Equals(NS_ConvertASCIItoUTF16(mCategory))) { + return NS_OK; + } + + nsAutoCString str; + nsCOMPtr strWrapper(do_QueryInterface(aSubject)); + if (strWrapper) { + strWrapper->GetData(str); + } + + if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID) == 0) { + // We may get an add notification even when we already have an entry. This + // is due to the notification happening asynchronously, so if the entry gets + // added and an nsCategoryObserver gets instantiated before events get + // processed, we'd get the notification for an existing entry. + // Do nothing in that case. + if (mHash.GetWeak(str)) { + return NS_OK; + } + + nsCOMPtr catMan = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID); + if (!catMan) { + return NS_OK; + } + + nsCString entryValue; + catMan->GetCategoryEntry(mCategory, str, entryValue); + + nsCOMPtr service = do_GetService(entryValue.get()); + + if (service) { + mHash.InsertOrUpdate(str, service); + } + if (mCallback) { + mCallback(mClosure); + } + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID) == 0) { + mHash.Remove(str); + if (mCallback) { + mCallback(mClosure); + } + } else if (strcmp(aTopic, NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID) == 0) { + mHash.Clear(); + if (mCallback) { + mCallback(mClosure); + } + } + return NS_OK; +} diff --git a/xpcom/components/nsCategoryCache.h b/xpcom/components/nsCategoryCache.h new file mode 100644 index 0000000000..f2d7ba32a3 --- /dev/null +++ b/xpcom/components/nsCategoryCache.h @@ -0,0 +1,123 @@ +/* -*- 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/. */ + +#ifndef nsCategoryCache_h_ +#define nsCategoryCache_h_ + +#include "mozilla/Attributes.h" + +#include "nsIObserver.h" + +#include "nsServiceManagerUtils.h" + +#include "nsCOMArray.h" +#include "nsInterfaceHashtable.h" + +#include "nsXPCOM.h" +#include "MainThreadUtils.h" + +class nsCategoryObserver final : public nsIObserver { + ~nsCategoryObserver(); + + public: + explicit nsCategoryObserver(const nsACString& aCategory); + + void ListenerDied(); + void SetListener(void(aCallback)(void*), void* aClosure); + nsInterfaceHashtable& GetHash() { + return mHash; + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + private: + void RemoveObservers(); + + nsInterfaceHashtable mHash; + nsCString mCategory; + void (*mCallback)(void*); + void* mClosure; + bool mObserversRemoved; +}; + +/** + * This is a helper class that caches services that are registered in a certain + * category. The intended usage is that a service stores a variable of type + * nsCategoryCache in a member variable, where nsIFoo is the interface + * that these services should implement. The constructor of this class should + * then get the name of the category. + */ +template +class nsCategoryCache final { + public: + explicit nsCategoryCache(const char* aCategory) : mCategoryName(aCategory) { + MOZ_ASSERT(NS_IsMainThread()); + } + ~nsCategoryCache() { + MOZ_ASSERT(NS_IsMainThread()); + if (mObserver) { + mObserver->ListenerDied(); + } + } + + void GetEntries(nsCOMArray& aResult) { + MOZ_ASSERT(NS_IsMainThread()); + LazyInit(); + + AddEntries(aResult); + } + + /** + * This function returns an nsCOMArray of interface pointers to the cached + * category enries for the requested category. This was added in order to be + * used in call sites where the overhead of excessive allocations can be + * unacceptable. See bug 1360971 for an example. + */ + const nsCOMArray& GetCachedEntries() { + MOZ_ASSERT(NS_IsMainThread()); + LazyInit(); + + if (mCachedEntries.IsEmpty()) { + AddEntries(mCachedEntries); + } + return mCachedEntries; + } + + private: + void LazyInit() { + // Lazy initialization, so that services in this category can't + // cause reentrant getService (bug 386376) + if (!mObserver) { + mObserver = new nsCategoryObserver(mCategoryName); + mObserver->SetListener(nsCategoryCache::OnCategoryChanged, this); + } + } + + void AddEntries(nsCOMArray& aResult) { + for (nsISupports* entry : mObserver->GetHash().Values()) { + nsCOMPtr service = do_QueryInterface(entry); + if (service) { + aResult.AppendElement(service.forget()); + } + } + } + + static void OnCategoryChanged(void* aClosure) { + MOZ_ASSERT(NS_IsMainThread()); + auto self = static_cast*>(aClosure); + self->mCachedEntries.Clear(); + } + + private: + // Not to be implemented + nsCategoryCache(const nsCategoryCache&); + + nsCString mCategoryName; + RefPtr mObserver; + nsCOMArray mCachedEntries; +}; + +#endif diff --git a/xpcom/components/nsCategoryManager.cpp b/xpcom/components/nsCategoryManager.cpp new file mode 100644 index 0000000000..a12fd510f5 --- /dev/null +++ b/xpcom/components/nsCategoryManager.cpp @@ -0,0 +1,692 @@ +/* -*- 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 "nsCategoryManager.h" +#include "nsCategoryManagerUtils.h" + +#include "prio.h" +#include "prlock.h" +#include "nsArrayEnumerator.h" +#include "nsCOMPtr.h" +#include "nsTHashtable.h" +#include "nsClassHashtable.h" +#include "nsStringEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsPrintfCString.h" +#include "nsQuickSort.h" +#include "nsEnumeratorUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/ArenaAllocatorExtensions.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/Services.h" +#include "mozilla/SimpleEnumerator.h" + +#include "ManifestParser.h" +#include "nsSimpleEnumerator.h" + +using namespace mozilla; +class nsIComponentLoaderManager; + +/* + CategoryDatabase + contains 0 or more 1-1 mappings of string to Category + each Category contains 0 or more 1-1 mappings of string keys to string values + + In other words, the CategoryDatabase is a tree, whose root is a hashtable. + Internal nodes (or Categories) are hashtables. Leaf nodes are strings. + + The leaf strings are allocated in an arena, because we assume they're not + going to change much ;) +*/ + +// +// CategoryEnumerator class +// + +class CategoryEnumerator : public nsSimpleEnumerator, + private nsStringEnumeratorBase { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + + using nsStringEnumeratorBase::GetNext; + + const nsID& DefaultInterface() override { + return NS_GET_IID(nsISupportsCString); + } + + static CategoryEnumerator* Create( + nsClassHashtable& aTable); + + protected: + CategoryEnumerator() + : mArray(nullptr), mCount(0), mSimpleCurItem(0), mStringCurItem(0) {} + + ~CategoryEnumerator() override { delete[] mArray; } + + const char** mArray; + uint32_t mCount; + uint32_t mSimpleCurItem; + uint32_t mStringCurItem; +}; + +NS_IMPL_ISUPPORTS_INHERITED(CategoryEnumerator, nsSimpleEnumerator, + nsIUTF8StringEnumerator, nsIStringEnumerator) + +NS_IMETHODIMP +CategoryEnumerator::HasMoreElements(bool* aResult) { + *aResult = (mSimpleCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::GetNext(nsISupports** aResult) { + if (mSimpleCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + auto* str = new nsSupportsDependentCString(mArray[mSimpleCurItem++]); + + *aResult = str; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::HasMore(bool* aResult) { + *aResult = (mStringCurItem < mCount); + + return NS_OK; +} + +NS_IMETHODIMP +CategoryEnumerator::GetNext(nsACString& aResult) { + if (mStringCurItem >= mCount) { + return NS_ERROR_FAILURE; + } + + aResult = nsDependentCString(mArray[mStringCurItem++]); + return NS_OK; +} + +CategoryEnumerator* CategoryEnumerator::Create( + nsClassHashtable& aTable) { + auto* enumObj = new CategoryEnumerator(); + if (!enumObj) { + return nullptr; + } + + enumObj->mArray = new const char*[aTable.Count()]; + if (!enumObj->mArray) { + delete enumObj; + return nullptr; + } + + for (const auto& entry : aTable) { + // if a category has no entries, we pretend it doesn't exist + CategoryNode* aNode = entry.GetWeak(); + if (aNode->Count()) { + enumObj->mArray[enumObj->mCount++] = entry.GetKey(); + } + } + + return enumObj; +} + +class CategoryEntry final : public nsICategoryEntry { + NS_DECL_ISUPPORTS + NS_DECL_NSICATEGORYENTRY + NS_DECL_NSISUPPORTSCSTRING + NS_DECL_NSISUPPORTSPRIMITIVE + + CategoryEntry(const char* aKey, const char* aValue) + : mKey(aKey), mValue(aValue) {} + + const char* Key() const { return mKey; } + + static CategoryEntry* Cast(nsICategoryEntry* aEntry) { + return static_cast(aEntry); + } + + private: + ~CategoryEntry() = default; + + const char* mKey; + const char* mValue; +}; + +NS_IMPL_ISUPPORTS(CategoryEntry, nsICategoryEntry, nsISupportsCString) + +nsresult CategoryEntry::ToString(char** aResult) { + *aResult = moz_xstrdup(mKey); + return NS_OK; +} + +nsresult CategoryEntry::GetType(uint16_t* aType) { + *aType = TYPE_CSTRING; + return NS_OK; +} + +nsresult CategoryEntry::GetData(nsACString& aData) { + aData = mKey; + return NS_OK; +} + +nsresult CategoryEntry::SetData(const nsACString& aData) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult CategoryEntry::GetEntry(nsACString& aEntry) { + aEntry = mKey; + return NS_OK; +} + +nsresult CategoryEntry::GetValue(nsACString& aValue) { + aValue = mValue; + return NS_OK; +} + +static nsresult CreateEntryEnumerator(nsTHashtable& aTable, + nsISimpleEnumerator** aResult) { + nsCOMArray entries(aTable.Count()); + + for (auto iter = aTable.Iter(); !iter.Done(); iter.Next()) { + CategoryLeaf* leaf = iter.Get(); + if (leaf->value) { + entries.AppendElement(new CategoryEntry(leaf->GetKey(), leaf->value)); + } + } + + entries.Sort( + [](nsICategoryEntry* aA, nsICategoryEntry* aB, void*) { + return strcmp(CategoryEntry::Cast(aA)->Key(), + CategoryEntry::Cast(aB)->Key()); + }, + nullptr); + + return NS_NewArrayEnumerator(aResult, entries, NS_GET_IID(nsICategoryEntry)); +} + +// +// CategoryNode implementations +// + +CategoryNode* CategoryNode::Create(CategoryAllocator* aArena) { + return new (aArena) CategoryNode(); +} + +CategoryNode::~CategoryNode() = default; + +void* CategoryNode::operator new(size_t aSize, CategoryAllocator* aArena) { + return aArena->Allocate(aSize, mozilla::fallible); +} + +static inline const char* MaybeStrdup(const nsACString& aStr, + CategoryAllocator* aArena) { + if (aStr.IsLiteral()) { + return aStr.BeginReading(); + } + return ArenaStrdup(PromiseFlatCString(aStr).get(), *aArena); +} + +nsresult CategoryNode::GetLeaf(const nsACString& aEntryName, + nsACString& aResult) { + MutexAutoLock lock(mLock); + nsresult rv = NS_ERROR_NOT_AVAILABLE; + CategoryLeaf* ent = mTable.GetEntry(PromiseFlatCString(aEntryName).get()); + + if (ent && ent->value) { + aResult.Assign(ent->value); + return NS_OK; + } + + return rv; +} + +nsresult CategoryNode::AddLeaf(const nsACString& aEntryName, + const nsACString& aValue, bool aReplace, + nsACString& aResult, CategoryAllocator* aArena) { + aResult.SetIsVoid(true); + + auto entryName = PromiseFlatCString(aEntryName); + + MutexAutoLock lock(mLock); + CategoryLeaf* leaf = mTable.GetEntry(entryName.get()); + + if (!leaf) { + leaf = mTable.PutEntry(MaybeStrdup(aEntryName, aArena)); + if (!leaf) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (leaf->value && !aReplace) { + return NS_ERROR_INVALID_ARG; + } + + if (leaf->value) { + aResult.AssignLiteral(leaf->value, strlen(leaf->value)); + } else { + aResult.SetIsVoid(true); + } + leaf->value = MaybeStrdup(aValue, aArena); + return NS_OK; +} + +void CategoryNode::DeleteLeaf(const nsACString& aEntryName) { + // we don't throw any errors, because it normally doesn't matter + // and it makes JS a lot cleaner + MutexAutoLock lock(mLock); + + // we can just remove the entire hash entry without introspection + mTable.RemoveEntry(PromiseFlatCString(aEntryName).get()); +} + +nsresult CategoryNode::Enumerate(nsISimpleEnumerator** aResult) { + MutexAutoLock lock(mLock); + return CreateEntryEnumerator(mTable, aResult); +} + +size_t CategoryNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) { + // We don't measure the strings pointed to by the entries because the + // pointers are non-owning. + return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +// +// nsCategoryManager implementations +// + +NS_IMPL_QUERY_INTERFACE(nsCategoryManager, nsICategoryManager, + nsIMemoryReporter) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsCategoryManager::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +nsCategoryManager::Release() { return 1; } + +nsCategoryManager* nsCategoryManager::gCategoryManager; + +/* static */ +nsCategoryManager* nsCategoryManager::GetSingleton() { + if (!gCategoryManager) { + gCategoryManager = new nsCategoryManager(); + } + return gCategoryManager; +} + +/* static */ +void nsCategoryManager::Destroy() { + // The nsMemoryReporterManager gets destroyed before the nsCategoryManager, + // so we don't need to unregister the nsCategoryManager as a memory reporter. + // In debug builds we assert that unregistering fails, as a way (imperfect + // but better than nothing) of testing the "destroyed before" part. + MOZ_ASSERT(NS_FAILED(UnregisterWeakMemoryReporter(gCategoryManager))); + + delete gCategoryManager; + gCategoryManager = nullptr; +} + +nsresult nsCategoryManager::Create(REFNSIID aIID, void** aResult) { + return GetSingleton()->QueryInterface(aIID, aResult); +} + +nsCategoryManager::nsCategoryManager() + : mArena(), + mTable(), + mLock("nsCategoryManager"), + mSuppressNotifications(false) {} + +void nsCategoryManager::InitMemoryReporter() { + RegisterWeakMemoryReporter(this); +} + +nsCategoryManager::~nsCategoryManager() { + // the hashtable contains entries that must be deleted before the arena is + // destroyed, or else you will have PRLocks undestroyed and other Really + // Bad Stuff (TM) + mTable.Clear(); +} + +inline CategoryNode* nsCategoryManager::get_category(const nsACString& aName) { + CategoryNode* node; + if (!mTable.Get(PromiseFlatCString(aName).get(), &node)) { + return nullptr; + } + return node; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(CategoryManagerMallocSizeOf) + +NS_IMETHODIMP +nsCategoryManager::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/xpcom/category-manager", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(CategoryManagerMallocSizeOf), + "Memory used for the XPCOM category manager."); + + return NS_OK; +} + +size_t nsCategoryManager::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + size_t n = aMallocSizeOf(this); + + n += mArena.SizeOfExcludingThis(aMallocSizeOf); + + n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& data : mTable.Values()) { + // We don't measure the key string because it's a non-owning pointer. + n += data->SizeOfExcludingThis(aMallocSizeOf); + } + + return n; +} + +namespace { + +class CategoryNotificationRunnable : public Runnable { + public: + CategoryNotificationRunnable(nsISupports* aSubject, const char* aTopic, + const nsACString& aData) + : Runnable("CategoryNotificationRunnable"), + mSubject(aSubject), + mTopic(aTopic), + mData(aData) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr mSubject; + const char* mTopic; + NS_ConvertUTF8toUTF16 mData; +}; + +NS_IMETHODIMP +CategoryNotificationRunnable::Run() { + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->NotifyObservers(mSubject, mTopic, mData.get()); + } + + return NS_OK; +} + +} // namespace + +void nsCategoryManager::NotifyObservers(const char* aTopic, + const nsACString& aCategoryName, + const nsACString& aEntryName) { + if (mSuppressNotifications) { + return; + } + + RefPtr r; + + if (aEntryName.Length()) { + nsCOMPtr entry = + do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); + if (!entry) { + return; + } + + nsresult rv = entry->SetData(aEntryName); + if (NS_FAILED(rv)) { + return; + } + + r = new CategoryNotificationRunnable(entry, aTopic, aCategoryName); + } else { + r = new CategoryNotificationRunnable( + NS_ISUPPORTS_CAST(nsICategoryManager*, this), aTopic, aCategoryName); + } + + NS_DispatchToMainThread(r); +} + +NS_IMETHODIMP +nsCategoryManager::GetCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + nsACString& aResult) { + nsresult status = NS_ERROR_NOT_AVAILABLE; + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + status = category->GetLeaf(aEntryName, aResult); + } + + return status; +} + +NS_IMETHODIMP +nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + const nsACString& aValue, bool aPersist, + bool aReplace, nsACString& aResult) { + if (aPersist) { + NS_ERROR("Category manager doesn't support persistence."); + return NS_ERROR_INVALID_ARG; + } + + AddCategoryEntry(aCategoryName, aEntryName, aValue, aReplace, aResult); + return NS_OK; +} + +void nsCategoryManager::AddCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + const nsACString& aValue, + bool aReplace, nsACString& aOldValue) { + aOldValue.SetIsVoid(true); + + // Before we can insert a new entry, we'll need to + // find the |CategoryNode| to put it in... + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + + if (!category) { + // That category doesn't exist yet; let's make it. + category = mTable + .InsertOrUpdate( + MaybeStrdup(aCategoryName, &mArena), + UniquePtr{CategoryNode::Create(&mArena)}) + .get(); + } + } + + if (!category) { + return; + } + + nsresult rv = + category->AddLeaf(aEntryName, aValue, aReplace, aOldValue, &mArena); + + if (NS_SUCCEEDED(rv)) { + if (!aOldValue.IsEmpty()) { + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, + aCategoryName, aEntryName); + } + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_ADDED_OBSERVER_ID, aCategoryName, + aEntryName); + } +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategoryEntry(const nsACString& aCategoryName, + const nsACString& aEntryName, + bool aDontPersist) { + /* + Note: no errors are reported since failure to delete + probably won't hurt you, and returning errors seriously + inconveniences JS clients + */ + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + category->DeleteLeaf(aEntryName); + + NotifyObservers(NS_XPCOM_CATEGORY_ENTRY_REMOVED_OBSERVER_ID, aCategoryName, + aEntryName); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::DeleteCategory(const nsACString& aCategoryName) { + // the categories are arena-allocated, so we don't + // actually delete them. We just remove all of the + // leaf nodes. + + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (category) { + category->Clear(); + NotifyObservers(NS_XPCOM_CATEGORY_CLEARED_OBSERVER_ID, aCategoryName, + VoidCString()); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategory(const nsACString& aCategoryName, + nsISimpleEnumerator** aResult) { + CategoryNode* category; + { + MutexAutoLock lock(mLock); + category = get_category(aCategoryName); + } + + if (!category) { + return NS_NewEmptyEnumerator(aResult); + } + + return category->Enumerate(aResult); +} + +NS_IMETHODIMP +nsCategoryManager::EnumerateCategories(nsISimpleEnumerator** aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + MutexAutoLock lock(mLock); + CategoryEnumerator* enumObj = CategoryEnumerator::Create(mTable); + + if (!enumObj) { + return NS_ERROR_OUT_OF_MEMORY; + } + + *aResult = enumObj; + NS_ADDREF(*aResult); + return NS_OK; +} + +struct writecat_struct { + PRFileDesc* fd; + bool success; +}; + +nsresult nsCategoryManager::SuppressNotifications(bool aSuppress) { + mSuppressNotifications = aSuppress; + return NS_OK; +} + +/* + * CreateServicesFromCategory() + * + * Given a category, this convenience functions enumerates the category and + * creates a service of every CID or ContractID registered under the category. + * If observerTopic is non null and the service implements nsIObserver, + * this will attempt to notify the observer with the origin, observerTopic + * string as parameter. + */ +void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin, + const char* aObserverTopic, + const char16_t* aObserverData) { + nsresult rv; + + nsCOMPtr categoryManager = + do_GetService("@mozilla.org/categorymanager;1"); + if (!categoryManager) { + return; + } + + nsDependentCString category(aCategory); + + nsCOMPtr enumerator; + rv = categoryManager->EnumerateCategory(category, getter_AddRefs(enumerator)); + if (NS_FAILED(rv)) { + return; + } + + for (auto& categoryEntry : SimpleEnumerator(enumerator)) { + // From here on just skip any error we get. + nsAutoCString entryString; + categoryEntry->GetEntry(entryString); + + nsAutoCString contractID; + categoryEntry->GetValue(contractID); + + nsCOMPtr instance = do_GetService(contractID.get()); + if (!instance) { + LogMessage( + "While creating services from category '%s', could not create " + "service for entry '%s', contract ID '%s'", + aCategory, entryString.get(), contractID.get()); + continue; + } + + if (aObserverTopic) { + // try an observer, if it implements it. + nsCOMPtr observer = do_QueryInterface(instance); + if (observer) { + nsPrintfCString profilerStr("%s (%s)", aObserverTopic, + entryString.get()); + AUTO_PROFILER_MARKER_TEXT("Category observer notification", OTHER, + MarkerStack::Capture(), profilerStr); + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( + "Category observer notification -", OTHER, profilerStr); + + observer->Observe(aOrigin, aObserverTopic, + aObserverData ? aObserverData : u""); + } else { + LogMessage( + "While creating services from category '%s', service for entry " + "'%s', contract ID '%s' does not implement nsIObserver.", + aCategory, entryString.get(), contractID.get()); + } + } + } +} diff --git a/xpcom/components/nsCategoryManager.h b/xpcom/components/nsCategoryManager.h new file mode 100644 index 0000000000..e15760768d --- /dev/null +++ b/xpcom/components/nsCategoryManager.h @@ -0,0 +1,145 @@ +/* -*- 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/. */ + +#ifndef NSCATEGORYMANAGER_H +#define NSCATEGORYMANAGER_H + +#include "prio.h" +#include "nsClassHashtable.h" +#include "nsICategoryManager.h" +#include "nsIMemoryReporter.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/Attributes.h" + +class nsIMemoryReporter; + +typedef mozilla::ArenaAllocator<1024 * 8, 8> CategoryAllocator; + +/* 16d222a6-1dd2-11b2-b693-f38b02c021b2 */ +#define NS_CATEGORYMANAGER_CID \ + { \ + 0x16d222a6, 0x1dd2, 0x11b2, { \ + 0xb6, 0x93, 0xf3, 0x8b, 0x02, 0xc0, 0x21, 0xb2 \ + } \ + } + +/** + * a "leaf-node", managed by the nsCategoryNode hashtable. + * + * we need to keep a "persistent value" (which will be written to the registry) + * and a non-persistent value (for the current runtime): these are usually + * the same, except when aPersist==false. The strings are permanently arena- + * allocated, and will never go away. + */ +class CategoryLeaf : public nsDepCharHashKey { + public: + explicit CategoryLeaf(const char* aKey) + : nsDepCharHashKey(aKey), value(nullptr) {} + const char* value; +}; + +/** + * CategoryNode keeps a hashtable of its entries. + * the CategoryNode itself is permanently allocated in + * the arena. + */ +class CategoryNode { + public: + nsresult GetLeaf(const nsACString& aEntryName, nsACString& aResult); + + nsresult AddLeaf(const nsACString& aEntryName, const nsACString& aValue, + bool aReplace, nsACString& aResult, + CategoryAllocator* aArena); + + void DeleteLeaf(const nsACString& aEntryName); + + void Clear() { + mozilla::MutexAutoLock lock(mLock); + mTable.Clear(); + } + + uint32_t Count() { + mozilla::MutexAutoLock lock(mLock); + uint32_t tCount = mTable.Count(); + return tCount; + } + + nsresult Enumerate(nsISimpleEnumerator** aResult); + + // CategoryNode is arena-allocated, with the strings + static CategoryNode* Create(CategoryAllocator* aArena); + ~CategoryNode(); + void operator delete(void*) {} + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + private: + CategoryNode() : mLock("CategoryLeaf") {} + + void* operator new(size_t aSize, CategoryAllocator* aArena); + + nsTHashtable mTable; + mozilla::Mutex mLock MOZ_UNANNOTATED; +}; + +/** + * The main implementation of nsICategoryManager. + * + * This implementation is thread-safe. + */ +class nsCategoryManager final : public nsICategoryManager, + public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICATEGORYMANAGER + NS_DECL_NSIMEMORYREPORTER + + /** + * Suppress or unsuppress notifications of category changes to the + * observer service. This is to be used by nsComponentManagerImpl + * on startup while reading the stored category list. + */ + nsresult SuppressNotifications(bool aSuppress); + + void AddCategoryEntry(const nsACString& aCategory, const nsACString& aKey, + const nsACString& aValue, bool aReplace, + nsACString& aOldValue); + + void AddCategoryEntry(const nsACString& aCategory, const nsACString& aKey, + const nsACString& aValue, bool aReplace = true) { + nsCString oldValue; + return AddCategoryEntry(aCategory, aKey, aValue, aReplace, oldValue); + } + + static nsresult Create(REFNSIID aIID, void** aResult); + void InitMemoryReporter(); + + static nsCategoryManager* GetSingleton(); + static void Destroy(); + + private: + static nsCategoryManager* gCategoryManager; + + nsCategoryManager(); + ~nsCategoryManager(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + CategoryNode* get_category(const nsACString& aName); + void NotifyObservers( + const char* aTopic, + const nsACString& aCategoryName, // must be a static string + const nsACString& aEntryName); + + CategoryAllocator mArena; + nsClassHashtable mTable; + mozilla::Mutex mLock MOZ_UNANNOTATED; + bool mSuppressNotifications; +}; + +#endif diff --git a/xpcom/components/nsCategoryManagerUtils.h b/xpcom/components/nsCategoryManagerUtils.h new file mode 100644 index 0000000000..30db31bab6 --- /dev/null +++ b/xpcom/components/nsCategoryManagerUtils.h @@ -0,0 +1,14 @@ +/* -*- 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/. */ + +#ifndef nsCategoryManagerUtils_h__ +#define nsCategoryManagerUtils_h__ + +void NS_CreateServicesFromCategory(const char* aCategory, nsISupports* aOrigin, + const char* aObserverTopic, + const char16_t* aObserverData = nullptr); + +#endif diff --git a/xpcom/components/nsComponentManager.cpp b/xpcom/components/nsComponentManager.cpp new file mode 100644 index 0000000000..35954b1702 --- /dev/null +++ b/xpcom/components/nsComponentManager.cpp @@ -0,0 +1,1575 @@ +/* -*- 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 +#include "nscore.h" +#include "nsISupports.h" +#include "nspr.h" +#include "nsCRT.h" // for atoll + +#include "StaticComponents.h" + +#include "nsCategoryManager.h" +#include "nsCOMPtr.h" +#include "nsComponentManager.h" +#include "nsDirectoryService.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCategoryManager.h" +#include "nsLayoutModule.h" +#include "mozilla/MemoryReporting.h" +#include "nsIObserverService.h" +#include "nsIStringEnumerator.h" +#include "nsXPCOM.h" +#include "nsXPCOMPrivate.h" +#include "nsISupportsPrimitives.h" +#include "nsLocalFile.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "prcmon.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "prthread.h" +#include "private/pprthred.h" +#include "nsTArray.h" +#include "prio.h" +#include "ManifestParser.h" +#include "nsNetUtil.h" +#include "mozilla/Services.h" + +#include "mozilla/GenericFactory.h" +#include "nsSupportsPrimitives.h" +#include "nsArray.h" +#include "nsIMutableArray.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/FileUtils.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/URLPreloader.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Variant.h" + +#include // for placement new + +#include "mozilla/Omnijar.h" + +#include "mozilla/Logging.h" +#include "LogModulePrefWatcher.h" + +#ifdef MOZ_MEMORY +# include "mozmemory.h" +#endif + +using namespace mozilla; +using namespace mozilla::xpcom; + +static LazyLogModule nsComponentManagerLog("nsComponentManager"); + +#if 0 +# define SHOW_DENIED_ON_SHUTDOWN +# define SHOW_CI_ON_EXISTING_SERVICE +#endif + +namespace { + +class AutoIDString : public nsAutoCStringN { + public: + explicit AutoIDString(const nsID& aID) { + SetLength(NSID_LENGTH - 1); + aID.ToProvidedString( + *reinterpret_cast(BeginWriting())); + } +}; + +} // namespace + +namespace mozilla { +namespace xpcom { + +using ProcessSelector = Module::ProcessSelector; + +// Note: These must be kept in sync with the ProcessSelector definition in +// Module.h. +bool ProcessSelectorMatches(ProcessSelector aSelector) { + GeckoProcessType type = XRE_GetProcessType(); + if (type == GeckoProcessType_GPU) { + return !!(aSelector & Module::ALLOW_IN_GPU_PROCESS); + } + + if (type == GeckoProcessType_RDD) { + return !!(aSelector & Module::ALLOW_IN_RDD_PROCESS); + } + + if (type == GeckoProcessType_Socket) { + return !!(aSelector & (Module::ALLOW_IN_SOCKET_PROCESS)); + } + + if (type == GeckoProcessType_VR) { + return !!(aSelector & Module::ALLOW_IN_VR_PROCESS); + } + + if (type == GeckoProcessType_Utility) { + return !!(aSelector & Module::ALLOW_IN_UTILITY_PROCESS); + } + + // Only allow XPCOM modules which can be loaded in all processes to be loaded + // in the IPDLUnitTest process. + if (type == GeckoProcessType_IPDLUnitTest) { + return size_t(aSelector) == Module::kMaxProcessSelector; + } + + if (aSelector & Module::MAIN_PROCESS_ONLY) { + return type == GeckoProcessType_Default; + } + if (aSelector & Module::CONTENT_PROCESS_ONLY) { + return type == GeckoProcessType_Content; + } + return true; +} + +static bool gProcessMatchTable[Module::kMaxProcessSelector + 1]; + +bool FastProcessSelectorMatches(ProcessSelector aSelector) { + return gProcessMatchTable[size_t(aSelector)]; +} + +} // namespace xpcom +} // namespace mozilla + +namespace { + +/** + * A wrapper simple wrapper class, which can hold either a dynamic + * nsFactoryEntry instance, or a static StaticModule entry, and transparently + * forwards method calls to the wrapped object. + * + * This allows the same code to work with either static or dynamic modules + * without caring about the difference. + */ +class MOZ_STACK_CLASS EntryWrapper final { + public: + explicit EntryWrapper(nsFactoryEntry* aEntry) : mEntry(aEntry) {} + + explicit EntryWrapper(const StaticModule* aEntry) : mEntry(aEntry) {} + +#define MATCH(type, ifFactory, ifStatic) \ + struct Matcher { \ + type operator()(nsFactoryEntry* entry) { ifFactory; } \ + type operator()(const StaticModule* entry) { ifStatic; } \ + }; \ + return mEntry.match((Matcher())) + + const nsID& CID() { + MATCH(const nsID&, return entry->mCID, return entry->CID()); + } + + already_AddRefed GetFactory() { + MATCH(already_AddRefed, return entry->GetFactory(), + return entry->GetFactory()); + } + + /** + * Creates an instance of the underlying component. This should be used in + * preference to GetFactory()->CreateInstance() where appropriate, since it + * side-steps the necessity of creating a nsIFactory instance for static + * modules. + */ + nsresult CreateInstance(const nsIID& aIID, void** aResult) { + if (mEntry.is()) { + return mEntry.as()->CreateInstance(aIID, aResult); + } + return mEntry.as()->CreateInstance(aIID, aResult); + } + + /** + * Returns the cached service instance for this entry, if any. This should + * only be accessed while mLock is held. + */ + nsISupports* ServiceInstance() { + MATCH(nsISupports*, return entry->mServiceObject, + return entry->ServiceInstance()); + } + void SetServiceInstance(already_AddRefed aInst) { + if (mEntry.is()) { + mEntry.as()->mServiceObject = aInst; + } else { + return mEntry.as()->SetServiceInstance( + std::move(aInst)); + } + } + + /** + * Returns the description string for the module this entry belongs to. + * Currently always returns "". + */ + nsCString ModuleDescription() { return ""_ns; } + + private: + Variant mEntry; +}; + +} // namespace + +// this is safe to call during InitXPCOM +static already_AddRefed GetLocationFromDirectoryService( + const char* aProp) { + nsCOMPtr directoryService; + nsDirectoryService::Create(NS_GET_IID(nsIProperties), + getter_AddRefs(directoryService)); + + if (!directoryService) { + return nullptr; + } + + nsCOMPtr file; + nsresult rv = + directoryService->Get(aProp, NS_GET_IID(nsIFile), getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return file.forget(); +} + +static already_AddRefed CloneAndAppend(nsIFile* aBase, + const nsACString& aAppend) { + nsCOMPtr f; + aBase->Clone(getter_AddRefs(f)); + if (!f) { + return nullptr; + } + + f->AppendNative(aAppend); + return f.forget(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsComponentManagerImpl +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsComponentManagerImpl::Create(REFNSIID aIID, void** aResult) { + if (!gComponentManager) { + return NS_ERROR_FAILURE; + } + + return gComponentManager->QueryInterface(aIID, aResult); +} + +static const int CONTRACTID_HASHTABLE_INITIAL_LENGTH = 8; + +nsComponentManagerImpl::nsComponentManagerImpl() + : mFactories(CONTRACTID_HASHTABLE_INITIAL_LENGTH), + mContractIDs(CONTRACTID_HASHTABLE_INITIAL_LENGTH), + mLock("nsComponentManagerImpl.mLock"), + mStatus(NOT_INITIALIZED) {} + +nsTArray* + nsComponentManagerImpl::sModuleLocations; + +/* static */ +void nsComponentManagerImpl::InitializeModuleLocations() { + if (sModuleLocations) { + return; + } + + sModuleLocations = new nsTArray; +} + +nsresult nsComponentManagerImpl::Init() { + { + gProcessMatchTable[size_t(ProcessSelector::ANY_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ANY_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::MAIN_PROCESS_ONLY)] = + ProcessSelectorMatches(ProcessSelector::MAIN_PROCESS_ONLY); + gProcessMatchTable[size_t(ProcessSelector::CONTENT_PROCESS_ONLY)] = + ProcessSelectorMatches(ProcessSelector::CONTENT_PROCESS_ONLY); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GPU_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GPU_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_VR_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_VR_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_SOCKET_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_SOCKET_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_RDD_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_RDD_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GPU_AND_MAIN_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GPU_AND_MAIN_PROCESS); + gProcessMatchTable[size_t(ProcessSelector::ALLOW_IN_GPU_AND_VR_PROCESS)] = + ProcessSelectorMatches(ProcessSelector::ALLOW_IN_GPU_AND_VR_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_VR_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_RDD_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_RDD_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_AND_SOCKET_PROCESS); + gProcessMatchTable[size_t( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS)] = + ProcessSelectorMatches( + ProcessSelector::ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS); + } + + MOZ_ASSERT(NOT_INITIALIZED == mStatus); + + nsCOMPtr greDir = GetLocationFromDirectoryService(NS_GRE_DIR); + nsCOMPtr appDir = + GetLocationFromDirectoryService(NS_XPCOM_CURRENT_PROCESS_DIR); + + nsCategoryManager::GetSingleton()->SuppressNotifications(true); + + auto* catMan = nsCategoryManager::GetSingleton(); + for (const auto& cat : gStaticCategories) { + for (const auto& entry : cat) { + if (entry.Active()) { + catMan->AddCategoryEntry(cat.Name(), entry.Entry(), entry.Value()); + } + } + } + + bool loadChromeManifests; + switch (XRE_GetProcessType()) { + // We are going to assume that only a select few (see below) process types + // want to load chrome manifests, and that any new process types will not + // want to load them, because they're not going to be executing JS. + case GeckoProcessType_RemoteSandboxBroker: + default: + loadChromeManifests = false; + break; + + // XXX The check this code replaced implicitly let through all of these + // process types, but presumably only the default (parent) and content + // processes really need chrome manifests...? + case GeckoProcessType_Default: + case GeckoProcessType_Content: + case GeckoProcessType_GMPlugin: + loadChromeManifests = true; + break; + } + + if (loadChromeManifests) { + // This needs to be called very early, before anything in nsLayoutModule is + // used, and before any calls are made into the JS engine. + nsLayoutModuleInitialize(); + + mJSLoaderReady = true; + + // The overall order in which chrome.manifests are expected to be treated + // is the following: + // - greDir's omni.ja or greDir + // - appDir's omni.ja or appDir + + InitializeModuleLocations(); + ComponentLocation* cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + RefPtr greOmnijar = + mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); + if (greOmnijar) { + cl->location.Init(greOmnijar, "chrome.manifest"); + } else { + nsCOMPtr lf = CloneAndAppend(greDir, "chrome.manifest"_ns); + cl->location.Init(lf); + } + + RefPtr appOmnijar = + mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); + if (appOmnijar) { + cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + cl->location.Init(appOmnijar, "chrome.manifest"); + } else { + bool equals = false; + appDir->Equals(greDir, &equals); + if (!equals) { + cl = sModuleLocations->AppendElement(); + cl->type = NS_APP_LOCATION; + nsCOMPtr lf = CloneAndAppend(appDir, "chrome.manifest"_ns); + cl->location.Init(lf); + } + } + + RereadChromeManifests(false); + } + + nsCategoryManager::GetSingleton()->SuppressNotifications(false); + + RegisterWeakMemoryReporter(this); + + // NB: The logging preference watcher needs to be registered late enough in + // startup that it's okay to use the preference system, but also as soon as + // possible so that log modules enabled via preferences are turned on as + // early as possible. + // + // We can't initialize the preference watcher when the log module manager is + // initialized, as a number of things attempt to start logging before the + // preference system is initialized. + // + // The preference system is registered as a component so at this point during + // component manager initialization we know it is setup and we can register + // for notifications. + LogModulePrefWatcher::RegisterPrefWatcher(); + + // Unfortunately, we can't register the nsCategoryManager memory reporter + // in its constructor (which is triggered by the GetSingleton() call + // above) because the memory reporter manager isn't initialized at that + // point. So we wait until now. + nsCategoryManager::GetSingleton()->InitMemoryReporter(); + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Initialized.")); + + mStatus = NORMAL; + + MOZ_ASSERT(!XRE_IsContentProcess() || + CONTRACTID_HASHTABLE_INITIAL_LENGTH <= 8 || + mFactories.Count() > CONTRACTID_HASHTABLE_INITIAL_LENGTH / 3, + "Initial component hashtable size is too large"); + + return NS_OK; +} + +template +static void AssertNotMallocAllocated(T* aPtr) { +#if defined(DEBUG) && defined(MOZ_MEMORY) + jemalloc_ptr_info_t info; + jemalloc_ptr_info((void*)aPtr, &info); + MOZ_ASSERT(info.tag == TagUnknown); +#endif +} + +template +static void AssertNotStackAllocated(T* aPtr) { + // On all of our supported platforms, the stack grows down. Any address + // located below the address of our argument is therefore guaranteed not to be + // stack-allocated by the caller. + // + // For addresses above our argument, things get trickier. The main thread + // stack is traditionally placed at the top of the program's address space, + // but that is becoming less reliable as more and more systems adopt address + // space layout randomization strategies, so we have to guess how much space + // above our argument pointer we need to care about. + // + // On most systems, we're guaranteed at least several KiB at the top of each + // stack for TLS. We'd probably be safe assuming at least 4KiB in the stack + // segment above our argument address, but safer is... well, safer. + // + // For threads with huge stacks, it's theoretically possible that we could + // wind up being passed a stack-allocated string from farther up the stack, + // but this is a best-effort thing, so we'll assume we only care about the + // immediate caller. For that case, max 2KiB per stack frame is probably a + // reasonable guess most of the time, and is less than the ~4KiB that we + // expect for TLS, so go with that to avoid the risk of bumping into heap + // data just above the stack. +#ifdef DEBUG + static constexpr size_t kFuzz = 2048; + + MOZ_ASSERT(uintptr_t(aPtr) < uintptr_t(&aPtr) || + uintptr_t(aPtr) > uintptr_t(&aPtr) + kFuzz); +#endif +} + +static void DoRegisterManifest(NSLocationType aType, FileLocation& aFile, + bool aChromeOnly) { + auto result = URLPreloader::Read(aFile); + if (result.isOk()) { + nsCString buf(result.unwrap()); + ParseManifest(aType, aFile, buf.BeginWriting(), aChromeOnly); + } else if (NS_BOOTSTRAPPED_LOCATION != aType) { + nsCString uri; + aFile.GetURIString(uri); + LogMessage("Could not read chrome manifest '%s'.", uri.get()); + } +} + +void nsComponentManagerImpl::RegisterManifest(NSLocationType aType, + FileLocation& aFile, + bool aChromeOnly) { + DoRegisterManifest(aType, aFile, aChromeOnly); +} + +void nsComponentManagerImpl::ManifestManifest(ManifestProcessingContext& aCx, + int aLineNo, char* const* aArgv) { + char* file = aArgv[0]; + FileLocation f(aCx.mFile, file); + RegisterManifest(aCx.mType, f, aCx.mChromeOnly); +} + +void nsComponentManagerImpl::ManifestCategory(ManifestProcessingContext& aCx, + int aLineNo, char* const* aArgv) { + char* category = aArgv[0]; + char* key = aArgv[1]; + char* value = aArgv[2]; + + nsCategoryManager::GetSingleton()->AddCategoryEntry( + nsDependentCString(category), nsDependentCString(key), + nsDependentCString(value)); +} + +void nsComponentManagerImpl::RereadChromeManifests(bool aChromeOnly) { + for (uint32_t i = 0; i < sModuleLocations->Length(); ++i) { + ComponentLocation& l = sModuleLocations->ElementAt(i); + RegisterManifest(l.type, l.location, aChromeOnly); + } + + nsCOMPtr obs = services::GetObserverService(); + if (obs) { + obs->NotifyObservers(nullptr, "chrome-manifests-loaded", nullptr); + } +} + +nsresult nsComponentManagerImpl::Shutdown(void) { + MOZ_ASSERT(NORMAL == mStatus); + + mStatus = SHUTDOWN_IN_PROGRESS; + + // Shutdown the component manager + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Beginning Shutdown.")); + + UnregisterWeakMemoryReporter(this); + + // Release all cached factories + mContractIDs.Clear(); + mFactories.Clear(); // XXX release the objects, don't just clear + + StaticComponents::Shutdown(); + + delete sModuleLocations; + + mStatus = SHUTDOWN_COMPLETE; + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Shutdown complete.")); + + return NS_OK; +} + +nsComponentManagerImpl::~nsComponentManagerImpl() { + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Beginning destruction.")); + + if (SHUTDOWN_COMPLETE != mStatus) { + Shutdown(); + } + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: Destroyed.")); +} + +NS_IMPL_ISUPPORTS(nsComponentManagerImpl, nsIComponentManager, + nsIServiceManager, nsIComponentRegistrar, + nsISupportsWeakReference, nsIInterfaceRequestor, + nsIMemoryReporter) + +nsresult nsComponentManagerImpl::GetInterface(const nsIID& aUuid, + void** aResult) { + NS_WARNING("This isn't supported"); + // fall through to QI as anything QIable is a superset of what can be + // got via the GetInterface() + return QueryInterface(aUuid, aResult); +} + +Maybe nsComponentManagerImpl::LookupByCID(const nsID& aCID) { + return LookupByCID(MonitorAutoLock(mLock), aCID); +} + +Maybe nsComponentManagerImpl::LookupByCID(const MonitorAutoLock&, + const nsID& aCID) { + if (const StaticModule* module = StaticComponents::LookupByCID(aCID)) { + return Some(EntryWrapper(module)); + } + if (nsFactoryEntry* entry = mFactories.Get(&aCID)) { + return Some(EntryWrapper(entry)); + } + return Nothing(); +} + +Maybe nsComponentManagerImpl::LookupByContractID( + const nsACString& aContractID) { + return LookupByContractID(MonitorAutoLock(mLock), aContractID); +} + +Maybe nsComponentManagerImpl::LookupByContractID( + const MonitorAutoLock&, const nsACString& aContractID) { + if (const StaticModule* module = + StaticComponents::LookupByContractID(aContractID)) { + return Some(EntryWrapper(module)); + } + if (nsFactoryEntry* entry = mContractIDs.Get(aContractID)) { + // UnregisterFactory might have left a stale nsFactoryEntry in + // mContractIDs, so we should check to see whether this entry has + // anything useful. + if (entry->mFactory || entry->mServiceObject) { + return Some(EntryWrapper(entry)); + } + } + return Nothing(); +} + +already_AddRefed nsComponentManagerImpl::FindFactory( + const nsCID& aClass) { + Maybe e = LookupByCID(aClass); + if (!e) { + return nullptr; + } + + return e->GetFactory(); +} + +already_AddRefed nsComponentManagerImpl::FindFactory( + const char* aContractID, uint32_t aContractIDLen) { + Maybe entry = + LookupByContractID(nsDependentCString(aContractID, aContractIDLen)); + if (!entry) { + return nullptr; + } + + return entry->GetFactory(); +} + +/** + * GetClassObject() + * + * Given a classID, this finds the singleton ClassObject that implements the + * CID. Returns an interface of type aIID off the singleton classobject. + */ +NS_IMETHODIMP +nsComponentManagerImpl::GetClassObject(const nsCID& aClass, const nsIID& aIID, + void** aResult) { + nsresult rv; + + if (MOZ_LOG_TEST(nsComponentManagerLog, LogLevel::Debug)) { + char buf[NSID_LENGTH]; + aClass.ToProvidedString(buf); + PR_LogPrint("nsComponentManager: GetClassObject(%s)", buf); + } + + MOZ_ASSERT(aResult != nullptr); + + nsCOMPtr factory = FindFactory(aClass); + if (!factory) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + rv = factory->QueryInterface(aIID, aResult); + + MOZ_LOG( + nsComponentManagerLog, LogLevel::Warning, + ("\t\tGetClassObject() %s", NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + + return rv; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetClassObjectByContractID(const char* aContractID, + const nsIID& aIID, + void** aResult) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aContractID)) { + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + + MOZ_LOG(nsComponentManagerLog, LogLevel::Debug, + ("nsComponentManager: GetClassObjectByContractID(%s)", aContractID)); + + nsCOMPtr factory = FindFactory(aContractID, strlen(aContractID)); + if (!factory) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + rv = factory->QueryInterface(aIID, aResult); + + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("\t\tGetClassObjectByContractID() %s", + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + + return rv; +} + +/** + * CreateInstance() + * + * Create an instance of an object that implements an interface and belongs + * to the implementation aClass using the factory. The factory is immediately + * released and not held onto for any longer. + */ +NS_IMETHODIMP +nsComponentManagerImpl::CreateInstance(const nsCID& aClass, const nsIID& aIID, + void** aResult) { + // test this first, since there's no point in creating a component during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Creating new instance on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(aClass).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + *aResult = nullptr; + + Maybe entry = LookupByCID(aClass); + + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + +#ifdef SHOW_CI_ON_EXISTING_SERVICE + if (entry->ServiceInstance()) { + nsAutoCString message; + message = "You are calling CreateInstance \""_ns + AutoIDString(aClass) + + "\" when a service for this CID already exists!"_ns; + NS_ERROR(message.get()); + } +#endif + + nsresult rv; + nsCOMPtr factory = entry->GetFactory(); + if (factory) { + rv = factory->CreateInstance(aIID, aResult); + if (NS_SUCCEEDED(rv) && !*aResult) { + NS_ERROR("Factory did not return an object but returned success!"); + rv = NS_ERROR_SERVICE_NOT_AVAILABLE; + } + } else { + // Translate error values + rv = NS_ERROR_FACTORY_NOT_REGISTERED; + } + + if (MOZ_LOG_TEST(nsComponentManagerLog, LogLevel::Warning)) { + char buf[NSID_LENGTH]; + aClass.ToProvidedString(buf); + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("nsComponentManager: CreateInstance(%s) %s", buf, + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + } + + return rv; +} + +/** + * CreateInstanceByContractID() + * + * A variant of CreateInstance() that creates an instance of the object that + * implements the interface aIID and whose implementation has a contractID + * aContractID. + * + * This is only a convenience routine that turns around can calls the + * CreateInstance() with classid and iid. + */ +NS_IMETHODIMP +nsComponentManagerImpl::CreateInstanceByContractID(const char* aContractID, + const nsIID& aIID, + void** aResult) { + if (NS_WARN_IF(!aContractID)) { + return NS_ERROR_INVALID_ARG; + } + + // test this first, since there's no point in creating a component during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Creating new instance on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", + aContractID, AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + *aResult = nullptr; + + Maybe entry = + LookupByContractID(nsDependentCString(aContractID)); + + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + +#ifdef SHOW_CI_ON_EXISTING_SERVICE + if (entry->ServiceInstance()) { + nsAutoCString message; + message = + "You are calling CreateInstance \""_ns + + nsDependentCString(aContractID) + + nsLiteralCString( + "\" when a service for this CID already exists! " + "Add it to abusedContracts to track down the service consumer."); + NS_ERROR(message.get()); + } +#endif + + nsresult rv; + nsCOMPtr factory = entry->GetFactory(); + if (factory) { + rv = factory->CreateInstance(aIID, aResult); + if (NS_SUCCEEDED(rv) && !*aResult) { + NS_ERROR("Factory did not return an object but returned success!"); + rv = NS_ERROR_SERVICE_NOT_AVAILABLE; + } + } else { + // Translate error values + rv = NS_ERROR_FACTORY_NOT_REGISTERED; + } + + MOZ_LOG(nsComponentManagerLog, LogLevel::Warning, + ("nsComponentManager: CreateInstanceByContractID(%s) %s", aContractID, + NS_SUCCEEDED(rv) ? "succeeded" : "FAILED")); + + return rv; +} + +nsresult nsComponentManagerImpl::FreeServices() { + NS_ASSERTION(gXPCOMShuttingDown, + "Must be shutting down in order to free all services"); + + if (!gXPCOMShuttingDown) { + return NS_ERROR_FAILURE; + } + + for (nsFactoryEntry* entry : mFactories.Values()) { + entry->mFactory = nullptr; + entry->mServiceObject = nullptr; + } + + for (const auto& module : gStaticModules) { + module.SetServiceInstance(nullptr); + } + + return NS_OK; +} + +// This should only ever be called within the monitor! +nsComponentManagerImpl::PendingServiceInfo* +nsComponentManagerImpl::AddPendingService(const nsCID& aServiceCID, + PRThread* aThread) { + PendingServiceInfo* newInfo = mPendingServices.AppendElement(); + if (newInfo) { + newInfo->cid = &aServiceCID; + newInfo->thread = aThread; + } + return newInfo; +} + +// This should only ever be called within the monitor! +void nsComponentManagerImpl::RemovePendingService(MonitorAutoLock& aLock, + const nsCID& aServiceCID) { + uint32_t pendingCount = mPendingServices.Length(); + for (uint32_t index = 0; index < pendingCount; ++index) { + const PendingServiceInfo& info = mPendingServices.ElementAt(index); + if (info.cid->Equals(aServiceCID)) { + mPendingServices.RemoveElementAt(index); + aLock.NotifyAll(); + return; + } + } +} + +// This should only ever be called within the monitor! +PRThread* nsComponentManagerImpl::GetPendingServiceThread( + const nsCID& aServiceCID) const { + uint32_t pendingCount = mPendingServices.Length(); + for (uint32_t index = 0; index < pendingCount; ++index) { + const PendingServiceInfo& info = mPendingServices.ElementAt(index); + if (info.cid->Equals(aServiceCID)) { + return info.thread; + } + } + return nullptr; +} + +nsresult nsComponentManagerImpl::GetServiceLocked(Maybe& aLock, + EntryWrapper& aEntry, + const nsIID& aIID, + void** aResult) { + MOZ_ASSERT(aLock.isSome()); + if (!aLock.isSome()) { + return NS_ERROR_INVALID_ARG; + } + + if (auto* service = aEntry.ServiceInstance()) { + aLock.reset(); + return service->QueryInterface(aIID, aResult); + } + + PRThread* currentPRThread = PR_GetCurrentThread(); + MOZ_ASSERT(currentPRThread, "This should never be null!"); + + PRThread* pendingPRThread; + while ((pendingPRThread = GetPendingServiceThread(aEntry.CID()))) { + if (pendingPRThread == currentPRThread) { + NS_ERROR("Recursive GetService!"); + return NS_ERROR_NOT_AVAILABLE; + } + + aLock->Wait(); + } + + // It's still possible that the other thread failed to create the + // service so we're not guaranteed to have an entry or service yet. + if (auto* service = aEntry.ServiceInstance()) { + aLock.reset(); + return service->QueryInterface(aIID, aResult); + } + + DebugOnly newInfo = + AddPendingService(aEntry.CID(), currentPRThread); + NS_ASSERTION(newInfo, "Failed to add info to the array!"); + + // We need to not be holding the service manager's lock while calling + // CreateInstance, because it invokes user code which could try to re-enter + // the service manager: + + nsCOMPtr service; + auto cleanup = MakeScopeExit([&]() { + // `service` must be released after the lock is released, so if we fail and + // still have a reference, release the lock before releasing it. + if (service) { + MOZ_ASSERT(aLock.isSome()); + aLock.reset(); + service = nullptr; + } + }); + nsresult rv; + mLock.AssertCurrentThreadOwns(); + { + MonitorAutoUnlock unlock(mLock); + AUTO_PROFILER_MARKER_TEXT( + "GetService", OTHER, MarkerStack::Capture(), + nsDependentCString(nsIDToCString(aEntry.CID()).get())); + rv = aEntry.CreateInstance(aIID, getter_AddRefs(service)); + } + if (NS_SUCCEEDED(rv) && !service) { + NS_ERROR("Factory did not return an object but returned success"); + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + +#ifdef DEBUG + pendingPRThread = GetPendingServiceThread(aEntry.CID()); + MOZ_ASSERT(pendingPRThread == currentPRThread, + "Pending service array has been changed!"); +#endif + MOZ_ASSERT(aLock.isSome()); + RemovePendingService(*aLock, aEntry.CID()); + + if (NS_FAILED(rv)) { + return rv; + } + + NS_ASSERTION(!aEntry.ServiceInstance(), + "Created two instances of a service!"); + + aEntry.SetServiceInstance(service.forget()); + + aLock.reset(); + + *aResult = do_AddRef(aEntry.ServiceInstance()).take(); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetService(const nsCID& aClass, const nsIID& aIID, + void** aResult) { + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Getting service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(aClass).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + Maybe lock(std::in_place, mLock); + + Maybe entry = LookupByCID(*lock, aClass); + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + return GetServiceLocked(lock, *entry, aIID, aResult); +} + +nsresult nsComponentManagerImpl::GetService(ModuleID aId, const nsIID& aIID, + void** aResult) { + const auto& entry = gStaticModules[size_t(aId)]; + + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Getting service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(entry.CID()).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + Maybe lock(std::in_place, mLock); + + Maybe wrapper; + if (entry.Overridable()) { + // If we expect this service to be overridden by test code, we need to look + // it up by contract ID every time. + wrapper = LookupByContractID(*lock, entry.ContractID()); + if (!wrapper) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + } else if (!entry.Active()) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } else { + wrapper.emplace(&entry); + } + return GetServiceLocked(lock, *wrapper, aIID, aResult); +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsServiceInstantiated(const nsCID& aClass, + const nsIID& aIID, + bool* aResult) { + // Now we want to get the service if we already got it. If not, we don't want + // to create an instance of it. mmh! + + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Checking for service on shutdown. Denied.\n" + " CID: %s\n IID: %s\n", + AutoIDString(aClass).get(), AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (Maybe entry = LookupByCID(aClass)) { + if (auto* service = entry->ServiceInstance()) { + nsCOMPtr instance; + nsresult rv = service->QueryInterface(aIID, getter_AddRefs(instance)); + *aResult = (instance != nullptr); + return rv; + } + } + + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsServiceInstantiatedByContractID( + const char* aContractID, const nsIID& aIID, bool* aResult) { + // Now we want to get the service if we already got it. If not, we don't want + // to create an instance of it. mmh! + + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Checking for service on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", + aContractID, AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + if (Maybe entry = + LookupByContractID(nsDependentCString(aContractID))) { + if (auto* service = entry->ServiceInstance()) { + nsCOMPtr instance; + nsresult rv = service->QueryInterface(aIID, getter_AddRefs(instance)); + *aResult = (instance != nullptr); + return rv; + } + } + + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetServiceByContractID(const char* aContractID, + const nsIID& aIID, + void** aResult) { + // test this first, since there's no point in returning a service during + // shutdown -- whether it's available or not would depend on the order it + // occurs in the list + if (gXPCOMShuttingDown) { + // When processing shutdown, don't process new GetService() requests +#ifdef SHOW_DENIED_ON_SHUTDOWN + fprintf(stderr, + "Getting service on shutdown. Denied.\n" + " ContractID: %s\n IID: %s\n", + aContractID, AutoIDString(aIID).get()); +#endif /* SHOW_DENIED_ON_SHUTDOWN */ + return NS_ERROR_UNEXPECTED; + } + + AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE("GetServiceByContractID", OTHER, + aContractID); + Maybe lock(std::in_place, mLock); + + Maybe entry = + LookupByContractID(*lock, nsDependentCString(aContractID)); + if (!entry) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + return GetServiceLocked(lock, *entry, aIID, aResult); +} + +NS_IMETHODIMP +nsComponentManagerImpl::RegisterFactory(const nsCID& aClass, const char* aName, + const char* aContractID, + nsIFactory* aFactory) { + if (!aFactory) { + // If a null factory is passed in, this call just wants to reset + // the contract ID to point to an existing CID entry. + if (!aContractID) { + return NS_ERROR_INVALID_ARG; + } + + nsDependentCString contractID(aContractID); + + MonitorAutoLock lock(mLock); + nsFactoryEntry* oldf = mFactories.Get(&aClass); + if (oldf) { + StaticComponents::InvalidateContractID(contractID); + mContractIDs.InsertOrUpdate(contractID, oldf); + return NS_OK; + } + + if (StaticComponents::LookupByCID(aClass)) { + // If this is the CID of a static module, just reset the invalid bit of + // the static entry for this contract ID, and assume it points to the + // correct class. + if (StaticComponents::InvalidateContractID(contractID, false)) { + mContractIDs.Remove(contractID); + return NS_OK; + } + } + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + auto f = MakeUnique(aClass, aFactory); + + MonitorAutoLock lock(mLock); + return mFactories.WithEntryHandle(&f->mCID, [&](auto&& entry) { + if (entry) { + return NS_ERROR_FACTORY_EXISTS; + } + if (StaticComponents::LookupByCID(f->mCID)) { + return NS_ERROR_FACTORY_EXISTS; + } + if (aContractID) { + nsDependentCString contractID(aContractID); + mContractIDs.InsertOrUpdate(contractID, f.get()); + // We allow dynamically-registered contract IDs to override static + // entries, so invalidate any static entry for this contract ID. + StaticComponents::InvalidateContractID(contractID); + } + entry.Insert(f.release()); + + return NS_OK; + }); +} + +NS_IMETHODIMP +nsComponentManagerImpl::UnregisterFactory(const nsCID& aClass, + nsIFactory* aFactory) { + // Don't release the dying factory or service object until releasing + // the component manager monitor. + nsCOMPtr dyingFactory; + nsCOMPtr dyingServiceObject; + + { + MonitorAutoLock lock(mLock); + auto entry = mFactories.Lookup(&aClass); + nsFactoryEntry* f = entry ? entry.Data() : nullptr; + if (!f || f->mFactory != aFactory) { + // Note: We do not support unregistering static factories. + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + entry.Remove(); + + // This might leave a stale contractid -> factory mapping in + // place, so null out the factory entry (see + // nsFactoryEntry::GetFactory) + f->mFactory.swap(dyingFactory); + f->mServiceObject.swap(dyingServiceObject); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::AutoRegister(nsIFile* aLocation) { + XRE_AddManifestLocation(NS_EXTENSION_LOCATION, aLocation); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsCIDRegistered(const nsCID& aClass, bool* aResult) { + *aResult = LookupByCID(aClass).isSome(); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::IsContractIDRegistered(const char* aClass, + bool* aResult) { + if (NS_WARN_IF(!aClass)) { + return NS_ERROR_INVALID_ARG; + } + + Maybe entry = LookupByContractID(nsDependentCString(aClass)); + + *aResult = entry.isSome(); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetContractIDs(nsTArray& aResult) { + aResult = ToTArray>(mContractIDs.Keys()); + + for (const auto& entry : gContractEntries) { + if (!entry.Invalid()) { + aResult.AppendElement(entry.ContractID()); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::ContractIDToCID(const char* aContractID, + nsCID** aResult) { + { + MonitorAutoLock lock(mLock); + Maybe entry = + LookupByContractID(lock, nsDependentCString(aContractID)); + if (entry) { + *aResult = (nsCID*)moz_xmalloc(sizeof(nsCID)); + **aResult = entry->CID(); + return NS_OK; + } + } + *aResult = nullptr; + return NS_ERROR_FACTORY_NOT_REGISTERED; +} + +MOZ_DEFINE_MALLOC_SIZE_OF(ComponentManagerMallocSizeOf) + +NS_IMETHODIMP +nsComponentManagerImpl::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + MOZ_COLLECT_REPORT("explicit/xpcom/component-manager", KIND_HEAP, UNITS_BYTES, + SizeOfIncludingThis(ComponentManagerMallocSizeOf), + "Memory used for the XPCOM component manager."); + + return NS_OK; +} + +size_t nsComponentManagerImpl::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + + n += mFactories.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& data : mFactories.Values()) { + n += data->SizeOfIncludingThis(aMallocSizeOf); + } + + n += mContractIDs.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& key : mContractIDs.Keys()) { + // We don't measure the nsFactoryEntry data because it's owned by + // mFactories (which is measured above). + n += key.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + if (sModuleLocations) { + n += sModuleLocations->ShallowSizeOfIncludingThis(aMallocSizeOf); + } + + n += mPendingServices.ShallowSizeOfExcludingThis(aMallocSizeOf); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mMon + // - sModuleLocations' entries + + return n; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsFactoryEntry +//////////////////////////////////////////////////////////////////////////////// + +nsFactoryEntry::nsFactoryEntry(const nsCID& aCID, nsIFactory* aFactory) + : mCID(aCID), mFactory(aFactory) {} + +already_AddRefed nsFactoryEntry::GetFactory() { + nsComponentManagerImpl::gComponentManager->mLock.AssertNotCurrentThreadOwns(); + + nsCOMPtr factory = mFactory; + return factory.forget(); +} + +nsresult nsFactoryEntry::CreateInstance(const nsIID& aIID, void** aResult) { + nsCOMPtr factory = GetFactory(); + NS_ENSURE_TRUE(factory, NS_ERROR_FAILURE); + return factory->CreateInstance(aIID, aResult); +} + +size_t nsFactoryEntry::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) { + size_t n = aMallocSizeOf(this); + + // Measurement of the following members may be added later if DMD finds it is + // worthwhile: + // - mCID; + // - mFactory; + // - mServiceObject; + + return n; +} + +//////////////////////////////////////////////////////////////////////////////// +// Static Access Functions +//////////////////////////////////////////////////////////////////////////////// + +nsresult NS_GetComponentManager(nsIComponentManager** aResult) { + if (!nsComponentManagerImpl::gComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + return NS_OK; +} + +nsresult NS_GetServiceManager(nsIServiceManager** aResult) { + if (!nsComponentManagerImpl::gComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + return NS_OK; +} + +nsresult NS_GetComponentRegistrar(nsIComponentRegistrar** aResult) { + if (!nsComponentManagerImpl::gComponentManager) { + return NS_ERROR_NOT_INITIALIZED; + } + + NS_ADDREF(*aResult = nsComponentManagerImpl::gComponentManager); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::AddBootstrappedManifestLocation(nsIFile* aLocation) { + NS_ENSURE_ARG_POINTER(aLocation); + + nsString path; + nsresult rv = aLocation->GetPath(path); + if (NS_FAILED(rv)) { + return rv; + } + + if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) { + return XRE_AddJarManifestLocation(NS_BOOTSTRAPPED_LOCATION, aLocation); + } + + nsCOMPtr manifest = CloneAndAppend(aLocation, "chrome.manifest"_ns); + return XRE_AddManifestLocation(NS_BOOTSTRAPPED_LOCATION, manifest); +} + +NS_IMETHODIMP +nsComponentManagerImpl::RemoveBootstrappedManifestLocation(nsIFile* aLocation) { + NS_ENSURE_ARG_POINTER(aLocation); + + nsCOMPtr cr = mozilla::services::GetChromeRegistry(); + if (!cr) { + return NS_ERROR_FAILURE; + } + + nsString path; + nsresult rv = aLocation->GetPath(path); + if (NS_FAILED(rv)) { + return rv; + } + + nsComponentManagerImpl::ComponentLocation elem; + elem.type = NS_BOOTSTRAPPED_LOCATION; + + if (Substring(path, path.Length() - 4).EqualsLiteral(".xpi")) { + elem.location.Init(aLocation, "chrome.manifest"); + } else { + nsCOMPtr lf = CloneAndAppend(aLocation, "chrome.manifest"_ns); + elem.location.Init(lf); + } + + // Remove reference. + nsComponentManagerImpl::sModuleLocations->RemoveElement( + elem, ComponentLocationComparator()); + + rv = cr->CheckForNewChrome(); + return rv; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetComponentJSMs(nsIUTF8StringEnumerator** aJSMs) { + nsCOMPtr result = + StaticComponents::GetComponentJSMs(); + result.forget(aJSMs); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetComponentESModules( + nsIUTF8StringEnumerator** aESModules) { + nsCOMPtr result = + StaticComponents::GetComponentESModules(); + result.forget(aESModules); + return NS_OK; +} + +NS_IMETHODIMP +nsComponentManagerImpl::GetManifestLocations(nsIArray** aLocations) { + NS_ENSURE_ARG_POINTER(aLocations); + *aLocations = nullptr; + + if (!sModuleLocations) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr locations = nsArray::Create(); + nsresult rv; + for (uint32_t i = 0; i < sModuleLocations->Length(); ++i) { + ComponentLocation& l = sModuleLocations->ElementAt(i); + FileLocation loc = l.location; + nsCString uriString; + loc.GetURIString(uriString); + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), uriString); + if (NS_SUCCEEDED(rv)) { + locations->AppendElement(uri); + } + } + + locations.forget(aLocations); + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +XRE_AddManifestLocation(NSLocationType aType, nsIFile* aLocation) { + nsComponentManagerImpl::InitializeModuleLocations(); + nsComponentManagerImpl::ComponentLocation* c = + nsComponentManagerImpl::sModuleLocations->AppendElement(); + c->type = aType; + c->location.Init(aLocation); + + if (nsComponentManagerImpl::gComponentManager && + nsComponentManagerImpl::NORMAL == + nsComponentManagerImpl::gComponentManager->mStatus) { + nsComponentManagerImpl::gComponentManager->RegisterManifest( + aType, c->location, false); + } + + return NS_OK; +} + +EXPORT_XPCOM_API(nsresult) +XRE_AddJarManifestLocation(NSLocationType aType, nsIFile* aLocation) { + nsComponentManagerImpl::InitializeModuleLocations(); + nsComponentManagerImpl::ComponentLocation* c = + nsComponentManagerImpl::sModuleLocations->AppendElement(); + + c->type = aType; + c->location.Init(aLocation, "chrome.manifest"); + + if (nsComponentManagerImpl::gComponentManager && + nsComponentManagerImpl::NORMAL == + nsComponentManagerImpl::gComponentManager->mStatus) { + nsComponentManagerImpl::gComponentManager->RegisterManifest( + aType, c->location, false); + } + + return NS_OK; +} + +// Expose some important global interfaces to rust for the rust xpcom API. These +// methods return a non-owning reference to the component manager, which should +// live for the lifetime of XPCOM. +extern "C" { + +const nsIComponentManager* Gecko_GetComponentManager() { + return nsComponentManagerImpl::gComponentManager; +} + +const nsIServiceManager* Gecko_GetServiceManager() { + return nsComponentManagerImpl::gComponentManager; +} + +const nsIComponentRegistrar* Gecko_GetComponentRegistrar() { + return nsComponentManagerImpl::gComponentManager; +} + +// FFI-compatible version of `GetServiceHelper::operator()`. +nsresult Gecko_GetServiceByModuleID(ModuleID aId, const nsIID* aIID, + void** aResult) { + return nsComponentManagerImpl::gComponentManager->GetService(aId, *aIID, + aResult); +} + +// FFI-compatible version of `CreateInstanceHelper::operator()`. +nsresult Gecko_CreateInstanceByModuleID(ModuleID aId, const nsIID* aIID, + void** aResult) { + const auto& entry = gStaticModules[size_t(aId)]; + if (!entry.Active()) { + return NS_ERROR_FACTORY_NOT_REGISTERED; + } + + nsresult rv = entry.CreateInstance(*aIID, aResult); + return rv; +} +} diff --git a/xpcom/components/nsComponentManager.h b/xpcom/components/nsComponentManager.h new file mode 100644 index 0000000000..5ad555b53b --- /dev/null +++ b/xpcom/components/nsComponentManager.h @@ -0,0 +1,218 @@ +/* -*- 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/. */ + +#ifndef nsComponentManager_h__ +#define nsComponentManager_h__ + +#include "nsXPCOM.h" + +#include "nsIComponentManager.h" +#include "nsIComponentRegistrar.h" +#include "nsIMemoryReporter.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/Atomics.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Module.h" +#include "mozilla/Monitor.h" +#include "mozilla/UniquePtr.h" +#include "nsXULAppAPI.h" +#include "nsIFactory.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "PLDHashTable.h" +#include "prtime.h" +#include "nsCOMPtr.h" +#include "nsWeakReference.h" +#include "nsCOMArray.h" +#include "nsTHashMap.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsTArray.h" + +#include "mozilla/Components.h" +#include "mozilla/Maybe.h" +#include "mozilla/Omnijar.h" +#include "mozilla/Attributes.h" + +struct nsFactoryEntry; +struct PRThread; + +#define NS_COMPONENTMANAGER_CID \ + { /* 91775d60-d5dc-11d2-92fb-00e09805570f */ \ + 0x91775d60, 0xd5dc, 0x11d2, { \ + 0x92, 0xfb, 0x00, 0xe0, 0x98, 0x05, 0x57, 0x0f \ + } \ + } + +//////////////////////////////////////////////////////////////////////////////// + +namespace { +class EntryWrapper; +} // namespace + +namespace mozilla { +namespace xpcom { + +bool ProcessSelectorMatches(Module::ProcessSelector aSelector); +bool FastProcessSelectorMatches(Module::ProcessSelector aSelector); + +} // namespace xpcom +} // namespace mozilla + +class nsComponentManagerImpl final : public nsIComponentManager, + public nsIServiceManager, + public nsSupportsWeakReference, + public nsIComponentRegistrar, + public nsIInterfaceRequestor, + public nsIMemoryReporter { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICOMPONENTMANAGER + NS_DECL_NSICOMPONENTREGISTRAR + NS_DECL_NSIMEMORYREPORTER + + static nsresult Create(REFNSIID aIID, void** aResult); + + nsresult RegistryLocationForFile(nsIFile* aFile, nsCString& aResult); + nsresult FileForRegistryLocation(const nsCString& aLocation, nsIFile** aSpec); + + NS_DECL_NSISERVICEMANAGER + + // nsComponentManagerImpl methods: + nsComponentManagerImpl(); + + static nsComponentManagerImpl* gComponentManager; + nsresult Init(); + + nsresult Shutdown(void); + + nsresult FreeServices(); + + already_AddRefed FindFactory(const nsCID& aClass); + already_AddRefed FindFactory(const char* aContractID, + uint32_t aContractIDLen); + + already_AddRefed LoadFactory(nsFactoryEntry* aEntry); + + nsTHashMap mFactories; + nsTHashMap mContractIDs; + + mozilla::Monitor mLock MOZ_UNANNOTATED; + + mozilla::Maybe LookupByCID(const nsID& aCID); + mozilla::Maybe LookupByCID(const mozilla::MonitorAutoLock&, + const nsID& aCID); + + mozilla::Maybe LookupByContractID( + const nsACString& aContractID); + mozilla::Maybe LookupByContractID( + const mozilla::MonitorAutoLock&, const nsACString& aContractID); + + nsresult GetService(mozilla::xpcom::ModuleID, const nsIID& aIID, + void** aResult); + + static bool JSLoaderReady() { return gComponentManager->mJSLoaderReady; } + + static void InitializeStaticModules(); + static void InitializeModuleLocations(); + + struct ComponentLocation { + NSLocationType type; + mozilla::FileLocation location; + }; + + class ComponentLocationComparator { + public: + bool Equals(const ComponentLocation& aA, + const ComponentLocation& aB) const { + return (aA.type == aB.type && aA.location.Equals(aB.location)); + } + }; + + static nsTArray* sModuleLocations; + + // Mutex not held + void RegisterManifest(NSLocationType aType, mozilla::FileLocation& aFile, + bool aChromeOnly); + + struct ManifestProcessingContext { + ManifestProcessingContext(NSLocationType aType, + mozilla::FileLocation& aFile, bool aChromeOnly) + : mType(aType), mFile(aFile), mChromeOnly(aChromeOnly) {} + + ~ManifestProcessingContext() = default; + + NSLocationType mType; + mozilla::FileLocation mFile; + bool mChromeOnly; + }; + + void ManifestManifest(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + void ManifestCategory(ManifestProcessingContext& aCx, int aLineNo, + char* const* aArgv); + + void RereadChromeManifests(bool aChromeOnly = true); + + // Shutdown + enum { + NOT_INITIALIZED, + NORMAL, + SHUTDOWN_IN_PROGRESS, + SHUTDOWN_COMPLETE + } mStatus; + + struct PendingServiceInfo { + const nsCID* cid; + PRThread* thread; + }; + + inline PendingServiceInfo* AddPendingService(const nsCID& aServiceCID, + PRThread* aThread); + inline void RemovePendingService(mozilla::MonitorAutoLock& aLock, + const nsCID& aServiceCID); + inline PRThread* GetPendingServiceThread(const nsCID& aServiceCID) const; + + nsTArray mPendingServices; + + bool mJSLoaderReady = false; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + private: + ~nsComponentManagerImpl(); + + nsresult GetServiceLocked(mozilla::Maybe& aLock, + EntryWrapper& aEntry, const nsIID& aIID, + void** aResult); +}; + +#define NS_MAX_FILENAME_LEN 1024 + +#define NS_ERROR_IS_DIR NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_XPCOM, 24) + +struct nsFactoryEntry { + // nsIComponentRegistrar.registerFactory support + nsFactoryEntry(const nsCID& aClass, nsIFactory* aFactory); + + ~nsFactoryEntry() = default; + + already_AddRefed GetFactory(); + + nsresult CreateInstance(const nsIID& aIID, void** aResult); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + const nsCID mCID; + + nsCOMPtr mFactory; + nsCOMPtr mServiceObject; +}; + +#endif // nsComponentManager_h__ diff --git a/xpcom/components/nsComponentManagerUtils.cpp b/xpcom/components/nsComponentManagerUtils.cpp new file mode 100644 index 0000000000..1977e4b3dd --- /dev/null +++ b/xpcom/components/nsComponentManagerUtils.cpp @@ -0,0 +1,259 @@ +/* -*- 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/. */ + +#ifndef nsXPCOM_h__ +# include "nsXPCOM.h" +#endif + +#ifndef nsCOMPtr_h__ +# include "nsCOMPtr.h" +#endif + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include "nsIComponentManager.h" + +#ifndef MOZILLA_INTERNAL_API + +nsresult CallGetService(const nsCID& aCID, const nsIID& aIID, void** aResult) { + nsCOMPtr servMgr; + nsresult status = NS_GetServiceManager(getter_AddRefs(servMgr)); + if (servMgr) { + status = servMgr->GetService(aCID, aIID, aResult); + } + return status; +} + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsCOMPtr servMgr; + nsresult status = NS_GetServiceManager(getter_AddRefs(servMgr)); + if (servMgr) { + status = servMgr->GetServiceByContractID(aContractID, aIID, aResult); + } + return status; +} + +#else + +# include "nsComponentManager.h" + +nsresult CallGetService(const nsCID& aCID, const nsIID& aIID, void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (!compMgr) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetService(aCID, aIID, aResult); +} + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (!compMgr) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetServiceByContractID(aContractID, + aIID, aResult); +} + +#endif + +#ifndef MOZILLA_INTERNAL_API + +nsresult CallCreateInstance(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsCOMPtr compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) { + status = compMgr->CreateInstance(aCID, aIID, aResult); + } + return status; +} + +nsresult CallCreateInstance(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsCOMPtr compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) + status = compMgr->CreateInstanceByContractID(aContractID, aIID, aResult); + return status; +} + +nsresult CallGetClassObject(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsCOMPtr compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) { + status = compMgr->GetClassObject(aCID, aIID, aResult); + } + return status; +} + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsCOMPtr compMgr; + nsresult status = NS_GetComponentManager(getter_AddRefs(compMgr)); + if (compMgr) + status = compMgr->GetClassObjectByContractID(aContractID, aIID, aResult); + return status; +} + +#else + +# include "nsComponentManager.h" + +nsresult CallCreateInstance(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::CreateInstance(aCID, aIID, aResult); +} + +nsresult CallCreateInstance(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::CreateInstanceByContractID( + aContractID, aIID, aResult); +} + +nsresult CallGetClassObject(const nsCID& aCID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetClassObject(aCID, aIID, aResult); +} + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult) { + nsComponentManagerImpl* compMgr = nsComponentManagerImpl::gComponentManager; + if (NS_WARN_IF(!compMgr)) { + return NS_ERROR_NOT_INITIALIZED; + } + + return compMgr->nsComponentManagerImpl::GetClassObjectByContractID( + aContractID, aIID, aResult); +} + +#endif + +nsresult nsCreateInstanceByCID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallCreateInstance(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsCreateInstanceByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallCreateInstance(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsCreateInstanceFromFactory::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = mFactory->CreateInstance(aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetClassObjectByCID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetClassObject(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetClassObjectByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetClassObject(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetServiceByCID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetService(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + return status; +} + +nsresult nsGetServiceByCIDWithError::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetService(mCID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} + +nsresult nsGetServiceByContractID::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult status = CallGetService(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + return status; +} + +nsresult nsGetServiceByContractIDWithError::operator()( + const nsIID& aIID, void** aInstancePtr) const { + nsresult status = CallGetService(mContractID, aIID, aInstancePtr); + if (NS_FAILED(status)) { + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = status; + } + return status; +} diff --git a/xpcom/components/nsComponentManagerUtils.h b/xpcom/components/nsComponentManagerUtils.h new file mode 100644 index 0000000000..b6f15aabaa --- /dev/null +++ b/xpcom/components/nsComponentManagerUtils.h @@ -0,0 +1,170 @@ +/* -*- 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/. */ + +#ifndef nsComponentManagerUtils_h__ +#define nsComponentManagerUtils_h__ + +#include "nscore.h" +#include "nsCOMPtr.h" + +#include "nsIFactory.h" + +nsresult CallCreateInstance(const nsCID& aCID, const nsIID& aIID, + void** aResult); + +nsresult CallCreateInstance(const char* aContractID, const nsIID& aIID, + void** aResult); + +nsresult CallGetClassObject(const nsCID& aCID, const nsIID& aIID, + void** aResult); + +nsresult CallGetClassObject(const char* aContractID, const nsIID& aIID, + void** aResult); + +class MOZ_STACK_CLASS nsCreateInstanceByCID final : public nsCOMPtr_helper { + public: + nsCreateInstanceByCID(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsCreateInstanceByContractID final + : public nsCOMPtr_helper { + public: + nsCreateInstanceByContractID(const char* aContractID, nsresult* aErrorPtr) + : mContractID(aContractID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsCreateInstanceFromFactory final + : public nsCOMPtr_helper { + public: + nsCreateInstanceFromFactory(nsIFactory* aFactory, nsresult* aErrorPtr) + : mFactory(aFactory), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + nsIFactory* MOZ_NON_OWNING_REF mFactory; + nsresult* mErrorPtr; +}; + +inline const nsCreateInstanceByCID do_CreateInstance(const nsCID& aCID, + nsresult* aError = 0) { + return nsCreateInstanceByCID(aCID, aError); +} + +inline const nsCreateInstanceByContractID do_CreateInstance( + const char* aContractID, nsresult* aError = 0) { + return nsCreateInstanceByContractID(aContractID, aError); +} + +inline const nsCreateInstanceFromFactory do_CreateInstance( + nsIFactory* aFactory, nsresult* aError = 0) { + return nsCreateInstanceFromFactory(aFactory, aError); +} + +class MOZ_STACK_CLASS nsGetClassObjectByCID final : public nsCOMPtr_helper { + public: + nsGetClassObjectByCID(const nsCID& aCID, nsresult* aErrorPtr) + : mCID(aCID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const nsCID& mCID; + nsresult* mErrorPtr; +}; + +class MOZ_STACK_CLASS nsGetClassObjectByContractID final + : public nsCOMPtr_helper { + public: + nsGetClassObjectByContractID(const char* aContractID, nsresult* aErrorPtr) + : mContractID(aContractID), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + const char* mContractID; + nsresult* mErrorPtr; +}; + +/** + * do_GetClassObject can be used to improve performance of callers + * that call |CreateInstance| many times. They can cache the factory + * and call do_CreateInstance or CallCreateInstance with the cached + * factory rather than having the component manager retrieve it every + * time. + */ +inline const nsGetClassObjectByCID do_GetClassObject(const nsCID& aCID, + nsresult* aError = 0) { + return nsGetClassObjectByCID(aCID, aError); +} + +inline const nsGetClassObjectByContractID do_GetClassObject( + const char* aContractID, nsresult* aError = 0) { + return nsGetClassObjectByContractID(aContractID, aError); +} + +// type-safe shortcuts for calling |CreateInstance| +template +inline nsresult CallCreateInstance(const nsCID& aClass, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallCreateInstance(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult CallCreateInstance(const char* aContractID, + DestinationType** aDestination) { + MOZ_ASSERT(aContractID, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return CallCreateInstance(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult CallCreateInstance(nsIFactory* aFactory, + DestinationType** aDestination) { + MOZ_ASSERT(aFactory, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return aFactory->CreateInstance(nullptr, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult CallGetClassObject(const nsCID& aClass, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetClassObject(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult CallGetClassObject(const char* aContractID, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetClassObject(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +#endif /* nsComponentManagerUtils_h__ */ diff --git a/xpcom/components/nsICategoryManager.idl b/xpcom/components/nsICategoryManager.idl new file mode 100644 index 0000000000..12c1a9e4a9 --- /dev/null +++ b/xpcom/components/nsICategoryManager.idl @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" +#include "nsISupportsPrimitives.idl" + +interface nsISimpleEnumerator; + +%{C++ +#include "nsString.h" +%} + +/* + * nsICategoryManager + */ + +[scriptable, builtinclass, uuid(de021d54-57a3-4025-ae63-4c8eedbe74c0)] +interface nsICategoryEntry : nsISupportsCString +{ + readonly attribute ACString entry; + + readonly attribute ACString value; +}; + +[builtinclass, scriptable, uuid(3275b2cd-af6d-429a-80d7-f0c5120342ac)] +interface nsICategoryManager : nsISupports +{ + /** + * Get the value for the given category's entry. + * @param aCategory The name of the category ("protocol") + * @param aEntry The entry you're looking for ("http") + * @return The value. + */ + ACString getCategoryEntry(in ACString aCategory, in ACString aEntry); + + /** + * Add an entry to a category. + * @param aCategory The name of the category ("protocol") + * @param aEntry The entry to be added ("http") + * @param aValue The value for the entry ("moz.httprulez.1") + * @param aPersist Should this data persist between invocations? + * @param aReplace Should we replace an existing entry? + * @return Previous entry, if any + */ + ACString addCategoryEntry(in ACString aCategory, in ACString aEntry, + in ACString aValue, in boolean aPersist, + in boolean aReplace); + + /** + * Delete an entry from the category. + * @param aCategory The name of the category ("protocol") + * @param aEntry The entry to be added ("http") + * @param aPersist Delete persistent data from registry, if present? + */ + void deleteCategoryEntry(in ACString aCategory, in ACString aEntry, + in boolean aPersist); + + /** + * Delete a category and all entries. + * @param aCategory The category to be deleted. + */ + void deleteCategory(in ACString aCategory); + + /** + * Enumerate the entries in a category. + * @param aCategory The category to be enumerated. + * @return a simple enumerator, each result QIs to + * nsICategoryEntry. + */ + nsISimpleEnumerator enumerateCategory(in ACString aCategory); + + + /** + * Enumerate all existing categories + * @param aCategory The category to be enumerated. + * @return a simple enumerator, each result QIs to + * nsISupportsCString. + */ + nsISimpleEnumerator enumerateCategories(); + + %{C++ + template + nsresult + GetCategoryEntry(const char (&aCategory)[N], const nsACString& aEntry, + nsACString& aResult) + { + return GetCategoryEntry(nsLiteralCString(aCategory), + aEntry, aResult); + } + + template + nsresult + GetCategoryEntry(const char (&aCategory)[N], const char (&aEntry)[M], + nsACString& aResult) + { + return GetCategoryEntry(nsLiteralCString(aCategory), + nsLiteralCString(aEntry), + aResult); + } + + nsresult + AddCategoryEntry(const nsACString& aCategory, const nsACString& aEntry, + const nsACString& aValue, bool aPersist, bool aReplace) + { + nsCString oldValue; + return AddCategoryEntry(aCategory, aEntry, aValue, aPersist, aReplace, + oldValue); + } + + template + nsresult + AddCategoryEntry(const char (&aCategory)[N], const nsACString& aEntry, + const nsACString& aValue, bool aPersist, bool aReplace) + { + nsCString oldValue; + return AddCategoryEntry(nsLiteralCString(aCategory), aEntry, aValue, + aPersist, aReplace, oldValue); + } + + template + nsresult + DeleteCategoryEntry(const char (&aCategory)[N], const nsACString& aEntry, bool aPersist) + { + return DeleteCategoryEntry(nsLiteralCString(aCategory), aEntry, aPersist); + } + + + template + nsresult + EnumerateCategory(const char (&aCategory)[N], nsISimpleEnumerator** aResult) + { + return EnumerateCategory(nsLiteralCString(aCategory), aResult); + } + %} +}; diff --git a/xpcom/components/nsIClassInfo.idl b/xpcom/components/nsIClassInfo.idl new file mode 100644 index 0000000000..1ebef34ecf --- /dev/null +++ b/xpcom/components/nsIClassInfo.idl @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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 "nsISupports.idl" + +interface nsIXPCScriptable; + +/** + * Provides information about a specific implementation class. If you want + * your class to implement nsIClassInfo, see nsIClassInfoImpl.h for + * instructions--you most likely do not want to inherit from nsIClassInfo. + */ + +[scriptable, uuid(a60569d7-d401-4677-ba63-2aa5971af25d)] +interface nsIClassInfo : nsISupports +{ + /** + * Returns a list of the interfaces which instances of this class promise + * to implement. Note that nsISupports is an implicit member of any such + * list, and need not be included. + */ + readonly attribute Array interfaces; + + /** + * Return an object to assist XPConnect in supplying JavaScript-specific + * behavior to callers of the instance object, or null if not needed. + */ + nsIXPCScriptable getScriptableHelper(); + + /** + * A contract ID through which an instance of this class can be created + * (or accessed as a service, if |flags & SINGLETON|), or null/void. + */ + readonly attribute AUTF8String contractID; + + /** + * A human readable string naming the class, or null/void. + */ + readonly attribute AUTF8String classDescription; + + /** + * A class ID through which an instance of this class can be created + * (or accessed as a service, if |flags & SINGLETON|), or null. + */ + readonly attribute nsCIDPtr classID; + + /** + * Bitflags for 'flags' attribute. + */ + const uint32_t SINGLETON = 1 << 0; + const uint32_t THREADSAFE = 1 << 1; + const uint32_t SINGLETON_CLASSINFO = 1 << 5; + + // The high order bit is RESERVED for consumers of these flags. + // No implementor of this interface should ever return flags + // with this bit set. + const uint32_t RESERVED = 1 << 31; + + + readonly attribute uint32_t flags; + + /** + * Also a class ID through which an instance of this class can be created + * (or accessed as a service, if |flags & SINGLETON|). If the class does + * not have a CID, it should return NS_ERROR_NOT_AVAILABLE. This attribute + * exists so C++ callers can avoid allocating and freeing a CID, as would + * happen if they used classID. + */ + [noscript] readonly attribute nsCID classIDNoAlloc; + +}; diff --git a/xpcom/components/nsIComponentManager.idl b/xpcom/components/nsIComponentManager.idl new file mode 100644 index 0000000000..9abde9960c --- /dev/null +++ b/xpcom/components/nsIComponentManager.idl @@ -0,0 +1,117 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/** + * The nsIComponentManager interface. + */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIFactory; +interface nsIArray; +interface nsIUTF8StringEnumerator; + +[scriptable, builtinclass, uuid(d604ffc3-1ba3-4f6c-b65f-1ed4199364c3)] +interface nsIComponentManager : nsISupports +{ + /** + * getClassObject + * + * Returns the factory object that can be used to create instances of + * CID aClass + * + * @param aClass The classid of the factory that is being requested + */ + void getClassObject(in nsCIDRef aClass, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * getClassObjectByContractID + * + * Returns the factory object that can be used to create instances of + * CID aClass + * + * @param aClass The classid of the factory that is being requested + */ + void getClassObjectByContractID(in string aContractID, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + + /** + * createInstance + * + * Create an instance of the CID aClass and return the interface aIID. + * + * @param aClass : ClassID of object instance requested + * @param aIID : IID of interface requested + */ + [noscript] + void createInstance(in nsCIDRef aClass, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * createInstanceByContractID + * + * Create an instance of the CID that implements aContractID and return the + * interface aIID. + * + * @param aContractID : aContractID of object instance requested + * @param aIID : IID of interface requested + */ + [noscript] + void createInstanceByContractID(in string aContractID, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * addBootstrappedManifestLocation + * + * Adds a bootstrapped manifest location on runtime. + * + * @param aLocation : A directory where chrome.manifest resides, + * or an XPI with it on the root. + */ + void addBootstrappedManifestLocation(in nsIFile aLocation); + + /** + * removeBootstrappedManifestLocation + * + * Removes a bootstrapped manifest location on runtime. + * + * @param aLocation : A directory where chrome.manifest resides, + * or an XPI with it on the root. + */ + void removeBootstrappedManifestLocation(in nsIFile aLocation); + + /** + * getManifestLocations + * + * Get an array of nsIURIs of all registered and builtin manifest locations. + */ + nsIArray getManifestLocations(); + + /** + * Returns a list of JSM URLs which are used to create components. This + * should only be used in automation. + */ + nsIUTF8StringEnumerator getComponentJSMs(); + + /** + * Returns a list of ESM URLs which are used to create components. This + * should only be used in automation. + */ + nsIUTF8StringEnumerator getComponentESModules(); +}; + + +%{ C++ +#ifdef MOZILLA_INTERNAL_API +#include "nsComponentManagerUtils.h" +#endif +%} C++ diff --git a/xpcom/components/nsIComponentRegistrar.idl b/xpcom/components/nsIComponentRegistrar.idl new file mode 100644 index 0000000000..6b85caffab --- /dev/null +++ b/xpcom/components/nsIComponentRegistrar.idl @@ -0,0 +1,100 @@ +/* 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/. */ + +/** + * The nsIComponentRegistrar interface. + */ + +#include "nsISupports.idl" + +interface nsIFile; +interface nsIFactory; + +[builtinclass, scriptable, uuid(2417cbfe-65ad-48a6-b4b6-eb84db174392)] +interface nsIComponentRegistrar : nsISupports +{ + /** + * autoRegister + * + * Register a .manifest file, or an entire directory containing + * these files. Registration lasts for this run only, and is not cached. + * + * @note Formerly this method would register component files directly. This + * is no longer supported. + */ + void autoRegister(in nsIFile aSpec); + + /** + * registerFactory + * + * Register a factory with a given ContractID, CID and Class Name. + * + * @param aClass : CID of object + * @param aClassName : Class Name of CID (unused) + * @param aContractID : ContractID associated with CID aClass. May be null + * if no contract ID is needed. + * @param aFactory : Factory that will be registered for CID aClass. + * If aFactory is null, the contract will be associated + * with a previously registered CID. + */ + void registerFactory(in nsCIDRef aClass, + in string aClassName, + in string aContractID, + in nsIFactory aFactory); + + /** + * unregisterFactory + * + * Unregister a factory associated with CID aClass. + * + * @param aClass : CID being unregistered + * @param aFactory : Factory previously registered to create instances of + * CID aClass. + * + * @throws NS_ERROR* Method failure. + */ + void unregisterFactory(in nsCIDRef aClass, + in nsIFactory aFactory); + + /** + * isCIDRegistered + * + * Returns true if a factory is registered for the CID. + * + * @param aClass : CID queried for registeration + * @return : true if a factory is registered for CID + * false otherwise. + */ + boolean isCIDRegistered(in nsCIDRef aClass); + + /** + * isContractIDRegistered + * + * Returns true if a factory is registered for the contract id. + * + * @param aClass : contract id queried for registeration + * @return : true if a factory is registered for contract id + * false otherwise. + */ + boolean isContractIDRegistered(in string aContractID); + + /** + * getContractIDs + * + * Return the list of all registered ContractIDs. + * + * @return : Array of ContractIDs. Elements of the array are the string + * encoding of Contract IDs. + */ + Array getContractIDs(); + + /** + * contractIDToCID + * + * Returns the CID for a given Contract ID, if one exists and is registered. + * + * @return : Contract ID. + */ + nsCIDPtr contractIDToCID(in string aContractID); +}; diff --git a/xpcom/components/nsIFactory.idl b/xpcom/components/nsIFactory.idl new file mode 100644 index 0000000000..e0b3e6e756 --- /dev/null +++ b/xpcom/components/nsIFactory.idl @@ -0,0 +1,27 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * A class factory allows the creation of nsISupports derived + * components without specifying a concrete base class. + */ + +[scriptable, object, uuid(1bb40a56-9223-41e6-97d4-da97bdeb6a4d)] +interface nsIFactory : nsISupports { + /** + * Creates an instance of a component. + * + * @param iid The IID of the interface being requested in + * the component which is being currently created. + * @param result [out] Pointer to the newly created instance, if successful. + * @throws NS_NOINTERFACE - Interface not accessible. + * NS_ERROR* - Method failure. + */ + void createInstance(in nsIIDRef iid, + [retval, iid_is(iid)] out nsQIResult result); + +}; diff --git a/xpcom/components/nsIServiceManager.idl b/xpcom/components/nsIServiceManager.idl new file mode 100644 index 0000000000..dd613070e9 --- /dev/null +++ b/xpcom/components/nsIServiceManager.idl @@ -0,0 +1,70 @@ +/* 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 "nsISupports.idl" + +/** + * The nsIServiceManager manager interface provides a means to obtain + * global services in an application. The service manager depends on the + * repository to find and instantiate factories to obtain services. + * + * Users of the service manager must first obtain a pointer to the global + * service manager by calling NS_GetServiceManager. After that, + * they can request specific services by calling GetService. When they are + * finished they can NS_RELEASE() the service as usual. + * + * A user of a service may keep references to particular services indefinitely + * and only must call Release when it shuts down. + */ + +[builtinclass, scriptable, uuid(8bb35ed9-e332-462d-9155-4a002ab5c958)] +interface nsIServiceManager : nsISupports +{ + /** + * getServiceByContractID + * + * Returns the instance that implements aClass or aContractID and the + * interface aIID. This may result in the instance being created. + * + * @param aClass or aContractID : aClass or aContractID of object + * instance requested + * @param aIID : IID of interface requested + * @param result : resulting service + */ + void getService(in nsCIDRef aClass, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + void getServiceByContractID(in string aContractID, + in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult result); + + /** + * isServiceInstantiated + * + * isServiceInstantiated will return a true if the service has already + * been created, or false otherwise. Throws if the service does not + * implement the given IID. + * + * @param aClass or aContractID : aClass or aContractID of object + * instance requested + * @param aIID : IID of interface requested + * @throws NS_NOINTERFACE if the IID given isn't supported by the object + */ + boolean isServiceInstantiated(in nsCIDRef aClass, in nsIIDRef aIID); + boolean isServiceInstantiatedByContractID(in string aContractID, in nsIIDRef aIID); +}; + + +%{C++ +// Observing xpcom autoregistration. Topics will be 'start' and 'stop'. +#define NS_XPCOM_AUTOREGISTRATION_OBSERVER_ID "xpcom-autoregistration" + +#ifdef MOZILLA_INTERNAL_API +#include "nsXPCOM.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#endif +%} diff --git a/xpcom/components/nsServiceManagerUtils.h b/xpcom/components/nsServiceManagerUtils.h new file mode 100644 index 0000000000..4bfbc804cc --- /dev/null +++ b/xpcom/components/nsServiceManagerUtils.h @@ -0,0 +1,56 @@ +/* -*- 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/. */ + +#ifndef nsServiceManagerUtils_h__ +#define nsServiceManagerUtils_h__ + +#include "nsCOMPtr.h" +#include "nsString.h" + +inline nsGetServiceByCID do_GetService(const nsCID& aCID) { + return nsGetServiceByCID(aCID); +} + +inline nsGetServiceByCIDWithError do_GetService(const nsCID& aCID, + nsresult* aError) { + return nsGetServiceByCIDWithError(aCID, aError); +} + +inline nsGetServiceByContractID do_GetService(const char* aContractID) { + return nsGetServiceByContractID(aContractID); +} + +inline nsGetServiceByContractIDWithError do_GetService(const char* aContractID, + nsresult* aError) { + return nsGetServiceByContractIDWithError(aContractID, aError); +} + +nsresult CallGetService(const nsCID& aClass, const nsIID& aIID, void** aResult); + +nsresult CallGetService(const char* aContractID, const nsIID& aIID, + void** aResult); + +// type-safe shortcuts for calling |GetService| +template +inline nsresult CallGetService(const nsCID& aClass, + DestinationType** aDestination) { + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetService(aClass, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +template +inline nsresult CallGetService(const char* aContractID, + DestinationType** aDestination) { + MOZ_ASSERT(aContractID, "null parameter"); + MOZ_ASSERT(aDestination, "null parameter"); + + return CallGetService(aContractID, NS_GET_TEMPLATE_IID(DestinationType), + reinterpret_cast(aDestination)); +} + +#endif diff --git a/xpcom/components/test/python.ini b/xpcom/components/test/python.ini new file mode 100644 index 0000000000..9792769c24 --- /dev/null +++ b/xpcom/components/test/python.ini @@ -0,0 +1,4 @@ +[DEFAULT] +subsuite = xpcom + +[test_gen_static_components.py] diff --git a/xpcom/components/test/test_gen_static_components.py b/xpcom/components/test/test_gen_static_components.py new file mode 100644 index 0000000000..85f731c082 --- /dev/null +++ b/xpcom/components/test/test_gen_static_components.py @@ -0,0 +1,150 @@ +# 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/. + +import os +import sys +import unittest + +import mozunit + +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +import gen_static_components +from gen_static_components import BackgroundTasksSelector + + +class TestGenStaticComponents(unittest.TestCase): + def test_string(self): + # A string: we default to NO_TASKS. + clas = { + "cid": "{a8566880-0bc7-4822-adb9-748c9af5cce7}", + "contract_ids": ["@mozilla.org/dummy-class;1"], + "jsm": "resource:///modules/DummyClass.jsm", + "js_name": "dummyClass", + "constructor": "DummyClassImpl", + "categories": { + "dummy1": ["m-dummy1", "m-dummy2"], + }, + "protocol_config": { + "scheme": "dummy", + "flags": [], + }, + } + + substs = gen_static_components.gen_substs([{"Classes": [clas]}]) + + self.assertEqual(substs["category_count"], 1) + self.assertEqual( + [s.strip() for s in substs["categories"].splitlines()], + [ + '{ { 0x0 } /* "dummy1" */,', + "0, 2 },", + ], + ) + self.assertEqual( + [s.strip() for s in substs["category_entries"].splitlines()], + [ + '/* "dummy1" */', + '{ { 0x7 } /* "m-dummy1" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + '{ { 0x2b } /* "m-dummy2" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + ], + ) + + def test_dict(self): + # A dict, but no backgroundtasks selector: we default to NO_TASKS. + clas = { + "cid": "{a8566880-0bc7-4822-adb9-748c9af5cce7}", + "contract_ids": ["@mozilla.org/dummy-class;1"], + "jsm": "resource:///modules/DummyClass.jsm", + "js_name": "dummyClass", + "constructor": "DummyClassImpl", + "categories": { + "dummy1": { + "name": ["m-dummy1", "m-dummy2"], + }, + }, + "protocol_config": { + "scheme": "dummy", + "flags": [], + }, + } + + substs = gen_static_components.gen_substs([{"Classes": [clas]}]) + + self.assertEqual(substs["category_count"], 1) + self.assertEqual( + [s.strip() for s in substs["categories"].splitlines()], + [ + '{ { 0x0 } /* "dummy1" */,', + "0, 2 },", + ], + ) + self.assertEqual( + [s.strip() for s in substs["category_entries"].splitlines()], + [ + '/* "dummy1" */', + '{ { 0x7 } /* "m-dummy1" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + '{ { 0x2b } /* "m-dummy2" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::NO_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + ], + ) + + def test_dict_with_selector(self): + # A dict with a selector. + clas = { + "cid": "{a8566880-0bc7-4822-adb9-748c9af5cce7}", + "contract_ids": ["@mozilla.org/dummy-class;1"], + "jsm": "resource:///modules/DummyClass.jsm", + "js_name": "dummyClass", + "constructor": "DummyClassImpl", + "categories": { + "dummy1": { + "name": ["m-dummy1", "m-dummy2"], + "backgroundtasks": BackgroundTasksSelector.ALL_TASKS, + }, + }, + "protocol_config": { + "scheme": "dummy", + "flags": [], + }, + } + + substs = gen_static_components.gen_substs([{"Classes": [clas]}]) + + self.assertEqual(substs["category_count"], 1) + self.assertEqual( + [s.strip() for s in substs["categories"].splitlines()], + [ + '{ { 0x0 } /* "dummy1" */,', + "0, 2 },", + ], + ) + self.assertEqual( + [s.strip() for s in substs["category_entries"].splitlines()], + [ + '/* "dummy1" */', + '{ { 0x7 } /* "m-dummy1" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::ALL_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + '{ { 0x2b } /* "m-dummy2" */,', + '{ 0x10 } /* "@mozilla.org/dummy-class;1" */,', + "Module::BackgroundTasksSelector::ALL_TASKS,", + "Module::ProcessSelector::ANY_PROCESS },", + ], + ) + + +if __name__ == "__main__": + mozunit.main() diff --git a/xpcom/docs/cc-macros.rst b/xpcom/docs/cc-macros.rst new file mode 100644 index 0000000000..7877b3aabe --- /dev/null +++ b/xpcom/docs/cc-macros.rst @@ -0,0 +1,190 @@ +======================================= +How to make a C++ class cycle collected +======================================= + +Should my class be cycle collected? +=================================== + +First, you need to decide if your class should be cycle +collected. There are three main criteria: + +* It can be part of a cycle of strong references, including + refcounted objects and JS. Usually, this happens when it can hold + alive and be held alive by cycle collected objects or JS. + +* It must be refcounted. + +* It must be single threaded. The cycle collector can only work with + objects that are used on a single thread. The main thread and DOM + worker and worklet threads each have their own cycle collectors. + +If your class meets the first criteria but not the second, then +whatever class uniquely owns it should be cycle collected, assuming +that is refcounted, and this class should be traversed and unlinked as +part of that. + +The cycle collector supports both nsISupports and non-nsISupports +(known as "native" in CC nomenclature) refcounting. However, we do not +support native cycle collection in the presence of inheritance, if two +classes related by inheritance need different CC +implementations. (This is because we use QueryInterface to find the +right CC implementation for an object.) + +Once you've decided to make a class cycle collected, there are a few +things you need to add to your implementation: + +* Cycle collected refcounting. Special refcounting is needed so that + the CC can tell when an object is created, used, or destroyed, so + that it can determine if an object is potentially part of a garbage + cycle. + +* Traversal. Once the CC has decided an object **might** be garbage, + it needs to know what other cycle collected objects it holds strong + references to. This is done with a "traverse" method. + +* Unlinking. Once the CC has decided that an object **is** garbage, it + needs to break the cycles by clearing out all strong references to + other cycle collected objects. This is done with an "unlink" + method. This usually looks very similar to the traverse method. + +The traverse and unlink methods, along with other methods needed for +cycle collection, are defined on a special inner class object, called a +"participant", for performance reasons. The existence of the +participant is mostly hidden behind macros so you shouldn't need +to worry about it. + +Next, we'll go over what the declaration and definition of these parts +look like. (Spoiler: there are lots of `ALL_CAPS` macros.) This will +mostly cover the most common variants. If you need something slightly +different, you should look at the location of the declaration of the +macros we mention here and see if the variants already exist. + + +Reference counting +================== + +nsISupports +----------- + +If your class inherits from nsISupports, you'll need to add +`NS_DECL_CYCLE_COLLECTING_ISUPPORTS` to the class declaration. This +will declare the QueryInterface (QI), AddRef and Release methods you +need to implement nsISupports, as well as the actual refcount field. + +In the `.cpp` file for your class you'll have to define the +QueryInterface, AddRef and Release methods. The ins and outs of +defining the QI method is out-of-scope for this document, but you'll +need to use the special cycle collection variants of the macros, like +`NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION`. (This is because we use +the nsISupports system to define a special interface used to +dynamically find the correct CC participant for the object.) + +Finally, you'll have to actually define the AddRef and Release methods +for your class. If your class is called `MyClass`, then you'd do this +with the declarations `NS_IMPL_CYCLE_COLLECTING_ADDREF(MyClass)` and +`NS_IMPL_CYCLE_COLLECTING_RELEASE(MyClass)`. + +non-nsISupports +--------------- + +If your class does **not** inherit from nsISupports, you'll need to +add `NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING` to the class +declaration. This will give inline definitions for the AddRef and +Release methods, as well as the actual refcount field. + +Cycle collector participant +=========================== + +Next we need to declare and define the cycle collector +participant. This is mostly boilerplate hidden behind macros, but you +will need to specify which fields are to be traversed and unlinked +because they are strong references to cycle collected objects. + +Declaration +----------- + +First, we need to add a declaration for the participant. As before, +let's say your class is `MyClass`. + +The basic way to declare this for an nsISupports class is +`NS_DECL_CYCLE_COLLECTION_CLASS(MyClass)`. + +If your class inherits from multiple classes that inherit from +nsISupports classes, say `Parent1` and `Parent2`, then you'll need to +use `NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(MyClass, Parent1)` to +tell the CC to cast to nsISupports via `Parent1`. You probably want to +pick the first class it inherits from. (The cycle collector needs to be +able to cast `MyClass*` to `nsISupports*`.) + +Another situation you might encounter is that your nsISupports class +inherits from another cycle collected class `CycleCollectedParent`. In +that case, your participant declaration will look like +`NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MyClass, +CycleCollectedParent)`. (This is needed so that the CC can cast from +nsISupports down to `MyClass`.) Note that we do not support inheritance +for non-nsISupports classes. + +If your class is non-nsISupports, then you'll need to use the `NATIVE` +family of macros, like +`NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(MyClass)`. + +In addition to these modifiers, these different variations have +further `SCRIPT_HOLDER` variations which are needed if your class +holds alive JavaScript objects. This is because the tracing of JS +objects held alive by this class must be declared separately from the +tracing of C++ objects held alive by this class so that the garbage +collector can also use the tracing. For example, +`NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(MyClass, +Parent1)`. + +There are also `WRAPPERCACHE` variants of the macros which you need to +use if your class is wrapper cached. These are effectively a +specialized form of `SCRIPT_HOLDER`, as a cached wrapper is a +JS object held alive by the C++ object. For example, +`NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS_AMBIGUOUS(MyClass, +Parent1)`. + +There is yet another variant of these macros, `SKIPPABLE`. This +document won't go into detail here about how this works, but the basic +idea is that a class can tell the CC when it is definitely alive, +which lets the CC skip it. This is a very important optimization for +things like DOM elements in active documents, but a new class you are +making cycle collected is likely not common enough to worry about. + +Implementation +-------------- + +Finally, you must write the actual implementation of the CC +participant, in the .cpp file for your class. This will define the +traverse and unlink methods, and some other random helper +functions. In the simplest case, this can be done with a single macro +like this: `NS_IMPL_CYCLE_COLLECTION(MyClass, mField1, mField2, +mField3)`, where `mField1` and the rest are the names of the fields of +your class that are strong references to cycle collected +objects. There is some template magic that says how many common types +like RefPtr, nsCOMPtr, and even some arrays, should be traversed and +unlinked. There’s also a variant `NS_IMPL_CYCLE_COLLECTION_INHERITED`, +which you should use when there’s a parent class that is also cycle +collected, to ensure that fields of the parent class are traversed and +unlinked. The name of that parent class is passed in as the second +argument. If either of these work, then you are done. Your class is +now cycle collected. Note that this does not work for fields that are +JS objects. + +However, if that doesn’t work, you’ll have to get into the details a +bit more. A good place to start is by copying the definition of +`NS_IMPL_CYCLE_COLLECTION`. + +For a script holder method, you also need to define a trace method in +addition to the traverse and unlink, using +`NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN` and other similar +macros. You'll need to include all of the JS fields that your class +holds alive. The trace method will be used by the GC as well as the +CC, so if you miss something you can end up with use-after-free +crashes. You'll also need to call `mozilla::HoldJSObjects(this);` in +the ctor for your class, and `mozilla::DropJSObjects(this);` in the +dtor. This will register (and unregister) each instance of your object +with the JS runtime, to ensure that it gets traced properly. This +does not apply if you have a wrapper cached class that does not have +any additional JS fields, as nsWrapperCache deals with all of that +for you. diff --git a/xpcom/docs/collections.rst b/xpcom/docs/collections.rst new file mode 100644 index 0000000000..b4075fda9b --- /dev/null +++ b/xpcom/docs/collections.rst @@ -0,0 +1,95 @@ +XPCOM Collections +================= + +``nsTArray`` and ``AutoTArray`` +------------------------------- + +``nsTArray`` is a typesafe array for holding various objects. + +Rust Bindings +~~~~~~~~~~~~~ + +When the ``thin_vec`` crate is built in Gecko, ``thin_vec::ThinVec`` is +guaranteed to have the same memory layout and allocation strategy as +``nsTArray``, meaning that the two types may be used interchangeably across +FFI boundaries. The type is **not** safe to pass by-value over FFI +boundaries, due to Rust and C++ differing in when they run destructors. + +The element type ``T`` must be memory-compatible with both Rust and C++ code +to use over FFI. + +``nsTHashMap`` and ``nsTHashSet`` +--------------------------------- + +These types are the recommended interface for writing new XPCOM hashmaps and +hashsets in XPCOM code. + +Supported Hash Keys +~~~~~~~~~~~~~~~~~~~ + +The following types are supported as the key parameter to ``nsTHashMap`` and +``nsTHashSet``. + +========================== ====================== +Type Hash Key +========================== ====================== +``T*`` ``nsPtrHashKey`` +``T*`` ``nsPtrHashKey`` +``nsCString`` ``nsCStringHashKey`` +``nsString`` ``nsStringHashKey`` +``uint32_t`` ``nsUint32HashKey`` +``uint64_t`` ``nsUint64HashKey`` +``intptr_t`` ``IntPtrHashKey`` +``nsCOMPtr`` ``nsISupportsHashKey`` +``RefPtr`` ``nsRefPtrHashKey`` +``nsID`` ``nsIDHashKey`` +========================== ====================== + +Any key not in this list must inherit from the ``PLDHashEntryHdr`` class to +implement manual hashing behaviour. + +Class Reference +~~~~~~~~~~~~~~~ + +.. note:: + + The ``nsTHashMap`` and ``nsTHashSet`` types are not declared exactly like + this in code. This is intended largely as a practical reference. + +.. cpp:class:: template nsTHashMap + + The ``nsTHashMap`` class is currently defined as a thin type alias + around ``nsBaseHashtable``. See the methods defined on that class for + more detailed documentation. + + https://searchfox.org/mozilla-central/source/xpcom/ds/nsBaseHashtable.h + + .. cpp:function:: uint32_t Count() const + + .. cpp:function:: bool IsEmpty() const + + .. cpp:function:: bool Get(KeyType aKey, V* aData) const + + Get the value, returning a flag indicating the presence of the entry + in the table. + + .. cpp:function:: V Get(KeyType aKey) const + + Get the value, returning a default-initialized object if the entry is + not present in the table. + + .. cpp:function:: Maybe MaybeGet(KeyType aKey) const + + Get the value, returning Nothing if the entry is not present in the table. + + .. cpp:function:: V& LookupOrInsert(KeyType aKey, Args&&... aArgs) const + + .. cpp:function:: V& LookupOrInsertWith(KeyType aKey, F&& aFunc) const + +.. cpp:class:: template nsTHashSet + + The ``nsTHashSet`` class is currently defined as a thin type alias + around ``nsTBaseHashSet``. See the methods defined on that class for + more detailed documentation. + + https://searchfox.org/mozilla-central/source/xpcom/ds/nsTHashSet.h diff --git a/xpcom/docs/hashtables.rst b/xpcom/docs/hashtables.rst new file mode 100644 index 0000000000..8debd11eb7 --- /dev/null +++ b/xpcom/docs/hashtables.rst @@ -0,0 +1,141 @@ +XPCOM Hashtable Guide +===================== + +.. note:: + + For a deep-dive into the underlying mechanisms that power our hashtables, + check out the :ref:`XPCOM Hashtable Technical Details` + document. + +What Is a Hashtable? +-------------------- + +A hashtable is a data construct that stores a set of **items**. Each +item has a **key** that identifies the item. Items are found, added, and +removed from the hashtable by using the key. Hashtables may seem like +arrays, but there are important differences: + ++-------------------------+----------------------+----------------------+ +| | Array | Hashtable | ++=========================+======================+======================+ +| **Keys** | *integer:* arrays | *any type:* almost | +| | are always keyed on | any datatype can be | +| | integers and must | used as key, | +| | be contiguous. | including strings, | +| | | integers, XPCOM | +| | | interface pointers, | +| | | IIDs, and almost | +| | | anything else. Keys | +| | | can be disjunct | +| | | (i.e. you can store | +| | | entries with keys 1, | +| | | 5, and 3000). | ++-------------------------+----------------------+----------------------+ +| **Lookup Time** | *O(1):* lookup time | *O(1):* lookup time | +| | is a simple constant | is mostly-constant, | +| | | but the constant | +| | | time can be larger | +| | | than an array lookup | ++-------------------------+----------------------+----------------------+ +| **Sorting** | *sorted:* stored | *unsorted:* stored | +| | sorted; iterated | unsorted; cannot be | +| | over in a sorted | iterated over in a | +| | fashion. | sorted manner. | ++-------------------------+----------------------+----------------------+ +| **Inserting/Removing** | *O(n):* adding and | *O(1):* adding and | +| | removing items from | removing items from | +| | a large array can be | hashtables is a | +| | time-consuming | quick operation | ++-------------------------+----------------------+----------------------+ +| **Wasted space** | *none:* Arrays are | *some:* hashtables | +| | packed structures, | are not packed | +| | so there is no | structures; | +| | wasted space. | depending on the | +| | | implementation, | +| | | there may be | +| | | significant wasted | +| | | memory. | ++-------------------------+----------------------+----------------------+ + +In their implementation, hashtables take the key and apply a +mathematical **hash function** to **randomize** the key and then use the +hash to find the location in the hashtable. Good hashtable +implementations will automatically resize the hashtable in memory if +extra space is needed, or if too much space has been allocated. + +.. _When_Should_I_Use_a_Hashtable.3F: + +When Should I Use a Hashtable? +------------------------------ + +Hashtables are useful for + +- sets of data that need swift **random access** +- with **non-integral keys** or **non-contiguous integral keys** +- or where **items will be frequently added or removed** + +Hashtables should *not* be used for + +- Sets that need to be **sorted** +- Very small datasets (less than 12-16 items) +- Data that does not need random access + +In these situations, an array, a linked-list, or various tree data +structures are more efficient. + +.. _Which_Hashtable_Should_I_Use.3F: + +Which Hashtable Should I Use? +----------------------------- + +If there is **no** key type, you should use an ``nsTHashSet``. + +If there is a key type, you should use an ``nsTHashMap``. + +``nsTHashMap`` is a template with two parameters. The first is the hash key +and the second is the data to be stored as the value in the map. Most of +the time, you can simply pass the raw key type as the first parameter, +so long as its supported by `nsTHashMap.h `_. +It is also possible to specify custom keys if necessary. See `nsHashKeys.h +`_ for examples. + +There are a number of more esoteric hashkey classes in nsHashKeys.h, and +you can always roll your own if none of these fit your needs (make sure +you're not duplicating an existing hashkey class though!) + +Once you've determined what hashtable and hashkey classes you need, you +can put it all together. A few examples: + +- A hashtable that maps UTF-8 origin names to a DOM Window - + ``nsTHashMap>`` +- A hashtable that maps 32-bit integers to floats - + ``nsTHashMap`` +- A hashtable that maps ``nsISupports`` pointers to reference counted + ``CacheEntry``\ s - + ``nsTHashMap, RefPtr>`` +- A hashtable that maps ``JSContext`` pointers to a ``ContextInfo`` + struct - ``nsTHashMap>`` +- A hashset of strings - ``nsTHashSet`` + +.. _nsBaseHashtable_and_friends:_nsDataHashtable.2C_nsInterfaceHashtable.2C_and_nsClassHashtable: + +Hashtable API +------------- + +The hashtable classes all expose the same basic API. There are three +key methods, ``Get``, ``InsertOrUpdate``, and ``Remove``, which retrieve entries from the +hashtable, write entries into the hashtable, and remove entries from the +hashtable respectively. See `nsBaseHashtable.h `_ +for more details. + +The hashtables that hold references to pointers (nsRefPtrHashtable and +nsInterfaceHashtable) also have GetWeak methods that return non-AddRefed +pointers. + +Note that ``nsRefPtrHashtable``, ``nsInterfaceHashtable`` and ``nsClassHashtable`` +are legacy hashtable types which have some extra methods, and don't have automatic +key type handling. + +All of these hashtable classes can be iterated over via the ``Iterator`` +class, with normal C++11 iterators or using the ``Keys()`` / ``Values()`` ranges, +and all can be cleared via the ``Clear`` method. diff --git a/xpcom/docs/hashtables_detailed.rst b/xpcom/docs/hashtables_detailed.rst new file mode 100644 index 0000000000..200c47490d --- /dev/null +++ b/xpcom/docs/hashtables_detailed.rst @@ -0,0 +1,121 @@ +XPCOM Hashtable Technical Details +================================= + +.. note:: + + This is a deep-dive into the underlying mechanisms that power the XPCOM + hashtables. Some of this information is quite old and may be out of date. If + you're looking for how to use XPCOM hashtables, you should consider reading + the :ref:`XPCOM Hashtable Guide` instead. + +Mozilla's Hashtable Implementations +----------------------------------- + +Mozilla has several hashtable implementations, which have been tested +and tuned, and hide the inner complexities of hashtable implementations: + +- ``PLHashTable`` - low-level C API; entry class pointers are constant; + more efficient for large entry structures; often wastes memory making + many small heap allocations. +- ``nsTHashtable`` - low-level C++ wrapper around ``PLDHash``; + generates callback functions and handles most casting automagically. + Client writes their own entry class which can include complex key and + data types. +- ``nsTHashMap/nsInterfaceHashtable/nsClassHashtable`` - + simplifies the common usage pattern mapping a simple keytype to a + simple datatype; client does not need to declare or manage an entry class; + ``nsTHashMap`` datatype is a scalar such as ``uint64_t``; + ``nsInterfaceHashtable`` datatype is an XPCOM interface; + ``nsClassHashtable`` datatype is a class pointer owned by the + hashtable. + +.. _PLHashTable: + +PLHashTable +~~~~~~~~~~~ + +``PLHashTable`` is a part of NSPR. The header file can be found at `plhash.h +`_. + +There are two situations where ``PLHashTable`` may be preferable: + +- You need entry-pointers to remain constant. +- The entries stored in the table are very large (larger than 12 + words). + +.. _nsTHashtable: + +nsTHashtable +~~~~~~~~~~~~ + +To use ``nsTHashtable``, you must declare an entry-class. This +entry class contains the key and the data that you are hashing. It also +declares functions that manipulate the key. In most cases, the functions +of this entry class can be entirely inline. For examples of entry classes, +see the declarations at `nsHashKeys.h +`_. + +The template parameter is the entry class. After construction, use the +functions ``PutEntry/GetEntry/RemoveEntry`` to alter the hashtable. The +``Iterator`` class will do iteration, but beware that the iteration will +occur in a seemingly-random order (no sorting). + +- ``nsTHashtable``\ s can be allocated on the stack, as class members, + or on the heap. +- Entry pointers can and do change when items are added to or removed + from the hashtable. Do not keep long-lasting pointers to entries. +- because of this, ``nsTHashtable`` is not inherently thread-safe. If + you use a hashtable in a multi-thread environment, you must provide + locking as appropriate. + +Before using ``nsTHashtable``, see if ``nsBaseHashtable`` and relatives +will work for you. They are much easier to use, because you do not have +to declare an entry class. If you are hashing a simple key type to a +simple data type, they are generally a better choice. + +.. _nsBaseHashtable_and_friends:nsTHashMap.2C_nsInterfaceHashtable.2C_and_nsClassHashtable: + +nsBaseHashtable and friends: nsTHashMap, nsInterfaceHashtable, and nsClassHashtable +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These C++ templates provide a high-level interface for using hashtables +that hides most of the complexities of the underlying implementation. They +provide the following features: + +- hashtable operations can be completed without using an entry class, + making code easier to read +- optional thread-safety: the hashtable can manage a read-write lock + around the table +- predefined key classes provide automatic cleanup of + strings/interfaces +- ``nsInterfaceHashtable`` and ``nsClassHashtable`` automatically + release/delete objects to avoid leaks. + +``nsBaseHashtable`` is not used directly; choose one of the three +derivative classes based on the data type you want to store. The +``KeyClass`` is taken from `nsHashKeys.h +`_ and is the same for all +three classes: + +- ``nsTHashMap`` - ``DataType`` is a simple + type such as ``uint32_t`` or ``bool``. +- ``nsInterfaceHashtable`` - ``Interface`` is an + XPCOM interface such as ``nsISupports`` or ``nsIDocShell`` +- ``nsClassHashtable`` - ``T`` is any C++ class. The + hashtable stores a pointer to the object, and deletes that object + when the entry is removed. + +The important files to read are +`nsBaseHashtable.h `_ +and +`nsHashKeys.h `_. +These classes can be used on the stack, as a class member, or on the heap. + +.. _Using_nsTHashtable_as_a_hash-set: + +Using nsTHashtable as a hash-set +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +A hash set only tracks the existence of keys: it does not associate data +with the keys. This can be done using ``nsTHashtable``. +The appropriate entries are GetEntry and PutEntry. diff --git a/xpcom/docs/huntingleaks.rst b/xpcom/docs/huntingleaks.rst new file mode 100644 index 0000000000..9e0585da4e --- /dev/null +++ b/xpcom/docs/huntingleaks.rst @@ -0,0 +1,22 @@ +Hunting Leaks +============= + +.. contents:: Table of Contents + :local: + :depth: 2 + +Different tools and techniques are used to hunt leaks: + +.. list-table:: + :header-rows: 1 + + * - Tools + - Description + * - :ref:`Bloatview` + - BloatView is a tool that shows information about cumulative memory usage and leaks. + * - :ref:`Refcount Tracing and Balancing` + - Refcount tracing and balancing are advanced techniques for tracking down leak of refcounted objects found with BloatView. + * - `GC and CC logs `_ + - Garbage collector (GC) and cycle collector (CC) logs give information about why various JS and C++ objects are alive in the heap. + * - :ref:`DMD Heap Scan Mode` + - Heap profiler within Firefox diff --git a/xpcom/docs/index.rst b/xpcom/docs/index.rst new file mode 100644 index 0000000000..bf4e3157c4 --- /dev/null +++ b/xpcom/docs/index.rst @@ -0,0 +1,19 @@ +XPCOM +===== + +These pages contain documentation for Mozilla's Cross-Platform Component Object Model (XPCOM) module. It abstracts core systems functionality for cross-platform use. The component architecture follows the standard COM approach. + +.. toctree:: + :maxdepth: 1 + + logging + stringguide + refptr + thread-safety + huntingleaks + collections + xpidl + writing-xpcom-interface + hashtables + hashtables_detailed + cc-macros diff --git a/xpcom/docs/logging.rst b/xpcom/docs/logging.rst new file mode 100644 index 0000000000..05b39df17c --- /dev/null +++ b/xpcom/docs/logging.rst @@ -0,0 +1,435 @@ +Gecko Logging +============= + +A minimal C++ logging framework is provided for use in core Gecko code. It is +enabled for all builds and is thread-safe. + +This page covers enabling logging for particular logging module, configuring +the logging output, and how to use the logging facilities in native code. + +Enabling and configuring logging +++++++++++++++++++++++++++++++++ + +Caveat: sandboxing when logging to a file +----------------------------------------- + +A sandboxed content process cannot write to ``stderr`` or any file. The easiest +way to log these processes is to disable the content sandbox by setting the +preference ``security.sandbox.content.level`` to ``0``, or setting the environment +variable ``MOZ_DISABLE_CONTENT_SANDBOX`` to ``1``. + +On Windows, you can still see child process messages by using DOS (not the +``MOZ_LOG_FILE`` variable defined below) to redirect output to a file. For +example: ``MOZ_LOG="CameraChild:5" mach run >& my_log_file.txt`` will include +debug messages from the camera's child actor that lives in a (sandboxed) content +process. + +Logging to the Firefox Profiler +------------------------------- + +When a log statement is logged on a thread and the `Firefox Profiler +`_ is profiling that thread, the log statements is +recorded as a profiler marker. + +This allows getting logs alongside profiler markers and lots of performance +and contextual information, in a way that doesn't require disabling the +sandbox, and works across all processes. + +The profile can be downloaded and shared e.g. via Bugzilla or email, or +uploaded, and the logging statements will be visible either in the marker chart +or the marker table. + +While it is possible to manually configure logging module and start the profiler +with the right set of threads to profile, ``about:logging`` makes this task a lot +simpler and error-proof. + + +The ``MOZ_LOG`` syntax +---------------------- + +Logging is configured using a special but simple syntax: which module should be +enabled, at which level, and what logging options should be enabled or disabled. + +The syntax is a list of terms, separated by commas. There are two types of +terms: + +- A log module and its level, separated by a colon (``:``), such as + ``example_module:5`` to enable the module ``example_module`` at logging level + ``5`` (verbose). This `searchfox query + `_ + returns the complete list of modules available. +- A special string in the following table, to configure the logging behaviour. + Some configuration switch take an integer parameter, in which case it's + separated from the string by a colon (``:``). Most switches only apply in a + specific output context, noted in the **Context** column. + ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| Special module name | Context | Action | ++======================+=========+===========================================================================================+ +| append | File | Append new logs to existing log file. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| sync | File | Print each log synchronously, this is useful to check behavior in real time or get logs | +| | | immediately before crash. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| raw | File | Print exactly what has been specified in the format string, without the | +| | | process/thread/timestamp, etc. prefix. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| timestamp | File | Insert timestamp at start of each log line. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| rotate:**N** | File | | This limits the produced log files' size. Only most recent **N megabytes** of log data | +| | | | is saved. We rotate four log files with .0, .1, .2, .3 extensions. Note: this option | +| | | | disables 'append' and forces 'timestamp'. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| maxsize:**N** | File | Limit the log to **N** MB. Only work in append mode. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| prependheader | File | Prepend a simple header while distinguishing logging. Useful in append mode. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ +| profilerstacks | Profiler| | When profiling with the Firefox Profiler and log modules are enabled, capture the call | +| | | | stack for each log statement. | ++----------------------+---------+-------------------------------------------------------------------------------------------+ + +This syntax is used for most methods of enabling logging, with the exception of +settings preferences directly, see :ref:`this section ` for directions. + + +Enabling Logging +---------------- + +Enabling logging can be done in a variety of ways: + +- via environment variables +- via command line switches +- using ``about:config`` preferences +- using ``about:logging`` + +The first two allow logging from the start of the application and are also +useful in case of a crash (when ``sync`` output is requested, this can also be +done with ``about:config`` as well to a certain extent). The last two +allow enabling and disabling logging at runtime and don't require using the +command-line. + +By default all logging output is disabled. + +Enabling logging using ``about:logging`` +'''''''''''''''''''''''''''''''''''''''' + +``about:logging`` allows enabling logging by entering a ``MOZ_LOG`` string in the +text input, and validating. + +Options allow logging to a file or using the Firefox Profiler, that can be +started and stopped right from the page. + +Logging presets for common scenarios are available in a drop-down. They can be +associated with a profiler preset. + +It is possible, via URL parameters, to select a particular logging +configuration, or to override certain parameters in a preset. This is useful to +ask a user to gather logs efficiently without having to fiddle with prefs and/or +environment variable. + +URL parameters are described in the following table: + ++---------------------+---------------------------------------------------------------------------------------------+ +| Parameter | Description | ++=====================+=============================================================================================+ +| ``preset`` | a `logging preset `_ | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``logging-preset`` | alias for ``preset`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``modules`` | a string in ``MOZ_LOG`` syntax | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``module`` | alias for ``modules`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``threads`` | a list of threads to profile, overrides what a profiler preset would have picked | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``thread`` | alias for ``threads`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``output`` | either ``profiler`` or ``file`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``output-type`` | alias for ``output`` | ++---------------------+---------------------------------------------------------------------------------------------+ +| ``profiler-preset`` | a `profiler preset `_ | ++---------------------+---------------------------------------------------------------------------------------------+ + +If a preset is selected, then ``threads`` or ``modules`` can be used to override the +profiled threads or logging modules enabled, but keeping other aspects of the +preset. If no preset is selected, then a generic profiling preset is used, +``firefox-platform``. For example: + +:: + + about:logging?output=profiler&preset=media-playback&modules=cubeb:4,AudioSinkWrapper:4:AudioSink:4 + +will profile the threads in the ``Media`` profiler preset, but will only log +specific log modules (instead of the `long list +`_ +in the ``media-playback`` preset). In addition, it disallows logging to a file. + +Enabling logging using environment variables +'''''''''''''''''''''''''''''''''''''''''''' + +On UNIX, setting and environment variable can be done in a variety of ways + +:: + + set MOZ_LOG="example_logger:3" + export MOZ_LOG="example_logger:3" + MOZ_LOG="example_logger:3" ./mach run + +In the Windows Command Prompt (``cmd.exe``), don't use quotes: + +:: + + set MOZ_LOG=example_logger:3 + +If you want this on GeckoView example, use the following adb command to launch process: + +:: + + adb shell am start -n org.mozilla.geckoview_example/.GeckoViewActivity --es env0 "MOZ_LOG=example_logger:3" + +There are special module names to change logging behavior. You can specify one or more special module names without logging level. + +For example, if you want to specify ``sync``, ``timestamp`` and ``rotate``: + +:: + + set MOZ_LOG="example_logger:3,timestamp,sync,rotate:10" + +Enabling logging usually outputs the logging statements to the terminal. To +have the logs written to a file instead (one file per process), the environment +variable ``MOZ_LOG_FILE`` can be used. Logs will be written at this path +(either relative or absolute), suffixed by a process type and its PID. +``MOZ_LOG`` files are text files and have the extension ``.moz_log``. + +For example, setting: + +:: + + set MOZ_LOG_FILE="firefox-logs" + +can create a number of files like so: + +:: + + firefox-log-main.96353.moz_log + firefox-log-child.96354.moz_log + +respectively for a parent process of PID 96353 and a child process of PID +96354. + +Enabling logging using command-line flags +''''''''''''''''''''''''''''''''''''''''' + +The ``MOZ_LOG`` syntax can be used with the command line switch on the same +name, and specifying a file with ``MOZ_LOG_FILE`` works in the same way: + +:: + + ./mach run -MOZ_LOG=timestamp,rotate:200,example_module:5 -MOZ_LOG_FILE=%TEMP%\firefox-logs + +will enable verbose (``5``) logging for the module ``example_module``, with +timestamp prepended to each line, rotate the logs with 4 files of each 50MB +(for a total of 200MB), and write the output to the temporary directory on +Windows, with name starting with ``firefox-logs``. + +.. _Enabling logging using preferences: + +Enabling logging using preferences +'''''''''''''''''''''''''''''''''' + +To adjust the logging after Firefox has started, you can set prefs under the +`logging.` prefix. For example, setting `logging.foo` to `3` will set the log +module `foo` to start logging at level 3. A number of special prefs can be set, +described in the table below: + ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| Preference name | Preference | Preference value | Description | ++=====================================+============+===============================+========================================================+ +| ``logging.config.clear_on_startup`` | bool | -- | Whether to clear all prefs under ``logging.`` | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| ``logging.config.LOG_FILE`` | string | A path (relative or absolute) | The path to which the log files will be written. | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| ``logging.config.add_timestamp`` | bool | -- | Whether to prefix all lines by a timestamp. | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| ``logging.config.sync`` | bool | -- | Whether to flush the stream after each log statements. | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ +| ``logging.config.profilerstacks`` | bool | -- | | When logging to the Firefox Profiler, whether to | +| | | | | include the call stack in each logging statement. | ++-------------------------------------+------------+-------------------------------+--------------------------------------------------------+ + +Enabling logging in Rust code +----------------------------- + +We're gradually adding more Rust code to Gecko, and Rust crates typically use a +different approach to logging. Many Rust libraries use the `log +`_ crate to log messages, which works together with +`env_logger `_ at the application level to control +what's actually printed via `RUST_LOG`. + +You can set an overall logging level, though it could be quite verbose: + +:: + + set RUST_LOG="debug" + +You can also target individual modules by path: + +:: + + set RUST_LOG="style::style_resolver=debug" + +.. note:: + For Linux/MacOS users, you need to use `export` rather than `set`. + +.. note:: + Sometimes it can be useful to only log child processes and ignore the parent + process. In Firefox 57 and later, you can use `RUST_LOG_CHILD` instead of + `RUST_LOG` to specify log settings that will only apply to child processes. + +The `log` crate lists the available `log levels `_: + ++-----------+---------------------------------------------------------------------------------------------------------+ +| Log Level | Purpose | ++===========+=========================================================================================================+ +| error | Designates very serious errors. | ++-----------+---------------------------------------------------------------------------------------------------------+ +| warn | Designates hazardous situations. | ++-----------+---------------------------------------------------------------------------------------------------------+ +| info | Designates useful information. | ++-----------+---------------------------------------------------------------------------------------------------------+ +| debug | Designates lower priority information. | ++-----------+---------------------------------------------------------------------------------------------------------+ +| trace | Designates very low priority, often extremely verbose, information. | ++-----------+---------------------------------------------------------------------------------------------------------+ + +It is common for debug and trace to be disabled at compile time in release builds, so you may need a debug build if you want logs from those levels. + +Check the `env_logger `_ docs for more details on logging options. + +Additionally, a mapping from `RUST_LOG` is available. When using the `MOZ_LOG` +syntax, it is possible to enable logging in rust crate using a similar syntax: + +:: + + MOZ_LOG=rust_crate_name::*:4 + +will enable `debug` logging for all log statements in the crate +``rust_crate_name``. + +`*` can be replaced by a series of modules if more specificity is needed: + +:: + + MOZ_LOG=rust_crate_name::module::submodule:4 + +will enable `debug` logging for all log statements in the sub-module +``submodule`` of the module ``module`` of the crate ``rust_crate_name``. + + +A table mapping Rust log levels to `MOZ_LOG` log level is available below: + ++----------------+---------------+-----------------+ +| Rust log level | MOZ_LOG level | Numerical value | ++================+===============+=================+ +| off | Disabled | 0 | ++----------------+---------------+-----------------+ +| error | Error | 1 | ++----------------+---------------+-----------------+ +| warn | Warning | 2 | ++----------------+---------------+-----------------+ +| info | Info | 3 | ++----------------+---------------+-----------------+ +| debug | Debug | 4 | ++----------------+---------------+-----------------+ +| trace | Verbose | 5 | ++----------------+---------------+-----------------+ + +Working with ``MOZ_LOG`` in the code +++++++++++++++++++++++++++++++++++++ + +Declaring a Log Module +---------------------- + +``LazyLogModule`` defers the creation the backing ``LogModule`` in a thread-safe manner and is the preferred method to declare a log module. Multiple ``LazyLogModules`` with the same name can be declared, all will share the same backing ``LogModule``. This makes it much simpler to share a log module across multiple translation units. ``LazyLogLodule`` provides a conversion operator to ``LogModule*`` and is suitable for passing into the logging macros detailed below. + +Note: Log module names can only contain specific characters. The first character must be a lowercase or uppercase ASCII char, underscore, dash, or dot. Subsequent characters may be any of those, or an ASCII digit. + +.. code-block:: c++ + + #include "mozilla/Logging.h" + + static mozilla::LazyLogModule sFooLog("foo"); + + +Logging interface +----------------- + +A basic interface is provided in the form of 2 macros and an enum class. + ++----------------------------------------+----------------------------------------------------------------------------+ +| MOZ_LOG(module, level, message) | Outputs the given message if the module has the given log level enabled: | +| | | +| | * module: The log module to use. | +| | * level: The log level of the message. | +| | * message: A printf-style message to output. Must be enclosed in | +| | parentheses. | ++----------------------------------------+----------------------------------------------------------------------------+ +| MOZ_LOG_TEST(module, level) | Checks if the module has the given level enabled: | +| | | +| | * module: The log module to use. | +| | * level: The output level of the message. | ++----------------------------------------+----------------------------------------------------------------------------+ + + ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Log Level | Numeric Value | Purpose | ++===========+===============+=========================================================================================+ +| Disabled | 0 | Indicates logging is disabled. This should not be used directly in code. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Error | 1 | An error occurred, generally something you would consider asserting in a debug build. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Warning | 2 | A warning often indicates an unexpected state. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Info | 3 | An informational message, often indicates the current program state. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Debug | 4 | A debug message, useful for debugging but too verbose to be turned on normally. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ +| Verbose | 5 | A message that will be printed a lot, useful for debugging program flow and will | +| | | probably impact performance. | ++-----------+---------------+-----------------------------------------------------------------------------------------+ + +Example Usage +------------- + +.. code-block:: c++ + + #include "mozilla/Logging.h" + + using mozilla::LogLevel; + + static mozilla::LazyLogModule sLogger("example_logger"); + + static void DoStuff() + { + MOZ_LOG(sLogger, LogLevel::Info, ("Doing stuff.")); + + int i = 0; + int start = Time::NowMS(); + MOZ_LOG(sLogger, LogLevel::Debug, ("Starting loop.")); + while (i++ < 10) { + MOZ_LOG(sLogger, LogLevel::Verbose, ("i = %d", i)); + } + + // Only calculate the elapsed time if the Warning level is enabled. + if (MOZ_LOG_TEST(sLogger, LogLevel::Warning)) { + int elapsed = Time::NowMS() - start; + if (elapsed > 1000) { + MOZ_LOG(sLogger, LogLevel::Warning, ("Loop took %dms!", elapsed)); + } + } + + if (i != 10) { + MOZ_LOG(sLogger, LogLevel::Error, ("i should be 10!")); + } + } diff --git a/xpcom/docs/refptr.rst b/xpcom/docs/refptr.rst new file mode 100644 index 0000000000..f71acc6828 --- /dev/null +++ b/xpcom/docs/refptr.rst @@ -0,0 +1,81 @@ +Reference Counting Helpers +========================== + +RefPtr versus nsCOMPtr +---------------------- + +The general rule of thumb is to use ``nsCOMPtr`` when ``T`` is an +interface type which inherits from ``nsISupports``, and ``RefPtr`` when +``T`` is a concrete type. + +This basic rule derives from some ``nsCOMPtr`` code being factored into +the ``nsCOMPtr_base`` base class, which stores the pointer as a +``nsISupports*``. This design was intended to save some space in the binary +(though it is unclear if it still does). Since ``nsCOMPtr`` stores the +pointer as ``nsISupports*``, it must be possible to unambiguously cast from +``T*`` to ``nsISupports**``. Many concrete classes inherit from more than +one XPCOM interface, meaning that they cannot be used with ``nsCOMPtr``, +which leads to the suggestion to use ``RefPtr`` for these classes. + +``nsCOMPtr`` also requires that the target type ``T`` be a valid target +for ``QueryInterface`` so that it can assert that the stored pointer is a +canonical ``T`` pointer (i.e. that ``mRawPtr->QueryInterface(T_IID) == +mRawPtr``). + +do_XXX() nsCOMPtr helpers +------------------------- + +There are a number of ``do_XXX`` helper methods across the codebase which can +be assigned into ``nsCOMPtr`` (and sometimes ``RefPtr``) to perform explicit +operations based on the target pointer type. + +In general, when these operations succeed, they will initialize the smart +pointer with a valid value, and otherwise they will silently initialize the +smart pointer to ``nullptr``. + +``do_QueryInterface`` and ``do_QueryObject`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Attempts to cast the provided object to the target class using the XPCOM +``QueryInterface`` mechanism. In general, use ``do_QueryInterface`` may only +be used to cast between interface types in a ``nsCOMPtr``, and +``do_QueryObject`` in situations when downcasting to concrete types. + + +``do_GetInterface`` +~~~~~~~~~~~~~~~~~~~ + +Looks up an object implementing the requested interface using the +``nsIInterfaceRequestor`` interface. If the target object doesn't implement +``nsIInterfaceRequestor`` or doesn't provide the given interface, initializes +the smart pointer with ``nullptr``. + + +``do_GetService`` +~~~~~~~~~~~~~~~~~ + +Looks up the component defined by the passed-in CID or ContractID string in +the component manager, and returns a pointer to the service instance. This +may start the service if it hasn't been started already. The resulting +service will be cast to the target interface type using ``QueryInterface``. + + +``do_CreateInstance`` +~~~~~~~~~~~~~~~~~~~~~ + +Looks up the component defined by the passed-in CID or ContractID string in +the component manager, creates and returns a new instance. The resulting +object will be cast to the target interface type using ``QueryInterface``. + + +``do_QueryReferent`` and ``do_GetWeakReference`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When passed a ``nsIWeakReference*`` (e.g. from a ``nsWeakPtr``), +``do_QueryReferent`` attempts to re-acquire a strong reference to the held +type, and cast it to the target type with ``QueryInterface``. Initializes the +smart pointer with ``nullptr`` if either of these steps fail. + +In contrast ``do_GetWeakReference`` does the opposite, using +``QueryInterface`` to cast the type to ``nsISupportsWeakReference*``, and +acquire a ``nsIWeakReference*`` to the passed-in object. diff --git a/xpcom/docs/stringguide.rst b/xpcom/docs/stringguide.rst new file mode 100644 index 0000000000..97613276a4 --- /dev/null +++ b/xpcom/docs/stringguide.rst @@ -0,0 +1,1110 @@ +String Guide +============ + +Most of the Mozilla code uses a C++ class hierarchy to pass string data, +rather than using raw pointers. This guide documents the string classes which +are visible to code within the Mozilla codebase (code which is linked into +``libxul``). + +Introduction +------------ + +The string classes are a library of C++ classes which are used to manage +buffers of wide (16-bit) and narrow (8-bit) character strings. The headers +and implementation are in the `xpcom/string +`_ directory. All +strings are stored as a single contiguous buffer of characters. + +The 8-bit and 16-bit string classes have completely separate base classes, +but share the same APIs. As a result, you cannot assign a 8-bit string to a +16-bit string without some kind of conversion helper class or routine. For +the purpose of this document, we will refer to the 16-bit string classes in +class documentation. Every 16-bit class has an equivalent 8-bit class: + +===================== ====================== +Wide Narrow +===================== ====================== +``nsAString`` ``nsACString`` +``nsString`` ``nsCString`` +``nsAutoString`` ``nsAutoCString`` +``nsDependentString`` ``nsDependentCString`` +===================== ====================== + +The string classes distinguish, as part of the type hierarchy, between +strings that must have a null-terminator at the end of their buffer +(``ns[C]String``) and strings that are not required to have a null-terminator +(``nsA[C]String``). nsA[C]String is the base of the string classes (since it +imposes fewer requirements) and ``ns[C]String`` is a class derived from it. +Functions taking strings as parameters should generally take one of these +four types. + +In order to avoid unnecessary copying of string data (which can have +significant performance cost), the string classes support different ownership +models. All string classes support the following three ownership models +dynamically: + +* reference counted, copy-on-write, buffers (the default) + +* adopted buffers (a buffer that the string class owns, but is not reference + counted, because it came from somewhere else) + +* dependent buffers, that is, an underlying buffer that the string class does + not own, but that the caller that constructed the string guarantees will + outlive the string instance + +Auto strings will prefer reference counting an existing reference-counted +buffer over their stack buffer, but will otherwise use their stack buffer for +anything that will fit in it. + +There are a number of additional string classes: + + +* Classes which exist primarily as constructors for the other types, + particularly ``nsDependent[C]String`` and ``nsDependent[C]Substring``. These + types are really just convenient notation for constructing an + ``nsA[C]String`` with a non-default ownership mode; they should not be + thought of as different types. + +* ``nsLiteral[C]String`` which should rarely be constructed explicitly but + usually through the ``""_ns`` and ``u""_ns`` user-defined string literals. + ``nsLiteral[C]String`` is trivially constructible and destructible, and + therefore does not emit construction/destruction code when stored in static, + as opposed to the other string classes. + +The Major String Classes +------------------------ + +The list below describes the main base classes. Once you are familiar with +them, see the appendix describing What Class to Use When. + + +* **nsAString**/**nsACString**: the abstract base class for all strings. It + provides an API for assignment, individual character access, basic + manipulation of characters in the string, and string comparison. This class + corresponds to the XPIDL ``AString`` or ``ACString`` parameter types. + ``nsA[C]String`` is not necessarily null-terminated. + +* **nsString**/**nsCString**: builds on ``nsA[C]String`` by guaranteeing a + null-terminated storage. This allows for a method (``.get()``) to access the + underlying character buffer. + +The remainder of the string classes inherit from either ``nsA[C]String`` or +``ns[C]String``. Thus, every string class is compatible with ``nsA[C]String``. + +.. note:: + + In code which is generic over string width, ``nsA[C]String`` is sometimes + known as ``nsTSubstring``. ``nsAString`` is a type alias for + ``nsTSubstring``, and ``nsACString`` is a type alias for + ``nsTSubstring``. + +.. note:: + + The type ``nsLiteral[C]String`` technically does not inherit from + ``nsA[C]String``, but instead inherits from ``nsStringRepr``. This + allows the type to not generate destructors when stored in static + storage. + + It can be implicitly coerced to ``const ns[C]String&`` (though can never + be accessed mutably) and generally acts as-if it was a subclass of + ``ns[C]String`` in most cases. + +Since every string derives from ``nsAString`` (or ``nsACString``), they all +share a simple API. Common read-only methods include: + +* ``.Length()`` - the number of code units (bytes for 8-bit string classes and ``char16_t`` for 16-bit string classes) in the string. +* ``.IsEmpty()`` - the fastest way of determining if the string has any value. Use this instead of testing ``string.Length() == 0`` +* ``.Equals(string)`` - ``true`` if the given string has the same value as the current string. Approximately the same as ``operator==``. + +Common methods that modify the string: + +* ``.Assign(string)`` - Assigns a new value to the string. Approximately the same as ``operator=``. +* ``.Append(string)`` - Appends a value to the string. +* ``.Insert(string, position)`` - Inserts the given string before the code unit at position. +* ``.Truncate(length)`` - shortens the string to the given length. + +More complete documentation can be found in the `Class Reference`_. + +As function parameters +~~~~~~~~~~~~~~~~~~~~~~ + +In general, use ``nsA[C]String`` references to pass strings across modules. For example: + +.. code-block:: c++ + + // when passing a string to a method, use const nsAString& + nsFoo::PrintString(const nsAString& str); + + // when getting a string from a method, use nsAString& + nsFoo::GetString(nsAString& result); + +The Concrete Classes - which classes to use when +------------------------------------------------ + +The concrete classes are for use in code that actually needs to store string +data. The most common uses of the concrete classes are as local variables, +and members in classes or structs. + +.. digraph:: concreteclasses + + node [shape=rectangle] + + "nsA[C]String" -> "ns[C]String"; + "ns[C]String" -> "nsDependent[C]String"; + "nsA[C]String" -> "nsDependent[C]Substring"; + "nsA[C]String" -> "ns[C]SubstringTuple"; + "ns[C]String" -> "nsAuto[C]StringN"; + "ns[C]String" -> "nsLiteral[C]String" [style=dashed]; + "nsAuto[C]StringN" -> "nsPromiseFlat[C]String"; + "nsAuto[C]StringN" -> "nsPrintfCString"; + +The following is a list of the most common concrete classes. Once you are +familiar with them, see the appendix describing What Class to Use When. + +* ``ns[C]String`` - a null-terminated string whose buffer is allocated on the + heap. Destroys its buffer when the string object goes away. + +* ``nsAuto[C]String`` - derived from ``nsString``, a string which owns a 64 + code unit buffer in the same storage space as the string itself. If a string + less than 64 code units is assigned to an ``nsAutoString``, then no extra + storage will be allocated. For larger strings, a new buffer is allocated on + the heap. + + If you want a number other than 64, use the templated types ``nsAutoStringN`` + / ``nsAutoCStringN``. (``nsAutoString`` and ``nsAutoCString`` are just + typedefs for ``nsAutoStringN<64>`` and ``nsAutoCStringN<64>``, respectively.) + +* ``nsDependent[C]String`` - derived from ``nsString``, this string does not + own its buffer. It is useful for converting a raw string pointer (``const + char16_t*`` or ``const char*``) into a class of type ``nsAString``. Note that + you must null-terminate buffers used by to ``nsDependentString``. If you + don't want to or can't null-terminate the buffer, use + ``nsDependentSubstring``. + +* ``nsPrintfCString`` - derived from ``nsCString``, this string behaves like an + ``nsAutoCString``. The constructor takes parameters which allows it to + construct a 8-bit string from a printf-style format string and parameter + list. + +There are also a number of concrete classes that are created as a side-effect +of helper routines, etc. You should avoid direct use of these classes. Let +the string library create the class for you. + +* ``ns[C]SubstringTuple`` - created via string concatenation +* ``nsDependent[C]Substring`` - created through ``Substring()`` +* ``nsPromiseFlat[C]String`` - created through ``PromiseFlatString()`` +* ``nsLiteral[C]String`` - created through the ``""_ns`` and ``u""_ns`` user-defined literals + +Of course, there are times when it is necessary to reference these string +classes in your code, but as a general rule they should be avoided. + +Iterators +--------- + +Because Mozilla strings are always a single buffer, iteration over the +characters in the string is done using raw pointers: + +.. code-block:: c++ + + /** + * Find whether there is a tab character in `data` + */ + bool HasTab(const nsAString& data) { + const char16_t* cur = data.BeginReading(); + const char16_t* end = data.EndReading(); + + for (; cur < end; ++cur) { + if (char16_t('\t') == *cur) { + return true; + } + } + return false; + } + +Note that ``end`` points to the character after the end of the string buffer. +It should never be dereferenced. + +Writing to a mutable string is also simple: + +.. code-block:: c++ + + /** + * Replace every tab character in `data` with a space. + */ + void ReplaceTabs(nsAString& data) { + char16_t* cur = data.BeginWriting(); + char16_t* end = data.EndWriting(); + + for (; cur < end; ++cur) { + if (char16_t('\t') == *cur) { + *cur = char16_t(' '); + } + } + } + +You may change the length of a string via ``SetLength()``. Note that +Iterators become invalid after changing the length of a string. If a string +buffer becomes smaller while writing it, use ``SetLength`` to inform the +string class of the new size: + +.. code-block:: c++ + + /** + * Remove every tab character from `data` + */ + void RemoveTabs(nsAString& data) { + int len = data.Length(); + char16_t* cur = data.BeginWriting(); + char16_t* end = data.EndWriting(); + + while (cur < end) { + if (char16_t('\t') == *cur) { + len -= 1; + end -= 1; + if (cur < end) + memmove(cur, cur + 1, (end - cur) * sizeof(char16_t)); + } else { + cur += 1; + } + } + + data.SetLength(len); + } + +Note that using ``BeginWriting()`` to make a string longer is not OK. +``BeginWriting()`` must not be used to write past the logical length of the +string indicated by ``EndWriting()`` or ``Length()``. Calling +``SetCapacity()`` before ``BeginWriting()`` does not affect what the previous +sentence says. To make the string longer, call ``SetLength()`` before +``BeginWriting()`` or use the ``BulkWrite()`` API described below. + +Bulk Write +---------- + +``BulkWrite()`` allows capacity-aware cache-friendly low-level writes to the +string's buffer. + +Capacity-aware means that the caller is made aware of how the +caller-requested buffer capacity was rounded up to mozjemalloc buckets. This +is useful when initially requesting best-case buffer size without yet knowing +the true size need. If the data that actually needs to be written is larger +than the best-case estimate but still fits within the rounded-up capacity, +there is no need to reallocate despite requesting the best-case capacity. + +Cache-friendly means that the zero terminator for C compatibility is written +after the new content of the string has been written, so the result is a +forward-only linear write access pattern instead of a non-linear +back-and-forth sequence resulting from using ``SetLength()`` followed by +``BeginWriting()``. + +Low-level means that writing via a raw pointer is possible as with +``BeginWriting()``. + +``BulkWrite()`` takes three arguments: The new capacity (which may be rounded +up), the number of code units at the beginning of the string to preserve +(typically the old logical length), and a boolean indicating whether +reallocating a smaller buffer is OK if the requested capacity would fit in a +buffer that's smaller than current one. It returns a ``mozilla::Result`` which +contains either a usable ``mozilla::BulkWriteHandle`` (where ``T`` is the +string's ``char_type``) or an ``nsresult`` explaining why none can be had +(presumably OOM). + +The actual writes are performed through the returned +``mozilla::BulkWriteHandle``. You must not access the string except via this +handle until you call ``Finish()`` on the handle in the success case or you let +the handle go out of scope without calling ``Finish()`` in the failure case, in +which case the destructor of the handle puts the string in a mostly harmless but +consistent state (containing a single REPLACEMENT CHARACTER if a capacity +greater than 0 was requested, or in the ``char`` case if the three-byte UTF-8 +representation of the REPLACEMENT CHARACTER doesn't fit, an ASCII SUBSTITUTE). + +``mozilla::BulkWriteHandle`` autoconverts to a writable +``mozilla::Span`` and also provides explicit access to itself as ``Span`` +(``AsSpan()``) or via component accessors named consistently with those on +``Span``: ``Elements()`` and ``Length()``. (The latter is not the logical +length of the string but the writable length of the buffer.) The buffer +exposed via these methods includes the prefix that you may have requested to +be preserved. It's up to you to skip past it so as to not overwrite it. + +If there's a need to request a different capacity before you are ready to +call ``Finish()``, you can call ``RestartBulkWrite()`` on the handle. It +takes three arguments that match the first three arguments of +``BulkWrite()``. It returns ``mozilla::Result`` to +indicate success or OOM. Calling ``RestartBulkWrite()`` invalidates +previously-obtained span, raw pointer or length. + +Once you are done writing, call ``Finish()``. It takes two arguments: the new +logical length of the string (which must not exceed the capacity returned by +the ``Length()`` method of the handle) and a boolean indicating whether it's +OK to attempt to reallocate a smaller buffer in case a smaller mozjemalloc +bucket could accommodate the new logical length. + +Helper Classes and Functions +---------------------------- + +Converting Cocoa strings +~~~~~~~~~~~~~~~~~~~~~~~~ + +Use ``mozilla::CopyCocoaStringToXPCOMString()`` in +``mozilla/MacStringHelpers.h`` to convert Cocoa strings to XPCOM strings. + +Searching strings - looking for substrings, characters, etc. +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``nsReadableUtils.h`` header provides helper methods for searching in runnables. + +.. code-block:: c++ + + bool FindInReadable(const nsAString& pattern, + nsAString::const_iterator start, nsAString::const_iterator end, + nsStringComparator& aComparator = nsDefaultStringComparator()); + +To use this, ``start`` and ``end`` should point to the beginning and end of a +string that you would like to search. If the search string is found, +``start`` and ``end`` will be adjusted to point to the beginning and end of +the found pattern. The return value is ``true`` or ``false``, indicating +whether or not the string was found. + +An example: + +.. code-block:: c++ + + const nsAString& str = GetSomeString(); + nsAString::const_iterator start, end; + + str.BeginReading(start); + str.EndReading(end); + + constexpr auto valuePrefix = u"value="_ns; + + if (FindInReadable(valuePrefix, start, end)) { + // end now points to the character after the pattern + valueStart = end; + } + +Checking for Memory Allocation failure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Like other types in Gecko, the string classes use infallible memory +allocation by default, so you do not need to check for success when +allocating/resizing "normal" strings. + +Most functions that modify strings (``Assign()``, ``SetLength()``, etc.) also +have an overload that takes a ``mozilla::fallible_t`` parameter. These +overloads return ``false`` instead of aborting if allocation fails. Use them +when creating/allocating strings which may be very large, and which the +program could recover from if the allocation fails. + +Substrings (string fragments) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is very simple to refer to a substring of an existing string without +actually allocating new space and copying the characters into that substring. +``Substring()`` is the preferred method to create a reference to such a +string. + +.. code-block:: c++ + + void ProcessString(const nsAString& str) { + const nsAString& firstFive = Substring(str, 0, 5); // from index 0, length 5 + // firstFive is now a string representing the first 5 characters + } + +Unicode Conversion +------------------ + +Strings can be stored in two basic formats: 8-bit code unit (byte/``char``) +strings, or 16-bit code unit (``char16_t``) strings. Any string class with a +capital "C" in the classname contains 8-bit bytes. These classes include +``nsCString``, ``nsDependentCString``, and so forth. Any string class without +the "C" contains 16-bit code units. + +A 8-bit string can be in one of many character encodings while a 16-bit +string is always in potentially-invalid UTF-16. (You can make a 16-bit string +guaranteed-valid UTF-16 by passing it to ``EnsureUTF16Validity()``.) The most +common encodings are: + + +* ASCII - 7-bit encoding for basic English-only strings. Each ASCII value + is stored in exactly one byte in the array with the most-significant 8th bit + set to zero. + +* `UCS2 `_ - 16-bit encoding for a + subset of Unicode, `BMP `_. The Unicode + value of a character stored in UCS2 is stored in exactly one 16-bit + ``char16_t`` in a string class. + +* `UTF-8 `_ - 8-bit encoding for + Unicode characters. Each Unicode characters is stored in up to 4 bytes in a + string class. UTF-8 is capable of representing the entire Unicode character + repertoire, and it efficiently maps to `UTF-32 + `_. (Gtk and Rust natively use + UTF-8.) + +* `UTF-16 `_ - 16-bit encoding for + Unicode storage, backwards compatible with UCS2. The Unicode value of a + character stored in UTF-16 may require one or two 16-bit ``char16_t`` in a + string class. The contents of ``nsAString`` always has to be regarded as in + this encoding instead of UCS2. UTF-16 is capable of representing the entire + Unicode character repertoire, and it efficiently maps to UTF-32. (Win32 W + APIs and Mac OS X natively use UTF-16.) + +* Latin1 - 8-bit encoding for the first 256 Unicode code points. Used for + HTTP headers and for size-optimized storage in text node and SpiderMonkey + strings. Latin1 converts to UTF-16 by zero-extending each byte to a 16-bit + code unit. Note that this kind of "Latin1" is not available for encoding + HTML, CSS, JS, etc. Specifying ``charset=latin1`` means the same as + ``charset=windows-1252``. Windows-1252 is a similar but different encoding + used for interchange. + +In addition, there exist multiple other (legacy) encodings. The Web-relevant +ones are defined in the `Encoding Standard `_. +Conversions from these encodings to +UTF-8 and UTF-16 are provided by `mozilla::Encoding +`_. +Additionally, on Windows the are some rare cases (e.g. drag&drop) where it's +necessary to call a system API with data encoded in the Windows +locale-dependent legacy encoding instead of UTF-16. In those rare cases, use +``MultiByteToWideChar``/``WideCharToMultiByte`` from kernel32.dll. Do not use +``iconv`` on *nix. We only support UTF-8-encoded file paths on *nix, non-path +Gtk strings are always UTF-8 and Cocoa and Java strings are always UTF-16. + +When working with existing code, it is important to examine the current usage +of the strings that you are manipulating, to determine the correct conversion +mechanism. + +When writing new code, it can be confusing to know which storage class and +encoding is the most appropriate. There is no single answer to this question, +but the important points are: + + +* **Surprisingly many strings are very often just ASCII.** ASCII is a subset of + UTF-8 and is, therefore, efficient to represent as UTF-8. Representing ASCII + as UTF-16 bad both for memory usage and cache locality. + +* **Rust strongly prefers UTF-8.** If your C++ code is interacting with Rust + code, using UTF-8 in ``nsACString`` and merely validating it when converting + to Rust strings is more efficient than using ``nsAString`` on the C++ side. + +* **Networking code prefers 8-bit strings.** Networking code tends to use 8-bit + strings: either with UTF-8 or Latin1 (byte value is the Unicode scalar value) + semantics. + +* **JS and DOM prefer UTF-16.** Most Gecko code uses UTF-16 for compatibility + with JS strings and DOM string which are potentially-invalid UTF-16. However, + both DOM text nodes and JS strings store strings that only contain code points + below U+0100 as Latin1 (byte value is the Unicode scalar value). + +* **Windows and Cocoa use UTF-16.** Windows system APIs take UTF-16. Cocoa + ``NSString`` is UTF-16. + +* **Gtk uses UTF-8.** Gtk APIs take UTF-8 for non-file paths. In the Gecko + case, we support only UTF-8 file paths outside Windows, so all Gtk strings + are UTF-8 for our purposes though file paths received from Gtk may not be + valid UTF-8. + +To assist with ASCII, Latin1, UTF-8, and UTF-16 conversions, there are some +helper methods and classes. Some of these classes look like functions, +because they are most often used as temporary objects on the stack. + +Short zero-terminated ASCII strings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you have a short zero-terminated string that you are certain is always +ASCII, use these special-case methods instead of the conversions described in +the later sections. + +* If you are assigning an ASCII literal to an ``nsACString``, use + ``AssignLiteral()``. +* If you are assigning a literal to an ``nsAString``, use ``AssignLiteral()`` + and make the literal a ``u""`` literal. If the literal has to be a ``""`` + literal (as opposed to ``u""``) and is ASCII, still use ``AppendLiteral()``, + but be aware that this involves a run-time inflation. +* If you are assigning a zero-terminated ASCII string that's not a literal from + the compiler's point of view at the call site and you don't know the length + of the string either (e.g. because it was looked up from an array of literals + of varying lengths), use ``AssignASCII()``. + +UTF-8 / UTF-16 conversion +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. cpp:function:: NS_ConvertUTF8toUTF16(const nsACString&) + + a ``nsAutoString`` subclass that converts a UTF-8 encoded ``nsACString`` + or ``const char*`` to a 16-bit UTF-16 string. If you need a ``const + char16_t*`` buffer, you can use the ``.get()`` method. For example: + + .. code-block:: c++ + + /* signature: void HandleUnicodeString(const nsAString& str); */ + object->HandleUnicodeString(NS_ConvertUTF8toUTF16(utf8String)); + + /* signature: void HandleUnicodeBuffer(const char16_t* str); */ + object->HandleUnicodeBuffer(NS_ConvertUTF8toUTF16(utf8String).get()); + +.. cpp:function:: NS_ConvertUTF16toUTF8(const nsAString&) + + a ``nsAutoCString`` which converts a 16-bit UTF-16 string (``nsAString``) + to a UTF-8 encoded string. As above, you can use ``.get()`` to access a + ``const char*`` buffer. + + .. code-block:: c++ + + /* signature: void HandleUTF8String(const nsACString& str); */ + object->HandleUTF8String(NS_ConvertUTF16toUTF8(utf16String)); + + /* signature: void HandleUTF8Buffer(const char* str); */ + object->HandleUTF8Buffer(NS_ConvertUTF16toUTF8(utf16String).get()); + +.. cpp:function:: CopyUTF8toUTF16(const nsACString&, nsAString&) + + converts and copies: + + .. code-block:: c++ + + // return a UTF-16 value + void Foo::GetUnicodeValue(nsAString& result) { + CopyUTF8toUTF16(mLocalUTF8Value, result); + } + +.. cpp:function:: AppendUTF8toUTF16(const nsACString&, nsAString&) + + converts and appends: + + .. code-block:: c++ + + // return a UTF-16 value + void Foo::GetUnicodeValue(nsAString& result) { + result.AssignLiteral("prefix:"); + AppendUTF8toUTF16(mLocalUTF8Value, result); + } + +.. cpp:function:: CopyUTF16toUTF8(const nsAString&, nsACString&) + + converts and copies: + + .. code-block:: c++ + + // return a UTF-8 value + void Foo::GetUTF8Value(nsACString& result) { + CopyUTF16toUTF8(mLocalUTF16Value, result); + } + +.. cpp:function:: AppendUTF16toUTF8(const nsAString&, nsACString&) + + converts and appends: + + .. code-block:: c++ + + // return a UTF-8 value + void Foo::GetUnicodeValue(nsACString& result) { + result.AssignLiteral("prefix:"); + AppendUTF16toUTF8(mLocalUTF16Value, result); + } + + +Latin1 / UTF-16 Conversion +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following should only be used when you can guarantee that the original +string is ASCII or Latin1 (in the sense that the byte value is the Unicode +scalar value; not in the windows-1252 sense). These helpers are very similar +to the UTF-8 / UTF-16 conversion helpers above. + + +UTF-16 to Latin1 converters +``````````````````````````` + +These converters are **very dangerous** because they **lose information** +during the conversion process. You should **avoid UTF-16 to Latin1 +conversions** unless your strings are guaranteed to be Latin1 or ASCII. (In +the future, these conversions may start asserting in debug builds that their +input is in the permissible range.) If the input is actually in the Latin1 +range, each 16-bit code unit in narrowed to an 8-bit byte by removing the +high half. Unicode code points above U+00FF result in garbage whose nature +must not be relied upon. (In the future the nature of the garbage will be CPU +architecture-dependent.) If you want to ``printf()`` something and don't care +what happens to non-ASCII, please convert to UTF-8 instead. + + +.. cpp:function:: NS_LossyConvertUTF16toASCII(const nsAString&) + + A ``nsAutoCString`` which holds a temporary buffer containing the Latin1 + value of the string. + +.. cpp:function:: void LossyCopyUTF16toASCII(Span, nsACString&) + + Does an in-place conversion from UTF-16 into an Latin1 string object. + +.. cpp:function:: void LossyAppendUTF16toASCII(Span, nsACString&) + + Appends a UTF-16 string to a Latin1 string. + +Latin1 to UTF-16 converters +``````````````````````````` + +These converters are very dangerous because they will **produce wrong results +for non-ASCII UTF-8 or windows-1252 input** into a meaningless UTF-16 string. +You should **avoid ASCII to UTF-16 conversions** unless your strings are +guaranteed to be ASCII or Latin1 in the sense of the byte value being the +Unicode scalar value. Every byte is zero-extended into a 16-bit code unit. + +It is correct to use these on most HTTP header values, but **it's always +wrong to use these on HTTP response bodies!** (Use ``mozilla::Encoding`` to +deal with response bodies.) + +.. cpp:function:: NS_ConvertASCIItoUTF16(const nsACString&) + + A ``nsAutoString`` which holds a temporary buffer containing the value of + the Latin1 to UTF-16 conversion. + +.. cpp:function:: void CopyASCIItoUTF16(Span, nsAString&) + + does an in-place conversion from Latin1 to UTF-16. + +.. cpp:function:: void AppendASCIItoUTF16(Span, nsAString&) + + appends a Latin1 string to a UTF-16 string. + +Comparing ns*Strings with C strings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can compare ``ns*Strings`` with C strings by converting the ``ns*String`` +to a C string, or by comparing directly against a C String. + +.. cpp:function:: bool nsAString::EqualsASCII(const char*) + + Compares with an ASCII C string. + +.. cpp:function:: bool nsAString::EqualsLiteral(...) + + Compares with a string literal. + +Common Patterns +--------------- + +Literal Strings +~~~~~~~~~~~~~~~ + +A literal string is a raw string value that is written in some C++ code. For +example, in the statement ``printf("Hello World\n");`` the value ``"Hello +World\n"`` is a literal string. It is often necessary to insert literal +string values when an ``nsAString`` or ``nsACString`` is required. Two +user-defined literals are provided that implicitly convert to ``const +nsString&`` resp. ``const nsCString&``: + +* ``""_ns`` for 8-bit literals, converting implicitly to ``const nsCString&`` +* ``u""_ns`` for 16-bit literals, converting implicitly to ``const nsString&`` + +The benefits of the user-defined literals may seem unclear, given that +``nsDependentCString`` will also wrap a string value in an ``nsCString``. The +advantage of the user-defined literals is twofold. + +* The length of these strings is calculated at compile time, so the string does + not need to be scanned at runtime to determine its length. + +* Literal strings live for the lifetime of the binary, and can be moved between + the ``ns[C]String`` classes without being copied or freed. + +Here are some examples of proper usage of the literals (both standard and +user-defined): + +.. code-block:: c++ + + // call Init(const nsLiteralString&) - enforces that it's only called with literals + Init(u"start value"_ns); + + // call Init(const nsAString&) + Init(u"start value"_ns); + + // call Init(const nsACString&) + Init("start value"_ns); + +In case a literal is defined via a macro, you can just convert it to +``nsLiteralString`` or ``nsLiteralCString`` using their constructor. You +could consider not using a macro at all but a named ``constexpr`` constant +instead. + +In some cases, an 8-bit literal is defined via a macro, either within code or +from the environment, but it can't be changed or is used both as an 8-bit and +a 16-bit string. In these cases, you can use the +``NS_LITERAL_STRING_FROM_CSTRING`` macro to construct a ``nsLiteralString`` +and do the conversion at compile-time. + +String Concatenation +~~~~~~~~~~~~~~~~~~~~ + +Strings can be concatenated together using the + operator. The resulting +string is a ``const nsSubstringTuple`` object. The resulting object can be +treated and referenced similarly to a ``nsAString`` object. Concatenation *does +not copy the substrings*. The strings are only copied when the concatenation +is assigned into another string object. The ``nsSubstringTuple`` object holds +pointers to the original strings. Therefore, the ``nsSubstringTuple`` object is +dependent on all of its substrings, meaning that their lifetime must be at +least as long as the ``nsSubstringTuple`` object. + +For example, you can use the value of two strings and pass their +concatenation on to another function which takes an ``const nsAString&``: + +.. code-block:: c++ + + void HandleTwoStrings(const nsAString& one, const nsAString& two) { + // call HandleString(const nsAString&) + HandleString(one + two); + } + +NOTE: The two strings are implicitly combined into a temporary ``nsString`` +in this case, and the temporary string is passed into ``HandleString``. If +``HandleString`` assigns its input into another ``nsString``, then the string +buffer will be shared in this case negating the cost of the intermediate +temporary. You can concatenate N strings and store the result in a temporary +variable: + +.. code-block:: c++ + + constexpr auto start = u"start "_ns; + constexpr auto middle = u"middle "_ns; + constexpr auto end = u"end"_ns; + // create a string with 3 dependent fragments - no copying involved! + nsString combinedString = start + middle + end; + + // call void HandleString(const nsAString&); + HandleString(combinedString); + +It is safe to concatenate user-defined literals because the temporary +``nsLiteral[C]String`` objects will live as long as the temporary +concatenation object (of type ``nsSubstringTuple``). + +.. code-block:: c++ + + // call HandlePage(const nsAString&); + // safe because the concatenated-string will live as long as its substrings + HandlePage(u"start "_ns + u"end"_ns); + +Local Variables +~~~~~~~~~~~~~~~ + +Local variables within a function are usually stored on the stack. The +``nsAutoString``/``nsAutoCString`` classes are subclasses of the +``nsString``/``nsCString`` classes. They own a 64-character buffer allocated +in the same storage space as the string itself. If the ``nsAutoString`` is +allocated on the stack, then it has at its disposal a 64-character stack +buffer. This allows the implementation to avoid allocating extra memory when +dealing with small strings. ``nsAutoStringN``/``nsAutoCStringN`` are more +general alternatives that let you choose the number of characters in the +inline buffer. + +.. code-block:: c++ + + ... + nsAutoString value; + GetValue(value); // if the result is less than 64 code units, + // then this just saved us an allocation + ... + +Member Variables +~~~~~~~~~~~~~~~~ + +In general, you should use the concrete classes ``nsString`` and +``nsCString`` for member variables. + +.. code-block:: c++ + + class Foo { + ... + // these store UTF-8 and UTF-16 values respectively + nsCString mLocalName; + nsString mTitle; + }; + +A common incorrect pattern is to use ``nsAutoString``/``nsAutoCString`` +for member variables. As described in `Local Variables`_, these classes have +a built in buffer that make them very large. This means that if you include +them in a class, they bloat the class by 64 bytes (``nsAutoCString``) or 128 +bytes (``nsAutoString``). + + +Raw Character Pointers +~~~~~~~~~~~~~~~~~~~~~~ + +``PromiseFlatString()`` and ``PromiseFlatCString()`` can be used to create a +temporary buffer which holds a null-terminated buffer containing the same +value as the source string. ``PromiseFlatString()`` will create a temporary +buffer if necessary. This is most often used in order to pass an +``nsAString`` to an API which requires a null-terminated string. + +In the following example, an ``nsAString`` is combined with a literal string, +and the result is passed to an API which requires a simple character buffer. + +.. code-block:: c++ + + // Modify the URL and pass to AddPage(const char16_t* url) + void AddModifiedPage(const nsAString& url) { + constexpr auto httpPrefix = u"http://"_ns; + const nsAString& modifiedURL = httpPrefix + url; + + // creates a temporary buffer + AddPage(PromiseFlatString(modifiedURL).get()); + } + +``PromiseFlatString()`` is smart when handed a string that is already +null-terminated. It avoids creating the temporary buffer in such cases. + +.. code-block:: c++ + + // Modify the URL and pass to AddPage(const char16_t* url) + void AddModifiedPage(const nsAString& url, PRBool addPrefix) { + if (addPrefix) { + // MUST create a temporary buffer - string is multi-fragmented + constexpr auto httpPrefix = u"http://"_ns; + AddPage(PromiseFlatString(httpPrefix + modifiedURL)); + } else { + // MIGHT create a temporary buffer, does a runtime check + AddPage(PromiseFlatString(url).get()); + } + } + +.. note:: + + It is **not** possible to efficiently transfer ownership of a string + class' internal buffer into an owned ``char*`` which can be safely + freed by other components due to the COW optimization. + + If working with a legacy API which requires malloced ``char*`` buffers, + prefer using ``ToNewUnicode``, ``ToNewCString`` or ``ToNewUTF8String`` + over ``strdup`` to create owned ``char*`` pointers. + +``printf`` and a UTF-16 string +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For debugging, it's useful to ``printf`` a UTF-16 string (``nsString``, +``nsAutoString``, etc). To do this usually requires converting it to an 8-bit +string, because that's what ``printf`` expects. Use: + +.. code-block:: c++ + + printf("%s\n", NS_ConvertUTF16toUTF8(yourString).get()); + +Sequence of appends without reallocating +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``SetCapacity()`` allows you to give the string a hint of the future string +length caused by a sequence of appends (excluding appends that convert +between UTF-16 and UTF-8 in either direction) in order to avoid multiple +allocations during the sequence of appends. However, the other +allocation-avoidance features of XPCOM strings interact badly with +``SetCapacity()`` making it something of a footgun. + +``SetCapacity()`` is appropriate to use before a sequence of multiple +operations from the following list (without operations that are not on the +list between the ``SetCapacity()`` call and operations from the list): + +* ``Append()`` +* ``AppendASCII()`` +* ``AppendLiteral()`` +* ``AppendPrintf()`` +* ``AppendInt()`` +* ``AppendFloat()`` +* ``LossyAppendUTF16toASCII()`` +* ``AppendASCIItoUTF16()`` + +**DO NOT** call ``SetCapacity()`` if the subsequent operations on the string +do not meet the criteria above. Operations that undo the benefits of +``SetCapacity()`` include but are not limited to: + +* ``SetLength()`` +* ``Truncate()`` +* ``Assign()`` +* ``AssignLiteral()`` +* ``Adopt()`` +* ``CopyASCIItoUTF16()`` +* ``LossyCopyUTF16toASCII()`` +* ``AppendUTF16toUTF8()`` +* ``AppendUTF8toUTF16()`` +* ``CopyUTF16toUTF8()`` +* ``CopyUTF8toUTF16()`` + +If your string is an ``nsAuto[C]String`` and you are calling +``SetCapacity()`` with a constant ``N``, please instead declare the string as +``nsAuto[C]StringN`` without calling ``SetCapacity()`` (while being +mindful of not using such a large ``N`` as to overflow the run-time stack). + +There is no need to include room for the null terminator: it is the job of +the string class. + +Note: Calling ``SetCapacity()`` does not give you permission to use the +pointer obtained from ``BeginWriting()`` to write past the current length (as +returned by ``Length()``) of the string. Please use either ``BulkWrite()`` or +``SetLength()`` instead. + +.. _stringguide.xpidl: + +XPIDL +----- + +The string library is also available through IDL. By declaring attributes and +methods using the specially defined IDL types, string classes are used as +parameters to the corresponding methods. + +XPIDL String types +~~~~~~~~~~~~~~~~~~ + +The C++ signatures follow the abstract-type convention described above, such +that all method parameters are based on the abstract classes. The following +table describes the purpose of each string type in IDL. + ++-----------------+----------------+----------------------------------------------------------------------------------+ +| XPIDL Type | C++ Type | Purpose | ++=================+================+==================================================================================+ +| ``string`` | ``char*`` | Raw character pointer to ASCII (7-bit) string, no string classes used. | +| | | | +| | | High bit is not guaranteed across XPConnect boundaries. | ++-----------------+----------------+----------------------------------------------------------------------------------+ +| ``wstring`` | ``char16_t*`` | Raw character pointer to UTF-16 string, no string classes used. | ++-----------------+----------------+----------------------------------------------------------------------------------+ +| ``AString`` | ``nsAString`` | UTF-16 string. | ++-----------------+----------------+----------------------------------------------------------------------------------+ +| ``ACString`` | ``nsACString`` | 8-bit string. All bits are preserved across XPConnect boundaries. | ++-----------------+----------------+----------------------------------------------------------------------------------+ +| ``AUTF8String`` | ``nsACString`` | UTF-8 string. | +| | | | +| | | Converted to UTF-16 as necessary when value is used across XPConnect boundaries. | ++-----------------+----------------+----------------------------------------------------------------------------------+ + +Callers should prefer using the string classes ``AString``, ``ACString`` and +``AUTF8String`` over the raw pointer types ``string`` and ``wstring`` in +almost all situations. + +C++ Signatures +~~~~~~~~~~~~~~ + +In XPIDL, ``in`` parameters are read-only, and the C++ signatures for +``*String`` parameters follows the above guidelines by using ``const +nsAString&`` for these parameters. ``out`` and ``inout`` parameters are +defined simply as ``nsAString&`` so that the callee can write to them. + +.. code-block:: + + interface nsIFoo : nsISupports { + attribute AString utf16String; + AUTF8String getValue(in ACString key); + }; + +.. code-block:: c++ + + class nsIFoo : public nsISupports { + NS_IMETHOD GetUtf16String(nsAString& aResult) = 0; + NS_IMETHOD SetUtf16String(const nsAString& aValue) = 0; + NS_IMETHOD GetValue(const nsACString& aKey, nsACString& aResult) = 0; + }; + +In the above example, ``utf16String`` is treated as a UTF-16 string. The +implementation of ``GetUtf16String()`` will use ``aResult.Assign`` to +"return" the value. In ``SetUtf16String()`` the value of the string can be +used through a variety of methods including `Iterators`_, +``PromiseFlatString``, and assignment to other strings. + +In ``GetValue()``, the first parameter, ``aKey``, is treated as a raw +sequence of 8-bit values. Any non-ASCII characters in ``aKey`` will be +preserved when crossing XPConnect boundaries. The implementation of +``GetValue()`` will assign a UTF-8 encoded 8-bit string into ``aResult``. If +the this method is called across XPConnect boundaries, such as from a script, +then the result will be decoded from UTF-8 into UTF-16 and used as a Unicode +value. + +String Guidelines +----------------- + +Follow these simple rules in your code to keep your fellow developers, +reviewers, and users happy. + +* Use the most abstract string class that you can. Usually this is: + * ``nsAString`` for function parameters + * ``nsString`` for member variables + * ``nsAutoString`` for local (stack-based) variables +* Use the ``""_ns`` and ``u""_ns`` user-defined literals to represent literal strings (e.g. ``"foo"_ns``) as nsAString-compatible objects. +* Use string concatenation (i.e. the "+" operator) when combining strings. +* Use ``nsDependentString`` when you have a raw character pointer that you need to convert to an nsAString-compatible string. +* Use ``Substring()`` to extract fragments of existing strings. +* Use `iterators`_ to parse and extract string fragments. + +Class Reference +--------------- + +.. cpp:class:: template nsTSubstring + + .. note:: + + The ``nsTSubstring`` class is usually written as + ``nsAString`` or ``nsACString``. + + .. cpp:function:: size_type Length() const + + .. cpp:function:: bool IsEmpty() const + + .. cpp:function:: bool IsVoid() const + + .. cpp:function:: const char_type* BeginReading() const + + .. cpp:function:: const char_type* EndReading() const + + .. cpp:function:: bool Equals(const self_type&, comparator_type = ...) const + + .. cpp:function:: char_type First() const + + .. cpp:function:: char_type Last() const + + .. cpp:function:: size_type CountChar(char_type) const + + .. cpp:function:: int32_t FindChar(char_type, index_type aOffset = 0) const + + .. cpp:function:: void Assign(const self_type&) + + .. cpp:function:: void Append(const self_type&) + + .. cpp:function:: void Insert(const self_type&, index_type aPos) + + .. cpp:function:: void Cut(index_type aCutStart, size_type aCutLength) + + .. cpp:function:: void Replace(index_type aCutStart, size_type aCutLength, const self_type& aStr) + + .. cpp:function:: void Truncate(size_type aLength) + + .. cpp:function:: void SetIsVoid(bool) + + Make it null. XPConnect and WebIDL will convert void nsAStrings to + JavaScript ``null``. + + .. cpp:function:: char_type* BeginWriting() + + .. cpp:function:: char_type* EndWriting() + + .. cpp:function:: void SetCapacity(size_type) + + Inform the string about buffer size need before a sequence of calls + to ``Append()`` or converting appends that convert between UTF-16 and + Latin1 in either direction. (Don't use if you use appends that + convert between UTF-16 and UTF-8 in either direction.) Calling this + method does not give you permission to use ``BeginWriting()`` to + write past the logical length of the string. Use ``SetLength()`` or + ``BulkWrite()`` as appropriate. + + .. cpp:function:: void SetLength(size_type) + + .. cpp:function:: Result, nsresult> BulkWrite(size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking) + + +Original Document Information +----------------------------- + +This document was originally hosted on MDN as part of the XPCOM guide. + +* Author: `Alec Flett `_ +* Copyright Information: Portions of this content are © 1998–2007 by individual mozilla.org contributors; content available under a Creative Commons license. +* Thanks to David Baron for `actual docs `_, +* Peter Annema for lots of direction +* Myk Melez for some more docs +* David Bradley for a diagram +* Revised by Darin Fisher for Mozilla 1.7 +* Revised by Jungshik Shin to clarify character encoding issues +* Migrated to in-tree documentation by Nika Layzell diff --git a/xpcom/docs/thread-safety.rst b/xpcom/docs/thread-safety.rst new file mode 100644 index 0000000000..be2f156804 --- /dev/null +++ b/xpcom/docs/thread-safety.rst @@ -0,0 +1,354 @@ +**Thread safety analysis in Gecko** +=================================== + +Clang thread-safety analysis is supported in Gecko. This means +builds will generate warnings when static analysis detects an issue with +locking of mutex/monitor-protected members and data structures. Note +that Chrome uses the same feature. An example warning: :: + + warning: dom/media/AudioStream.cpp:504:22 [-Wthread-safety-analysis] + reading variable 'mDataSource' requires holding mutex 'mMonitor' + +If your patch causes warnings like this, you’ll need to resolve them; +they will be errors on checkin. + +This analysis depends on thread-safety attributions in the source. These +have been added to Mozilla’s Mutex and Monitor classes and subclasses, +but in practice the analysis is largely dependent on additions to the +code being checked, in particular adding MOZ_GUARDED_BY(mutex) attributions +on the definitions of member variables. Like this: :: + + mozilla::Mutex mLock; + bool mShutdown MOZ_GUARDED_BY(mLock); + +For background on Clang’s thread-safety support, see `their +documentation `__. + +Newly added Mutexes and Monitors **MUST** use thread-safety annotations, +and we are enabling static checks to verify this. Legacy uses of Mutexes +and Monitors are marked with MOZ_UNANNOTATED. + +If you’re modifying code that has been annotated with +MOZ_GUARDED_BY()/MOZ_REQUIRES()/etc, you should **make sure that the annotations +are updated properly**; e.g. if you change the thread-usage of a member +variable or method you should mark it accordingly, comment, and resolve +any warnings. Since the warnings will be errors in autoland/m-c, you +won’t be able to land code with active warnings. + +**Annotating locking and usage requirements in class definitions** +------------------------------------------------------------------ + +Values that require a lock to access, or which are simply used from more +than one thread, should always have documentation in the definition +about the locking requirements and/or what threads it’s touched from: :: + + // This array is accessed from both the direct video thread, and the graph + // thread. Protected by mMutex. + + nsTArray> mFrames + MOZ_GUARDED_BY(mMutex); + + // Set on MainThread, deleted on either MainThread mainthread, used on + // MainThread or IO Thread in DoStopSession + nsCOMPtr mReconnectDelayTimer MOZ_GUARDED_BY(mMutex); + +It’s strongly recommended to group values by access pattern, but it’s +**critical** to make it clear what the requirements to access a value +are. With values protected by Mutexes and Monitors, adding a +MOZ_GUARDED_BY(mutex/monitor) should be sufficient, though you may want to +also document what threads access it, and if they read or write to it. + +Values which have more complex access requirements (see single-writer +and time-based-locking below) need clear documentation where they’re +defined: :: + + MutexSingleWriter mMutex; + + // mResource should only be modified on the main thread with the lock. + // So it can be read without lock on the main thread or on other threads + // with the lock. + RefPtr mResource MOZ_GUARDED_BY(mMutex); + +**WARNING:** thread-safety analysis is not magic; it depends on you telling +it the requirements around access. If you don’t mark something as +MOZ_GUARDED_BY() it won’t figure it out for you, and you can end up with a data +race. When writing multithreaded code, you should always be thinking about +which threads can access what and when, and document this. + +**How to annotate different locking patterns in Gecko** +------------------------------------------------------- + +Gecko uses a number of different locking patterns. They include: + +- **Always Lock** - + Multiple threads may read and write the value + +- **Single Writer** - + One thread does all the writing, other threads + read the value, but code on the writing thread also reads it + without the lock + +- **Out-of-band invariants** - + A value may be accessed from other threads, + but only after or before certain other events or in a certain state, + like when after a listener has been added or before a processing + thread has been shut down. + +The simplest and easiest to check with static analysis is **Always +Lock**, and generally you should prefer this pattern. This is very +simple; you add MOZ_GUARDED_BY(mutex/monitor), and must own the lock to +access the value. This can be implemented by some combination of direct +Lock/AutoLock calls in the method; an assertion that the lock is already +held by the current thread, or annotating the method as requiring the +lock (MOZ_REQUIRES(mutex)) in the method definition: :: + + // Ensures mSize is initialized, if it can be. + // mLock must be held when this is called, and mInput must be non-null. + void EnsureSizeInitialized() MOZ_REQUIRES(mLock); + ... + // This lock protects mSeekable, mInput, mSize, and mSizeInitialized. + Mutex mLock; + int64_t mSize MOZ_GUARDED_BY(mLock); + +**Single Writer** is tricky for static analysis normally, since it +doesn’t know what thread an access will occur on. In general, you should +prefer using Always Lock in non-performance-sensitive code, especially +since these mutexes are almost always uncontended and therefore cheap to +lock. + +To support this fairly common pattern in Mozilla code, we’ve added +MutexSingleWriter and MonitorSingleWriter subclasses. To use these, you +need to subclass SingleWriterLockOwner on one object (typically the +object containing the Mutex), implement ::OnWritingThread(), and pass +the object to the constructor for MutexSingleWriter. In code that +accesses the guarded value from the writing thread, you need to add +mMutex.AssertIsOnWritingThread(), which both does a debug-only runtime +assertion by calling OnWritingThread(), and also asserts to the static +analyzer that the lock is held (which it isn’t). + +There is one case this causes problems with: when a method needs to +access the value (without the lock), and then decides to write to the +value from the same method, taking the lock. To the static analyzer, +this looks like a double-lock. Either you will need to add +MOZ_NO_THREAD_SAFETY_ANALYSIS to the method, move the write into another +method you call, or locally disable the warning with +MOZ_PUSH_IGNORE_THREAD_SAFETY and MOZ_POP_THREAD_SAFETY. We’re discussing with +the clang static analysis developers how to better handle this. + +Note also that this provides no checking that the lock is taken to write +to the value: :: + + MutexSingleWriter mMutex; + // mResource should only be modified on the main thread with the lock. + // So it can be read without lock on the main thread or on other threads + // with the lock. + RefPtr mResource MOZ_GUARDED_BY(mMutex); + ... + nsresult ChannelMediaResource::Listener::OnStartRequest(nsIRequest *aRequest) { + mMutex.AssertOnWritingThread(); + + // Read from the only writing thread; no lock needed + if (!mResource) { + return NS_OK; + } + return mResource->OnStartRequest(aRequest, mOffset); + } + +If you need to assert you’re on the writing thread, then later take a +lock to modify a value, it will cause a warning: â€acquiring mutex +'mMutex' that is already heldâ€. You can resolve this by turning off +thread-safety analysis for the lock: :: + + mMutex.AssertOnWritingThread(); + ... + { + MOZ_PUSH_IGNORE_THREAD_SAFETY + MutexSingleWriterAutoLock lock(mMutex); + MOZ_POP_THREAD_SAFETY + +**Out-of-band Invariants** is used in a number of places (and may be +combined with either of the above patterns). It's using other knowledge +about the execution pattern of the code to assert that it's safe to avoid +taking certain locks. A primary example is when a value can +only be accessed from a single thread for part of its lifetime (this can +also be referred to as "time-based locking"). + +Note that thread-safety analysis always ignores constructors and destructors +(which shouldn’t have races with other threads barring really odd usages). +Since only a single thread can access during those time periods, locking is +not required there. However, if a method is called from a constructor, +that method may generate warnings since the compiler doesn't know if it +might be called from elsewhere: :: + + ... + class nsFoo { + public: + nsFoo() { + mBar = true; // Ok since we're in the constructor, no warning + Init(); + } + void Init() { // we're only called from the constructor + // This causes a thread-safety warning, since the compiler + // can't prove that Init() is only called from the constructor + mQuerty = true; + } + ... + mMutex mMutex; + uint32_t mBar MOZ_GUARDED_BY(mMutex); + uint32_t mQuerty MOZ_GUARDED_BY(mMutex); + } + +Another example might be a value that’s used from other threads, but only +if an observer has been installed. Thus code that always runs before the +observer is installed, or after it’s removed, does not need to lock. + +These patterns are impossible to statically check in most cases. If all +the periods where it’s accessed from one thread only are on the same +thread, you could use the Single Writer pattern support to cover this +case. You would add AssertIsOnWritingThread() calls to methods that meet +the criteria that only a single thread can access the value (but only if +that holds). Unlike regular uses of SingleWriter, however, there’s no way +to check if you added such an assertion to code that runs on the “right†+thread, but during a period where another thread might modify it. + +For this reason, we **strongly** suggest that you convert cases of +Out-of-band-invariants/Time-based-locking to Always Lock if you’re +refactoring the code or making major modifications. This is far less prone +to error, and also to future changes breaking the assumptions about other +threads accessing the value. In all but a few cases where code is on a very +‘hot’ path, this will have no impact on performance - taking an uncontended +lock is cheap. + +To quiet warnings where these patterns are in use, you'll need to either +add locks (preferred), or suppress the warnings with MOZ_NO_THREAD_SAFETY_ANALYSIS or +MOZ_PUSH_IGNORE_THREAD_SAFETY/MOZ_POP_THREAD_SAFETY. + +**This pattern especially needs good documentation in the code as to what +threads will access what members under what conditions!**:: + + // Can't be accessed by multiple threads yet + nsresult nsAsyncStreamCopier::InitInternal(nsIInputStream* source, + nsIOutputStream* sink, + nsIEventTarget* target, + uint32_t chunkSize, + bool closeSource, + bool closeSink) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + +and:: + + // We can't be accessed by another thread because this hasn't been + // added to the public list yet + MOZ_PUSH_IGNORE_THREAD_SAFETY + mRestrictedPortList.AppendElement(gBadPortList[i]); + MOZ_POP_THREAD_SAFETY + +and:: + + // This is called on entries in another entry's mCallback array, under the lock + // of that other entry. No other threads can access this entry at this time. + bool CacheEntry::Callback::DeferDoom(bool* aDoom) const { + +**Known limitations** +--------------------- + +**Static analysis can’t handle all reasonable patterns.** In particular, +per their documentation, it can’t handle conditional locks, like: :: + + if (OnMainThread()) { + mMutex.Lock(); + } + +You should resolve this either via MOZ_NO_THREAD_SAFETY_ANALYSIS on the +method, or MOZ_PUSH_IGNORE_THREAD_SAFETY/MOZ_POP_THREAD_SAFETY. + +**Sometimes the analyzer can’t figure out that two objects are both the +same Mutex**, and it will warn you. You may be able to resolve this by +making sure you’re using the same pattern to access the mutex: :: + + mChan->mMonitor->AssertCurrentThreadOwns(); + ... + { + - MonitorAutoUnlock guard(*monitor); + + MonitorAutoUnlock guard(*(mChan->mMonitor.get())); // avoids mutex warning + ok = node->SendUserMessage(port, std::move(aMessage)); + } + +**Maybe** doesn’t tell the static analyzer when the mutex +is owned or freed; follow locking via the MayBe<> by +**mutex->AssertCurrentThreadOwns();** (and ditto for Monitors): :: + + Maybe lock(std::in_place, *mMonitor); + mMonitor->AssertCurrentThreadOwns(); // for threadsafety analysis + +If you reset() the Maybe<>, you may need to surround it with +MOZ_PUSH_IGNORE_THREAD_SAFETY and MOZ_POP_THREAD_SAFETY macros: :: + + MOZ_PUSH_IGNORE_THREAD_SAFETY + aLock.reset(); + MOZ_POP_THREAD_SAFETY + +**Passing a protected value by-reference** sometimes will confuse the +analyzer. Use MOZ_PUSH_IGNORE_THREAD_SAFETY and MOZ_POP_THREAD_SAFETY macros to +resolve this. + +**Classes which need thread-safety annotations** +------------------------------------------------ + +- Mutex + +- StaticMutex + +- RecursiveMutex + +- BaseProfilerMutex + +- Monitor + +- StaticMonitor + +- ReentrantMonitor + +- RWLock + +- Anything that hides an internal Mutex/etc and presents a Mutex-like + API (::Lock(), etc). + +**Additional Notes** +-------------------- + +Some code passes **Proof-of-Lock** AutoLock parameters, as a poor form of +static analysis. While it’s hard to make mistakes if you pass an AutoLock +reference, it is possible to pass a lock to the wrong Mutex/Monitor. + +Proof-of-lock is basically redundant to MOZ_REQUIRES() and obsolete, and +depends on the optimizer to remove it, and per above it can be misused, +with effort. With MOZ_REQUIRES(), any proof-of-lock parameters can be removed, +though you don't have to do so immediately. + +In any method taking an aProofOfLock parameter, add a MOZ_REQUIRES(mutex) to +the definition (and optionally remove the proof-of-lock), or add a +mMutex.AssertCurrentThreadOwns() to the method: :: + + nsresult DispatchLockHeld(already_AddRefed aRunnable, + - nsIEventTarget* aSyncLoopTarget, + - const MutexAutoLock& aProofOfLock); + + nsIEventTarget* aSyncLoopTarget) MOZ_REQUIRES(mMutex); + +or (if for some reason it's hard to specify the mutex in the header):: + + nsresult DispatchLockHeld(already_AddRefed aRunnable, + - nsIEventTarget* aSyncLoopTarget, + - const MutexAutoLock& aProofOfLock); + + nsIEventTarget* aSyncLoopTarget) { + + mMutex.AssertCurrentThreadOwns(); + +In addition to MOZ_GUARDED_BY() there’s also MOZ_PT_GUARDED_BY(), which says +that the pointer isn’t guarded, but the data pointed to by the pointer +is. + +Classes that expose a Mutex-like interface can be annotated like Mutex; +see some of the examples in the tree that use MOZ_CAPABILITY and +MOZ_ACQUIRE()/MOZ_RELEASE(). + +Shared locks are supported, though we don’t use them much. See RWLock. diff --git a/xpcom/docs/writing-xpcom-interface.rst b/xpcom/docs/writing-xpcom-interface.rst new file mode 100644 index 0000000000..9eeb1c72a2 --- /dev/null +++ b/xpcom/docs/writing-xpcom-interface.rst @@ -0,0 +1,287 @@ +.. _writing_xpcom_interface: + +Tutorial for Writing a New XPCOM Interface +========================================== + +High Level Overview +------------------- + +In order to write code that works in native code (C++, Rust), and JavaScript contexts, it's necessary to have a mechanism to do so. For chrome privileged contexts, this is the XPCOM Interface Class. + +This mechanism starts with an :ref:`XPIDL` file to define the shape of the interface. In the `build system`_, this file is processed, and `Rust`_ and `C++`_ code is automatically generated. + +.. _build system: https://searchfox.org/mozilla-central/source/xpcom/idl-parser/xpidl +.. _Rust: https://searchfox.org/mozilla-central/source/__GENERATED__/dist/xpcrs/rt +.. _C++: https://searchfox.org/mozilla-central/source/__GENERATED__/dist/include + +Next, the interface's methods and attributes must be implemented. This can be done through either a JSM module, or through a C++ interface class. Once these steps are done, the new files must be added to the appropriate :code:`moz.build` files to ensure the build system knows how to find them and process them. + +Often these XPCOM components are wired into the :code:`Services` JavaScript object to allow for ergonomic access to the interface. For example, open the `Browser Console`_ and type :code:`Services.` to interactively access these components. + +.. _Browser Console: https://developer.mozilla.org/en-US/docs/Tools/Browser_Console + +From C++, components can be accessed via :code:`mozilla::components::ComponentName::Create()` using the :code:`name` option in the :code:`components.conf`. + +While :code:`Services` and :code:`mozilla::components` are the preferred means of accessing components, many are accessed through the historical (and somewhat arcane) :code:`createInstance` mechanism. New usage of these mechanisms should be avoided if possible. + +.. code:: javascript + + let component = Cc["@mozilla.org/component-name;1"].createInstance( + Ci.nsIComponentName + ); + +.. code:: c++ + + nsCOMPtr component = do_CreateInstance( + "@mozilla.org/component-name;1"); + +Writing an XPIDL +---------------- + +First decide on a name. Conventionally the interfaces are prefixed with :code:`nsI` (historically Netscape) or :code:`mozI` as they are defined in the global namespace. While the interface is global, the implementation of an interface can be defined in a namespace with no prefix. Historically many component implementations still use the :code:`ns` prefixes (notice that the :code:`I` was dropped), but this convention is no longer needed. + +This tutorial assumes the component is located at :code:`path/to` with the name :code:`ComponentName`. The interface name will be :code:`nsIComponentName`, while the implementation will be :code:`mozilla::ComponentName`. + +To start, create an :ref:`XPIDL` file: + +.. code:: bash + + touch path/to/nsIComponentName.idl + +And hook it up to the :code:`path/to/moz.build` + +.. code:: python + + XPIDL_SOURCES += [ + "nsIComponentName.idl", + ] + +Next write the initial :code:`.idl` file: :code:`path/to/nsIComponentName.idl` + +.. _contract_ids: +.. code:: c++ + + /* 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/. */ + + // This is the base include which defines nsISupports. This class defines + // the QueryInterface method. + #include "nsISupports.idl" + + // `scriptable` designates that this object will be used with JavaScript + // `uuid` The example below uses a UUID with all Xs. Replace the Xs with + // your own UUID generated here: + // http://mozilla.pettay.fi/cgi-bin/mozuuid.pl + + /** + * Make sure to document your interface. + */ + [scriptable, uuid(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)] + interface nsIComponentName : nsISupports { + + // Fill out your definition here. This example attribute only returns a bool. + + /** + * Make sure to document your attributes. + */ + readonly attribute bool isAlive; + }; + +This definition only includes one attribute, :code:`isAlive`, which will demonstrate that we've done our work correctly at the end. For a more comprehensive guide for this syntax, see the :ref:`XPIDL` docs. + +Once :code:`./mach build` is run, the XPIDL parser will read this file, and give any warnings if the syntax is wrong. It will then auto-generate the C++ (or Rust) code for us. For this example the generated :code:`nsIComponentName` class will be located in: + +:code:`{obj-directory}/dist/include/nsIComponentName.h` + +It might be useful to check out what was automatically generated here, or see the existing `generated C++ header files on SearchFox `_. + +Writing the C++ implementation +------------------------------ + +Now we have a definition for an interface, but no implementation. The interface could be backed by a JavaScript implementation using a JSM, but for this example we'll use a C++ implementation. + +Add the C++ sources to :code:`path/to/moz.build` + +.. code:: python + + EXPORTS.mozilla += [ + "ComponentName.h", + ] + + UNIFIED_SOURCES += [ + "ComponentName.cpp", + ] + +Now write the header: :code:`path/to/ComponentName.h` + +.. code:: c++ + + /* 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/. */ + #ifndef mozilla_nsComponentName_h__ + #define mozilla_nsComponentName_h__ + + // This will pull in the header auto-generated by the .idl file: + // {obj-directory}/dist/include/nsIComponentName.h + #include "nsIComponentName.h" + + // The implementation can be namespaced, while the XPCOM interface is globally namespaced. + namespace mozilla { + + // Notice how the class name does not need to be prefixed, as it is defined in the + // `mozilla` namespace. + class ComponentName final : public nsIComponentName { + // This first macro includes the necessary information to use the base nsISupports. + // This includes the QueryInterface method. + NS_DECL_ISUPPORTS + + // This second macro includes the declarations for the attributes. There is + // no need to duplicate these declarations. + // + // In our case it includes a declaration for the isAlive attribute: + // GetIsAlive(bool *aIsAlive) + NS_DECL_NSICOMPONENTNAME + + public: + ComponentName() = default; + + private: + // A private destructor must be declared. + ~ComponentName() = default; + }; + + } // namespace mozilla + + #endif + +Now write the definitions: :code:`path/to/ComponentName.cpp` + +.. code:: c++ + + /* 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 "ComponentName.h" + + namespace mozilla { + + // Use the macro to inject all of the definitions for nsISupports. + NS_IMPL_ISUPPORTS(ComponentName, nsIComponentName) + + // This is the actual implementation of the `isAlive` attribute. Note that the + // method name is somewhat different than the attribute. We specified "read-only" + // in the attribute, so only a getter, not a setter was defined for us. Here + // the name was adjusted to be `GetIsAlive`. + // + // Another common detail of implementing an XPIDL interface is that the return values + // are passed as out parameters. The methods are treated as fallible, and the return + // value is an `nsresult`. See the XPIDL documentation for the full nitty gritty + // details. + // + // A common way to know the exact function signature for a method implementation is + // to copy and paste from existing examples, or inspecting the generated file + // directly: {obj-directory}/dist/include/nsIComponentName.h + NS_IMETHODIMP + ComponentName::GetIsAlive(bool* aIsAlive) { + *aIsAlive = true; + return NS_OK; + } + + } // namespace: mozilla + +Registering the component +------------------------- + +At this point, the component should be correctly written, but it's not registered with the component system. In order to this, we'll need to create or modify the :code:`components.conf`. + +.. code:: bash + + touch path/to/components.conf + + +Now update the :code:`moz.build` to point to it. + +.. code:: python + + XPCOM_MANIFESTS += [ + "components.conf", + ] + +It is probably worth reading over :ref:`defining_xpcom_components`, but the following config will be sufficient to hook up our component to the :code:`Services` object. +Services should also be added to ``tools/lint/eslint/eslint-plugin-mozilla/lib/services.json``. +The easiest way to do that is to copy from ``/xpcom/components/services.json``. + +.. code:: python + + Classes = [ + { + # This CID is the ID for component entries, and needs a separate UUID from + # the .idl file. Replace the Xs with a uuid from: + # http://mozilla.pettay.fi/cgi-bin/mozuuid.pl + 'cid': '{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}', + 'interfaces': ['nsIComponentName'], + + # A contract ID is a human-readable identifier for an _implementation_ of + # an XPCOM interface. + # + # "@mozilla.org/process/environment;1" + # ^^^^^^^^^^^^ ^^^^^^^ ^^^^^^^^^^^ ^ + # | | | | + # | | | The version number, usually just 1. + # | | Component name + # | Module + # Domain + # + # This design goes back to a time when XPCOM was intended to be a generalized + # solution for the Gecko Runtime Environment (GRE). At this point most (if + # not all) of mozilla-central has an @mozilla domain. + 'contract_ids': ['@mozilla.org/component-name;1'], + + # This is the name of the C++ type that implements the interface. + 'type': 'mozilla::ComponentName', + + # The header file to pull in for the implementation of the interface. + 'headers': ['path/to/ComponentName.h'], + + # In order to hook up this interface to the `Services` object, we can + # provide the "js_name" parameter. This is an ergonomic way to access + # the component. + 'js_name': 'componentName', + }, + ] + +At this point the full :code:`moz.build` file should look like: + +.. code:: python + + # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- + # vim: set filetype=python: + # 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/. + + XPIDL_SOURCES += [ + "nsIComponentName.idl", + ] + + XPCOM_MANIFESTS += [ + "components.conf", + ] + + EXPORTS.mozilla += [ + "ComponentName.h", + ] + + UNIFIED_SOURCES += [ + "ComponentName.cpp", + ] + +This completes the implementation of a basic XPCOM Interface using C++. The component should be available via the `Browser Console`_ or other chrome contexts. + +.. code:: javascript + + console.log(Services.componentName.isAlive); + > true diff --git a/xpcom/docs/xpidl.rst b/xpcom/docs/xpidl.rst new file mode 100644 index 0000000000..9f1a36ebb7 --- /dev/null +++ b/xpcom/docs/xpidl.rst @@ -0,0 +1,390 @@ +XPIDL +===== + +**XPIDL** is an Interface Description Language used to specify XPCOM interface +classes. + +Interface Description Languages (IDL) are used to describe interfaces in a +language- and machine-independent way. IDLs make it possible to define +interfaces which can then be processed by tools to autogenerate +language-dependent interface specifications. + +An xpidl file is essentially just a series of declarations. At the top level, +we can define typedefs, native types, or interfaces. Interfaces may +furthermore contain typedefs, natives, methods, constants, or attributes. +Most declarations can have properties applied to them. + +Types +----- + +There are three ways to make types: a typedef, a native, or an interface. In +addition, there are a few built-in native types. The built-in native types +are those listed under the type_spec production above. The following is the +correspondence table: + +=========================== =============== =========================== ============================ ======================= ======================= +IDL Type Javascript Type C++ in parameter C++ out parameter Rust in parameter Rust out parameter +=========================== =============== =========================== ============================ ======================= ======================= +``boolean`` boolean ``bool`` ``bool*`` ``bool`` ``*mut bool`` +``char`` string ``char`` ``char*`` ``c_char`` ``*mut c_char`` +``double`` number ``double`` ``double*`` ``f64`` ``*mut f64`` +``float`` number ``float`` ``float*`` ``f32`` ``*mut f32`` +``long`` number ``int32_t`` ``int32_t*`` ``i32`` ``*mut i32`` +``long long`` number ``int64_t`` ``int64_t*`` ``i64`` ``*mut i64`` +``octet`` number ``uint8_t`` ``uint8_t*`` ``u8`` ``*mut u8`` +``short`` number ``uint16_t`` ``uint16_t*`` ``u16`` ``*mut u16`` +``string`` [#strptr]_ string ``const char*`` ``char**`` ``*const c_char`` ``*mut *mut c_char`` +``unsigned long`` number ``uint32_t`` ``uint32_t*`` ``u32`` ``*mut u32`` +``unsigned long long`` number ``uint64_t`` ``uint64_t*`` ``u64`` ``*mut u64`` +``unsigned short`` number ``uint16_t`` ``uint16_t*`` ``u16`` ``*mut u16`` +``wchar`` string ``char16_t`` ``char16_t*`` ``i16`` ``*mut i16`` +``wstring`` [#strptr]_ string ``const char16_t*`` ``char16_t**`` ``*const i16`` ``*mut *mut i16`` +``MozExternalRefCountType`` number ``MozExternalRefCountType`` ``MozExternalRefCountType*`` ``u32`` ``*mut u32`` +``Array`` [#array]_ array ``const nsTArray&`` ``nsTArray&`` ``*const ThinVec`` ``*mut ThinVec`` +=========================== =============== =========================== ============================ ======================= ======================= + +.. [#strptr] + + Prefer using the string class types such as ``AString``, ``AUTF8String`` + or ``ACString`` to this type. The behaviour of these types is documented + more in the :ref:`String Guide ` + +.. [#array] + + The C++ or Rust exposed type ``T`` will be an owned variant. (e.g. + ``ns[C]String``, ``RefPtr``, or ``uint32_t``) + + ``string``, ``wstring``, ``[ptr] native`` and ``[ref] native`` are + unsupported as element types. + + +In addition to this list, nearly every IDL file includes ``nsrootidl.idl`` in +some fashion, which also defines the following types: + +======================= ======================= ======================= ======================= ======================= ======================= +IDL Type Javascript Type C++ in parameter C++ out parameter Rust in parameter Rust out parameter +======================= ======================= ======================= ======================= ======================= ======================= +``PRTime`` number ``uint64_t`` ``uint64_t*`` ``u64`` ``*mut u64`` +``nsresult`` number ``nsresult`` ``nsresult*`` ``u32`` [#rsresult]_ ``*mut u32`` +``size_t`` number ``uint32_t`` ``uint32_t*`` ``u32`` ``*mut u32`` +``voidPtr`` N/A ``void*`` ``void**`` ``*mut c_void`` ``*mut *mut c_void`` +``charPtr`` N/A ``char*`` ``char**`` ``*mut c_char`` ``*mut *mut c_char`` +``unicharPtr`` N/A ``char16_t*`` ``char16_t**`` ``*mut i16`` ``*mut *mut i16`` +``nsIDRef`` ID object ``const nsID&`` ``nsID*`` ``*const nsID`` ``*mut nsID`` +``nsIIDRef`` ID object ``const nsIID&`` ``nsIID*`` ``*const nsIID`` ``*mut nsIID`` +``nsCIDRef`` ID object ``const nsCID&`` ``nsCID*`` ``*const nsCID`` ``*mut nsCID`` +``nsIDPtr`` ID object ``const nsID*`` ``nsID**`` ``*const nsID`` ``*mut *mut nsID`` +``nsIIDPtr`` ID object ``const nsIID*`` ``nsIID**`` ``*const nsIID`` ``*mut *mut nsIID`` +``nsCIDPtr`` ID object ``const nsCID*`` ``nsCID**`` ``*const nsCID`` ``*mut *mut nsCID`` +``nsID`` N/A ``nsID`` ``nsID*`` N/A N/A +``nsIID`` N/A ``nsIID`` ``nsIID*`` N/A N/A +``nsCID`` N/A ``nsCID`` ``nsCID*`` N/A N/A +``nsQIResult`` object ``void*`` ``void**`` ``*mut c_void`` ``*mut *mut c_void`` +``AUTF8String`` [#str]_ string ``const nsACString&`` ``nsACString&`` ``*const nsACString`` ``*mut nsACString`` +``ACString`` [#str]_ string ``const nsACString&`` ``nsACString&`` ``*const nsACString`` ``*mut nsACString`` +``AString`` [#str]_ string ``const nsAString&`` ``nsAString&`` ``*const nsAString`` ``*mut nsAString`` +``jsval`` any ``HandleValue`` ``MutableHandleValue`` N/A N/A +``jsid`` N/A ``jsid`` ``jsid*`` N/A N/A +``Promise`` Promise object ``dom::Promise*`` ``dom::Promise**`` N/A N/A +======================= ======================= ======================= ======================= ======================= ======================= + +.. [#rsresult] + + A bare ``u32`` is only for bare ``nsresult`` in/outparams in XPIDL. The + result should be wrapped as the ``nserror::nsresult`` type. + +.. [#str] + + The behaviour of these types is documented more in the :ref:`String Guide + ` + +Typedefs in IDL are basically as they are in C or C++: you define first the +type that you want to refer to and then the name of the type. Types can of +course be one of the fundamental types, or any other type declared via a +typedef, interface, or a native type. + +Native types are types which correspond to a given C++ type. Most native +types are not scriptable: if it is not present in the list above, then it is +certainly not scriptable (some of the above, particularly jsid, are not +scriptable). + +The contents of the parentheses of a native type declaration (although native +declarations without parentheses are parsable, I do not trust that they are +properly handled by the xpidl handlers) is a string equivalent to the C++ +type. XPIDL itself does not interpret this string, it just literally pastes +it anywhere the native type is used. The interpretation of the type can be +modified by using the ``[ptr]`` or ``[ref]`` attributes on the native +declaration. Other attributes are only intended for use in ``nsrootidl.idl``. + +WebIDL Interfaces +~~~~~~~~~~~~~~~~~ + +WebIDL interfaces are also valid XPIDL types. To declare a WebIDL interface in +XPIDL, write: + +.. code-block:: + + webidl InterfaceName; + +WebIDL types will be passed as ``mozilla::dom::InterfaceName*`` when used as +in-parameters, as ``mozilla::dom::InterfaceName**`` when used as out or +inout-parameters, and as ``RefPtr`` when used as +an array element. + +.. note:: + + Other WebIDL types (e.g. dictionaries, enums, and unions) are not currently + supported. + +Constants and CEnums +~~~~~~~~~~~~~~~~~~~~ + +Constants must be attached to an interface. The only constants supported are +those which become integer types when compiled to source code; string constants +and floating point constants are currently not supported. + +Often constants are used to describe a set of enum values. In cases like this +the ``cenum`` construct can be used to group constants together. Constants +grouped in a ``cenum`` will be reflected as-if they were declared directly on +the interface, in Rust and Javascript code. + +.. code-block:: + + cenum MyCEnum : 8 { + eSomeValue, // starts at 0 + eSomeOtherValue, + }; + +The number after the enum name, like ``: 8`` in the example above, defines the +width of enum values with the given type. The cenum's type may be referenced in +xpidl as ``nsIInterfaceName_MyCEnum``. + +Interfaces +---------- + +Interfaces are basically a collection of constants, methods, and attributes. +Interfaces can inherit from one-another, and every interface must eventually +inherit from ``nsISupports``. + +Interface Attributes +~~~~~~~~~~~~~~~~~~~~ + +Interfaces may have the following attributes: + +``uuid`` +```````` + +The internal unique identifier for the interface. it must be unique, and the +uuid must be generated when creating the interface. After that, it doesn't need +to be changed any more. + +Online tools such as http://mozilla.pettay.fi/cgi-bin/mozuuid.pl can help +generate UUIDs for new interfaces. + +``builtinclass`` +```````````````` + +JavaScript classes are forbidden from implementing this interface. All child +interfaces must also be marked with this property. + +``function`` +```````````` + +The JavaScript implementation of this interface may be a function that is +invoked on property calls instead of an object with the given property + +``scriptable`` +`````````````` + +This interface is usable by JavaScript classes. Must inherit from a +``scriptable`` interface. + +Methods and Attributes +~~~~~~~~~~~~~~~~~~~~~~ + +Interfaces declare a series of attributes and methods. Attributes in IDL are +akin to JavaScript properties, in that they are a getter and (optionally) a +setter pair. In JavaScript contexts, attributes are exposed as a regular +property access, while native code sees attributes as a Get and possibly a Set +method. + +Attributes can be declared readonly, in which case setting causes an error to +be thrown in script contexts and native contexts lack the Set method, by using +the ``readonly`` keyword. + +To native code, on attribute declared ``attribute type foo;`` is syntactic +sugar for the declaration of two methods ``type getFoo();`` and ``void +setFoo(in type foo);``. If ``foo`` were declared readonly, the latter method +would not be present. Attributes support all of the properties of methods with +the exception of ``optional_argc``, as this does not make sense for attributes. + +There are some special rules for attribute naming. As a result of vtable +munging by the MSVC++ compiler, an attribute with the name ``IID`` is +forbidden. Also like methods, if the first character of an attribute is +lowercase in IDL, it is made uppercase in native code only. + +Methods define a return type and a series of in and out parameters. When called +from a JavaScript context, they invocation looks as it is declared for the most +part; some parameter properties can adjust what the code looks like. The calls +are more mangled in native contexts. + +An important attribute for methods and attributes is scriptability. A method or +attribute is scriptable if it is declared in a ``scriptable`` interface and it +lacks a ``noscript`` or ``notxpcom`` property. Any method that is not +scriptable can only be accessed by native code. However, ``scriptable`` methods +must contain parameters and a return type that can be translated to script: any +native type, save a few declared in ``nsrootidl.idl`` (see above), may not be +used in a scriptable method or attribute. An exception to the above rule is if +a ``nsQIResult`` parameter has the ``iid_is`` property (a special case for some +QueryInterface-like operations). + +Methods and attributes are mangled on conversion to native code. If a method is +declared ``notxpcom``, the mangling of the return type is prevented, so it is +called mostly as it looks. Otherwise, the return type of the native method is +``nsresult``, and the return type acts as a final outparameter if it is not +``void``. The name is translated so that the first character is +unconditionally uppercase; subsequent characters are unaffected. However, the +presence of the ``binaryname`` property allows the user to select another name +to use in native code (to avoid conflicts with other functions). For example, +the method ``[binaryname(foo)] void bar();`` becomes ``nsresult Foo()`` in +native code (note that capitalization is still applied). However, the +capitalization is not applied when using ``binaryname`` with attributes; i.e., +``[binaryname(foo)] readonly attribute Quux bar;`` becomes ``Getfoo(Quux**)`` +in native code. + +The ``implicit_jscontext`` and ``optional_argc`` parameters are properties +which help native code implementations determine how the call was made from +script. If ``implicit_jscontext`` is present on a method, then an additional +``JSContext* cx`` parameter is added just after the regular list which receives +the context of the caller. If ``optional_argc`` is present, then an additional +``uint8_t _argc`` parameter is added at the end which receives the number of +optional arguments that were actually used (obviously, you need to have an +optional argument in the first place). Note that if both properties are set, +the ``JSContext* cx`` is added first, followed by the ``uint8_t _argc``, and +then ending with return value parameter. Finally, as an exception to everything +already mentioned, for attribute getters and setters the ``JSContext *cx`` +comes before any other arguments. + +Another native-only property is ``nostdcall``. Normally, declarations are made +in the stdcall ABI on Windows to be ABI-compatible with COM interfaces. Any +non-scriptable method or attribute with ``nostdcall`` instead uses the +``thiscall`` ABI convention. Methods without this property generally use +``NS_IMETHOD`` in their declarations and ``NS_IMETHODIMP`` in their definitions +to automatically add in the stdcall declaration specifier on requisite +compilers; those that use this method may use a plain ``nsresult`` instead. + +Another property, ``infallible``, is attribute-only. When present, it causes an +infallible C++ getter function definition to be generated for the attribute +alongside the normal fallible C++ getter declaration. It should only be used if +the fallible getter will be infallible in practice (i.e. always return +``NS_OK``) for all possible implementations. This infallible getter contains +code that calls the fallible getter, asserts success, and returns the gotten +value directly. The point of using this property is to make C++ code nicer -- a +call to the infallible getter is more concise and readable than a call to the +fallible getter. This property can only be used for attributes having built-in +or interface types, and within classes that are marked with ``builtinclass``. +The latter restriction is because C++ implementations of fallible getters can +be audited for infallibility, but JS implementations can always throw (e.g. due +to OOM). + +The ``must_use`` property is useful if the result of a method call or an +attribute get/set should always (or usually) be checked, which is frequently +the case. (e.g. a method that opens a file should almost certainly have its +result checked.) This property will cause ``[[nodiscard]]`` to be added to the +generated function declarations, which means certain compilers (e.g. clang and +GCC) will reports errors if these results are not used. + +Method Parameters +~~~~~~~~~~~~~~~~~ + +Each method parameter can be specified in one of three modes: ``in``, ``out``, +or ``inout``. An ``out`` parameter is essentially an auxiliary return value, +although these are moderately cumbersome to use from script contexts and should +therefore be avoided if reasonable. An ``inout`` parameter is an in parameter +whose value may be changed as a result of the method; these parameters are +rather annoying to use and should generally be avoided if at all possible. + +``out`` and ``inout`` parameters are reflected as objects having the ``.value`` +property which contains the real value of the parameter; the ``value`` +attribute is missing in the case of ``out`` parameters and is initialized to +the passed-in-value for ``inout`` parameters. The script code needs to set this +property to assign a value to the parameter. Regular ``in`` parameters are +reflected more or less normally, with numeric types all representing numbers, +booleans as ``true`` or ``false``, the various strings (including ``AString`` +etc.) as a JavaScript string, and ``nsID`` types as a ``Components.ID`` +instance. In addition, the ``jsval`` type is translated as the appropriate +JavaScript value (since a ``jsval`` is the internal representation of all +JavaScript values), and parameters with the ``nsIVeriant`` interface have their +types automatically boxed and unboxed as appropriate. + +The equivalent representations of all IDL types in native code is given in the +earlier tables; parameters of type ``inout`` follow their ``out`` form. Native +code should pay particular attention to not passing in null values for out +parameters (although some parts of the codebase are known to violate this, it +is strictly enforced at the JS<->native barrier). + +Representations of types additionally depend on some of the many types of +properties they may have. The ``array`` property turns the parameter into an array; +the parameter must also have a corresponding ``size_is`` property whose argument is +the parameter that has the size of the array. In native code, the type gains +another pointer indirection, and JavaScript arrays are used in script code. +Script code callers can ignore the value of array parameter, but implementors +must still set the values appropriately. + +.. note:: + + Prefer using the ``Array`` builtin over the ``[array]`` attribute for + new code. It is more ergonomic to use from both JS and C++. In the future, + ``[array]`` may be deprecated and removed. + +The ``const`` and ``shared`` properties are special to native code. As its name +implies, the ``const`` property makes its corresponding argument ``const``. The +``shared`` property is only meaningful for ``out`` or ``inout`` parameters and +it means that the pointer value should not be freed by the caller. Only simple +native pointer types like ``string``, ``wstring``, and ``octetPtr`` may be +declared shared. The shared property also makes its corresponding argument +const. + +The ``retval`` property indicates that the parameter is actually acting as the +return value, and it is only the need to assign properties to the parameter +that is causing it to be specified as a parameter. It has no effect on native +code, but script code uses it like a regular return value. Naturally, a method +which contains a ``retval`` parameter must be declared ``void``, and the +parameter itself must be an ``out`` parameter and the last parameter. + +Other properties are the ``optional`` and ``iid_is`` property. The ``optional`` +property indicates that script code may omit the property without problems; all +subsequent parameters must either by optional themselves or the retval +parameter. Note that optional out parameters still pass in a variable for the +parameter, but its value will be ignored. The ``iid_is`` parameter indicates +that the real IID of an ``nsQIResult`` parameter may be found in the +corresponding parameter, to allow script code to automatically unbox the type. + +Not all type combinations are possible. Native types with the various string +properties are all forbidden from being used as an ``inout`` parameter or as an +``array`` parameter. In addition, native types with the ``nsid`` property but +lacking either a ``ptr`` or ``ref`` property are forbidden unless the method is +``notxpcom`` and it is used as an ``in`` parameter. + +Ownership Rules +``````````````` + +For types that reference heap-allocated data (strings, arrays, interface +pointers, etc), you must follow the XPIDL data ownership conventions in order +to avoid memory corruption and security vulnerabilities: + +* For ``in`` parameters, the caller allocates and deallocates all data. If the + callee needs to use the data after the call completes, it must make a private + copy of the data, or, in the case of interface pointers, ``AddRef`` it. +* For ``out`` parameters, the callee creates the data, and transfers ownership + to the caller. For buffers, the callee allocates the buffer with ``malloc``, + and the caller frees the buffer with ``free``. For interface pointers, the + callee does the ``AddRef`` on behalf of the caller, and the caller must call + ``Release``. This manual reference/memory management should be performed + using the ``getter_AddRefs`` and ``getter_Transfers`` helpers in new code. +* For ``inout`` parameters, the callee must clean up the old data if it chooses + to replace it. Buffers must be deallocated with ``free``, and interface + pointers must be ``Release``'d. Afterwards, the above rules for ``out`` + apply. +* ``shared`` out-parameters should not be freed, as they are intended to refer + to constant string literals. diff --git a/xpcom/ds/ArenaAllocator.h b/xpcom/ds/ArenaAllocator.h new file mode 100644 index 0000000000..76498986ce --- /dev/null +++ b/xpcom/ds/ArenaAllocator.h @@ -0,0 +1,214 @@ +/* -*- 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/. */ + +#ifndef mozilla_ArenaAllocator_h +#define mozilla_ArenaAllocator_h + +#include +#include + +#include "mozilla/Assertions.h" +#include "mozilla/fallible.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/Poison.h" +#include "mozilla/TemplateLib.h" +#include "nsDebug.h" + +namespace mozilla { + +/** + * A very simple arena allocator based on NSPR's PLArena. + * + * The arena allocator only provides for allocation, all memory is retained + * until the allocator is destroyed. It's useful for situations where a large + * amount of small transient allocations are expected. + * + * Example usage: + * + * // Define an allocator that is page sized and returns allocations that are + * // 8-byte aligned. + * ArenaAllocator<4096, 8> a; + * for (int i = 0; i < 1000; i++) { + * DoSomething(a.Allocate(i)); + * } + */ +template +class ArenaAllocator { + public: + constexpr ArenaAllocator() : mHead(), mCurrent(nullptr) { + static_assert(mozilla::tl::FloorLog2::value == + mozilla::tl::CeilingLog2::value, + "ArenaAllocator alignment must be a power of two"); + } + + ArenaAllocator(const ArenaAllocator&) = delete; + ArenaAllocator& operator=(const ArenaAllocator&) = delete; + + /** + * Frees all internal arenas but does not call destructors for objects + * allocated out of the arena. + */ + ~ArenaAllocator() { Clear(); } + + /** + * Fallibly allocates a chunk of memory with the given size from the internal + * arenas. If the allocation size is larger than the chosen arena a size an + * entire arena is allocated and used. + */ + MOZ_ALWAYS_INLINE void* Allocate(size_t aSize, const fallible_t&) { + MOZ_RELEASE_ASSERT(aSize, "Allocation size must be non-zero"); + return InternalAllocate(AlignedSize(aSize)); + } + + void* Allocate(size_t aSize) { + void* p = Allocate(aSize, fallible); + if (MOZ_UNLIKELY(!p)) { + NS_ABORT_OOM(std::max(aSize, ArenaSize)); + } + + return p; + } + + /** + * Frees all entries. The allocator can be reused after this is called. + * + * NB: This will not run destructors of any objects that were allocated from + * the arena. + */ + void Clear() { + // Free all chunks. + auto a = mHead.next; + while (a) { + auto tmp = a; + a = a->next; + free(tmp); + } + + // Reset the list head. + mHead.next = nullptr; + mCurrent = nullptr; + } + + /** + * Adjusts the given size to the required alignment. + */ + static constexpr size_t AlignedSize(size_t aSize) { + return (aSize + (Alignment - 1)) & ~(Alignment - 1); + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t s = 0; + for (auto arena = mHead.next; arena; arena = arena->next) { + s += aMallocSizeOf(arena); + } + + return s; + } + + void Check() { + if (mCurrent) { + mCurrent->canary.Check(); + } + } + + private: + struct ArenaHeader { + /** + * The location in memory of the data portion of the arena. + */ + uintptr_t offset; + /** + * The location in memory of the end of the data portion of the arena. + */ + uintptr_t tail; + }; + + struct ArenaChunk { + constexpr ArenaChunk() : header{0, 0}, next(nullptr) {} + + explicit ArenaChunk(size_t aSize) + : header{AlignedSize(uintptr_t(this + 1)), uintptr_t(this) + aSize}, + next(nullptr) {} + + CorruptionCanary canary; + ArenaHeader header; + ArenaChunk* next; + + /** + * Allocates a chunk of memory out of the arena and advances the offset. + */ + void* Allocate(size_t aSize) { + MOZ_ASSERT(aSize <= Available()); + char* p = reinterpret_cast(header.offset); + MOZ_RELEASE_ASSERT(p); + header.offset += aSize; + canary.Check(); + MOZ_MAKE_MEM_UNDEFINED(p, aSize); + return p; + } + + /** + * Calculates the amount of space available for allocation in this chunk. + */ + size_t Available() const { return header.tail - header.offset; } + }; + + /** + * Allocates an arena chunk of the given size and initializes its header. + */ + ArenaChunk* AllocateChunk(size_t aSize) { + static const size_t kOffset = AlignedSize(sizeof(ArenaChunk)); + MOZ_ASSERT(kOffset < aSize); + + const size_t chunkSize = aSize + kOffset; + void* p = malloc(chunkSize); + if (!p) { + return nullptr; + } + + ArenaChunk* arena = new (KnownNotNull, p) ArenaChunk(chunkSize); + MOZ_MAKE_MEM_NOACCESS((void*)arena->header.offset, + arena->header.tail - arena->header.offset); + + // Insert into the head of the list. + arena->next = mHead.next; + mHead.next = arena; + + // Only update |mCurrent| if this is a standard allocation, large + // allocations will always end up full so there's no point in updating + // |mCurrent| in that case. + if (aSize == ArenaSize - kOffset) { + mCurrent = arena; + } + + return arena; + } + + MOZ_ALWAYS_INLINE void* InternalAllocate(size_t aSize) { + static_assert(ArenaSize > AlignedSize(sizeof(ArenaChunk)), + "Arena size must be greater than the header size"); + + static const size_t kMaxArenaCapacity = + ArenaSize - AlignedSize(sizeof(ArenaChunk)); + + if (mCurrent && aSize <= mCurrent->Available()) { + return mCurrent->Allocate(aSize); + } + + ArenaChunk* arena = AllocateChunk(std::max(kMaxArenaCapacity, aSize)); + return arena ? arena->Allocate(aSize) : nullptr; + } + + ArenaChunk mHead; + ArenaChunk* mCurrent; +}; + +} // namespace mozilla + +#endif // mozilla_ArenaAllocator_h diff --git a/xpcom/ds/ArenaAllocatorExtensions.h b/xpcom/ds/ArenaAllocatorExtensions.h new file mode 100644 index 0000000000..60819381d5 --- /dev/null +++ b/xpcom/ds/ArenaAllocatorExtensions.h @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +#ifndef mozilla_ArenaAllocatorExtensions_h +#define mozilla_ArenaAllocatorExtensions_h + +#include "mozilla/ArenaAllocator.h" +#include "mozilla/CheckedInt.h" +#include "nsAString.h" + +/** + * Extensions to the ArenaAllocator class. + */ +namespace mozilla { + +namespace detail { + +template +T* DuplicateString(const T* aSrc, const CheckedInt& aLen, + ArenaAllocator& aArena); + +} // namespace detail + +/** + * Makes an arena allocated null-terminated copy of the source string. The + * source string must be null-terminated. + * + * @param aSrc String to copy. + * @param aArena The arena to allocate the string copy out of. + * @return An arena allocated null-terminated string. + */ +template +T* ArenaStrdup(const T* aStr, ArenaAllocator& aArena) { + return detail::DuplicateString(aStr, nsCharTraits::length(aStr), aArena); +} + +/** + * Makes an arena allocated null-terminated copy of the source string. + * + * @param aSrc String to copy. + * @param aArena The arena to allocate the string copy out of. + * @return An arena allocated null-terminated string. + */ +template +T* ArenaStrdup(const detail::nsTStringRepr& aStr, + ArenaAllocator& aArena) { + return detail::DuplicateString(aStr.BeginReading(), aStr.Length(), aArena); +} + +/** + * Copies the source string and adds a null terminator. Source string does not + * have to be null terminated. + */ +template +T* detail::DuplicateString(const T* aSrc, const CheckedInt& aLen, + ArenaAllocator& aArena) { + const auto byteLen = (aLen + 1) * sizeof(T); + if (!byteLen.isValid()) { + return nullptr; + } + + T* p = static_cast(aArena.Allocate(byteLen.value(), mozilla::fallible)); + if (p) { + memcpy(p, aSrc, byteLen.value() - sizeof(T)); + p[aLen.value()] = T(0); + } + + return p; +} + +} // namespace mozilla + +#endif // mozilla_ArenaAllocatorExtensions_h diff --git a/xpcom/ds/ArrayAlgorithm.h b/xpcom/ds/ArrayAlgorithm.h new file mode 100644 index 0000000000..2556eef465 --- /dev/null +++ b/xpcom/ds/ArrayAlgorithm.h @@ -0,0 +1,109 @@ +/* -*- 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/. */ + +#ifndef ArrayAlgorithm_h___ +#define ArrayAlgorithm_h___ + +#include "nsTArray.h" + +#include "mozilla/Algorithm.h" +#include "mozilla/ResultExtensions.h" + +namespace mozilla { + +// An algorithm similar to TransformAbortOnErr, which creates a new nsTArray +// rather than inserting into an output iterator. The capacity of the result +// array is set to the number of elements that will be inserted if all +// transformations are successful. This variant is fallible, i.e. if setting the +// capacity fails, NS_ERROR_OUT_OF_MEMORY is returned as an error value. This +// variant only works with nsresult errors. +template < + typename SrcIter, typename Transform, + typename = std::enable_if_t::result_err_type, + nsresult>>> +Result::result_ok_type>, + nsresult> +TransformIntoNewArrayAbortOnErr(SrcIter aIter, SrcIter aEnd, + Transform aTransform, fallible_t) { + nsTArray::result_ok_type> + res; + if (!res.SetCapacity(std::distance(aIter, aEnd), fallible)) { + return Err(NS_ERROR_OUT_OF_MEMORY); + } + + auto transformRes = TransformAbortOnErr(aIter, aEnd, MakeBackInserter(res), + std::move(aTransform)); + if (NS_WARN_IF(transformRes.isErr())) { + return Err(transformRes.unwrapErr()); + } + + return res; +} + +template +auto TransformIntoNewArrayAbortOnErr(SrcRange& aRange, Transform aTransform, + fallible_t) { + using std::begin; + using std::end; + return TransformIntoNewArrayAbortOnErr(begin(aRange), end(aRange), aTransform, + fallible); +} + +// An algorithm similar to std::transform, which creates a new nsTArray +// rather than inserting into an output iterator. The capacity of the result +// array is set to the number of elements that will be inserted. This variant is +// fallible, i.e. if setting the capacity fails, NS_ERROR_OUT_OF_MEMORY is +// returned as an error value. This variant only works with nsresult errors. +template +Result>, + nsresult> +TransformIntoNewArray(SrcIter aIter, SrcIter aEnd, Transform aTransform, + fallible_t) { + nsTArray> res; + if (!res.SetCapacity(std::distance(aIter, aEnd), fallible)) { + return Err(NS_ERROR_OUT_OF_MEMORY); + } + + std::transform(aIter, aEnd, MakeBackInserter(res), std::move(aTransform)); + + return res; +} + +template +auto TransformIntoNewArray(SrcRange& aRange, Transform aTransform, fallible_t) { + using std::begin; + using std::end; + return TransformIntoNewArray(begin(aRange), end(aRange), aTransform, + fallible); +} + +// An algorithm similar to std::transform, which creates a new nsTArray +// rather than inserting into an output iterator. The capacity of the result +// array is set to the number of elements that will be inserted. This variant is +// infallible, i.e. if setting the capacity fails, the process is aborted. +template +nsTArray> +TransformIntoNewArray(SrcIter aIter, SrcIter aEnd, Transform aTransform) { + nsTArray> res; + res.SetCapacity(std::distance(aIter, aEnd)); + + std::transform(aIter, aEnd, MakeBackInserter(res), std::move(aTransform)); + + return res; +} + +template +auto TransformIntoNewArray(SrcRange& aRange, Transform aTransform) { + using std::begin; + using std::end; + return TransformIntoNewArray(begin(aRange), end(aRange), aTransform); +} + +} // namespace mozilla + +#endif // !defined(ArrayAlgorithm_h___) diff --git a/xpcom/ds/ArrayIterator.h b/xpcom/ds/ArrayIterator.h new file mode 100644 index 0000000000..cc6c8d1cb4 --- /dev/null +++ b/xpcom/ds/ArrayIterator.h @@ -0,0 +1,165 @@ +/* -*- 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/. */ + +// Common iterator implementation for array classes e.g. nsTArray. + +#ifndef mozilla_ArrayIterator_h +#define mozilla_ArrayIterator_h + +#include +#include + +namespace mozilla { + +namespace detail { +template +struct AddInnerConst; + +template +struct AddInnerConst { + using Type = const T&; +}; + +template +struct AddInnerConst { + using Type = const T*; +}; + +template +using AddInnerConstT = typename AddInnerConst::Type; +} // namespace detail + +// We have implemented a custom iterator class for array rather than using +// raw pointers into the backing storage to improve the safety of C++11-style +// range based iteration in the presence of array mutation, or script execution +// (bug 1299489). +// +// Mutating an array which is being iterated is still wrong, and will either +// cause elements to be missed or firefox to crash, but will not trigger memory +// safety problems due to the release-mode bounds checking found in ElementAt. +// +// Dereferencing this iterator returns type Element. When Element is a reference +// type, this iterator implements the full standard random access iterator spec, +// and can be treated in many ways as though it is a pointer. Otherwise, it is +// just enough to be used in range-based for loop. +template +class ArrayIterator { + public: + typedef ArrayType array_type; + typedef ArrayIterator iterator_type; + typedef typename array_type::index_type index_type; + typedef std::remove_reference_t value_type; + typedef ptrdiff_t difference_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef std::random_access_iterator_tag iterator_category; + typedef ArrayIterator, ArrayType> + const_iterator_type; + + private: + const array_type* mArray; + index_type mIndex; + + public: + ArrayIterator() : mArray(nullptr), mIndex(0) {} + ArrayIterator(const iterator_type& aOther) + : mArray(aOther.mArray), mIndex(aOther.mIndex) {} + ArrayIterator(const array_type& aArray, index_type aIndex) + : mArray(&aArray), mIndex(aIndex) {} + + iterator_type& operator=(const iterator_type& aOther) { + mArray = aOther.mArray; + mIndex = aOther.mIndex; + return *this; + } + + constexpr operator const_iterator_type() const { + return mArray ? const_iterator_type{*mArray, mIndex} + : const_iterator_type{}; + } + + bool operator==(const iterator_type& aRhs) const { + return mIndex == aRhs.mIndex; + } + bool operator!=(const iterator_type& aRhs) const { return !(*this == aRhs); } + bool operator<(const iterator_type& aRhs) const { + return mIndex < aRhs.mIndex; + } + bool operator>(const iterator_type& aRhs) const { + return mIndex > aRhs.mIndex; + } + bool operator<=(const iterator_type& aRhs) const { + return mIndex <= aRhs.mIndex; + } + bool operator>=(const iterator_type& aRhs) const { + return mIndex >= aRhs.mIndex; + } + + // These operators depend on the release mode bounds checks in + // ArrayIterator::ElementAt for safety. + value_type* operator->() const { + return const_cast(&mArray->ElementAt(mIndex)); + } + Element operator*() const { + return const_cast(mArray->ElementAt(mIndex)); + } + + iterator_type& operator++() { + ++mIndex; + return *this; + } + iterator_type operator++(int) { + iterator_type it = *this; + ++*this; + return it; + } + iterator_type& operator--() { + --mIndex; + return *this; + } + iterator_type operator--(int) { + iterator_type it = *this; + --*this; + return it; + } + + iterator_type& operator+=(difference_type aDiff) { + mIndex += aDiff; + return *this; + } + iterator_type& operator-=(difference_type aDiff) { + mIndex -= aDiff; + return *this; + } + + iterator_type operator+(difference_type aDiff) const { + iterator_type it = *this; + it += aDiff; + return it; + } + iterator_type operator-(difference_type aDiff) const { + iterator_type it = *this; + it -= aDiff; + return it; + } + + difference_type operator-(const iterator_type& aOther) const { + return static_cast(mIndex) - + static_cast(aOther.mIndex); + } + + Element operator[](difference_type aIndex) const { + return *this->operator+(aIndex); + } + + constexpr const array_type* GetArray() const { return mArray; } + + constexpr index_type GetIndex() const { return mIndex; } +}; + +} // namespace mozilla + +#endif // mozilla_ArrayIterator_h diff --git a/xpcom/ds/Atom.py b/xpcom/ds/Atom.py new file mode 100644 index 0000000000..1399ba840a --- /dev/null +++ b/xpcom/ds/Atom.py @@ -0,0 +1,64 @@ +# 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/. + + +class Atom: + def __init__(self, ident, string, ty="nsStaticAtom"): + self.ident = ident + self.string = string + self.ty = ty + self.atom_type = self.__class__.__name__ + self.hash = hash_string(string) + self.is_ascii_lowercase = is_ascii_lowercase(string) + + +class PseudoElementAtom(Atom): + def __init__(self, ident, string): + Atom.__init__(self, ident, string, ty="nsCSSPseudoElementStaticAtom") + + +class AnonBoxAtom(Atom): + def __init__(self, ident, string): + Atom.__init__(self, ident, string, ty="nsCSSAnonBoxPseudoStaticAtom") + + +class NonInheritingAnonBoxAtom(AnonBoxAtom): + def __init__(self, ident, string): + AnonBoxAtom.__init__(self, ident, string) + + +class InheritingAnonBoxAtom(AnonBoxAtom): + def __init__(self, ident, string): + AnonBoxAtom.__init__(self, ident, string) + + +GOLDEN_RATIO_U32 = 0x9E3779B9 + + +def rotate_left_5(value): + return ((value << 5) | (value >> 27)) & 0xFFFFFFFF + + +def wrapping_multiply(x, y): + return (x * y) & 0xFFFFFFFF + + +# Calculate the precomputed hash of the static atom. This is a port of +# mozilla::HashString(const char16_t*), which is what we use for atomizing +# strings. An assertion in nsAtomTable::RegisterStaticAtoms ensures that +# the value we compute here matches what HashString() would produce. +def hash_string(s): + h = 0 + for c in s: + h = wrapping_multiply(GOLDEN_RATIO_U32, rotate_left_5(h) ^ ord(c)) + return h + + +# Returns true if lowercasing this string in an ascii-case-insensitive way +# would leave the string unchanged. +def is_ascii_lowercase(s): + for c in s: + if c >= "A" and c <= "Z": + return False + return True diff --git a/xpcom/ds/AtomArray.h b/xpcom/ds/AtomArray.h new file mode 100644 index 0000000000..5b1e0a0eae --- /dev/null +++ b/xpcom/ds/AtomArray.h @@ -0,0 +1,19 @@ +/* -*- 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/. */ + +#ifndef mozilla_AtomArray_h +#define mozilla_AtomArray_h + +#include "mozilla/RefPtr.h" +#include "nsTArray.h" + +class nsAtom; + +namespace mozilla { +typedef AutoTArray, 4> AtomArray; +} + +#endif // mozilla_AtomArray_h diff --git a/xpcom/ds/Dafsa.cpp b/xpcom/ds/Dafsa.cpp new file mode 100644 index 0000000000..8854e0ff1d --- /dev/null +++ b/xpcom/ds/Dafsa.cpp @@ -0,0 +1,153 @@ +/* -*- 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/. */ + +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "Dafsa.h" + +#include "mozilla/Assertions.h" +#include "nsAString.h" + +const int mozilla::Dafsa::kKeyNotFound = -1; + +// Note the DAFSA implementation was lifted from eTLD code in Chromium that was +// originally lifted from Firefox. + +// Read next offset from pos. +// Returns true if an offset could be read, false otherwise. +static bool GetNextOffset(const unsigned char** pos, const unsigned char* end, + const unsigned char** offset) { + if (*pos == end) return false; + + // When reading an offset the byte array must always contain at least + // three more bytes to consume. First the offset to read, then a node + // to skip over and finally a destination node. No object can be smaller + // than one byte. + MOZ_ASSERT(*pos + 2 < end); + size_t bytes_consumed; + switch (**pos & 0x60) { + case 0x60: // Read three byte offset + *offset += (((*pos)[0] & 0x1F) << 16) | ((*pos)[1] << 8) | (*pos)[2]; + bytes_consumed = 3; + break; + case 0x40: // Read two byte offset + *offset += (((*pos)[0] & 0x1F) << 8) | (*pos)[1]; + bytes_consumed = 2; + break; + default: + *offset += (*pos)[0] & 0x3F; + bytes_consumed = 1; + } + if ((**pos & 0x80) != 0) { + *pos = end; + } else { + *pos += bytes_consumed; + } + return true; +} + +// Check if byte at offset is last in label. +static bool IsEOL(const unsigned char* offset, const unsigned char* end) { + MOZ_ASSERT(offset < end); + return (*offset & 0x80) != 0; +} + +// Check if byte at offset matches first character in key. +// This version matches characters not last in label. +static bool IsMatch(const unsigned char* offset, const unsigned char* end, + const char* key) { + MOZ_ASSERT(offset < end); + return *offset == *key; +} + +// Check if byte at offset matches first character in key. +// This version matches characters last in label. +static bool IsEndCharMatch(const unsigned char* offset, + const unsigned char* end, const char* key) { + MOZ_ASSERT(offset < end); + return *offset == (*key | 0x80); +} + +// Read return value at offset. +// Returns true if a return value could be read, false otherwise. +static bool GetReturnValue(const unsigned char* offset, + const unsigned char* end, int* return_value) { + MOZ_ASSERT(offset < end); + if ((*offset & 0xE0) == 0x80) { + *return_value = *offset & 0x0F; + return true; + } + return false; +} + +// Lookup a domain key in a byte array generated by make_dafsa.py. +// The rule type is returned if key is found, otherwise kKeyNotFound is +// returned. +static int LookupString(const unsigned char* graph, size_t length, + const char* key, size_t key_length) { + const unsigned char* pos = graph; + const unsigned char* end = graph + length; + const unsigned char* offset = pos; + const char* key_end = key + key_length; + while (GetNextOffset(&pos, end, &offset)) { + // char + end_char offsets + // char + return value + // char end_char offsets + // char return value + // end_char offsets + // return_value + bool did_consume = false; + if (key != key_end && !IsEOL(offset, end)) { + // Leading is not a match. Don't dive into this child + if (!IsMatch(offset, end, key)) continue; + did_consume = true; + ++offset; + ++key; + // Possible matches at this point: + // + end_char offsets + // + return value + // end_char offsets + // return value + // Remove all remaining nodes possible + while (!IsEOL(offset, end) && key != key_end) { + if (!IsMatch(offset, end, key)) return mozilla::Dafsa::kKeyNotFound; + ++key; + ++offset; + } + } + // Possible matches at this point: + // end_char offsets + // return_value + // If one or more elements were consumed, a failure + // to match is terminal. Otherwise, try the next node. + if (key == key_end) { + int return_value; + if (GetReturnValue(offset, end, &return_value)) return return_value; + // The DAFSA guarantees that if the first char is a match, all + // remaining char elements MUST match if the key is truly present. + if (did_consume) return mozilla::Dafsa::kKeyNotFound; + continue; + } + if (!IsEndCharMatch(offset, end, key)) { + if (did_consume) return mozilla::Dafsa::kKeyNotFound; // Unexpected + continue; + } + ++key; + pos = ++offset; // Dive into child + } + return mozilla::Dafsa::kKeyNotFound; // No match +} + +namespace mozilla { + +int Dafsa::Lookup(const nsACString& aKey) const { + return LookupString(mData.Elements(), mData.Length(), aKey.BeginReading(), + aKey.Length()); +} + +} // namespace mozilla diff --git a/xpcom/ds/Dafsa.h b/xpcom/ds/Dafsa.h new file mode 100644 index 0000000000..1a5584f632 --- /dev/null +++ b/xpcom/ds/Dafsa.h @@ -0,0 +1,54 @@ +/* -*- 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/. */ + +#ifndef mozilla_Dafsa_h +#define mozilla_Dafsa_h + +#include "stdint.h" + +#include "mozilla/Span.h" +#include "nsStringFwd.h" + +namespace mozilla { + +/** + * A deterministic acyclic finite state automaton suitable for storing static + * dictionaries of tagged ascii strings. Consider using this if you have a very + * large set of strings that need an associated enum value. + * + * Currently the string tag is limited by `make_dafsa.py` to a value of [0-4]. + * In theory [0-15] can easily be supported. + * + * See `make_dafsa.py` for more details. + */ +class Dafsa { + public: + using Graph = Span; + + /** + * Initializes the DAFSA with a binary encoding generated by `make_dafsa.py`. + */ + explicit Dafsa(const Graph& aData) : mData(aData) {} + + ~Dafsa() = default; + + /** + * Searches for the given string in the DAFSA. + * + * @param aKey The string to search for. + * @returns kKeyNotFound if not found, otherwise the associated tag. + */ + int Lookup(const nsACString& aKey) const; + + static const int kKeyNotFound; + + private: + const Graph mData; +}; + +} // namespace mozilla + +#endif // mozilla_Dafsa_h diff --git a/xpcom/ds/HTMLAtoms.py b/xpcom/ds/HTMLAtoms.py new file mode 100644 index 0000000000..ae4df76cc3 --- /dev/null +++ b/xpcom/ds/HTMLAtoms.py @@ -0,0 +1,257 @@ +# THIS FILE IS GENERATED BY THE HTML PARSER TRANSLATOR AND WILL BE OVERWRITTEN! +from Atom import Atom + +HTML_PARSER_ATOMS = [ + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink", "xlink"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xml_space", "xml:space"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xml_lang", "xml:lang"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_grab", "aria-grab"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_channel", "aria-channel"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_secret", "aria-secret"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_templateid", "aria-templateid"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("aria_datatype", "aria-datatype"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("local", "local"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xchannelselector", "xchannelselector"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("ychannelselector", "ychannelselector"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("enable_background", "enable-background"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("calcmode", "calcmode"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("specularexponent", "specularexponent"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("specularconstant", "specularconstant"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("gradienttransform", "gradienttransform"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("gradientunits", "gradientunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("rendering_intent", "rendering-intent"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("stddeviation", "stddeviation"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("basefrequency", "basefrequency"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("baseprofile", "baseprofile"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("baseProfile", "baseProfile"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("edgemode", "edgemode"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("repeatcount", "repeatcount"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("repeatdur", "repeatdur"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("spreadmethod", "spreadmethod"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("diffuseconstant", "diffuseconstant"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("surfacescale", "surfacescale"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("lengthadjust", "lengthadjust"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("origin", "origin"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("targetx", "targetx"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("targety", "targety"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("pathlength", "pathlength"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("definitionurl", "definitionurl"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("limitingconeangle", "limitingconeangle"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("markerheight", "markerheight"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("markerwidth", "markerwidth"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("maskunits", "maskunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("markerunits", "markerunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("maskcontentunits", "maskcontentunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("tablevalues", "tablevalues"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("primitiveunits", "primitiveunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("zoomandpan", "zoomandpan"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("kernelmatrix", "kernelmatrix"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("kerning", "kerning"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("kernelunitlength", "kernelunitlength"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("pointsatx", "pointsatx"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("pointsaty", "pointsaty"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("pointsatz", "pointsatz"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_href", "xlink:href"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_title", "xlink:title"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_role", "xlink:role"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_arcrole", "xlink:arcrole"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("arcrole", "arcrole"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xmlns_xlink", "xmlns:xlink"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_type", "xlink:type"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_show", "xlink:show"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("xlink_actuate", "xlink:actuate"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("color_rendering", "color-rendering"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("numoctaves", "numoctaves"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("onmousewheel", "onmousewheel"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("clippathunits", "clippathunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("glyph_orientation_vertical", "glyph-orientation-vertical"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("glyph_orientation_horizontal", "glyph-orientation-horizontal"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("glyphref", "glyphref"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("keypoints", "keypoints"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("attributename", "attributename"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("attributetype", "attributetype"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("startoffset", "startoffset"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("keysplines", "keysplines"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("preservealpha", "preservealpha"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("preserveaspectratio", "preserveaspectratio"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("alttext", "alttext"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("filterunits", "filterunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("keytimes", "keytimes"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("patterntransform", "patterntransform"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("patternunits", "patternunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("patterncontentunits", "patterncontentunits"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("stitchtiles", "stitchtiles"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("systemlanguage", "systemlanguage"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("textlength", "textlength"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("requiredfeatures", "requiredfeatures"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("requiredextensions", "requiredextensions"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("viewtarget", "viewtarget"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("viewbox", "viewbox"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("refx", "refx"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("refy", "refy"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fefunca", "fefunca"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fefuncb", "fefuncb"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feblend", "feblend"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feflood", "feflood"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feturbulence", "feturbulence"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("femergenode", "femergenode"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feimage", "feimage"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("femerge", "femerge"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fetile", "fetile"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fecomposite", "fecomposite"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altglyphdef", "altglyphdef"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altGlyphDef", "altGlyphDef"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fefuncg", "fefuncg"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fediffuselighting", "fediffuselighting"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fespecularlighting", "fespecularlighting"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altglyph", "altglyph"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altGlyph", "altGlyph"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("clippath", "clippath"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("textpath", "textpath"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altglyphitem", "altglyphitem"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("altGlyphItem", "altGlyphItem"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("animatetransform", "animatetransform"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("animatemotion", "animatemotion"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fedisplacementmap", "fedisplacementmap"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("animatecolor", "animatecolor"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fefuncr", "fefuncr"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fecomponenttransfer", "fecomponenttransfer"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fegaussianblur", "fegaussianblur"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("foreignobject", "foreignobject"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feoffset", "feoffset"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fespotlight", "fespotlight"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fepointlight", "fepointlight"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fedistantlight", "fedistantlight"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("lineargradient", "lineargradient"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("radialgradient", "radialgradient"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fedropshadow", "fedropshadow"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("fecolormatrix", "fecolormatrix"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("feconvolvematrix", "feconvolvematrix"), + # ATOM GENERATED BY HTML PARSER TRANSLATOR (WILL BE AUTOMATICALLY OVERWRITTEN): + Atom("femorphology", "femorphology"), +] diff --git a/xpcom/ds/IncrementalTokenizer.cpp b/xpcom/ds/IncrementalTokenizer.cpp new file mode 100644 index 0000000000..db6fddb82b --- /dev/null +++ b/xpcom/ds/IncrementalTokenizer.cpp @@ -0,0 +1,190 @@ +/* -*- 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/IncrementalTokenizer.h" + +#include "mozilla/AutoRestore.h" + +#include "nsIInputStream.h" +#include "IncrementalTokenizer.h" +#include + +namespace mozilla { + +IncrementalTokenizer::IncrementalTokenizer(Consumer&& aConsumer, + const char* aWhitespaces, + const char* aAdditionalWordChars, + uint32_t aRawMinBuffered) + : TokenizerBase(aWhitespaces, aAdditionalWordChars) +#ifdef DEBUG + , + mConsuming(false) +#endif + , + mNeedMoreInput(false), + mRollback(false), + mInputCursor(0), + mConsumer(std::move(aConsumer)) { + mInputFinished = false; + mMinRawDelivery = aRawMinBuffered; +} + +nsresult IncrementalTokenizer::FeedInput(const nsACString& aInput) { + NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(!mInputFinished); + + mInput.Cut(0, mInputCursor); + mInputCursor = 0; + + mInput.Append(aInput); + + return Process(); +} + +nsresult IncrementalTokenizer::FeedInput(nsIInputStream* aInput, + uint32_t aCount) { + NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(!mInputFinished); + MOZ_ASSERT(!mConsuming); + + mInput.Cut(0, mInputCursor); + mInputCursor = 0; + + nsresult rv = NS_OK; + while (NS_SUCCEEDED(rv) && aCount) { + nsCString::index_type remainder = mInput.Length(); + nsCString::index_type load = + std::min(aCount, PR_UINT32_MAX - remainder); + + if (!load) { + // To keep the API simple, we fail if the input data buffer if filled. + // It's highly unlikely there will ever be such amout of data cumulated + // unless a logic fault in the consumer code. + NS_ERROR("IncrementalTokenizer consumer not reading data?"); + return NS_ERROR_OUT_OF_MEMORY; + } + + if (!mInput.SetLength(remainder + load, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + auto buffer = mInput.BeginWriting() + remainder; + + uint32_t read; + rv = aInput->Read(buffer, load, &read); + if (NS_SUCCEEDED(rv)) { + // remainder + load fits the uint32_t size, so must remainder + read. + mInput.SetLength(remainder + read); + aCount -= read; + + rv = Process(); + } + } + + return rv; +} + +nsresult IncrementalTokenizer::FinishInput() { + NS_ENSURE_TRUE(mConsumer, NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(!mInputFinished); + MOZ_ASSERT(!mConsuming); + + mInput.Cut(0, mInputCursor); + mInputCursor = 0; + + mInputFinished = true; + nsresult rv = Process(); + mConsumer = nullptr; + return rv; +} + +bool IncrementalTokenizer::Next(Token& aToken) { + // Assert we are called only from the consumer callback + MOZ_ASSERT(mConsuming); + + if (mPastEof) { + return false; + } + + nsACString::const_char_iterator next = Parse(aToken); + mPastEof = aToken.Type() == TOKEN_EOF; + if (next == mCursor && !mPastEof) { + // Not enough input to make a deterministic decision. + return false; + } + + AssignFragment(aToken, mCursor, next); + mCursor = next; + return true; +} + +void IncrementalTokenizer::NeedMoreInput() { + // Assert we are called only from the consumer callback + MOZ_ASSERT(mConsuming); + + // When the input has been finished, we can't set the flag to prevent + // indefinite wait for more input (that will never come) + mNeedMoreInput = !mInputFinished; +} + +void IncrementalTokenizer::Rollback() { + // Assert we are called only from the consumer callback + MOZ_ASSERT(mConsuming); + + mRollback = true; +} + +nsresult IncrementalTokenizer::Process() { +#ifdef DEBUG + // Assert we are not re-entered + MOZ_ASSERT(!mConsuming); + + AutoRestore consuming(mConsuming); + mConsuming = true; +#endif + + MOZ_ASSERT(!mPastEof); + + nsresult rv = NS_OK; + + mInput.BeginReading(mCursor); + mCursor += mInputCursor; + mInput.EndReading(mEnd); + + while (NS_SUCCEEDED(rv) && !mPastEof) { + Token token; + nsACString::const_char_iterator next = Parse(token); + mPastEof = token.Type() == TOKEN_EOF; + if (next == mCursor && !mPastEof) { + // Not enough input to make a deterministic decision. + break; + } + + AssignFragment(token, mCursor, next); + + nsACString::const_char_iterator rollback = mCursor; + mCursor = next; + + mNeedMoreInput = mRollback = false; + + rv = mConsumer(token, *this); + if (NS_FAILED(rv)) { + break; + } + if (mNeedMoreInput || mRollback) { + mCursor = rollback; + mPastEof = false; + if (mNeedMoreInput) { + break; + } + } + } + + mInputCursor = mCursor - mInput.BeginReading(); + return rv; +} + +} // namespace mozilla diff --git a/xpcom/ds/IncrementalTokenizer.h b/xpcom/ds/IncrementalTokenizer.h new file mode 100644 index 0000000000..c2647052c9 --- /dev/null +++ b/xpcom/ds/IncrementalTokenizer.h @@ -0,0 +1,125 @@ +/* -*- 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/. */ + +#ifndef INCREMENTAL_TOKENIZER_H__ +#define INCREMENTAL_TOKENIZER_H__ + +#include "mozilla/Tokenizer.h" + +#include "nsError.h" +#include + +class nsIInputStream; + +namespace mozilla { + +class IncrementalTokenizer : public TokenizerBase { + public: + /** + * The consumer callback. The function is called for every single token + * as found in the input. Failure result returned by this callback stops + * the tokenization immediately and bubbles to result of Feed/FinishInput. + * + * Fragment()s of consumed tokens are ensured to remain valid until next call + * to Feed/FinishInput and are pointing to a single linear buffer. Hence, + * those can be safely used to accumulate the data for processing after + * Feed/FinishInput returned. + */ + typedef std::function + Consumer; + + /** + * For aWhitespaces and aAdditionalWordChars arguments see TokenizerBase. + * + * @param aConsumer + * A mandatory non-null argument, a function that consumes the tokens as + * they come when the tokenizer is fed. + * @param aRawMinBuffered + * When we have buffered at least aRawMinBuffered data, but there was no + * custom token found so far because of too small incremental feed chunks, + * deliver the raw data to preserve streaming and to save memory. This only + * has effect in OnlyCustomTokenizing mode. + */ + explicit IncrementalTokenizer(Consumer&& aConsumer, + const char* aWhitespaces = nullptr, + const char* aAdditionalWordChars = nullptr, + uint32_t aRawMinBuffered = 1024); + + /** + * Pushes the input to be tokenized. These directly call the Consumer + * callback on every found token. Result of the Consumer callback is returned + * here. + * + * The tokenizer must be initialized with a valid consumer prior call to these + * methods. It's not allowed to call Feed/FinishInput from inside the + * Consumer callback. + */ + nsresult FeedInput(const nsACString& aInput); + nsresult FeedInput(nsIInputStream* aInput, uint32_t aCount); + nsresult FinishInput(); + + /** + * Can only be called from inside the consumer callback. + * + * When there is still anything to read from the input, tokenize it, store + * the token type and value to aToken result and shift the cursor past this + * just parsed token. Each call to Next() reads another token from + * the input and shifts the cursor. + * + * Returns false if there is not enough data to deterministically recognize + * tokens or when the last returned token was EOF. + */ + [[nodiscard]] bool Next(Token& aToken); + + /** + * Can only be called from inside the consumer callback. + * + * Tells the tokenizer to revert the cursor and stop the async parsing until + * next feed of the input. This is useful when more than one token is needed + * to decide on the syntax but there is not enough input to get a next token + * (Next() returned false.) + */ + void NeedMoreInput(); + + /** + * Can only be called from inside the consumer callback. + * + * This makes the consumer callback be called again while parsing + * the input at the previous cursor position again. This is useful when + * the tokenizer state (custom tokens, tokenization mode) has changed and + * we want to re-parse the input again. + */ + void Rollback(); + + private: + // Loops over the input with TokenizerBase::Parse and calls the Consumer + // callback. + nsresult Process(); + +#ifdef DEBUG + // True when inside the consumer callback, used only for assertions. + bool mConsuming; +#endif // DEBUG + // Modifyable only from the Consumer callback, tells the parser to break, + // rollback and wait for more input. + bool mNeedMoreInput; + // Modifyable only from the Consumer callback, tells the parser to rollback + // and parse the input again, with (if modified) new settings of the + // tokenizer. + bool mRollback; + // The input buffer. Updated with each call to Feed/FinishInput. + nsCString mInput; + // Numerical index pointing at the current cursor position. We don't keep + // direct reference to the string buffer since the buffer gets often + // reallocated. + nsCString::index_type mInputCursor; + // Refernce to the consumer function. + Consumer mConsumer; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/ds/Observer.h b/xpcom/ds/Observer.h new file mode 100644 index 0000000000..3d189603eb --- /dev/null +++ b/xpcom/ds/Observer.h @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +#ifndef mozilla_Observer_h +#define mozilla_Observer_h + +#include "nsTObserverArray.h" + +namespace mozilla { + +/** + * Observer provides a way for a class to observe something. + * When an event has to be broadcasted to all Observer, Notify() method + * is called. + * T represents the type of the object passed in argument to Notify(). + * + * @see ObserverList. + */ +template +class Observer { + public: + virtual ~Observer() = default; + virtual void Notify(const T& aParam) = 0; +}; + +/** + * ObserverList tracks Observer and can notify them when Broadcast() is + * called. + * T represents the type of the object passed in argument to Broadcast() and + * sent to Observer objects through Notify(). + * + * @see Observer. + */ +template +class ObserverList { + public: + /** + * Note: When calling AddObserver, it's up to the caller to make sure the + * object isn't going to be release as long as RemoveObserver hasn't been + * called. + * + * @see RemoveObserver() + */ + void AddObserver(Observer* aObserver) { + mObservers.AppendElementUnlessExists(aObserver); + } + + /** + * Remove the observer from the observer list. + * @return Whether the observer has been found in the list. + */ + bool RemoveObserver(Observer* aObserver) { + return mObservers.RemoveElement(aObserver); + } + + uint32_t Length() { return mObservers.Length(); } + + /** + * Call Notify() on each item in the list. + */ + void Broadcast(const T& aParam) { + for (Observer* obs : mObservers.ForwardRange()) { + obs->Notify(aParam); + } + } + + protected: + nsTObserverArray*> mObservers; +}; + +} // namespace mozilla + +#endif // mozilla_Observer_h diff --git a/xpcom/ds/PLDHashTable.cpp b/xpcom/ds/PLDHashTable.cpp new file mode 100644 index 0000000000..2814813f68 --- /dev/null +++ b/xpcom/ds/PLDHashTable.cpp @@ -0,0 +1,870 @@ +/* -*- 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 +#include +#include +#include +#include "PLDHashTable.h" +#include "nsDebug.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/ScopeExit.h" +#include "nsAlgorithm.h" +#include "nsPointerHashKeys.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Maybe.h" +#include "mozilla/ChaosMode.h" + +using namespace mozilla; + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + +class AutoReadOp { + Checker& mChk; + + public: + explicit AutoReadOp(Checker& aChk) : mChk(aChk) { mChk.StartReadOp(); } + ~AutoReadOp() { mChk.EndReadOp(); } +}; + +class AutoWriteOp { + Checker& mChk; + + public: + explicit AutoWriteOp(Checker& aChk) : mChk(aChk) { mChk.StartWriteOp(); } + ~AutoWriteOp() { mChk.EndWriteOp(); } +}; + +class AutoIteratorRemovalOp { + Checker& mChk; + + public: + explicit AutoIteratorRemovalOp(Checker& aChk) : mChk(aChk) { + mChk.StartIteratorRemovalOp(); + } + ~AutoIteratorRemovalOp() { mChk.EndIteratorRemovalOp(); } +}; + +class AutoDestructorOp { + Checker& mChk; + + public: + explicit AutoDestructorOp(Checker& aChk) : mChk(aChk) { + mChk.StartDestructorOp(); + } + ~AutoDestructorOp() { mChk.EndDestructorOp(); } +}; + +#endif + +/* static */ +PLDHashNumber PLDHashTable::HashStringKey(const void* aKey) { + return HashString(static_cast(aKey)); +} + +/* static */ +PLDHashNumber PLDHashTable::HashVoidPtrKeyStub(const void* aKey) { + return nsPtrHashKey::HashKey(aKey); +} + +/* static */ +bool PLDHashTable::MatchEntryStub(const PLDHashEntryHdr* aEntry, + const void* aKey) { + const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry; + + return stub->key == aKey; +} + +/* static */ +bool PLDHashTable::MatchStringKey(const PLDHashEntryHdr* aEntry, + const void* aKey) { + const PLDHashEntryStub* stub = (const PLDHashEntryStub*)aEntry; + + // XXX tolerate null keys on account of sloppy Mozilla callers. + return stub->key == aKey || + (stub->key && aKey && + strcmp((const char*)stub->key, (const char*)aKey) == 0); +} + +/* static */ +void PLDHashTable::MoveEntryStub(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) { + memcpy(aTo, aFrom, aTable->mEntrySize); +} + +/* static */ +void PLDHashTable::ClearEntryStub(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry) { + memset(aEntry, 0, aTable->mEntrySize); +} + +static const PLDHashTableOps gStubOps = { + PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub, + PLDHashTable::MoveEntryStub, PLDHashTable::ClearEntryStub, nullptr}; + +/* static */ const PLDHashTableOps* PLDHashTable::StubOps() { + return &gStubOps; +} + +static bool SizeOfEntryStore(uint32_t aCapacity, uint32_t aEntrySize, + uint32_t* aNbytes) { + uint32_t slotSize = aEntrySize + sizeof(PLDHashNumber); + uint64_t nbytes64 = uint64_t(aCapacity) * uint64_t(slotSize); + *aNbytes = aCapacity * slotSize; + return uint64_t(*aNbytes) == nbytes64; // returns false on overflow +} + +// Compute max and min load numbers (entry counts). We have a secondary max +// that allows us to overload a table reasonably if it cannot be grown further +// (i.e. if ChangeTable() fails). The table slows down drastically if the +// secondary max is too close to 1, but 0.96875 gives only a slight slowdown +// while allowing 1.3x more elements. +static inline uint32_t MaxLoad(uint32_t aCapacity) { + return aCapacity - (aCapacity >> 2); // == aCapacity * 0.75 +} +static inline uint32_t MaxLoadOnGrowthFailure(uint32_t aCapacity) { + return aCapacity - (aCapacity >> 5); // == aCapacity * 0.96875 +} +static inline uint32_t MinLoad(uint32_t aCapacity) { + return aCapacity >> 2; // == aCapacity * 0.25 +} + +// Compute the minimum capacity (and the Log2 of that capacity) for a table +// containing |aLength| elements while respecting the following contraints: +// - table must be at most 75% full; +// - capacity must be a power of two; +// - capacity cannot be too small. +static inline void BestCapacity(uint32_t aLength, uint32_t* aCapacityOut, + uint32_t* aLog2CapacityOut) { + // Callers should ensure this is true. + MOZ_ASSERT(aLength <= PLDHashTable::kMaxInitialLength); + + // Compute the smallest capacity allowing |aLength| elements to be inserted + // without rehashing. + uint32_t capacity = (aLength * 4 + (3 - 1)) / 3; // == ceil(aLength * 4 / 3) + if (capacity < PLDHashTable::kMinCapacity) { + capacity = PLDHashTable::kMinCapacity; + } + + // Round up capacity to next power-of-two. + uint32_t log2 = CeilingLog2(capacity); + capacity = 1u << log2; + MOZ_ASSERT(capacity <= PLDHashTable::kMaxCapacity); + + *aCapacityOut = capacity; + *aLog2CapacityOut = log2; +} + +/* static */ MOZ_ALWAYS_INLINE uint32_t +PLDHashTable::HashShift(uint32_t aEntrySize, uint32_t aLength) { + if (aLength > kMaxInitialLength) { + MOZ_CRASH("Initial length is too large"); + } + + uint32_t capacity, log2; + BestCapacity(aLength, &capacity, &log2); + + uint32_t nbytes; + if (!SizeOfEntryStore(capacity, aEntrySize, &nbytes)) { + MOZ_CRASH("Initial entry store size is too large"); + } + + // Compute the hashShift value. + return kPLDHashNumberBits - log2; +} + +PLDHashTable::PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize, + uint32_t aLength) + : mOps(aOps), + mEntryStore(), + mGeneration(0), + mHashShift(HashShift(aEntrySize, aLength)), + mEntrySize(aEntrySize), + mEntryCount(0), + mRemovedCount(0) { + // An entry size greater than 0xff is unlikely, but let's check anyway. If + // you hit this, your hashtable would waste lots of space for unused entries + // and you should change your hash table's entries to pointers. + if (aEntrySize != uint32_t(mEntrySize)) { + MOZ_CRASH("Entry size is too large"); + } +} + +PLDHashTable& PLDHashTable::operator=(PLDHashTable&& aOther) { + if (this == &aOther) { + return *this; + } + + // |mOps| and |mEntrySize| are required to stay the same, they're + // conceptually part of the type -- indeed, if PLDHashTable was a templated + // type like nsTHashtable, they *would* be part of the type -- so it only + // makes sense to assign in cases where they match. + MOZ_RELEASE_ASSERT(mOps == aOther.mOps || !mOps); + MOZ_RELEASE_ASSERT(mEntrySize == aOther.mEntrySize || !mEntrySize); + + // Reconstruct |this|. + const PLDHashTableOps* ops = aOther.mOps; + this->~PLDHashTable(); + new (KnownNotNull, this) PLDHashTable(ops, aOther.mEntrySize, 0); + + // Move non-const pieces over. + mHashShift = std::move(aOther.mHashShift); + mEntryCount = std::move(aOther.mEntryCount); + mRemovedCount = std::move(aOther.mRemovedCount); + mEntryStore.Set(aOther.mEntryStore.Get(), &mGeneration); +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mChecker = std::move(aOther.mChecker); +#endif + + // Clear up |aOther| so its destruction will be a no-op and it reports being + // empty. + { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoDestructorOp op(mChecker); +#endif + aOther.mEntryCount = 0; + aOther.mEntryStore.Set(nullptr, &aOther.mGeneration); + } + + return *this; +} + +PLDHashNumber PLDHashTable::Hash1(PLDHashNumber aHash0) const { + return aHash0 >> mHashShift; +} + +void PLDHashTable::Hash2(PLDHashNumber aHash0, uint32_t& aHash2Out, + uint32_t& aSizeMaskOut) const { + uint32_t sizeLog2 = kPLDHashNumberBits - mHashShift; + uint32_t sizeMask = (PLDHashNumber(1) << sizeLog2) - 1; + aSizeMaskOut = sizeMask; + + // The incoming aHash0 always has the low bit unset (since we leave it + // free for the collision flag), and should have reasonably random + // data in the other 31 bits. We used the high bits of aHash0 for + // Hash1, so we use the low bits here. If the table size is large, + // the bits we use may overlap, but that's still more random than + // filling with 0s. + // + // Double hashing needs the second hash code to be relatively prime to table + // size, so we simply make hash2 odd. + // + // This also conveniently covers up the fact that we have the low bit + // unset since aHash0 has the low bit unset. + aHash2Out = (aHash0 & sizeMask) | 1; +} + +// Reserve mKeyHash 0 for free entries and 1 for removed-entry sentinels. Note +// that a removed-entry sentinel need be stored only if the removed entry had +// a colliding entry added after it. Therefore we can use 1 as the collision +// flag in addition to the removed-entry sentinel value. Multiplicative hash +// uses the high order bits of mKeyHash, so this least-significant reservation +// should not hurt the hash function's effectiveness much. + +// Match an entry's mKeyHash against an unstored one computed from a key. +/* static */ +bool PLDHashTable::MatchSlotKeyhash(Slot& aSlot, const PLDHashNumber aKeyHash) { + return (aSlot.KeyHash() & ~kCollisionFlag) == aKeyHash; +} + +// Compute the address of the indexed entry in table. +auto PLDHashTable::SlotForIndex(uint32_t aIndex) const -> Slot { + return mEntryStore.SlotForIndex(aIndex, mEntrySize, CapacityFromHashShift()); +} + +PLDHashTable::~PLDHashTable() { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoDestructorOp op(mChecker); +#endif + + if (!mEntryStore.IsAllocated()) { + return; + } + + // Clear any remaining live entries (if not trivially destructible). + if (mOps->clearEntry) { + mEntryStore.ForEachSlot(Capacity(), mEntrySize, [&](const Slot& aSlot) { + if (aSlot.IsLive()) { + mOps->clearEntry(this, aSlot.ToEntry()); + } + }); + } + + // Entry storage is freed last, by ~EntryStore(). +} + +void PLDHashTable::ClearAndPrepareForLength(uint32_t aLength) { + // Get these values before the destructor clobbers them. + const PLDHashTableOps* ops = mOps; + uint32_t entrySize = mEntrySize; + + this->~PLDHashTable(); + new (KnownNotNull, this) PLDHashTable(ops, entrySize, aLength); +} + +void PLDHashTable::Clear() { ClearAndPrepareForLength(kDefaultInitialLength); } + +// If |Reason| is |ForAdd|, the return value is always non-null and it may be +// a previously-removed entry. If |Reason| is |ForSearchOrRemove|, the return +// value is null on a miss, and will never be a previously-removed entry on a +// hit. This distinction is a bit grotty but this function is hot enough that +// these differences are worthwhile. (It's also hot enough that +// MOZ_ALWAYS_INLINE makes a significant difference.) +template +MOZ_ALWAYS_INLINE auto PLDHashTable::SearchTable(const void* aKey, + PLDHashNumber aKeyHash, + Success&& aSuccess, + Failure&& aFailure) const { + MOZ_ASSERT(mEntryStore.IsAllocated()); + NS_ASSERTION(!(aKeyHash & kCollisionFlag), "!(aKeyHash & kCollisionFlag)"); + + // Compute the primary hash address. + PLDHashNumber hash1 = Hash1(aKeyHash); + Slot slot = SlotForIndex(hash1); + + // Miss: return space for a new entry. + if (slot.IsFree()) { + return (Reason == ForAdd) ? aSuccess(slot) : aFailure(); + } + + // Hit: return entry. + PLDHashMatchEntry matchEntry = mOps->matchEntry; + if (MatchSlotKeyhash(slot, aKeyHash)) { + PLDHashEntryHdr* e = slot.ToEntry(); + if (matchEntry(e, aKey)) { + return aSuccess(slot); + } + } + + // Collision: double hash. + PLDHashNumber hash2; + uint32_t sizeMask; + Hash2(aKeyHash, hash2, sizeMask); + + // Save the first removed entry slot so Add() can recycle it. (Only used + // if Reason==ForAdd.) + Maybe firstRemoved; + + for (;;) { + if (Reason == ForAdd && !firstRemoved) { + if (MOZ_UNLIKELY(slot.IsRemoved())) { + firstRemoved.emplace(slot); + } else { + slot.MarkColliding(); + } + } + + hash1 -= hash2; + hash1 &= sizeMask; + + slot = SlotForIndex(hash1); + if (slot.IsFree()) { + if (Reason != ForAdd) { + return aFailure(); + } + return aSuccess(firstRemoved.refOr(slot)); + } + + if (MatchSlotKeyhash(slot, aKeyHash)) { + PLDHashEntryHdr* e = slot.ToEntry(); + if (matchEntry(e, aKey)) { + return aSuccess(slot); + } + } + } + + // NOTREACHED + return aFailure(); +} + +// This is a copy of SearchTable(), used by ChangeTable(), hardcoded to +// 1. assume |Reason| is |ForAdd|, +// 2. assume that |aKey| will never match an existing entry, and +// 3. assume that no entries have been removed from the current table +// structure. +// Avoiding the need for |aKey| means we can avoid needing a way to map entries +// to keys, which means callers can use complex key types more easily. +MOZ_ALWAYS_INLINE auto PLDHashTable::FindFreeSlot(PLDHashNumber aKeyHash) const + -> Slot { + MOZ_ASSERT(mEntryStore.IsAllocated()); + NS_ASSERTION(!(aKeyHash & kCollisionFlag), "!(aKeyHash & kCollisionFlag)"); + + // Compute the primary hash address. + PLDHashNumber hash1 = Hash1(aKeyHash); + Slot slot = SlotForIndex(hash1); + + // Miss: return space for a new entry. + if (slot.IsFree()) { + return slot; + } + + // Collision: double hash. + PLDHashNumber hash2; + uint32_t sizeMask; + Hash2(aKeyHash, hash2, sizeMask); + + for (;;) { + MOZ_ASSERT(!slot.IsRemoved()); + slot.MarkColliding(); + + hash1 -= hash2; + hash1 &= sizeMask; + + slot = SlotForIndex(hash1); + if (slot.IsFree()) { + return slot; + } + } + + // NOTREACHED +} + +bool PLDHashTable::ChangeTable(int32_t aDeltaLog2) { + MOZ_ASSERT(mEntryStore.IsAllocated()); + + // Look, but don't touch, until we succeed in getting new entry store. + int32_t oldLog2 = kPLDHashNumberBits - mHashShift; + int32_t newLog2 = oldLog2 + aDeltaLog2; + uint32_t newCapacity = 1u << newLog2; + if (newCapacity > kMaxCapacity) { + return false; + } + + uint32_t nbytes; + if (!SizeOfEntryStore(newCapacity, mEntrySize, &nbytes)) { + return false; // overflowed + } + + char* newEntryStore = (char*)calloc(1, nbytes); + if (!newEntryStore) { + return false; + } + + // We can't fail from here on, so update table parameters. + mHashShift = kPLDHashNumberBits - newLog2; + mRemovedCount = 0; + + // Assign the new entry store to table. + char* oldEntryStore = mEntryStore.Get(); + mEntryStore.Set(newEntryStore, &mGeneration); + PLDHashMoveEntry moveEntry = mOps->moveEntry; + + // Copy only live entries, leaving removed ones behind. + uint32_t oldCapacity = 1u << oldLog2; + EntryStore::ForEachSlot( + oldEntryStore, oldCapacity, mEntrySize, [&](const Slot& slot) { + if (slot.IsLive()) { + const PLDHashNumber key = slot.KeyHash() & ~kCollisionFlag; + Slot newSlot = FindFreeSlot(key); + MOZ_ASSERT(newSlot.IsFree()); + moveEntry(this, slot.ToEntry(), newSlot.ToEntry()); + newSlot.SetKeyHash(key); + } + }); + + free(oldEntryStore); + return true; +} + +MOZ_ALWAYS_INLINE PLDHashNumber +PLDHashTable::ComputeKeyHash(const void* aKey) const { + MOZ_ASSERT(mEntryStore.IsAllocated()); + + PLDHashNumber keyHash = mozilla::ScrambleHashCode(mOps->hashKey(aKey)); + + // Avoid 0 and 1 hash codes, they indicate free and removed entries. + if (keyHash < 2) { + keyHash -= 2; + } + keyHash &= ~kCollisionFlag; + + return keyHash; +} + +PLDHashEntryHdr* PLDHashTable::Search(const void* aKey) const { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoReadOp op(mChecker); +#endif + + if (!mEntryStore.IsAllocated()) { + return nullptr; + } + + return SearchTable( + aKey, ComputeKeyHash(aKey), + [&](Slot& slot) -> PLDHashEntryHdr* { return slot.ToEntry(); }, + [&]() -> PLDHashEntryHdr* { return nullptr; }); +} + +PLDHashEntryHdr* PLDHashTable::Add(const void* aKey, + const mozilla::fallible_t& aFallible) { + auto maybeEntryHandle = MakeEntryHandle(aKey, aFallible); + if (!maybeEntryHandle) { + return nullptr; + } + return maybeEntryHandle->OrInsert([&aKey, this](PLDHashEntryHdr* entry) { + if (mOps->initEntry) { + mOps->initEntry(entry, aKey); + } + }); +} + +PLDHashEntryHdr* PLDHashTable::Add(const void* aKey) { + return MakeEntryHandle(aKey).OrInsert([&aKey, this](PLDHashEntryHdr* entry) { + if (mOps->initEntry) { + mOps->initEntry(entry, aKey); + } + }); +} + +void PLDHashTable::Remove(const void* aKey) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoWriteOp op(mChecker); +#endif + + if (!mEntryStore.IsAllocated()) { + return; + } + + PLDHashNumber keyHash = ComputeKeyHash(aKey); + SearchTable( + aKey, keyHash, + [&](Slot& slot) { + RawRemove(slot); + ShrinkIfAppropriate(); + }, + [&]() { + // Do nothing. + }); +} + +void PLDHashTable::RemoveEntry(PLDHashEntryHdr* aEntry) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoWriteOp op(mChecker); +#endif + + RawRemove(aEntry); + ShrinkIfAppropriate(); +} + +void PLDHashTable::RawRemove(PLDHashEntryHdr* aEntry) { + Slot slot(mEntryStore.SlotForPLDHashEntry(aEntry, Capacity(), mEntrySize)); + RawRemove(slot); +} + +void PLDHashTable::RawRemove(Slot& aSlot) { + // Unfortunately, we can only do weak checking here. That's because + // RawRemove() can be called legitimately while an Enumerate() call is + // active, which doesn't fit well into how Checker's mState variable works. + MOZ_ASSERT(mChecker.IsWritable()); + + MOZ_ASSERT(mEntryStore.IsAllocated()); + + MOZ_ASSERT(aSlot.IsLive()); + + // Load keyHash first in case clearEntry() goofs it. + PLDHashNumber keyHash = aSlot.KeyHash(); + if (mOps->clearEntry) { + PLDHashEntryHdr* entry = aSlot.ToEntry(); + mOps->clearEntry(this, entry); + } + if (keyHash & kCollisionFlag) { + aSlot.MarkRemoved(); + mRemovedCount++; + } else { + aSlot.MarkFree(); + } + mEntryCount--; +} + +// Shrink or compress if a quarter or more of all entries are removed, or if the +// table is underloaded according to the minimum alpha, and is not minimal-size +// already. +void PLDHashTable::ShrinkIfAppropriate() { + uint32_t capacity = Capacity(); + if (mRemovedCount >= capacity >> 2 || + (capacity > kMinCapacity && mEntryCount <= MinLoad(capacity))) { + uint32_t log2; + BestCapacity(mEntryCount, &capacity, &log2); + + int32_t deltaLog2 = log2 - (kPLDHashNumberBits - mHashShift); + MOZ_ASSERT(deltaLog2 <= 0); + + (void)ChangeTable(deltaLog2); + } +} + +size_t PLDHashTable::ShallowSizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + AutoReadOp op(mChecker); +#endif + + return aMallocSizeOf(mEntryStore.Get()); +} + +size_t PLDHashTable::ShallowSizeOfIncludingThis( + MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); +} + +mozilla::Maybe PLDHashTable::MakeEntryHandle( + const void* aKey, const mozilla::fallible_t&) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mChecker.StartWriteOp(); + auto endWriteOp = MakeScopeExit([&] { mChecker.EndWriteOp(); }); +#endif + + // Allocate the entry storage if it hasn't already been allocated. + if (!mEntryStore.IsAllocated()) { + uint32_t nbytes; + // We already checked this in the constructor, so it must still be true. + MOZ_RELEASE_ASSERT( + SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes)); + mEntryStore.Set((char*)calloc(1, nbytes), &mGeneration); + if (!mEntryStore.IsAllocated()) { + return Nothing(); + } + } + + // If alpha is >= .75, grow or compress the table. If aKey is already in the + // table, we may grow once more than necessary, but only if we are on the + // edge of being overloaded. + uint32_t capacity = Capacity(); + if (mEntryCount + mRemovedCount >= MaxLoad(capacity)) { + // Compress if a quarter or more of all entries are removed. + int deltaLog2 = 1; + if (mRemovedCount >= capacity >> 2) { + deltaLog2 = 0; + } + + // Grow or compress the table. If ChangeTable() fails, allow overloading up + // to the secondary max. Once we hit the secondary max, return null. + if (!ChangeTable(deltaLog2) && + mEntryCount + mRemovedCount >= MaxLoadOnGrowthFailure(capacity)) { + return Nothing(); + } + } + + // Look for entry after possibly growing, so we don't have to add it, + // then skip it while growing the table and re-add it after. + PLDHashNumber keyHash = ComputeKeyHash(aKey); + Slot slot = SearchTable( + aKey, keyHash, [](Slot& found) -> Slot { return found; }, + []() -> Slot { + MOZ_CRASH("Nope"); + return Slot(nullptr, nullptr); + }); + + // The `EntryHandle` will handle ending the write op when it is destroyed. +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + endWriteOp.release(); +#endif + + return Some(EntryHandle{this, keyHash, slot}); +} + +PLDHashTable::EntryHandle PLDHashTable::MakeEntryHandle(const void* aKey) { + auto res = MakeEntryHandle(aKey, fallible); + if (!res) { + if (!mEntryStore.IsAllocated()) { + // We OOM'd while allocating the initial entry storage. + uint32_t nbytes; + (void)SizeOfEntryStore(CapacityFromHashShift(), mEntrySize, &nbytes); + NS_ABORT_OOM(nbytes); + } else { + // We failed to resize the existing entry storage, either due to OOM or + // because we exceeded the maximum table capacity or size; report it as + // an OOM. The multiplication by 2 gets us the size we tried to allocate, + // which is double the current size. + NS_ABORT_OOM(2 * EntrySize() * EntryCount()); + } + } + return res.extract(); +} + +PLDHashTable::EntryHandle::EntryHandle(PLDHashTable* aTable, + PLDHashNumber aKeyHash, Slot aSlot) + : mTable(aTable), mKeyHash(aKeyHash), mSlot(aSlot) {} + +PLDHashTable::EntryHandle::EntryHandle(EntryHandle&& aOther) noexcept + : mTable(std::exchange(aOther.mTable, nullptr)), + mKeyHash(aOther.mKeyHash), + mSlot(aOther.mSlot) {} + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED +PLDHashTable::EntryHandle::~EntryHandle() { + if (!mTable) { + return; + } + + mTable->mChecker.EndWriteOp(); +} +#endif + +void PLDHashTable::EntryHandle::Remove() { + MOZ_ASSERT(HasEntry()); + + mTable->RawRemove(mSlot); +} + +void PLDHashTable::EntryHandle::OrRemove() { + if (HasEntry()) { + Remove(); + } +} + +void PLDHashTable::EntryHandle::OccupySlot() { + MOZ_ASSERT(!HasEntry()); + + PLDHashNumber keyHash = mKeyHash; + if (mSlot.IsRemoved()) { + mTable->mRemovedCount--; + keyHash |= kCollisionFlag; + } + mSlot.SetKeyHash(keyHash); + mTable->mEntryCount++; +} + +PLDHashTable::Iterator::Iterator(Iterator&& aOther) + : mTable(aOther.mTable), + mCurrent(aOther.mCurrent), + mNexts(aOther.mNexts), + mNextsLimit(aOther.mNextsLimit), + mHaveRemoved(aOther.mHaveRemoved), + mEntrySize(aOther.mEntrySize) { + // No need to change |mChecker| here. + aOther.mTable = nullptr; + // We don't really have the concept of a null slot, so leave mCurrent. + aOther.mNexts = 0; + aOther.mNextsLimit = 0; + aOther.mHaveRemoved = false; + aOther.mEntrySize = 0; +} + +PLDHashTable::Iterator::Iterator(PLDHashTable* aTable) + : mTable(aTable), + mCurrent(mTable->mEntryStore.SlotForIndex(0, mTable->mEntrySize, + mTable->Capacity())), + mNexts(0), + mNextsLimit(mTable->EntryCount()), + mHaveRemoved(false), + mEntrySize(aTable->mEntrySize) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mTable->mChecker.StartReadOp(); +#endif + + if (ChaosMode::isActive(ChaosFeature::HashTableIteration) && + mTable->Capacity() > 0) { + // Start iterating at a random entry. It would be even more chaotic to + // iterate in fully random order, but that's harder. + uint32_t capacity = mTable->CapacityFromHashShift(); + uint32_t i = ChaosMode::randomUint32LessThan(capacity); + mCurrent = + mTable->mEntryStore.SlotForIndex(i, mTable->mEntrySize, capacity); + } + + // Advance to the first live entry, if there is one. + if (!Done() && IsOnNonLiveEntry()) { + MoveToNextLiveEntry(); + } +} + +PLDHashTable::Iterator::Iterator(PLDHashTable* aTable, EndIteratorTag aTag) + : mTable(aTable), + mCurrent(mTable->mEntryStore.SlotForIndex(0, mTable->mEntrySize, + mTable->Capacity())), + mNexts(mTable->EntryCount()), + mNextsLimit(mTable->EntryCount()), + mHaveRemoved(false), + mEntrySize(aTable->mEntrySize) { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mTable->mChecker.StartReadOp(); +#endif + + MOZ_ASSERT(Done()); +} + +PLDHashTable::Iterator::Iterator(const Iterator& aOther) + : mTable(aOther.mTable), + mCurrent(aOther.mCurrent), + mNexts(aOther.mNexts), + mNextsLimit(aOther.mNextsLimit), + mHaveRemoved(aOther.mHaveRemoved), + mEntrySize(aOther.mEntrySize) { + // TODO: Is this necessary? + MOZ_ASSERT(!mHaveRemoved); + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mTable->mChecker.StartReadOp(); +#endif +} + +PLDHashTable::Iterator::~Iterator() { + if (mTable) { + if (mHaveRemoved) { + mTable->ShrinkIfAppropriate(); + } +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mTable->mChecker.EndReadOp(); +#endif + } +} + +MOZ_ALWAYS_INLINE bool PLDHashTable::Iterator::IsOnNonLiveEntry() const { + MOZ_ASSERT(!Done()); + return !mCurrent.IsLive(); +} + +void PLDHashTable::Iterator::Next() { + MOZ_ASSERT(!Done()); + + mNexts++; + + // Advance to the next live entry, if there is one. + if (!Done()) { + MoveToNextLiveEntry(); + } +} + +MOZ_ALWAYS_INLINE void PLDHashTable::Iterator::MoveToNextLiveEntry() { + // Chaos mode requires wraparound to cover all possible entries, so we can't + // simply move to the next live entry and stop when we hit the end of the + // entry store. But we don't want to introduce extra branches into our inner + // loop. So we are going to exploit the structure of the entry store in this + // method to implement an efficient inner loop. + // + // The idea is that since we are really only iterating through the stored + // hashes and because we know that there are a power-of-two number of + // hashes, we can use masking to implement the wraparound for us. This + // method does have the downside of needing to recalculate where the + // associated entry is once we've found it, but that seems OK. + + // Our current slot and its associated hash. + Slot slot = mCurrent; + PLDHashNumber* p = slot.HashPtr(); + const uint32_t capacity = mTable->CapacityFromHashShift(); + const uint32_t mask = capacity - 1; + auto hashes = reinterpret_cast(mTable->mEntryStore.Get()); + uint32_t slotIndex = p - hashes; + + do { + slotIndex = (slotIndex + 1) & mask; + } while (!Slot::IsLiveHash(hashes[slotIndex])); + + // slotIndex now indicates where a live slot is. Rematerialize the entry + // and the slot. + mCurrent = mTable->mEntryStore.SlotForIndex(slotIndex, mEntrySize, capacity); +} + +void PLDHashTable::Iterator::Remove() { + mTable->RawRemove(mCurrent); + mHaveRemoved = true; +} diff --git a/xpcom/ds/PLDHashTable.h b/xpcom/ds/PLDHashTable.h new file mode 100644 index 0000000000..67e4d7fdcf --- /dev/null +++ b/xpcom/ds/PLDHashTable.h @@ -0,0 +1,807 @@ +/* -*- 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/. */ + +// See the comment at the top of mfbt/HashTable.h for a comparison between +// PLDHashTable and mozilla::HashTable. + +#ifndef PLDHashTable_h +#define PLDHashTable_h + +#include + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/fallible.h" +#include "nscore.h" + +using PLDHashNumber = mozilla::HashNumber; +static const uint32_t kPLDHashNumberBits = mozilla::kHashNumberBits; + +#if defined(DEBUG) || defined(FUZZING) +# define MOZ_HASH_TABLE_CHECKS_ENABLED 1 +#endif + +class PLDHashTable; +struct PLDHashTableOps; + +// Table entry header structure. +// +// In order to allow in-line allocation of key and value, we do not declare +// either here. Instead, the API uses const void *key as a formal parameter. +// The key need not be stored in the entry; it may be part of the value, but +// need not be stored at all. +// +// Callback types are defined below and grouped into the PLDHashTableOps +// structure, for single static initialization per hash table sub-type. +// +// Each hash table sub-type should make its entry type a subclass of +// PLDHashEntryHdr. PLDHashEntryHdr is merely a common superclass to present a +// uniform interface to PLDHashTable clients. The zero-sized base class +// optimization, employed by all of our supported C++ compilers, will ensure +// that this abstraction does not make objects needlessly larger. +struct PLDHashEntryHdr { + PLDHashEntryHdr() = default; + PLDHashEntryHdr(const PLDHashEntryHdr&) = delete; + PLDHashEntryHdr& operator=(const PLDHashEntryHdr&) = delete; + PLDHashEntryHdr(PLDHashEntryHdr&&) = default; + PLDHashEntryHdr& operator=(PLDHashEntryHdr&&) = default; + + private: + friend class PLDHashTable; +}; + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + +// This class does three kinds of checking: +// +// - that calls to one of |mOps| or to an enumerator do not cause re-entry into +// the table in an unsafe way; +// +// - that multiple threads do not access the table in an unsafe way; +// +// - that a table marked as immutable is not modified. +// +// "Safe" here means that multiple concurrent read operations are ok, but a +// write operation (i.e. one that can cause the entry storage to be reallocated +// or destroyed) cannot safely run concurrently with another read or write +// operation. This meaning of "safe" is only partial; for example, it does not +// cover whether a single entry in the table is modified by two separate +// threads. (Doing such checking would be much harder.) +// +// It does this with two variables: +// +// - mState, which embodies a tri-stage tagged union with the following +// variants: +// - Idle +// - Read(n), where 'n' is the number of concurrent read operations +// - Write +// +// - mIsWritable, which indicates if the table is mutable. +// +class Checker { + public: + constexpr Checker() : mState(kIdle), mIsWritable(true) {} + + Checker& operator=(Checker&& aOther) { + // Atomic<> doesn't have an |operator=(Atomic<>&&)|. + mState = uint32_t(aOther.mState); + mIsWritable = bool(aOther.mIsWritable); + + aOther.mState = kIdle; + // XXX Shouldn't we set mIsWritable to true here for consistency? + + return *this; + } + + static bool IsIdle(uint32_t aState) { return aState == kIdle; } + static bool IsRead(uint32_t aState) { + return kRead1 <= aState && aState <= kReadMax; + } + static bool IsRead1(uint32_t aState) { return aState == kRead1; } + static bool IsWrite(uint32_t aState) { return aState == kWrite; } + + bool IsIdle() const { return mState == kIdle; } + + bool IsWritable() const { return mIsWritable; } + + void SetNonWritable() { mIsWritable = false; } + + // NOTE: the obvious way to implement these functions is to (a) check + // |mState| is reasonable, and then (b) update |mState|. But the lack of + // atomicity in such an implementation can cause problems if we get unlucky + // thread interleaving between (a) and (b). + // + // So instead for |mState| we are careful to (a) first get |mState|'s old + // value and assign it a new value in single atomic operation, and only then + // (b) check the old value was reasonable. This ensures we don't have + // interleaving problems. + // + // For |mIsWritable| we don't need to be as careful because it can only in + // transition in one direction (from writable to non-writable). + + void StartReadOp() { + uint32_t oldState = mState++; // this is an atomic increment + MOZ_RELEASE_ASSERT(IsIdle(oldState) || IsRead(oldState)); + MOZ_RELEASE_ASSERT(oldState < kReadMax); // check for overflow + } + + void EndReadOp() { + uint32_t oldState = mState--; // this is an atomic decrement + MOZ_RELEASE_ASSERT(IsRead(oldState)); + } + + void StartWriteOp() { + MOZ_RELEASE_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_RELEASE_ASSERT(IsIdle(oldState)); + } + + void EndWriteOp() { + // Check again that the table is writable, in case it was marked as + // non-writable just after the IsWritable() assertion in StartWriteOp() + // occurred. + MOZ_RELEASE_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kIdle); + MOZ_RELEASE_ASSERT(IsWrite(oldState)); + } + + void StartIteratorRemovalOp() { + // When doing removals at the end of iteration, we go from Read1 state to + // Write and then back. + MOZ_RELEASE_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kWrite); + MOZ_RELEASE_ASSERT(IsRead1(oldState)); + } + + void EndIteratorRemovalOp() { + // Check again that the table is writable, in case it was marked as + // non-writable just after the IsWritable() assertion in + // StartIteratorRemovalOp() occurred. + MOZ_RELEASE_ASSERT(IsWritable()); + uint32_t oldState = mState.exchange(kRead1); + MOZ_RELEASE_ASSERT(IsWrite(oldState)); + } + + void StartDestructorOp() { + // A destructor op is like a write, but the table doesn't need to be + // writable. + uint32_t oldState = mState.exchange(kWrite); + MOZ_RELEASE_ASSERT(IsIdle(oldState)); + } + + void EndDestructorOp() { + uint32_t oldState = mState.exchange(kIdle); + MOZ_RELEASE_ASSERT(IsWrite(oldState)); + } + + private: + // Things of note about the representation of |mState|. + // - The values between kRead1..kReadMax represent valid Read(n) values. + // - kIdle and kRead1 are deliberately chosen so that incrementing the - + // former gives the latter. + // - 9999 concurrent readers should be enough for anybody. + static const uint32_t kIdle = 0; + static const uint32_t kRead1 = 1; + static const uint32_t kReadMax = 9999; + static const uint32_t kWrite = 10000; + + mozilla::Atomic mState; + mozilla::Atomic mIsWritable; +}; +#endif + +// A PLDHashTable may be allocated on the stack or within another structure or +// class. No entry storage is allocated until the first element is added. This +// means that empty hash tables are cheap, which is good because they are +// common. +// +// There used to be a long, math-heavy comment here about the merits of +// double hashing vs. chaining; it was removed in bug 1058335. In short, double +// hashing is more space-efficient unless the element size gets large (in which +// case you should keep using double hashing but switch to using pointer +// elements). Also, with double hashing, you can't safely hold an entry pointer +// and use it after an add or remove operation, unless you sample Generation() +// before adding or removing, and compare the sample after, dereferencing the +// entry pointer only if Generation() has not changed. +class PLDHashTable { + private: + // A slot represents a cached hash value and its associated entry stored in + // the hash table. The hash value and the entry are not stored contiguously. + struct Slot { + Slot(PLDHashEntryHdr* aEntry, PLDHashNumber* aKeyHash) + : mEntry(aEntry), mKeyHash(aKeyHash) {} + + Slot(const Slot&) = default; + Slot(Slot&& aOther) = default; + + Slot& operator=(Slot&& aOther) = default; + + bool operator==(const Slot& aOther) const { + return mEntry == aOther.mEntry; + } + + PLDHashNumber KeyHash() const { return *HashPtr(); } + void SetKeyHash(PLDHashNumber aHash) { *HashPtr() = aHash; } + + PLDHashEntryHdr* ToEntry() const { return mEntry; } + + bool IsFree() const { return KeyHash() == 0; } + bool IsRemoved() const { return KeyHash() == 1; } + bool IsLive() const { return IsLiveHash(KeyHash()); } + static bool IsLiveHash(uint32_t aHash) { return aHash >= 2; } + + void MarkFree() { *HashPtr() = 0; } + void MarkRemoved() { *HashPtr() = 1; } + void MarkColliding() { *HashPtr() |= kCollisionFlag; } + + void Next(uint32_t aEntrySize) { + char* p = reinterpret_cast(mEntry); + p += aEntrySize; + mEntry = reinterpret_cast(p); + mKeyHash++; + } + PLDHashNumber* HashPtr() const { return mKeyHash; } + + private: + PLDHashEntryHdr* mEntry; + PLDHashNumber* mKeyHash; + }; + + // This class maintains the invariant that every time the entry store is + // changed, the generation is updated. + // + // The data layout separates the cached hashes of entries and the entries + // themselves to save space. We could store the entries thusly: + // + // +--------+--------+---------+ + // | entry0 | entry1 | ... | + // +--------+--------+---------+ + // + // where the entries themselves contain the cached hash stored as their + // first member. PLDHashTable did this for a long time, with entries looking + // like: + // + // class PLDHashEntryHdr + // { + // PLDHashNumber mKeyHash; + // }; + // + // class MyEntry : public PLDHashEntryHdr + // { + // ... + // }; + // + // The problem with this setup is that, depending on the layout of + // `MyEntry`, there may be platform ABI-mandated padding between `mKeyHash` + // and the first member of `MyEntry`. This ABI-mandated padding is wasted + // space, and was surprisingly common, e.g. when MyEntry contained a single + // pointer on 64-bit platforms. + // + // As previously alluded to, the current setup stores things thusly: + // + // +-------+-------+-------+-------+--------+--------+---------+ + // | hash0 | hash1 | ..... | hashN | entry0 | entry1 | ... | + // +-------+-------+-------+-------+--------+--------+---------+ + // + // which contains no wasted space between the hashes themselves, and no + // wasted space between the entries themselves. malloc is guaranteed to + // return blocks of memory with at least word alignment on all of our major + // platforms. PLDHashTable mandates that the size of the hash table is + // always a power of two, so the alignment of the memory containing the + // first entry is always at least the alignment of the entire entry store. + // That means the alignment of `entry0` should be its natural alignment. + // Entries may have problems if they contain over-aligned members such as + // SIMD vector types, but this has not been a problem in practice. + // + // Note: It would be natural to store the generation within this class, but + // we can't do that without bloating sizeof(PLDHashTable) on 64-bit machines. + // So instead we store it outside this class, and Set() takes a pointer to it + // and ensures it is updated as necessary. + class EntryStore { + private: + char* mEntryStore; + + static char* Entries(char* aStore, uint32_t aCapacity) { + return aStore + aCapacity * sizeof(PLDHashNumber); + } + + char* Entries(uint32_t aCapacity) const { + return Entries(Get(), aCapacity); + } + + public: + EntryStore() : mEntryStore(nullptr) {} + + ~EntryStore() { + free(mEntryStore); + mEntryStore = nullptr; + } + + char* Get() const { return mEntryStore; } + bool IsAllocated() const { return !!mEntryStore; } + + Slot SlotForIndex(uint32_t aIndex, uint32_t aEntrySize, + uint32_t aCapacity) const { + char* entries = Entries(aCapacity); + auto entry = + reinterpret_cast(entries + aIndex * aEntrySize); + auto hashes = reinterpret_cast(Get()); + return Slot(entry, &hashes[aIndex]); + } + + Slot SlotForPLDHashEntry(PLDHashEntryHdr* aEntry, uint32_t aCapacity, + uint32_t aEntrySize) { + char* entries = Entries(aCapacity); + char* entry = reinterpret_cast(aEntry); + uint32_t entryOffset = entry - entries; + uint32_t slotIndex = entryOffset / aEntrySize; + return SlotForIndex(slotIndex, aEntrySize, aCapacity); + } + + template + void ForEachSlot(uint32_t aCapacity, uint32_t aEntrySize, F&& aFunc) { + ForEachSlot(Get(), aCapacity, aEntrySize, std::move(aFunc)); + } + + template + static void ForEachSlot(char* aStore, uint32_t aCapacity, + uint32_t aEntrySize, F&& aFunc) { + char* entries = Entries(aStore, aCapacity); + Slot slot(reinterpret_cast(entries), + reinterpret_cast(aStore)); + for (size_t i = 0; i < aCapacity; ++i) { + aFunc(slot); + slot.Next(aEntrySize); + } + } + + void Set(char* aEntryStore, uint16_t* aGeneration) { + mEntryStore = aEntryStore; + *aGeneration += 1; + } + }; + + // These fields are packed carefully. On 32-bit platforms, + // sizeof(PLDHashTable) is 20. On 64-bit platforms, sizeof(PLDHashTable) is + // 32; 28 bytes of data followed by 4 bytes of padding for alignment. + const PLDHashTableOps* const mOps; // Virtual operations; see below. + EntryStore mEntryStore; // (Lazy) entry storage and generation. + uint16_t mGeneration; // The storage generation. + uint8_t mHashShift; // Multiplicative hash shift. + const uint8_t mEntrySize; // Number of bytes in an entry. + uint32_t mEntryCount; // Number of entries in table. + uint32_t mRemovedCount; // Removed entry sentinels in table. + +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mutable Checker mChecker; +#endif + + public: + // Table capacity limit; do not exceed. The max capacity used to be 1<<23 but + // that occasionally that wasn't enough. Making it much bigger than 1<<26 + // probably isn't worthwhile -- tables that big are kind of ridiculous. + // Also, the growth operation will (deliberately) fail if |capacity * + // mEntrySize| overflows a uint32_t, and mEntrySize is always at least 8 + // bytes. + static const uint32_t kMaxCapacity = ((uint32_t)1 << 26); + + static const uint32_t kMinCapacity = 8; + + // Making this half of kMaxCapacity ensures it'll fit. Nobody should need an + // initial length anywhere nearly this large, anyway. + static const uint32_t kMaxInitialLength = kMaxCapacity / 2; + + // This gives a default initial capacity of 8. + static const uint32_t kDefaultInitialLength = 4; + + // Initialize the table with |aOps| and |aEntrySize|. The table's initial + // capacity is chosen such that |aLength| elements can be inserted without + // rehashing; if |aLength| is a power-of-two, this capacity will be + // |2*length|. However, because entry storage is allocated lazily, this + // initial capacity won't be relevant until the first element is added; prior + // to that the capacity will be zero. + // + // This will crash if |aEntrySize| and/or |aLength| are too large. + PLDHashTable(const PLDHashTableOps* aOps, uint32_t aEntrySize, + uint32_t aLength = kDefaultInitialLength); + + PLDHashTable(PLDHashTable&& aOther) + // Initialize fields which are checked by the move assignment operator + // and the destructor (which the move assignment operator calls). + : mOps(nullptr), mEntryStore(), mGeneration(0), mEntrySize(0) { + *this = std::move(aOther); + } + + PLDHashTable& operator=(PLDHashTable&& aOther); + + ~PLDHashTable(); + + // This should be used rarely. + const PLDHashTableOps* Ops() const { return mOps; } + + // Size in entries (gross, not net of free and removed sentinels) for table. + // This can be zero if no elements have been added yet, in which case the + // entry storage will not have yet been allocated. + uint32_t Capacity() const { + return mEntryStore.IsAllocated() ? CapacityFromHashShift() : 0; + } + + uint32_t EntrySize() const { return mEntrySize; } + uint32_t EntryCount() const { return mEntryCount; } + uint32_t Generation() const { return mGeneration; } + + // To search for a |key| in |table|, call: + // + // entry = table.Search(key); + // + // If |entry| is non-null, |key| was found. If |entry| is null, key was not + // found. + PLDHashEntryHdr* Search(const void* aKey) const; + + // To add an entry identified by |key| to table, call: + // + // entry = table.Add(key, mozilla::fallible); + // + // If |entry| is null upon return, then the table is severely overloaded and + // memory can't be allocated for entry storage. + // + // Otherwise, if the initEntry hook was provided, |entry| will be + // initialized. If the initEntry hook was not provided, the caller + // should initialize |entry| as appropriate. + PLDHashEntryHdr* Add(const void* aKey, const mozilla::fallible_t&); + + // This is like the other Add() function, but infallible, and so never + // returns null. + PLDHashEntryHdr* Add(const void* aKey); + + // To remove an entry identified by |key| from table, call: + // + // table.Remove(key); + // + // If |key|'s entry is found, it is cleared (via table->mOps->clearEntry). + // The table's capacity may be reduced afterwards. + void Remove(const void* aKey); + + // To remove an entry found by a prior search, call: + // + // table.RemoveEntry(entry); + // + // The entry, which must be present and in use, is cleared (via + // table->mOps->clearEntry). The table's capacity may be reduced afterwards. + void RemoveEntry(PLDHashEntryHdr* aEntry); + + // Remove an entry already accessed via Search() or Add(). + // + // NB: this is a "raw" or low-level method. It does not shrink the table if + // it is underloaded. Don't use it unless necessary and you know what you are + // doing, and if so, please explain in a comment why it is necessary instead + // of RemoveEntry(). + void RawRemove(PLDHashEntryHdr* aEntry); + + // This function is equivalent to + // ClearAndPrepareForLength(kDefaultInitialLength). + void Clear(); + + // This function clears the table's contents and frees its entry storage, + // leaving it in a empty state ready to be used again. Afterwards, when the + // first element is added the entry storage that gets allocated will have a + // capacity large enough to fit |aLength| elements without rehashing. + // + // It's conceptually the same as calling the destructor and then re-calling + // the constructor with the original |aOps| and |aEntrySize| arguments, and + // a new |aLength| argument. + void ClearAndPrepareForLength(uint32_t aLength); + + // Measure the size of the table's entry storage. If the entries contain + // pointers to other heap blocks, you have to iterate over the table and + // measure those separately; hence the "Shallow" prefix. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Like ShallowSizeOfExcludingThis(), but includes sizeof(*this). + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Mark a table as immutable for the remainder of its lifetime. This + // changes the implementation from asserting one set of invariants to + // asserting a different set. + void MarkImmutable() { +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + mChecker.SetNonWritable(); +#endif + } + + // If you use PLDHashEntryStub or a subclass of it as your entry struct, and + // if your entries move via memcpy and clear via memset(0), you can use these + // stub operations. + static const PLDHashTableOps* StubOps(); + + // The individual stub operations in StubOps(). + static PLDHashNumber HashVoidPtrKeyStub(const void* aKey); + static bool MatchEntryStub(const PLDHashEntryHdr* aEntry, const void* aKey); + static void MoveEntryStub(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + static void ClearEntryStub(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); + + // Hash/match operations for tables holding C strings. + static PLDHashNumber HashStringKey(const void* aKey); + static bool MatchStringKey(const PLDHashEntryHdr* aEntry, const void* aKey); + + class EntryHandle { + public: + EntryHandle(EntryHandle&& aOther) noexcept; +#ifdef MOZ_HASH_TABLE_CHECKS_ENABLED + ~EntryHandle(); +#endif + + EntryHandle(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&) = delete; + EntryHandle& operator=(EntryHandle&& aOther) = delete; + + // Is this slot currently occupied? + bool HasEntry() const { return mSlot.IsLive(); } + + explicit operator bool() const { return HasEntry(); } + + // Get the entry stored in this slot. May not be called unless the slot is + // currently occupied. + PLDHashEntryHdr* Entry() { + MOZ_ASSERT(HasEntry()); + return mSlot.ToEntry(); + } + + template + void Insert(F&& aInitEntry) { + MOZ_ASSERT(!HasEntry()); + OccupySlot(); + std::forward(aInitEntry)(Entry()); + } + + // If the slot is currently vacant, the slot is occupied and `initEntry` is + // invoked to initialize the entry. Returns the entry stored in now-occupied + // slot. + template + PLDHashEntryHdr* OrInsert(F&& aInitEntry) { + if (!HasEntry()) { + Insert(std::forward(aInitEntry)); + } + return Entry(); + } + + /** Removes the entry. Note that the table won't shrink on destruction of + * the EntryHandle. + * + * \pre HasEntry() + * \post !HasEntry() + */ + void Remove(); + + /** Removes the entry, if it exists. Note that the table won't shrink on + * destruction of the EntryHandle. + * + * \post !HasEntry() + */ + void OrRemove(); + + private: + friend class PLDHashTable; + + EntryHandle(PLDHashTable* aTable, PLDHashNumber aKeyHash, Slot aSlot); + + void OccupySlot(); + + PLDHashTable* mTable; + PLDHashNumber mKeyHash; + Slot mSlot; + }; + + template + auto WithEntryHandle(const void* aKey, F&& aFunc) + -> std::invoke_result_t { + return std::forward(aFunc)(MakeEntryHandle(aKey)); + } + + template + auto WithEntryHandle(const void* aKey, const mozilla::fallible_t& aFallible, + F&& aFunc) + -> std::invoke_result_t&&> { + return std::forward(aFunc)(MakeEntryHandle(aKey, aFallible)); + } + + // This is an iterator for PLDHashtable. Assertions will detect some, but not + // all, mid-iteration table modifications that might invalidate (e.g. + // reallocate) the entry storage. + // + // Any element can be removed during iteration using Remove(). If any + // elements are removed, the table may be resized once iteration ends. + // + // Example usage: + // + // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // auto entry = static_cast(iter.Get()); + // // ... do stuff with |entry| ... + // // ... possibly call iter.Remove() once ... + // } + // + // or: + // + // for (PLDHashTable::Iterator iter(&table); !iter.Done(); iter.Next()) { + // auto entry = static_cast(iter.Get()); + // // ... do stuff with |entry| ... + // // ... possibly call iter.Remove() once ... + // } + // + // The latter form is more verbose but is easier to work with when + // making subclasses of Iterator. + // + class Iterator { + public: + explicit Iterator(PLDHashTable* aTable); + struct EndIteratorTag {}; + Iterator(PLDHashTable* aTable, EndIteratorTag aTag); + Iterator(Iterator&& aOther); + ~Iterator(); + + // Have we finished? + bool Done() const { return mNexts == mNextsLimit; } + + // Get the current entry. + PLDHashEntryHdr* Get() const { + MOZ_ASSERT(!Done()); + MOZ_ASSERT(mCurrent.IsLive()); + return mCurrent.ToEntry(); + } + + // Advance to the next entry. + void Next(); + + // Remove the current entry. Must only be called once per entry, and Get() + // must not be called on that entry afterwards. + void Remove(); + + bool operator==(const Iterator& aOther) const { + MOZ_ASSERT(mTable == aOther.mTable); + return mNexts == aOther.mNexts; + } + + Iterator Clone() const { return {*this}; } + + protected: + PLDHashTable* mTable; // Main table pointer. + + private: + Slot mCurrent; // Pointer to the current entry. + uint32_t mNexts; // Number of Next() calls. + uint32_t mNextsLimit; // Next() call limit. + + bool mHaveRemoved; // Have any elements been removed? + uint8_t mEntrySize; // Size of entries. + + bool IsOnNonLiveEntry() const; + + void MoveToNextLiveEntry(); + + Iterator() = delete; + Iterator(const Iterator&); + Iterator& operator=(const Iterator&) = delete; + Iterator& operator=(const Iterator&&) = delete; + }; + + Iterator Iter() { return Iterator(this); } + + // Use this if you need to initialize an Iterator in a const method. If you + // use this case, you should not call Remove() on the iterator. + Iterator ConstIter() const { + return Iterator(const_cast(this)); + } + + private: + static uint32_t HashShift(uint32_t aEntrySize, uint32_t aLength); + + static const PLDHashNumber kCollisionFlag = 1; + + PLDHashNumber Hash1(PLDHashNumber aHash0) const; + void Hash2(PLDHashNumber aHash, uint32_t& aHash2Out, + uint32_t& aSizeMaskOut) const; + + static bool MatchSlotKeyhash(Slot& aSlot, const PLDHashNumber aHash); + Slot SlotForIndex(uint32_t aIndex) const; + + // We store mHashShift rather than sizeLog2 to optimize the collision-free + // case in SearchTable. + uint32_t CapacityFromHashShift() const { + return ((uint32_t)1 << (kPLDHashNumberBits - mHashShift)); + } + + PLDHashNumber ComputeKeyHash(const void* aKey) const; + + enum SearchReason { ForSearchOrRemove, ForAdd }; + + // Avoid using bare `Success` and `Failure`, as those names are commonly + // defined as macros. + template + auto SearchTable(const void* aKey, PLDHashNumber aKeyHash, + PLDSuccess&& aSucess, PLDFailure&& aFailure) const; + + Slot FindFreeSlot(PLDHashNumber aKeyHash) const; + + bool ChangeTable(int aDeltaLog2); + + void RawRemove(Slot& aSlot); + void ShrinkIfAppropriate(); + + mozilla::Maybe MakeEntryHandle(const void* aKey, + const mozilla::fallible_t&); + + EntryHandle MakeEntryHandle(const void* aKey); + + PLDHashTable(const PLDHashTable& aOther) = delete; + PLDHashTable& operator=(const PLDHashTable& aOther) = delete; +}; + +// Compute the hash code for a given key to be looked up, added, or removed. +// A hash code may have any PLDHashNumber value. +typedef PLDHashNumber (*PLDHashHashKey)(const void* aKey); + +// Compare the key identifying aEntry with the provided key parameter. Return +// true if keys match, false otherwise. +typedef bool (*PLDHashMatchEntry)(const PLDHashEntryHdr* aEntry, + const void* aKey); + +// Copy the data starting at aFrom to the new entry storage at aTo. Do not add +// reference counts for any strong references in the entry, however, as this +// is a "move" operation: the old entry storage at from will be freed without +// any reference-decrementing callback shortly. +typedef void (*PLDHashMoveEntry)(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + +// Clear the entry and drop any strong references it holds. This callback is +// invoked by Remove(), but only if the given key is found in the table. +typedef void (*PLDHashClearEntry)(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry); + +// Initialize a new entry. This function is called when +// Add() finds no existing entry for the given key, and must add a new one. +typedef void (*PLDHashInitEntry)(PLDHashEntryHdr* aEntry, const void* aKey); + +// Finally, the "vtable" structure for PLDHashTable. The first four hooks +// must be provided by implementations; they're called unconditionally by the +// generic PLDHashTable.cpp code. Hooks after these may be null. +// +// Summary of allocation-related hook usage with C++ placement new emphasis: +// initEntry Call placement new using default key-based ctor. +// moveEntry Call placement new using copy ctor, run dtor on old +// entry storage. +// clearEntry Run dtor on entry. +// +// Note the reason why initEntry is optional: the default hooks (stubs) clear +// entry storage. On a successful Add(tbl, key), the returned entry pointer +// addresses an entry struct whose entry members are still clear (null). Add() +// callers can test such members to see whether the entry was newly created by +// the Add() call that just succeeded. If placement new or similar +// initialization is required, define an |initEntry| hook. Of course, the +// |clearEntry| hook must zero or null appropriately. +// +// XXX assumes 0 is null for pointer types. +struct PLDHashTableOps { + // Mandatory hooks. All implementations must provide these. + PLDHashHashKey hashKey; + PLDHashMatchEntry matchEntry; + PLDHashMoveEntry moveEntry; + + // Optional hooks start here. If null, these are not called. + PLDHashClearEntry clearEntry; + PLDHashInitEntry initEntry; +}; + +// A minimal entry is a subclass of PLDHashEntryHdr and has a void* key pointer. +struct PLDHashEntryStub : public PLDHashEntryHdr { + const void* key; +}; + +#endif /* PLDHashTable_h */ diff --git a/xpcom/ds/PerfectHash.h b/xpcom/ds/PerfectHash.h new file mode 100644 index 0000000000..1e75855462 --- /dev/null +++ b/xpcom/ds/PerfectHash.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +/* Helper routines for perfecthash.py. Not to be used directly. */ + +#ifndef mozilla_PerfectHash_h +#define mozilla_PerfectHash_h + +#include + +namespace mozilla { +namespace perfecthash { + +// 32-bit FNV offset basis and prime value. +// NOTE: Must match values in |perfecthash.py| +constexpr uint32_t FNV_OFFSET_BASIS = 0x811C9DC5; +constexpr uint32_t FNV_PRIME = 16777619; + +/** + * Basic FNV hasher function used by perfecthash. Generic over the unit type. + */ +template +inline uint32_t Hash(uint32_t aBasis, const C* aKey, size_t aLen) { + for (size_t i = 0; i < aLen; ++i) { + aBasis = + (aBasis ^ static_cast>(aKey[i])) * FNV_PRIME; + } + return aBasis; +} + +/** + * Helper method for getting the index from a perfect hash. + * Called by code generated from |perfecthash.py|. + */ +template +inline const Entry& Lookup(const C* aKey, size_t aLen, + const Base (&aTable)[NBases], + const Entry (&aEntries)[NEntries]) { + uint32_t basis = aTable[Hash(FNV_OFFSET_BASIS, aKey, aLen) % NBases]; + return aEntries[Hash(basis, aKey, aLen) % NEntries]; +} + +} // namespace perfecthash +} // namespace mozilla + +#endif // !defined(mozilla_PerfectHash_h) diff --git a/xpcom/ds/SimpleEnumerator.h b/xpcom/ds/SimpleEnumerator.h new file mode 100644 index 0000000000..463b9ecaed --- /dev/null +++ b/xpcom/ds/SimpleEnumerator.h @@ -0,0 +1,73 @@ +/* -*- 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/. */ + +#ifndef mozilla_SimpleEnumerator_h +#define mozilla_SimpleEnumerator_h + +#include "nsCOMPtr.h" +#include "nsISimpleEnumerator.h" + +namespace mozilla { + +/** + * A wrapper class around nsISimpleEnumerator to support ranged iteration. This + * requires every element in the enumeration to implement the same interface, T. + * If any element does not implement this interface, the enumeration ends at + * that element, and triggers an assertion in debug builds. + * + * Typical usage looks something like: + * + * for (auto& docShell : SimpleEnumerator(docShellEnum)) { + * docShell.LoadURI(...); + * } + */ + +template +class SimpleEnumerator final { + public: + explicit SimpleEnumerator(nsISimpleEnumerator* aEnum) : mEnum(aEnum) {} + + class Entry { + public: + explicit Entry(T* aPtr) : mPtr(aPtr) {} + + explicit Entry(nsISimpleEnumerator& aEnum) : mEnum(&aEnum) { ++*this; } + + const nsCOMPtr& operator*() { + MOZ_ASSERT(mPtr); + return mPtr; + } + + Entry& operator++() { + MOZ_ASSERT(mEnum); + nsCOMPtr next; + if (NS_SUCCEEDED(mEnum->GetNext(getter_AddRefs(next)))) { + mPtr = do_QueryInterface(next); + MOZ_ASSERT(mPtr); + } else { + mPtr = nullptr; + } + return *this; + } + + bool operator!=(const Entry& aOther) const { return mPtr != aOther.mPtr; } + + private: + nsCOMPtr mPtr; + nsCOMPtr mEnum; + }; + + Entry begin() { return Entry(*mEnum); } + + Entry end() { return Entry(nullptr); } + + private: + nsCOMPtr mEnum; +}; + +} // namespace mozilla + +#endif // mozilla_SimpleEnumerator_h diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py new file mode 100644 index 0000000000..fb41a00bd1 --- /dev/null +++ b/xpcom/ds/StaticAtoms.py @@ -0,0 +1,2636 @@ +# 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/. + +# flake8: noqa + +import sys + +from Atom import ( + Atom, + InheritingAnonBoxAtom, + NonInheritingAnonBoxAtom, + PseudoElementAtom, +) +from HTMLAtoms import HTML_PARSER_ATOMS + +# Static atom definitions, used to generate nsGkAtomList.h. +# +# Each atom is defined by a call to Atom, PseudoElementAtom, +# NonInheritingAnonBoxAtom or InheritingAnonBoxAtom. +# +# The first argument is the atom's identifier. +# The second argument is the atom's string value. +# +# Please keep the Atom() definitions on one line as this is parsed by the +# htmlparser: parser/html/java/htmlparser +# Please keep "START ATOMS" and "END ATOMS" comments as the parser uses them. +# +# It is not possible to conditionally define static atoms with #ifdef etc. +# fmt: off +STATIC_ATOMS = [ + # START ATOMS + # -------------------------------------------------------------------------- + # Generic atoms + # -------------------------------------------------------------------------- + Atom("SystemPrincipal", "[System Principal]"), + Atom("_empty", ""), + Atom("_0", "0"), + Atom("_1", "1"), + Atom("mozframetype", "mozframetype"), + Atom("_moz_abspos", "_moz_abspos"), + Atom("_moz_activated", "_moz_activated"), + Atom("_moz_anonclass", "_moz_anonclass"), + Atom("_moz_resizing", "_moz_resizing"), + Atom("moztype", "_moz-type"), + Atom("mozdirty", "_moz_dirty"), + Atom("mozdisallowselectionprint", "mozdisallowselectionprint"), + Atom("mozdonotsend", "moz-do-not-send"), + Atom("mozfwcontainer", "moz-forward-container"), # Used by MailNews. + Atom("mozgeneratedcontentbefore", "_moz_generated_content_before"), + Atom("mozgeneratedcontentafter", "_moz_generated_content_after"), + Atom("mozgeneratedcontentmarker", "_moz_generated_content_marker"), + Atom("mozgeneratedcontentimage", "_moz_generated_content_image"), + Atom("mozquote", "_moz_quote"), + Atom("mozsignature", "moz-signature"), # Used by MailNews. + Atom("_moz_bullet_font", "-moz-bullet-font"), + Atom("_moz_is_glyph", "-moz-is-glyph"), + Atom("_moz_original_size", "_moz_original_size"), + Atom("_moz_print_preview", "-moz-print-preview"), + Atom("_moz_non_native_content_theme", "-moz-non-native-content-theme"), + Atom("menuactive", "_moz-menuactive"), + Atom("_poundDefault", "#default"), + Atom("_asterisk", "*"), + Atom("a", "a"), + Atom("abbr", "abbr"), + Atom("abort", "abort"), + Atom("above", "above"), + Atom("acceltext", "acceltext"), + Atom("accept", "accept"), + Atom("acceptcharset", "accept-charset"), + Atom("accessiblenode", "accessible-node"), + Atom("accesskey", "accesskey"), + Atom("acronym", "acronym"), + Atom("action", "action"), + Atom("active", "active"), + Atom("activateontab", "activateontab"), + Atom("actuate", "actuate"), + Atom("address", "address"), + Atom("adoptedsheetclones", "adoptedsheetclones"), + Atom("after", "after"), + Atom("align", "align"), + Atom("alink", "alink"), + Atom("all", "all"), + Atom("allow", "allow"), + Atom("allowdownloads", "allow-downloads"), + Atom("allowevents", "allowevents"), + Atom("allowforms", "allow-forms"), + Atom("allowfullscreen", "allowfullscreen"), + Atom("allowmodals", "allow-modals"), + Atom("alloworientationlock", "allow-orientation-lock"), + Atom("allowpointerlock", "allow-pointer-lock"), + Atom("allowpopupstoescapesandbox", "allow-popups-to-escape-sandbox"), + Atom("allowpopups", "allow-popups"), + Atom("allowpresentation", "allow-presentation"), + Atom("allowstorageaccessbyuseractivatetion", "allow-storage-access-by-user-activation"), + Atom("allowsameorigin", "allow-same-origin"), + Atom("allowscripts", "allow-scripts"), + Atom("allowscriptstoclose", "allowscriptstoclose"), + Atom("allowtopnavigation", "allow-top-navigation"), + Atom("allowtopnavigationbyuseractivation", "allow-top-navigation-by-user-activation"), + Atom("allowtopnavigationcustomprotocols", "allow-top-navigation-to-custom-protocols"), + Atom("allowuntrusted", "allowuntrusted"), + Atom("alt", "alt"), + Atom("alternate", "alternate"), + Atom("always", "always"), + Atom("ancestor", "ancestor"), + Atom("ancestorOrSelf", "ancestor-or-self"), + Atom("anchor", "anchor"), + Atom("_and", "and"), + Atom("animations", "animations"), + Atom("anonid", "anonid"), + Atom("anonlocation", "anonlocation"), + Atom("any", "any"), + Atom("any_hover", "any-hover"), + Atom("any_pointer", "any-pointer"), + Atom("applet", "applet"), + Atom("applyImports", "apply-imports"), + Atom("applyTemplates", "apply-templates"), + Atom("archive", "archive"), + Atom("area", "area"), + Atom("aria", "aria"), + Atom("aria_activedescendant", "aria-activedescendant"), + Atom("aria_atomic", "aria-atomic"), + Atom("aria_autocomplete", "aria-autocomplete"), + Atom("aria_busy", "aria-busy"), + Atom("aria_checked", "aria-checked"), + Atom("aria_controls", "aria-controls"), + Atom("aria_current", "aria-current"), + Atom("aria_describedby", "aria-describedby"), + Atom("aria_description", "aria-description"), + Atom("aria_disabled", "aria-disabled"), + Atom("aria_dropeffect", "aria-dropeffect"), + Atom("aria_expanded", "aria-expanded"), + Atom("aria_flowto", "aria-flowto"), + Atom("aria_haspopup", "aria-haspopup"), + Atom("aria_hidden", "aria-hidden"), + Atom("aria_invalid", "aria-invalid"), + Atom("aria_labelledby", "aria-labelledby"), + Atom("aria_level", "aria-level"), + Atom("aria_live", "aria-live"), + Atom("aria_multiline", "aria-multiline"), + Atom("aria_multiselectable", "aria-multiselectable"), + Atom("aria_owns", "aria-owns"), + Atom("aria_posinset", "aria-posinset"), + Atom("aria_pressed", "aria-pressed"), + Atom("aria_readonly", "aria-readonly"), + Atom("aria_relevant", "aria-relevant"), + Atom("aria_required", "aria-required"), + Atom("aria_selected", "aria-selected"), + Atom("aria_setsize", "aria-setsize"), + Atom("aria_sort", "aria-sort"), + Atom("aria_valuemax", "aria-valuemax"), + Atom("aria_valuemin", "aria-valuemin"), + Atom("aria_valuenow", "aria-valuenow"), + Atom("arrow", "arrow"), + Atom("article", "article"), + Atom("as", "as"), + Atom("ascending", "ascending"), + Atom("aside", "aside"), + Atom("aspectRatio", "aspect-ratio"), + Atom("async", "async"), + Atom("attribute", "attribute"), + Atom("attributes", "attributes"), + Atom("attributeSet", "attribute-set"), + Atom("_auto", "auto"), + Atom("autocapitalize", "autocapitalize"), + Atom("autocheck", "autocheck"), + Atom("autocomplete", "autocomplete"), + Atom("autocomplete_richlistbox", "autocomplete-richlistbox"), + Atom("autofocus", "autofocus"), + Atom("autoplay", "autoplay"), + Atom("axis", "axis"), + Atom("b", "b"), + Atom("background", "background"), + Atom("bar", "bar"), + Atom("base", "base"), + Atom("basefont", "basefont"), + Atom("baseline", "baseline"), + Atom("bdi", "bdi"), + Atom("bdo", "bdo"), + Atom("before", "before"), + Atom("behavior", "behavior"), + Atom("below", "below"), + Atom("bgcolor", "bgcolor"), + Atom("bgsound", "bgsound"), + Atom("big", "big"), + Atom("binding", "binding"), + Atom("bindings", "bindings"), + Atom("bindToUntrustedContent", "bindToUntrustedContent"), + Atom("black", "black"), + Atom("block", "block"), + Atom("block_size", "block-size"), + Atom("blockquote", "blockquote"), + Atom("blur", "blur"), + Atom("body", "body"), + Atom("boolean", "boolean"), + Atom("border", "border"), + Atom("bordercolor", "bordercolor"), + Atom("both", "both"), + Atom("bottom", "bottom"), + Atom("bottomend", "bottomend"), + Atom("bottomstart", "bottomstart"), + Atom("bottomleft", "bottomleft"), + Atom("bottommargin", "bottommargin"), + Atom("bottomright", "bottomright"), + Atom("box", "box"), + Atom("br", "br"), + Atom("browser", "browser"), + Atom("mozbrowser", "mozbrowser"), + Atom("button", "button"), + Atom("callTemplate", "call-template"), + Atom("canvas", "canvas"), + Atom("caption", "caption"), + Atom("captionBox", "caption-box"), + Atom("capture", "capture"), + Atom("caseOrder", "case-order"), + Atom("cdataSectionElements", "cdata-section-elements"), + Atom("ceiling", "ceiling"), + Atom("cell", "cell"), + Atom("cellpadding", "cellpadding"), + Atom("cellspacing", "cellspacing"), + Atom("center", "center"), + Atom("change", "change"), + Atom("_char", "char"), + Atom("characterData", "characterData"), + Atom("charcode", "charcode"), + Atom("charoff", "charoff"), + Atom("charset", "charset"), + Atom("checkbox", "checkbox"), + Atom("checkboxLabel", "checkbox-label"), + Atom("checked", "checked"), + Atom("child", "child"), + Atom("children", "children"), + Atom("childList", "childList"), + Atom("child_item_count", "child-item-count"), + Atom("choose", "choose"), + Atom("chromemargin", "chromemargin"), + Atom("exposeToUntrustedContent", "exposeToUntrustedContent"), + Atom("circ", "circ"), + Atom("circle", "circle"), + Atom("cite", "cite"), + Atom("_class", "class"), + Atom("classid", "classid"), + Atom("clear", "clear"), + Atom("click", "click"), + Atom("clickcount", "clickcount"), + Atom("movetoclick", "movetoclick"), + Atom("clip", "clip"), + Atom("close", "close"), + Atom("closed", "closed"), + Atom("closemenu", "closemenu"), + Atom("code", "code"), + Atom("codebase", "codebase"), + Atom("codetype", "codetype"), + Atom("col", "col"), + Atom("colgroup", "colgroup"), + Atom("collapse", "collapse"), + Atom("collapsed", "collapsed"), + Atom("color", "color"), + Atom("color_gamut", "color-gamut"), + Atom("color_index", "color-index"), + Atom("color_scheme", "color-scheme"), + Atom("cols", "cols"), + Atom("colspan", "colspan"), + Atom("combobox", "combobox"), + Atom("command", "command"), + Atom("commandupdater", "commandupdater"), + Atom("comment", "comment"), + Atom("compact", "compact"), + Atom("concat", "concat"), + Atom("constructor", "constructor"), + Atom("consumeoutsideclicks", "consumeoutsideclicks"), + Atom("container", "container"), + Atom("contains", "contains"), + Atom("content", "content"), + Atom("contenteditable", "contenteditable"), + Atom("headerContentDisposition", "content-disposition"), + Atom("headerContentLanguage", "content-language"), + Atom("contentLocation", "content-location"), + Atom("headerContentScriptType", "content-script-type"), + Atom("headerContentStyleType", "content-style-type"), + Atom("headerContentType", "content-type"), + Atom("consumeanchor", "consumeanchor"), + Atom("context", "context"), + Atom("contextmenu", "contextmenu"), + Atom("control", "control"), + Atom("controls", "controls"), + Atom("coords", "coords"), + Atom("copy", "copy"), + Atom("copyOf", "copy-of"), + Atom("count", "count"), + Atom("crop", "crop"), + Atom("crossorigin", "crossorigin"), + Atom("curpos", "curpos"), + Atom("current", "current"), + Atom("cutoutregion", "cutoutregion"), + Atom("cycler", "cycler"), + Atom("dashed", "dashed"), + Atom("data", "data"), + Atom("dataAtShortcutkeys", "data-at-shortcutkeys"), + Atom("datalist", "datalist"), + Atom("datal10nid", "data-l10n-id"), + Atom("datal10nargs", "data-l10n-args"), + Atom("datal10nattrs", "data-l10n-attrs"), + Atom("datal10nname", "data-l10n-name"), + Atom("datal10nsync", "data-l10n-sync"), + Atom("dataType", "data-type"), + Atom("dateTime", "date-time"), + Atom("date", "date"), + Atom("datetime", "datetime"), + Atom("datetime_local", "datetime-local"), + Atom("datetimeInputBoxWrapper", "datetime-input-box-wrapper"), + Atom("dd", "dd"), + Atom("decimal", "decimal"), + Atom("decimalFormat", "decimal-format"), + Atom("decimalSeparator", "decimal-separator"), + Atom("declare", "declare"), + Atom("decoderDoctor", "decoder-doctor"), + Atom("decoding", "decoding"), + Atom("decrement", "decrement"), + Atom("_default", "default"), + Atom("headerDefaultStyle", "default-style"), + Atom("defer", "defer"), + Atom("del", "del"), + Atom("delegatesanchor", "delegatesanchor"), + Atom("deletion", "deletion"), + Atom("deprecation", "deprecation"), + Atom("descendant", "descendant"), + Atom("descendantOrSelf", "descendant-or-self"), + Atom("descending", "descending"), + Atom("description", "description"), + Atom("destructor", "destructor"), + Atom("details", "details"), + Atom("deviceAspectRatio", "device-aspect-ratio"), + Atom("deviceHeight", "device-height"), + Atom("devicePixelRatio", "device-pixel-ratio"), + Atom("deviceWidth", "device-width"), + Atom("dfn", "dfn"), + Atom("dialog", "dialog"), + Atom("difference", "difference"), + Atom("digit", "digit"), + Atom("dir", "dir"), + Atom("dirAutoSetBy", "dirAutoSetBy"), + Atom("directory", "directory"), + Atom("disableOutputEscaping", "disable-output-escaping"), + Atom("disabled", "disabled"), + Atom("disableglobalhistory", "disableglobalhistory"), + Atom("disablehistory", "disablehistory"), + Atom("disablefullscreen", "disablefullscreen"), + Atom("disclosure_closed", "disclosure-closed"), + Atom("disclosure_open", "disclosure-open"), + Atom("display", "display"), + Atom("displayMode", "display-mode"), + Atom("distinct", "distinct"), + Atom("div", "div"), + Atom("dl", "dl"), + Atom("docAbstract", "doc-abstract"), + Atom("docAcknowledgments", "doc-acknowledgments"), + Atom("docAfterword", "doc-afterword"), + Atom("docAppendix", "doc-appendix"), + Atom("docBacklink", "doc-backlink"), + Atom("docBiblioentry", "doc-biblioentry"), + Atom("docBibliography", "doc-bibliography"), + Atom("docBiblioref", "doc-biblioref"), + Atom("docChapter", "doc-chapter"), + Atom("docColophon", "doc-colophon"), + Atom("docConclusion", "doc-conclusion"), + Atom("docCover", "doc-cover"), + Atom("docCredit", "doc-credit"), + Atom("docCredits", "doc-credits"), + Atom("docDedication", "doc-dedication"), + Atom("docEndnote", "doc-endnote"), + Atom("docEndnotes", "doc-endnotes"), + Atom("docEpigraph", "doc-epigraph"), + Atom("docEpilogue", "doc-epilogue"), + Atom("docErrata", "doc-errata"), + Atom("docExample", "doc-example"), + Atom("docFootnote", "doc-footnote"), + Atom("docForeword", "doc-foreword"), + Atom("docGlossary", "doc-glossary"), + Atom("docGlossref", "doc-glossref"), + Atom("docIndex", "doc-index"), + Atom("docIntroduction", "doc-introduction"), + Atom("docNoteref", "doc-noteref"), + Atom("docNotice", "doc-notice"), + Atom("docPagebreak", "doc-pagebreak"), + Atom("docPagelist", "doc-pagelist"), + Atom("docPart", "doc-part"), + Atom("docPreface", "doc-preface"), + Atom("docPrologue", "doc-prologue"), + Atom("docPullquote", "doc-pullquote"), + Atom("docQna", "doc-qna"), + Atom("docSubtitle", "doc-subtitle"), + Atom("docTip", "doc-tip"), + Atom("docToc", "doc-toc"), + Atom("doctypePublic", "doctype-public"), + Atom("doctypeSystem", "doctype-system"), + Atom("document", "document"), + Atom("down", "down"), + Atom("download", "download"), + Atom("drag", "drag"), + Atom("draggable", "draggable"), + Atom("dragging", "dragging"), + Atom("dragSession", "dragSession"), + Atom("drawintitlebar", "drawintitlebar"), + Atom("drawtitle", "drawtitle"), + Atom("dropAfter", "dropAfter"), + Atom("dropBefore", "dropBefore"), + Atom("dropOn", "dropOn"), + Atom("dropMarker", "dropmarker"), + Atom("dt", "dt"), + Atom("e", "e"), + Atom("editable", "editable"), + Atom("editing", "editing"), + Atom("editor", "editor"), + Atom("element", "element"), + Atom("elementAvailable", "element-available"), + Atom("elements", "elements"), + Atom("em", "em"), + Atom("embed", "embed"), + Atom("empty", "empty"), + Atom("encoding", "encoding"), + Atom("enctype", "enctype"), + Atom("end", "end"), + Atom("endEvent", "endEvent"), + Atom("enterkeyhint", "enterkeyhint"), + Atom("error", "error"), + Atom("ethiopic_numeric", "ethiopic-numeric"), + Atom("even", "even"), + Atom("event", "event"), + Atom("events", "events"), + Atom("excludeResultPrefixes", "exclude-result-prefixes"), + Atom("exportparts", "exportparts"), + Atom("explicit_name", "explicit-name"), + Atom("extends", "extends"), + Atom("extensionElementPrefixes", "extension-element-prefixes"), + Atom("face", "face"), + Atom("fallback", "fallback"), + Atom("_false", "false"), + Atom("farthest", "farthest"), + Atom("featurePolicyViolation", "feature-policy-violation"), + Atom("field", "field"), + Atom("fieldset", "fieldset"), + Atom("file", "file"), + Atom("figcaption", "figcaption"), + Atom("figure", "figure"), + Atom("findbar", "findbar"), + Atom("firstInput", "first-input"), + Atom("fixed", "fixed"), + Atom("flags", "flags"), + Atom("flex", "flex"), + Atom("flip", "flip"), + Atom("floating", "floating"), + Atom("floor", "floor"), + Atom("flowlength", "flowlength"), + Atom("focus", "focus"), + Atom("focused", "focused"), + Atom("followanchor", "followanchor"), + Atom("following", "following"), + Atom("followingSibling", "following-sibling"), + Atom("font", "font"), + Atom("fontWeight", "font-weight"), + Atom("footer", "footer"), + Atom("_for", "for"), + Atom("forEach", "for-each"), + Atom("forcedColors", "forced-colors"), + Atom("invertedColors", "inverted-colors"), + Atom("forceOwnRefreshDriver", "forceOwnRefreshDriver"), + Atom("form", "form"), + Atom("formaction", "formaction"), + Atom("format", "format"), + Atom("formatNumber", "format-number"), + Atom("formenctype", "formenctype"), + Atom("formmethod", "formmethod"), + Atom("formnovalidate", "formnovalidate"), + Atom("formtarget", "formtarget"), + Atom("frame", "frame"), + Atom("frameborder", "frameborder"), + Atom("frameset", "frameset"), + Atom("from", "from"), + Atom("fullscreenchange", "fullscreenchange"), + Atom("fullscreenerror", "fullscreenerror"), + Atom("functionAvailable", "function-available"), + Atom("generateId", "generate-id"), + Atom("generic", "generic"), + Atom("getter", "getter"), + Atom("graphicsDocument", "graphics-document"), + Atom("graphicsObject", "graphics-object"), + Atom("graphicsSymbol", "graphics-symbol"), + Atom("grid", "grid"), + Atom("group", "group"), + Atom("groups", "groups"), + Atom("groupbox", "groupbox"), + Atom("groupingSeparator", "grouping-separator"), + Atom("groupingSize", "grouping-size"), + Atom("grow", "grow"), + Atom("h1", "h1"), + Atom("h2", "h2"), + Atom("h3", "h3"), + Atom("h4", "h4"), + Atom("h5", "h5"), + Atom("h6", "h6"), + Atom("handheldFriendly", "HandheldFriendly"), + Atom("handler", "handler"), + Atom("handlers", "handlers"), + Atom("HARD", "HARD"), + Atom("hasSameNode", "has-same-node"), + Atom("hbox", "hbox"), + Atom("head", "head"), + Atom("header", "header"), + Atom("headers", "headers"), + Atom("hebrew", "hebrew"), + Atom("height", "height"), + Atom("hgroup", "hgroup"), + Atom("hidden", "hidden"), + Atom("hidechrome", "hidechrome"), + Atom("hidecolumnpicker", "hidecolumnpicker"), + Atom("high", "high"), + Atom("highest", "highest"), + Atom("horizontal", "horizontal"), + Atom("hover", "hover"), + Atom("hr", "hr"), + Atom("href", "href"), + Atom("hreflang", "hreflang"), + Atom("hsides", "hsides"), + Atom("hspace", "hspace"), + Atom("html", "html"), + Atom("httpEquiv", "http-equiv"), + Atom("i", "i"), + Atom("icon", "icon"), + Atom("id", "id"), + Atom("_if", "if"), + Atom("iframe", "iframe"), + Atom("ignorekeys", "ignorekeys"), + Atom("ignoreuserfocus", "ignoreuserfocus"), + Atom("image", "image"), + Atom("imageClickedPoint", "image-clicked-point"), + Atom("imagesizes", "imagesizes"), + Atom("imagesrcset", "imagesrcset"), + Atom("img", "img"), + Atom("implementation", "implementation"), + Atom("implements", "implements"), + Atom("import", "import"), + Atom("include", "include"), + Atom("includes", "includes"), + Atom("incontentshell", "incontentshell"), + Atom("increment", "increment"), + Atom("indent", "indent"), + Atom("indeterminate", "indeterminate"), + Atom("index", "index"), + Atom("inert", "inert"), + Atom("infinity", "infinity"), + Atom("inherits", "inherits"), + Atom("inheritOverflow", "inherit-overflow"), + Atom("inheritstyle", "inheritstyle"), + Atom("initial_scale", "initial-scale"), + Atom("input", "input"), + Atom("inputmode", "inputmode"), + Atom("ins", "ins"), + Atom("insertafter", "insertafter"), + Atom("insertbefore", "insertbefore"), + Atom("insertion", "insertion"), + Atom("integer", "integer"), + Atom("integrity", "integrity"), + Atom("internal", "internal"), + Atom("internals", "internals"), + Atom("intersection", "intersection"), + Atom("intersectionobserverlist", "intersectionobserverlist"), + Atom("is", "is"), + Atom("ismap", "ismap"), + Atom("itemid", "itemid"), + Atom("itemprop", "itemprop"), + Atom("itemref", "itemref"), + Atom("itemscope", "itemscope"), + Atom("itemtype", "itemtype"), + Atom("japanese_formal", "japanese-formal"), + Atom("japanese_informal", "japanese-informal"), + Atom("kbd", "kbd"), + Atom("keepcurrentinview", "keepcurrentinview"), + Atom("keepobjectsalive", "keepobjectsalive"), + Atom("key", "key"), + Atom("keycode", "keycode"), + Atom("keydown", "keydown"), + Atom("keygen", "keygen"), + Atom("keypress", "keypress"), + Atom("keyset", "keyset"), + Atom("keysystem", "keysystem"), + Atom("keyup", "keyup"), + Atom("kind", "kind"), + Atom("korean_hangul_formal", "korean-hangul-formal"), + Atom("korean_hanja_formal", "korean-hanja-formal"), + Atom("korean_hanja_informal", "korean-hanja-informal"), + Atom("label", "label"), + Atom("lang", "lang"), + Atom("language", "language"), + Atom("last", "last"), + Atom("layer", "layer"), + Atom("LayerActivity", "LayerActivity"), + Atom("layout_guess", "layout-guess"), + Atom("leading", "leading"), + Atom("leaf", "leaf"), + Atom("left", "left"), + Atom("leftmargin", "leftmargin"), + Atom("legend", "legend"), + Atom("length", "length"), + Atom("letterValue", "letter-value"), + Atom("level", "level"), + Atom("lhs", "lhs"), + Atom("li", "li"), + Atom("line", "line"), + Atom("link", "link"), + Atom("linkset", "linkset"), + # Atom("list", "list"), # "list" is present below + Atom("listbox", "listbox"), + Atom("listener", "listener"), + Atom("listheader", "listheader"), + Atom("listing", "listing"), + Atom("listitem", "listitem"), + Atom("load", "load"), + Atom("loading", "loading"), + Atom("triggeringprincipal", "triggeringprincipal"), + Atom("localedir", "localedir"), + Atom("localName", "local-name"), + Atom("localization", "localization"), + Atom("longdesc", "longdesc"), + Atom("loop", "loop"), + Atom("low", "low"), + Atom("lowerFirst", "lower-first"), + Atom("lowest", "lowest"), + Atom("lowsrc", "lowsrc"), + Atom("ltr", "ltr"), + Atom("lwtheme", "lwtheme"), + Atom("main", "main"), + Atom("map", "map"), + Atom("manifest", "manifest"), + Atom("marginBottom", "margin-bottom"), + Atom("marginLeft", "margin-left"), + Atom("marginRight", "margin-right"), + Atom("marginTop", "margin-top"), + Atom("marginheight", "marginheight"), + Atom("marginwidth", "marginwidth"), + Atom("mark", "mark"), + Atom("marquee", "marquee"), + Atom("match", "match"), + Atom("max", "max"), + Atom("maxheight", "maxheight"), + Atom("maximum_scale", "maximum-scale"), + Atom("maxlength", "maxlength"), + Atom("maxpos", "maxpos"), + Atom("maxwidth", "maxwidth"), + Atom("measure", "measure"), + Atom("media", "media"), + Atom("mediaType", "media-type"), + Atom("menu", "menu"), + Atom("menubar", "menubar"), + Atom("menucaption", "menucaption"), + Atom("menugroup", "menugroup"), + Atom("menuitem", "menuitem"), + Atom("menulist", "menulist"), + Atom("menupopup", "menupopup"), + Atom("menuseparator", "menuseparator"), + Atom("mesh", "mesh"), + Atom("message", "message"), + Atom("meta", "meta"), + Atom("referrer", "referrer"), + Atom("referrerpolicy", "referrerpolicy"), + Atom("renderroot", "renderroot"), + Atom("headerReferrerPolicy", "referrer-policy"), + Atom("meter", "meter"), + Atom("method", "method"), + Atom("middle", "middle"), + Atom("min", "min"), + Atom("minheight", "minheight"), + Atom("minimum_scale", "minimum-scale"), + Atom("minlength", "minlength"), + Atom("minpos", "minpos"), + Atom("minusSign", "minus-sign"), + Atom("minwidth", "minwidth"), + Atom("mixed", "mixed"), + Atom("messagemanagergroup", "messagemanagergroup"), + Atom("mod", "mod"), + Atom("_module", "module"), + Atom("mode", "mode"), + Atom("modifiers", "modifiers"), + Atom("monochrome", "monochrome"), + Atom("mouseover", "mouseover"), + Atom("mozAccessiblecaret", "moz-accessiblecaret"), + Atom("mozCustomContentContainer", "moz-custom-content-container"), + Atom("mozGrabber", "mozGrabber"), + Atom("mozNativeAnonymous", "-moz-native-anonymous"), + Atom("mozprivatebrowsing", "mozprivatebrowsing"), + Atom("mozResizer", "mozResizer"), + Atom("mozResizingInfo", "mozResizingInfo"), + Atom("mozResizingShadow", "mozResizingShadow"), + Atom("mozTableAddColumnAfter", "mozTableAddColumnAfter"), + Atom("mozTableAddColumnBefore", "mozTableAddColumnBefore"), + Atom("mozTableAddRowAfter", "mozTableAddRowAfter"), + Atom("mozTableAddRowBefore", "mozTableAddRowBefore"), + Atom("mozTableRemoveRow", "mozTableRemoveRow"), + Atom("mozTableRemoveColumn", "mozTableRemoveColumn"), + Atom("moz_opaque", "moz-opaque"), + Atom("moz_action_hint", "mozactionhint"), + Atom("multicol", "multicol"), + Atom("multiple", "multiple"), + Atom("muted", "muted"), + Atom("name", "name"), + Atom("_namespace", "namespace"), + Atom("namespaceAlias", "namespace-alias"), + Atom("namespaceUri", "namespace-uri"), + Atom("NaN", "NaN"), + Atom("n", "n"), + Atom("nav", "nav"), + Atom("ne", "ne"), + Atom("never", "never"), + Atom("_new", "new"), + Atom("newline", "newline"), + Atom("nextRemoteTabId", "nextRemoteTabId"), + Atom("no", "no"), + Atom("noautofocus", "noautofocus"), + Atom("noautohide", "noautohide"), + Atom("norolluponanchor", "norolluponanchor"), + Atom("noBar", "no-bar"), + Atom("nobr", "nobr"), + Atom("nodefaultsrc", "nodefaultsrc"), + Atom("nodeSet", "node-set"), + Atom("noembed", "noembed"), + Atom("noframes", "noframes"), + Atom("nohref", "nohref"), + Atom("nomodule", "nomodule"), + Atom("nonce", "nonce"), + Atom("none", "none"), + Atom("noresize", "noresize"), + Atom("normal", "normal"), + Atom("normalizeSpace", "normalize-space"), + Atom("noscript", "noscript"), + Atom("noshade", "noshade"), + Atom("notification", "notification"), + Atom("novalidate", "novalidate"), + Atom("_not", "not"), + Atom("nowrap", "nowrap"), + Atom("number", "number"), + Atom("nw", "nw"), + Atom("object", "object"), + Atom("objectType", "object-type"), + Atom("observes", "observes"), + Atom("odd", "odd"), + Atom("OFF", "OFF"), + Atom("ol", "ol"), + Atom("omitXmlDeclaration", "omit-xml-declaration"), + Atom("onabort", "onabort"), + Atom("onmozaccesskeynotfound", "onmozaccesskeynotfound"), + Atom("onactivate", "onactivate"), + Atom("onafterprint", "onafterprint"), + Atom("onafterscriptexecute", "onafterscriptexecute"), + Atom("onanimationcancel", "onanimationcancel"), + Atom("onanimationend", "onanimationend"), + Atom("onanimationiteration", "onanimationiteration"), + Atom("onanimationstart", "onanimationstart"), + Atom("onAppCommand", "onAppCommand"), + Atom("onaudioprocess", "onaudioprocess"), + Atom("onauxclick", "onauxclick"), + Atom("onbeforecopy", "onbeforecopy"), + Atom("onbeforecut", "onbeforecut"), + Atom("onbeforeinput", "onbeforeinput"), + Atom("onbeforepaste", "onbeforepaste"), + Atom("onbeforeprint", "onbeforeprint"), + Atom("onbeforescriptexecute", "onbeforescriptexecute"), + Atom("onbeforeunload", "onbeforeunload"), + Atom("onblocked", "onblocked"), + Atom("onblur", "onblur"), + Atom("onbounce", "onbounce"), + Atom("onboundschange", "onboundschange"), + Atom("onbroadcast", "onbroadcast"), + Atom("onbufferedamountlow", "onbufferedamountlow"), + Atom("oncached", "oncached"), + Atom("oncancel", "oncancel"), + Atom("onchange", "onchange"), + Atom("onchargingchange", "onchargingchange"), + Atom("onchargingtimechange", "onchargingtimechange"), + Atom("onchecking", "onchecking"), + Atom("onCheckboxStateChange", "onCheckboxStateChange"), + Atom("onCheckKeyPressEventModel", "onCheckKeyPressEventModel"), + Atom("onclick", "onclick"), + Atom("onclose", "onclose"), + Atom("oncommand", "oncommand"), + Atom("oncommandupdate", "oncommandupdate"), + Atom("oncomplete", "oncomplete"), + Atom("oncompositionend", "oncompositionend"), + Atom("oncompositionstart", "oncompositionstart"), + Atom("oncompositionupdate", "oncompositionupdate"), + Atom("onconnect", "onconnect"), + Atom("onconnectionavailable", "onconnectionavailable"), + Atom("oncontextmenu", "oncontextmenu"), + Atom("oncontextlost", "oncontextlost"), + Atom("oncontextrestored", "oncontextrestored"), + Atom("oncopy", "oncopy"), + Atom("oncut", "oncut"), + Atom("ondblclick", "ondblclick"), + Atom("ondischargingtimechange", "ondischargingtimechange"), + Atom("ondownloading", "ondownloading"), + Atom("onDOMActivate", "onDOMActivate"), + Atom("onDOMAttrModified", "onDOMAttrModified"), + Atom("onDOMCharacterDataModified", "onDOMCharacterDataModified"), + Atom("onDOMFocusIn", "onDOMFocusIn"), + Atom("onDOMFocusOut", "onDOMFocusOut"), + Atom("onDOMMouseScroll", "onDOMMouseScroll"), + Atom("onDOMNodeInserted", "onDOMNodeInserted"), + Atom("onDOMNodeInsertedIntoDocument", "onDOMNodeInsertedIntoDocument"), + Atom("onDOMNodeRemoved", "onDOMNodeRemoved"), + Atom("onDOMNodeRemovedFromDocument", "onDOMNodeRemovedFromDocument"), + Atom("onDOMSubtreeModified", "onDOMSubtreeModified"), + Atom("ondata", "ondata"), + Atom("ondrag", "ondrag"), + Atom("ondragdrop", "ondragdrop"), + Atom("ondragend", "ondragend"), + Atom("ondragenter", "ondragenter"), + Atom("ondragexit", "ondragexit"), + Atom("ondragleave", "ondragleave"), + Atom("ondragover", "ondragover"), + Atom("ondragstart", "ondragstart"), + Atom("ondrain", "ondrain"), + Atom("ondrop", "ondrop"), + Atom("onerror", "onerror"), + Atom("onfinish", "onfinish"), + Atom("onfocus", "onfocus"), + Atom("onfocusin", "onfocusin"), + Atom("onfocusout", "onfocusout"), + Atom("onfullscreenchange", "onfullscreenchange"), + Atom("onfullscreenerror", "onfullscreenerror"), + Atom("onget", "onget"), + Atom("onhashchange", "onhashchange"), + Atom("oninput", "oninput"), + Atom("oninputsourceschange", "oninputsourceschange"), + Atom("oninstall", "oninstall"), + Atom("oninvalid", "oninvalid"), + Atom("onkeydown", "onkeydown"), + Atom("onkeypress", "onkeypress"), + Atom("onkeyup", "onkeyup"), + Atom("onlanguagechange", "onlanguagechange"), + Atom("onlevelchange", "onlevelchange"), + Atom("onload", "onload"), + Atom("onloading", "onloading"), + Atom("onloadingdone", "onloadingdone"), + Atom("onloadingerror", "onloadingerror"), + Atom("onpopstate", "onpopstate"), + Atom("only", "only"), # this one is not an event + Atom("onmerchantvalidation", "onmerchantvalidation"), + Atom("onmessage", "onmessage"), + Atom("onmessageerror", "onmessageerror"), + Atom("onmidimessage", "onmidimessage"), + Atom("onmousedown", "onmousedown"), + Atom("onmouseenter", "onmouseenter"), + Atom("onmouseleave", "onmouseleave"), + Atom("onmouselongtap", "onmouselongtap"), + Atom("onmousemove", "onmousemove"), + Atom("onmouseout", "onmouseout"), + Atom("onmouseover", "onmouseover"), + Atom("onMozMouseHittest", "onMozMouseHittest"), + Atom("onMozMouseExploreByTouch", "onMozMouseExploreByTouch"), + Atom("onmouseup", "onmouseup"), + Atom("onMozAfterPaint", "onMozAfterPaint"), + Atom("onmozfullscreenchange", "onmozfullscreenchange"), + Atom("onmozfullscreenerror", "onmozfullscreenerror"), + Atom("onmozpointerlockchange", "onmozpointerlockchange"), + Atom("onmozpointerlockerror", "onmozpointerlockerror"), + Atom("onMozMousePixelScroll", "onMozMousePixelScroll"), + Atom("onMozScrolledAreaChanged", "onMozScrolledAreaChanged"), + Atom("onmute", "onmute"), + Atom("onnotificationclick", "onnotificationclick"), + Atom("onnotificationclose", "onnotificationclose"), + Atom("onnoupdate", "onnoupdate"), + Atom("onobsolete", "onobsolete"), + Atom("ononline", "ononline"), + Atom("onoffline", "onoffline"), + Atom("onopen", "onopen"), + Atom("onorientationchange", "onorientationchange"), + Atom("onoverflow", "onoverflow"), + Atom("onpagehide", "onpagehide"), + Atom("onpageshow", "onpageshow"), + Atom("onpaste", "onpaste"), + Atom("onpayerdetailchange", "onpayerdetailchange"), + Atom("onpaymentmethodchange", "onpaymentmethodchange"), + Atom("onpointerlockchange", "onpointerlockchange"), + Atom("onpointerlockerror", "onpointerlockerror"), + Atom("onpopuphidden", "onpopuphidden"), + Atom("onpopuphiding", "onpopuphiding"), + Atom("onpopuppositioned", "onpopuppositioned"), + Atom("onpopupshowing", "onpopupshowing"), + Atom("onpopupshown", "onpopupshown"), + Atom("onprocessorerror", "onprocessorerror"), + Atom("onprioritychange", "onprioritychange"), + Atom("onpush", "onpush"), + Atom("onpushsubscriptionchange", "onpushsubscriptionchange"), + Atom("onRadioStateChange", "onRadioStateChange"), + Atom("onreadystatechange", "onreadystatechange"), + Atom("onrejectionhandled", "onrejectionhandled"), + Atom("onremove", "onremove"), + Atom("onrequestprogress", "onrequestprogress"), + Atom("onresourcetimingbufferfull", "onresourcetimingbufferfull"), + Atom("onresponseprogress", "onresponseprogress"), + Atom("onRequest", "onRequest"), + Atom("onreset", "onreset"), + Atom("onresize", "onresize"), + Atom("onscroll", "onscroll"), + Atom("onsecuritypolicyviolation", "onsecuritypolicyviolation"), + Atom("onselect", "onselect"), + Atom("onselectionchange", "onselectionchange"), + Atom("onselectend", "onselectend"), + Atom("onselectstart", "onselectstart"), + Atom("onset", "onset"), + Atom("onshippingaddresschange", "onshippingaddresschange"), + Atom("onshippingoptionchange", "onshippingoptionchange"), + Atom("onshow", "onshow"), + Atom("onslotchange", "onslotchange"), + Atom("onsqueeze", "onsqueeze"), + Atom("onsqueezeend", "onsqueezeend"), + Atom("onsqueezestart", "onsqueezestart"), + Atom("onstatechange", "onstatechange"), + Atom("onstorage", "onstorage"), + Atom("onsubmit", "onsubmit"), + Atom("onsuccess", "onsuccess"), + Atom("onsystemstatusbarclick", "onsystemstatusbarclick"), + Atom("ontypechange", "ontypechange"), + Atom("onterminate", "onterminate"), + Atom("ontext", "ontext"), + Atom("ontoggle", "ontoggle"), + Atom("ontonechange", "ontonechange"), + Atom("ontouchstart", "ontouchstart"), + Atom("ontouchend", "ontouchend"), + Atom("ontouchmove", "ontouchmove"), + Atom("ontouchcancel", "ontouchcancel"), + Atom("ontransitioncancel", "ontransitioncancel"), + Atom("ontransitionend", "ontransitionend"), + Atom("ontransitionrun", "ontransitionrun"), + Atom("ontransitionstart", "ontransitionstart"), + Atom("onuncapturederror", "onuncapturederror"), + Atom("onunderflow", "onunderflow"), + Atom("onunhandledrejection", "onunhandledrejection"), + Atom("onunload", "onunload"), + Atom("onunmute", "onunmute"), + Atom("onupdatefound", "onupdatefound"), + Atom("onupdateready", "onupdateready"), + Atom("onupgradeneeded", "onupgradeneeded"), + Atom("onversionchange", "onversionchange"), + Atom("onvisibilitychange", "onvisibilitychange"), + Atom("onvoiceschanged", "onvoiceschanged"), + Atom("onvrdisplayactivate", "onvrdisplayactivate"), + Atom("onvrdisplayconnect", "onvrdisplayconnect"), + Atom("onvrdisplaydeactivate", "onvrdisplaydeactivate"), + Atom("onvrdisplaydisconnect", "onvrdisplaydisconnect"), + Atom("onvrdisplaypresentchange", "onvrdisplaypresentchange"), + Atom("onwebkitAnimationEnd", "onwebkitAnimationEnd"), + Atom("onwebkitAnimationIteration", "onwebkitAnimationIteration"), + Atom("onwebkitAnimationStart", "onwebkitAnimationStart"), + Atom("onwebkitTransitionEnd", "onwebkitTransitionEnd"), + Atom("onwebkitanimationend", "onwebkitanimationend"), + Atom("onwebkitanimationiteration", "onwebkitanimationiteration"), + Atom("onwebkitanimationstart", "onwebkitanimationstart"), + Atom("onwebkittransitionend", "onwebkittransitionend"), + Atom("onwheel", "onwheel"), + Atom("open", "open"), + Atom("optgroup", "optgroup"), + Atom("optimum", "optimum"), + Atom("option", "option"), + Atom("_or", "or"), + Atom("order", "order"), + Atom("orient", "orient"), + Atom("orientation", "orientation"), + Atom("origin_trial", "origin-trial"), + Atom("otherwise", "otherwise"), + Atom("output", "output"), + Atom("overflow", "overflow"), + Atom("overflowBlock", "overflow-block"), + Atom("overflowInline", "overflow-inline"), + Atom("overlay", "overlay"), + Atom("p", "p"), + Atom("pack", "pack"), + Atom("page", "page"), + Atom("pageincrement", "pageincrement"), + Atom("paint", "paint"), + Atom("paint_order", "paint-order"), + Atom("panel", "panel"), + Atom("paragraph", "paragraph"), + Atom("param", "param"), + Atom("parameter", "parameter"), + Atom("parent", "parent"), + Atom("parentfocused", "parentfocused"), + Atom("parsererror", "parsererror"), + Atom("part", "part"), + Atom("password", "password"), + Atom("pattern", "pattern"), + Atom("patternSeparator", "pattern-separator"), + Atom("perMille", "per-mille"), + Atom("percent", "percent"), + Atom("persist", "persist"), + Atom("phase", "phase"), + Atom("picture", "picture"), + Atom("ping", "ping"), + Atom("pinned", "pinned"), + Atom("placeholder", "placeholder"), + Atom("plaintext", "plaintext"), + Atom("playbackrate", "playbackrate"), + Atom("pointSize", "point-size"), + Atom("poly", "poly"), + Atom("polygon", "polygon"), + Atom("popover", "popover"), + Atom("popovertarget", "popovertarget"), + Atom("popovertargetaction", "popovertargetaction"), + Atom("popup", "popup"), + Atom("popupalign", "popupalign"), + Atom("popupanchor", "popupanchor"), + Atom("popupgroup", "popupgroup"), + Atom("popupset", "popupset"), + Atom("popupsinherittooltip", "popupsinherittooltip"), + Atom("portal", "portal"), + Atom("position", "position"), + Atom("poster", "poster"), + Atom("pre", "pre"), + Atom("preceding", "preceding"), + Atom("precedingSibling", "preceding-sibling"), + Atom("prefersReducedMotion", "prefers-reduced-motion"), + Atom("prefersReducedTransparency", "prefers-reduced-transparency"), + Atom("prefersColorScheme", "prefers-color-scheme"), + Atom("prefersContrast", "prefers-contrast"), + Atom("prefix", "prefix"), + Atom("prefwidth", "prefwidth"), + Atom("dynamicRange", "dynamic-range"), + Atom("videoDynamicRange", "video-dynamic-range"), + Atom("scripting", "scripting"), + Atom("preload", "preload"), + Atom("preserve", "preserve"), + Atom("preserveSpace", "preserve-space"), + Atom("preventdefault", "preventdefault"), + Atom("previewDiv", "preview-div"), + Atom("primary", "primary"), + Atom("print", "print"), + Atom("printisfocuseddoc", "printisfocuseddoc"), + Atom("printselectionranges", "printselectionranges"), + Atom("priority", "priority"), + Atom("processingInstruction", "processing-instruction"), + Atom("profile", "profile"), + Atom("progress", "progress"), + Atom("prompt", "prompt"), + Atom("properties", "properties"), + Atom("property", "property"), + Atom("pubdate", "pubdate"), + Atom("q", "q"), + Atom("radio", "radio"), + Atom("radioLabel", "radio-label"), + Atom("radiogroup", "radiogroup"), + Atom("range", "range"), + Atom("readonly", "readonly"), + Atom("rect", "rect"), + Atom("rectangle", "rectangle"), + Atom("refresh", "refresh"), + Atom("rel", "rel"), + Atom("relativeBounds", "relative-bounds"), + Atom("rem", "rem"), + Atom("remote", "remote"), + Atom("removeelement", "removeelement"), + Atom("renderingobserverset", "renderingobserverset"), + Atom("repeat", "repeat"), + Atom("replace", "replace"), + Atom("requestcontextid", "requestcontextid"), + Atom("required", "required"), + Atom("reserved", "reserved"), + Atom("reset", "reset"), + Atom("resizeafter", "resizeafter"), + Atom("resizebefore", "resizebefore"), + Atom("resizer", "resizer"), + Atom("resolution", "resolution"), + Atom("resources", "resources"), + Atom("result", "result"), + Atom("resultPrefix", "result-prefix"), + Atom("retargetdocumentfocus", "retargetdocumentfocus"), + Atom("rev", "rev"), + Atom("reverse", "reverse"), + Atom("reversed", "reversed"), + Atom("rhs", "rhs"), + Atom("richlistbox", "richlistbox"), + Atom("richlistitem", "richlistitem"), + Atom("right", "right"), + Atom("rightmargin", "rightmargin"), + Atom("role", "role"), + Atom("rolluponmousewheel", "rolluponmousewheel"), + Atom("round", "round"), + Atom("row", "row"), + Atom("rows", "rows"), + Atom("rowspan", "rowspan"), + Atom("rb", "rb"), + Atom("rp", "rp"), + Atom("rt", "rt"), + Atom("rtc", "rtc"), + Atom("rtl", "rtl"), + Atom("ruby", "ruby"), + Atom("rubyBase", "ruby-base"), + Atom("rubyBaseContainer", "ruby-base-container"), + Atom("rubyText", "ruby-text"), + Atom("rubyTextContainer", "ruby-text-container"), + Atom("rules", "rules"), + Atom("s", "s"), + Atom("safe_area_inset_top", "safe-area-inset-top"), + Atom("safe_area_inset_bottom", "safe-area-inset-bottom"), + Atom("safe_area_inset_left", "safe-area-inset-left"), + Atom("safe_area_inset_right", "safe-area-inset-right"), + Atom("samp", "samp"), + Atom("sandbox", "sandbox"), + Atom("sbattr", "sbattr"), + Atom("scale", "scale"), + Atom("scan", "scan"), + Atom("scheme", "scheme"), + Atom("scope", "scope"), + Atom("scoped", "scoped"), + Atom("screen", "screen"), + Atom("screenX", "screenX"), + Atom("screenx", "screenx"), + Atom("screenY", "screenY"), + Atom("screeny", "screeny"), + Atom("script", "script"), + Atom("scrollbar", "scrollbar"), + Atom("scrollbarThumb", "scrollbar-thumb"), + Atom("scrollamount", "scrollamount"), + Atom("scrollbarbutton", "scrollbarbutton"), + Atom("scrollbarDownBottom", "scrollbar-down-bottom"), + Atom("scrollbarDownTop", "scrollbar-down-top"), + Atom("scrollbarInlineSize", "scrollbar-inline-size"), + Atom("scrollbarUpBottom", "scrollbar-up-bottom"), + Atom("scrollbarUpTop", "scrollbar-up-top"), + Atom("scrollbox", "scrollbox"), + Atom("scrollcorner", "scrollcorner"), + Atom("scrolldelay", "scrolldelay"), + Atom("scrolling", "scrolling"), + Atom("scrollPosition", "scroll-position"), + Atom("se", "se"), + Atom("section", "section"), + Atom("select", "select"), + Atom("selected", "selected"), + Atom("selectedIndex", "selectedIndex"), + Atom("selectedindex", "selectedindex"), + Atom("selectmenu", "selectmenu"), + Atom("self", "self"), + Atom("seltype", "seltype"), + Atom("setcookie", "set-cookie"), + Atom("setter", "setter"), + Atom("shadow", "shadow"), + Atom("shape", "shape"), + Atom("show", "show"), + Atom("showcaret", "showcaret"), + Atom("sibling", "sibling"), + Atom("simple", "simple"), + Atom("simp_chinese_formal", "simp-chinese-formal"), + Atom("simp_chinese_informal", "simp-chinese-informal"), + Atom("single", "single"), + Atom("size", "size"), + Atom("sizes", "sizes"), + Atom("sizemode", "sizemode"), + Atom("sizetopopup", "sizetopopup"), + Atom("slider", "slider"), + Atom("small", "small"), + Atom("smooth", "smooth"), + Atom("snap", "snap"), + Atom("solid", "solid"), + Atom("sort", "sort"), + Atom("sortActive", "sortActive"), + Atom("sortDirection", "sortDirection"), + Atom("sorted", "sorted"), + Atom("sorthints", "sorthints"), + Atom("source", "source"), + Atom("sourcetext", "sourcetext"), + Atom("space", "space"), + Atom("spacer", "spacer"), + Atom("span", "span"), + Atom("spellcheck", "spellcheck"), + Atom("split", "split"), + Atom("splitter", "splitter"), + Atom("square", "square"), + Atom("src", "src"), + Atom("srcdoc", "srcdoc"), + Atom("srclang", "srclang"), + Atom("srcset", "srcset"), + Atom("standalone", "standalone"), + Atom("standby", "standby"), + Atom("start", "start"), + Atom("startsWith", "starts-with"), + Atom("state", "state"), + Atom("statusbar", "statusbar"), + Atom("step", "step"), + Atom("stop", "stop"), + Atom("stretch", "stretch"), + Atom("strike", "strike"), + Atom("string", "string"), + Atom("stringLength", "string-length"), + Atom("stripSpace", "strip-space"), + Atom("strong", "strong"), + Atom("style", "style"), + Atom("stylesheet", "stylesheet"), + Atom("stylesheetPrefix", "stylesheet-prefix"), + Atom("submit", "submit"), + Atom("substate", "substate"), + Atom("substring", "substring"), + Atom("substringAfter", "substring-after"), + Atom("substringBefore", "substring-before"), + Atom("sub", "sub"), + Atom("suggestion", "suggestion"), + Atom("sum", "sum"), + Atom("sup", "sup"), + Atom("summary", "summary"), + Atom("sw", "sw"), + # Atom("_switch", "switch"), # "switch" is present below + Atom("systemProperty", "system-property"), + Atom("tab", "tab"), + Atom("tabindex", "tabindex"), + Atom("table", "table"), + Atom("tabpanel", "tabpanel"), + Atom("tabpanels", "tabpanels"), + Atom("tag", "tag"), + Atom("target", "target"), + Atom("targets", "targets"), + Atom("tbody", "tbody"), + Atom("td", "td"), + Atom("tel", "tel"), + Atom("_template", "template"), + Atom("text_decoration", "text-decoration"), + Atom("terminate", "terminate"), + Atom("term", "term"), + Atom("test", "test"), + Atom("text", "text"), + Atom("textAlign", "text-align"), + Atom("textarea", "textarea"), + Atom("textbox", "textbox"), + Atom("textLink", "text-link"), + Atom("textNodeDirectionalityMap", "textNodeDirectionalityMap"), + Atom("textOverlay", "text-overlay"), + Atom("tfoot", "tfoot"), + Atom("th", "th"), + Atom("thead", "thead"), + Atom("thumb", "thumb"), + Atom("time", "time"), + Atom("title", "title"), + Atom("titlebar", "titlebar"), + Atom("titletip", "titletip"), + Atom("token", "token"), + Atom("tokenize", "tokenize"), + Atom("toolbar", "toolbar"), + Atom("toolbarbutton", "toolbarbutton"), + Atom("toolbaritem", "toolbaritem"), + Atom("toolbarpaletteitem", "toolbarpaletteitem"), + Atom("toolbox", "toolbox"), + Atom("tooltip", "tooltip"), + Atom("tooltiptext", "tooltiptext"), + Atom("top", "top"), + Atom("topleft", "topleft"), + Atom("topmargin", "topmargin"), + Atom("topright", "topright"), + Atom("tr", "tr"), + Atom("track", "track"), + Atom("trad_chinese_formal", "trad-chinese-formal"), + Atom("trad_chinese_informal", "trad-chinese-informal"), + Atom("trailing", "trailing"), + Atom("transform", "transform"), + Atom("transform_3d", "transform-3d"), + Atom("transformiix", "transformiix"), + Atom("translate", "translate"), + Atom("transparent", "transparent"), + Atom("tree", "tree"), + Atom("treecell", "treecell"), + Atom("treechildren", "treechildren"), + Atom("treecol", "treecol"), + Atom("treecolpicker", "treecolpicker"), + Atom("treecols", "treecols"), + Atom("treeitem", "treeitem"), + Atom("treerow", "treerow"), + Atom("treeseparator", "treeseparator"), + Atom("_true", "true"), + Atom("truespeed", "truespeed"), + Atom("tt", "tt"), + Atom("type", "type"), + Atom("u", "u"), + Atom("ul", "ul"), + Atom("unparsedEntityUri", "unparsed-entity-uri"), + Atom("up", "up"), + Atom("update", "update"), + Atom("upperFirst", "upper-first"), + Atom("use", "use"), + Atom("useAttributeSets", "use-attribute-sets"), + Atom("usemap", "usemap"), + Atom("user_scalable", "user-scalable"), + Atom("validate", "validate"), + Atom("valign", "valign"), + Atom("value", "value"), + Atom("values", "values"), + Atom("valueOf", "value-of"), + Atom("valuetype", "valuetype"), + Atom("var", "var"), + Atom("variable", "variable"), + Atom("vendor", "vendor"), + Atom("vendorUrl", "vendor-url"), + Atom("version", "version"), + Atom("vertical", "vertical"), + Atom("audio", "audio"), + Atom("video", "video"), + Atom("viewport", "viewport"), + Atom("viewport_fit", "viewport-fit"), + Atom("viewport_height", "viewport-height"), + Atom("viewport_initial_scale", "viewport-initial-scale"), + Atom("viewport_maximum_scale", "viewport-maximum-scale"), + Atom("viewport_minimum_scale", "viewport-minimum-scale"), + Atom("viewport_user_scalable", "viewport-user-scalable"), + Atom("viewport_width", "viewport-width"), + Atom("visibility", "visibility"), + Atom("visuallyselected", "visuallyselected"), + Atom("vlink", "vlink"), + Atom("_void", "void"), + Atom("vsides", "vsides"), + Atom("vspace", "vspace"), + Atom("w", "w"), + Atom("wbr", "wbr"), + Atom("webkitdirectory", "webkitdirectory"), + Atom("when", "when"), + Atom("white", "white"), + Atom("width", "width"), + Atom("willChange", "will-change"), + Atom("window", "window"), + Atom("headerWindowTarget", "window-target"), + Atom("windowtype", "windowtype"), + Atom("withParam", "with-param"), + Atom("wrap", "wrap"), + Atom("headerDNSPrefetchControl", "x-dns-prefetch-control"), + Atom("headerCSP", "content-security-policy"), + Atom("headerCSPReportOnly", "content-security-policy-report-only"), + Atom("headerXFO", "x-frame-options"), + Atom("x_western", "x-western"), + Atom("xml", "xml"), + Atom("xml_stylesheet", "xml-stylesheet"), + Atom("xmlns", "xmlns"), + Atom("xmp", "xmp"), + Atom("xul", "xul"), + Atom("yes", "yes"), + Atom("z_index", "z-index"), + Atom("zeroDigit", "zero-digit"), + Atom("zlevel", "zlevel"), + Atom("percentage", "%"), + Atom("A", "A"), + Atom("alignment_baseline", "alignment-baseline"), + Atom("amplitude", "amplitude"), + Atom("animate", "animate"), + Atom("animateColor", "animateColor"), + Atom("animateMotion", "animateMotion"), + Atom("animateTransform", "animateTransform"), + Atom("arithmetic", "arithmetic"), + Atom("atop", "atop"), + Atom("azimuth", "azimuth"), + Atom("B", "B"), + Atom("backgroundColor", "background-color"), + Atom("background_image", "background-image"), + Atom("baseFrequency", "baseFrequency"), + Atom("baseline_shift", "baseline-shift"), + Atom("bias", "bias"), + Atom("caption_side", "caption-side"), + Atom("clip_path", "clip-path"), + Atom("clip_rule", "clip-rule"), + Atom("clipPath", "clipPath"), + Atom("clipPathUnits", "clipPathUnits"), + Atom("cm", "cm"), + Atom("colorBurn", "color-burn"), + Atom("colorDodge", "color-dodge"), + Atom("colorInterpolation", "color-interpolation"), + Atom("colorInterpolationFilters", "color-interpolation-filters"), + Atom("colorProfile", "color-profile"), + Atom("cursor", "cursor"), + Atom("cx", "cx"), + Atom("cy", "cy"), + Atom("d", "d"), + Atom("darken", "darken"), + Atom("defs", "defs"), + Atom("deg", "deg"), + Atom("desc", "desc"), + Atom("diffuseConstant", "diffuseConstant"), + Atom("dilate", "dilate"), + Atom("direction", "direction"), + Atom("disable", "disable"), + Atom("disc", "disc"), + Atom("discrete", "discrete"), + Atom("divisor", "divisor"), + Atom("dominant_baseline", "dominant-baseline"), + Atom("duplicate", "duplicate"), + Atom("dx", "dx"), + Atom("dy", "dy"), + Atom("edgeMode", "edgeMode"), + Atom("ellipse", "ellipse"), + Atom("elevation", "elevation"), + Atom("erode", "erode"), + Atom("ex", "ex"), + Atom("exact", "exact"), + Atom("exclusion", "exclusion"), + Atom("exponent", "exponent"), + Atom("feBlend", "feBlend"), + Atom("feColorMatrix", "feColorMatrix"), + Atom("feComponentTransfer", "feComponentTransfer"), + Atom("feComposite", "feComposite"), + Atom("feConvolveMatrix", "feConvolveMatrix"), + Atom("feDiffuseLighting", "feDiffuseLighting"), + Atom("feDisplacementMap", "feDisplacementMap"), + Atom("feDistantLight", "feDistantLight"), + Atom("feDropShadow", "feDropShadow"), + Atom("feFlood", "feFlood"), + Atom("feFuncA", "feFuncA"), + Atom("feFuncB", "feFuncB"), + Atom("feFuncG", "feFuncG"), + Atom("feFuncR", "feFuncR"), + Atom("feGaussianBlur", "feGaussianBlur"), + Atom("feImage", "feImage"), + Atom("feMerge", "feMerge"), + Atom("feMergeNode", "feMergeNode"), + Atom("feMorphology", "feMorphology"), + Atom("feOffset", "feOffset"), + Atom("fePointLight", "fePointLight"), + Atom("feSpecularLighting", "feSpecularLighting"), + Atom("feSpotLight", "feSpotLight"), + Atom("feTile", "feTile"), + Atom("feTurbulence", "feTurbulence"), + Atom("fill", "fill"), + Atom("fill_opacity", "fill-opacity"), + Atom("fill_rule", "fill-rule"), + Atom("filter", "filter"), + Atom("filterUnits", "filterUnits"), + Atom("_float", "float"), + Atom("flood_color", "flood-color"), + Atom("flood_opacity", "flood-opacity"), + Atom("font_face", "font-face"), + Atom("font_face_format", "font-face-format"), + Atom("font_face_name", "font-face-name"), + Atom("font_face_src", "font-face-src"), + Atom("font_face_uri", "font-face-uri"), + Atom("font_family", "font-family"), + Atom("font_size", "font-size"), + Atom("font_size_adjust", "font-size-adjust"), + Atom("font_stretch", "font-stretch"), + Atom("font_style", "font-style"), + Atom("font_variant", "font-variant"), + Atom("formatting", "formatting"), + Atom("foreignObject", "foreignObject"), + Atom("fractalNoise", "fractalNoise"), + Atom("fr", "fr"), + Atom("fx", "fx"), + Atom("fy", "fy"), + Atom("G", "G"), + Atom("g", "g"), + Atom("gamma", "gamma"), + Atom("glyphRef", "glyphRef"), + Atom("grad", "grad"), + Atom("gradientTransform", "gradientTransform"), + Atom("gradientUnits", "gradientUnits"), + Atom("hardLight", "hard-light"), + Atom("hue", "hue"), + Atom("hueRotate", "hueRotate"), + Atom("identity", "identity"), + Atom("image_rendering", "image-rendering"), + Atom("in", "in"), + Atom("in2", "in2"), + Atom("intercept", "intercept"), + Atom("k1", "k1"), + Atom("k2", "k2"), + Atom("k3", "k3"), + Atom("k4", "k4"), + Atom("kernelMatrix", "kernelMatrix"), + Atom("kernelUnitLength", "kernelUnitLength"), + Atom("lengthAdjust", "lengthAdjust"), + Atom("letter_spacing", "letter-spacing"), + Atom("lighten", "lighten"), + Atom("lighter", "lighter"), + Atom("lighting_color", "lighting-color"), + Atom("limitingConeAngle", "limitingConeAngle"), + Atom("linear", "linear"), + Atom("linearGradient", "linearGradient"), + Atom("list_item", "list-item"), + Atom("list_style_type", "list-style-type"), + Atom("luminanceToAlpha", "luminanceToAlpha"), + Atom("luminosity", "luminosity"), + Atom("magnify", "magnify"), + Atom("marker", "marker"), + Atom("marker_end", "marker-end"), + Atom("marker_mid", "marker-mid"), + Atom("marker_start", "marker-start"), + Atom("markerHeight", "markerHeight"), + Atom("markerUnits", "markerUnits"), + Atom("markerWidth", "markerWidth"), + Atom("mask", "mask"), + Atom("maskContentUnits", "maskContentUnits"), + Atom("mask_type", "mask-type"), + Atom("maskUnits", "maskUnits"), + Atom("matrix", "matrix"), + Atom("metadata", "metadata"), + Atom("missingGlyph", "missing-glyph"), + Atom("mm", "mm"), + Atom("mpath", "mpath"), + Atom("noStitch", "noStitch"), + Atom("numOctaves", "numOctaves"), + Atom("multiply", "multiply"), + Atom("objectBoundingBox", "objectBoundingBox"), + Atom("offset", "offset"), + Atom("onSVGLoad", "onSVGLoad"), + Atom("onSVGScroll", "onSVGScroll"), + Atom("onzoom", "onzoom"), + Atom("opacity", "opacity"), + Atom("_operator", "operator"), + Atom("out", "out"), + Atom("over", "over"), + Atom("overridePreserveAspectRatio", "overridePreserveAspectRatio"), + Atom("pad", "pad"), + Atom("path", "path"), + Atom("pathLength", "pathLength"), + Atom("patternContentUnits", "patternContentUnits"), + Atom("patternTransform", "patternTransform"), + Atom("patternUnits", "patternUnits"), + Atom("pc", "pc"), + Atom("pointer", "pointer"), + Atom("pointer_events", "pointer-events"), + Atom("points", "points"), + Atom("pointsAtX", "pointsAtX"), + Atom("pointsAtY", "pointsAtY"), + Atom("pointsAtZ", "pointsAtZ"), + Atom("polyline", "polyline"), + Atom("preserveAlpha", "preserveAlpha"), + Atom("preserveAspectRatio", "preserveAspectRatio"), + Atom("primitiveUnits", "primitiveUnits"), + Atom("pt", "pt"), + Atom("px", "px"), + Atom("R", "R"), + Atom("r", "r"), + Atom("rad", "rad"), + Atom("radialGradient", "radialGradient"), + Atom("radius", "radius"), + Atom("reflect", "reflect"), + Atom("refX", "refX"), + Atom("refY", "refY"), + Atom("requiredExtensions", "requiredExtensions"), + Atom("requiredFeatures", "requiredFeatures"), + Atom("rotate", "rotate"), + Atom("rx", "rx"), + Atom("ry", "ry"), + Atom("saturate", "saturate"), + Atom("saturation", "saturation"), + Atom("set", "set"), + Atom("seed", "seed"), + Atom("shape_rendering", "shape-rendering"), + Atom("simpleScopeChain", "simpleScopeChain"), + Atom("skewX", "skewX"), + Atom("skewY", "skewY"), + Atom("slope", "slope"), + Atom("slot", "slot"), + Atom("softLight", "soft-light"), + Atom("spacing", "spacing"), + Atom("spacingAndGlyphs", "spacingAndGlyphs"), + Atom("specularConstant", "specularConstant"), + Atom("specularExponent", "specularExponent"), + Atom("spreadMethod", "spreadMethod"), + Atom("startOffset", "startOffset"), + Atom("stdDeviation", "stdDeviation"), + Atom("stitch", "stitch"), + Atom("stitchTiles", "stitchTiles"), + Atom("stop_color", "stop-color"), + Atom("stop_opacity", "stop-opacity"), + Atom("stroke", "stroke"), + Atom("stroke_dasharray", "stroke-dasharray"), + Atom("stroke_dashoffset", "stroke-dashoffset"), + Atom("stroke_linecap", "stroke-linecap"), + Atom("stroke_linejoin", "stroke-linejoin"), + Atom("stroke_miterlimit", "stroke-miterlimit"), + Atom("stroke_opacity", "stroke-opacity"), + Atom("stroke_width", "stroke-width"), + Atom("strokeWidth", "strokeWidth"), + Atom("surfaceScale", "surfaceScale"), + Atom("svg", "svg"), + Atom("svgSwitch", "switch"), + Atom("symbol", "symbol"), + Atom("systemLanguage", "systemLanguage"), + Atom("tableValues", "tableValues"), + Atom("targetX", "targetX"), + Atom("targetY", "targetY"), + Atom("text_anchor", "text-anchor"), + Atom("text_rendering", "text-rendering"), + Atom("textLength", "textLength"), + Atom("textPath", "textPath"), + Atom("transform_origin", "transform-origin"), + Atom("tref", "tref"), + Atom("tspan", "tspan"), + Atom("turbulence", "turbulence"), + Atom("unicode_bidi", "unicode-bidi"), + Atom("userSpaceOnUse", "userSpaceOnUse"), + Atom("view", "view"), + Atom("viewBox", "viewBox"), + Atom("viewTarget", "viewTarget"), + Atom("white_space", "white-space"), + Atom("word_spacing", "word-spacing"), + Atom("writing_mode", "writing-mode"), + Atom("x", "x"), + Atom("x1", "x1"), + Atom("x2", "x2"), + Atom("xChannelSelector", "xChannelSelector"), + Atom("xor_", "xor"), + Atom("y", "y"), + Atom("y1", "y1"), + Atom("y2", "y2"), + Atom("yChannelSelector", "yChannelSelector"), + Atom("z", "z"), + Atom("zoomAndPan", "zoomAndPan"), + Atom("vector_effect", "vector-effect"), + Atom("vertical_align", "vertical-align"), + Atom("accumulate", "accumulate"), + Atom("additive", "additive"), + Atom("attributeName", "attributeName"), + Atom("attributeType", "attributeType"), + Atom("auto_reverse", "auto-reverse"), + Atom("begin", "begin"), + Atom("beginEvent", "beginEvent"), + Atom("by", "by"), + Atom("calcMode", "calcMode"), + Atom("dur", "dur"), + Atom("keyPoints", "keyPoints"), + Atom("keySplines", "keySplines"), + Atom("keyTimes", "keyTimes"), + Atom("mozAnimateMotionDummyAttr", "_mozAnimateMotionDummyAttr"), + Atom("onbegin", "onbegin"), + Atom("onbeginEvent", "onbeginEvent"), + Atom("onend", "onend"), + Atom("onendEvent", "onendEvent"), + Atom("onrepeat", "onrepeat"), + Atom("onrepeatEvent", "onrepeatEvent"), + Atom("repeatCount", "repeatCount"), + Atom("repeatDur", "repeatDur"), + Atom("repeatEvent", "repeatEvent"), + Atom("restart", "restart"), + Atom("to", "to"), + Atom("abs_", "abs"), + Atom("accent_", "accent"), + Atom("accentunder_", "accentunder"), + Atom("actiontype_", "actiontype"), + Atom("alignmentscope_", "alignmentscope"), + Atom("altimg_", "altimg"), + Atom("altimg_height_", "altimg-height"), + Atom("altimg_valign_", "altimg-valign"), + Atom("altimg_width_", "altimg-width"), + Atom("annotation_", "annotation"), + Atom("annotation_xml_", "annotation-xml"), + Atom("apply_", "apply"), + Atom("approx_", "approx"), + Atom("arccos_", "arccos"), + Atom("arccosh_", "arccosh"), + Atom("arccot_", "arccot"), + Atom("arccoth_", "arccoth"), + Atom("arccsc_", "arccsc"), + Atom("arccsch_", "arccsch"), + Atom("arcsec_", "arcsec"), + Atom("arcsech_", "arcsech"), + Atom("arcsin_", "arcsin"), + Atom("arcsinh_", "arcsinh"), + Atom("arctan_", "arctan"), + Atom("arctanh_", "arctanh"), + Atom("arg_", "arg"), + Atom("bevelled_", "bevelled"), + Atom("bind_", "bind"), + Atom("bvar_", "bvar"), + Atom("card_", "card"), + Atom("cartesianproduct_", "cartesianproduct"), + Atom("cbytes_", "cbytes"), + Atom("cd_", "cd"), + Atom("cdgroup_", "cdgroup"), + Atom("cerror_", "cerror"), + Atom("charalign_", "charalign"), + Atom("ci_", "ci"), + Atom("closure_", "closure"), + Atom("cn_", "cn"), + Atom("codomain_", "codomain"), + Atom("columnalign_", "columnalign"), + Atom("columnalignment_", "columnalignment"), + Atom("columnlines_", "columnlines"), + Atom("columnspacing_", "columnspacing"), + Atom("columnspan_", "columnspan"), + Atom("columnwidth_", "columnwidth"), + Atom("complexes_", "complexes"), + Atom("compose_", "compose"), + Atom("condition_", "condition"), + Atom("conjugate_", "conjugate"), + Atom("cos_", "cos"), + Atom("cosh_", "cosh"), + Atom("cot_", "cot"), + Atom("coth_", "coth"), + Atom("crossout_", "crossout"), + Atom("csc_", "csc"), + Atom("csch_", "csch"), + Atom("cs_", "cs"), + Atom("csymbol_", "csymbol"), + Atom("csp", "csp"), + Atom("curl_", "curl"), + Atom("decimalpoint_", "decimalpoint"), + Atom("definitionURL_", "definitionURL"), + Atom("degree_", "degree"), + Atom("denomalign_", "denomalign"), + Atom("depth_", "depth"), + Atom("determinant_", "determinant"), + Atom("diff_", "diff"), + Atom("displaystyle_", "displaystyle"), + Atom("divergence_", "divergence"), + Atom("divide_", "divide"), + Atom("domain_", "domain"), + Atom("domainofapplication_", "domainofapplication"), + Atom("edge_", "edge"), + Atom("el", "el"), + Atom("emptyset_", "emptyset"), + Atom("eq_", "eq"), + Atom("equalcolumns_", "equalcolumns"), + Atom("equalrows_", "equalrows"), + Atom("equivalent_", "equivalent"), + Atom("eulergamma_", "eulergamma"), + Atom("exists_", "exists"), + Atom("exp_", "exp"), + Atom("exponentiale_", "exponentiale"), + Atom("factorial_", "factorial"), + Atom("factorof_", "factorof"), + Atom("fence_", "fence"), + Atom("fn_", "fn"), + Atom("fontfamily_", "fontfamily"), + Atom("fontsize_", "fontsize"), + Atom("fontstyle_", "fontstyle"), + Atom("fontweight_", "fontweight"), + Atom("forall_", "forall"), + Atom("framespacing_", "framespacing"), + Atom("gcd_", "gcd"), + Atom("geq_", "geq"), + Atom("groupalign_", "groupalign"), + Atom("gt_", "gt"), + Atom("ident_", "ident"), + Atom("imaginaryi_", "imaginaryi"), + Atom("imaginary_", "imaginary"), + Atom("implies_", "implies"), + Atom("indentalignfirst_", "indentalignfirst"), + Atom("indentalign_", "indentalign"), + Atom("indentalignlast_", "indentalignlast"), + Atom("indentshiftfirst_", "indentshiftfirst"), + Atom("indentshift_", "indentshift"), + Atom("indenttarget_", "indenttarget"), + Atom("integers_", "integers"), + Atom("intersect_", "intersect"), + Atom("interval_", "interval"), + Atom("int_", "int"), + Atom("inverse_", "inverse"), + Atom("lambda_", "lambda"), + Atom("laplacian_", "laplacian"), + Atom("largeop_", "largeop"), + Atom("lcm_", "lcm"), + Atom("leq_", "leq"), + Atom("limit_", "limit"), + Atom("linebreak_", "linebreak"), + Atom("linebreakmultchar_", "linebreakmultchar"), + Atom("linebreakstyle_", "linebreakstyle"), + Atom("linethickness_", "linethickness"), + Atom("list_", "list"), + Atom("ln_", "ln"), + Atom("location_", "location"), + Atom("logbase_", "logbase"), + Atom("log_", "log"), + Atom("longdivstyle_", "longdivstyle"), + Atom("lowlimit_", "lowlimit"), + Atom("lquote_", "lquote"), + Atom("lspace_", "lspace"), + Atom("lt_", "lt"), + Atom("maction_", "maction"), + Atom("maligngroup_", "maligngroup"), + Atom("malignmark_", "malignmark"), + Atom("mathbackground_", "mathbackground"), + Atom("mathcolor_", "mathcolor"), + Atom("mathsize_", "mathsize"), + Atom("mathvariant_", "mathvariant"), + Atom("matrixrow_", "matrixrow"), + Atom("maxsize_", "maxsize"), + Atom("mean_", "mean"), + Atom("median_", "median"), + Atom("menclose_", "menclose"), + Atom("merror_", "merror"), + Atom("mfenced_", "mfenced"), + Atom("mfrac_", "mfrac"), + Atom("mglyph_", "mglyph"), + Atom("mi_", "mi"), + Atom("minlabelspacing_", "minlabelspacing"), + Atom("minsize_", "minsize"), + Atom("minus_", "minus"), + Atom("mlabeledtr_", "mlabeledtr"), + Atom("mlongdiv_", "mlongdiv"), + Atom("mmultiscripts_", "mmultiscripts"), + Atom("mn_", "mn"), + Atom("momentabout_", "momentabout"), + Atom("moment_", "moment"), + Atom("mo_", "mo"), + Atom("movablelimits_", "movablelimits"), + Atom("mover_", "mover"), + Atom("mpadded_", "mpadded"), + Atom("mphantom_", "mphantom"), + Atom("mprescripts_", "mprescripts"), + Atom("mroot_", "mroot"), + Atom("mrow_", "mrow"), + Atom("mscarries_", "mscarries"), + Atom("mscarry_", "mscarry"), + Atom("msgroup_", "msgroup"), + Atom("msline_", "msline"), + Atom("ms_", "ms"), + Atom("mspace_", "mspace"), + Atom("msqrt_", "msqrt"), + Atom("msrow_", "msrow"), + Atom("mstack_", "mstack"), + Atom("mstyle_", "mstyle"), + Atom("msub_", "msub"), + Atom("msubsup_", "msubsup"), + Atom("msup_", "msup"), + Atom("mtable_", "mtable"), + Atom("mtd_", "mtd"), + Atom("mtext_", "mtext"), + Atom("mtr_", "mtr"), + Atom("munder_", "munder"), + Atom("munderover_", "munderover"), + Atom("naturalnumbers_", "naturalnumbers"), + Atom("neq_", "neq"), + Atom("notanumber_", "notanumber"), + Atom("notation_", "notation"), + Atom("note_", "note"), + Atom("notin_", "notin"), + Atom("notprsubset_", "notprsubset"), + Atom("notsubset_", "notsubset"), + Atom("numalign_", "numalign"), + Atom("other", "other"), + Atom("outerproduct_", "outerproduct"), + Atom("partialdiff_", "partialdiff"), + Atom("piece_", "piece"), + Atom("piecewise_", "piecewise"), + Atom("pi_", "pi"), + Atom("plus_", "plus"), + Atom("power_", "power"), + Atom("primes_", "primes"), + Atom("product_", "product"), + Atom("prsubset_", "prsubset"), + Atom("quotient_", "quotient"), + Atom("rationals_", "rationals"), + Atom("real_", "real"), + Atom("reals_", "reals"), + Atom("reln_", "reln"), + Atom("root_", "root"), + Atom("rowalign_", "rowalign"), + Atom("rowlines_", "rowlines"), + Atom("rowspacing_", "rowspacing"), + Atom("rquote_", "rquote"), + Atom("rspace_", "rspace"), + Atom("scalarproduct_", "scalarproduct"), + Atom("schemaLocation_", "schemaLocation"), + Atom("scriptlevel_", "scriptlevel"), + Atom("scriptminsize_", "scriptminsize"), + Atom("scriptsizemultiplier_", "scriptsizemultiplier"), + Atom("scriptsize_", "scriptsize"), + Atom("sdev_", "sdev"), + Atom("sech_", "sech"), + Atom("sec_", "sec"), + Atom("selection_", "selection"), + Atom("selector_", "selector"), + Atom("semantics_", "semantics"), + Atom("separator_", "separator"), + Atom("separators_", "separators"), + Atom("sep_", "sep"), + Atom("setdiff_", "setdiff"), + # Atom("set_", "set"), # "set" is present above + Atom("share_", "share"), + Atom("shift_", "shift"), + Atom("side_", "side"), + Atom("sinh_", "sinh"), + Atom("sin_", "sin"), + Atom("stackalign_", "stackalign"), + Atom("stretchy_", "stretchy"), + Atom("subscriptshift_", "subscriptshift"), + Atom("subset_", "subset"), + Atom("superscriptshift_", "superscriptshift"), + Atom("symmetric_", "symmetric"), + Atom("tanh_", "tanh"), + Atom("tan_", "tan"), + Atom("tendsto_", "tendsto"), + Atom("times_", "times"), + Atom("transpose_", "transpose"), + Atom("union_", "union"), + Atom("uplimit_", "uplimit"), + Atom("variance_", "variance"), + Atom("vectorproduct_", "vectorproduct"), + Atom("vector_", "vector"), + Atom("voffset_", "voffset"), + Atom("xref_", "xref"), + Atom("math", "math"), # the only one without an underscore + Atom("booleanFromString", "boolean-from-string"), + Atom("countNonEmpty", "count-non-empty"), + Atom("daysFromDate", "days-from-date"), + Atom("secondsFromDateTime", "seconds-from-dateTime"), + Atom("tabbrowser_arrowscrollbox", "tabbrowser-arrowscrollbox"), + # Simple gestures support + Atom("onMozSwipeGestureMayStart", "onMozSwipeGestureMayStart"), + Atom("onMozSwipeGestureStart", "onMozSwipeGestureStart"), + Atom("onMozSwipeGestureUpdate", "onMozSwipeGestureUpdate"), + Atom("onMozSwipeGestureEnd", "onMozSwipeGestureEnd"), + Atom("onMozSwipeGesture", "onMozSwipeGesture"), + Atom("onMozMagnifyGestureStart", "onMozMagnifyGestureStart"), + Atom("onMozMagnifyGestureUpdate", "onMozMagnifyGestureUpdate"), + Atom("onMozMagnifyGesture", "onMozMagnifyGesture"), + Atom("onMozRotateGestureStart", "onMozRotateGestureStart"), + Atom("onMozRotateGestureUpdate", "onMozRotateGestureUpdate"), + Atom("onMozRotateGesture", "onMozRotateGesture"), + Atom("onMozTapGesture", "onMozTapGesture"), + Atom("onMozPressTapGesture", "onMozPressTapGesture"), + Atom("onMozEdgeUIStarted", "onMozEdgeUIStarted"), + Atom("onMozEdgeUICanceled", "onMozEdgeUICanceled"), + Atom("onMozEdgeUICompleted", "onMozEdgeUICompleted"), + # Pointer events + Atom("onpointerdown", "onpointerdown"), + Atom("onpointermove", "onpointermove"), + Atom("onpointerup", "onpointerup"), + Atom("onpointercancel", "onpointercancel"), + Atom("onpointerover", "onpointerover"), + Atom("onpointerout", "onpointerout"), + Atom("onpointerenter", "onpointerenter"), + Atom("onpointerleave", "onpointerleave"), + Atom("ongotpointercapture", "ongotpointercapture"), + Atom("onlostpointercapture", "onlostpointercapture"), + # orientation support + Atom("ondevicemotion", "ondevicemotion"), + Atom("ondeviceorientation", "ondeviceorientation"), + Atom("ondeviceorientationabsolute", "ondeviceorientationabsolute"), + Atom("onmozorientationchange", "onmozorientationchange"), + Atom("onuserproximity", "onuserproximity"), + # light sensor support + Atom("ondevicelight", "ondevicelight"), + # MediaDevices device change event + Atom("ondevicechange", "ondevicechange"), + # Internal Visual Viewport events + Atom("onmozvisualresize", "onmozvisualresize"), + Atom("onmozvisualscroll", "onmozvisualscroll"), + # Miscellaneous events included for memory usage optimization (see bug 1542885) + Atom("onDOMAutoComplete", "onDOMAutoComplete"), + Atom("onDOMContentLoaded", "onDOMContentLoaded"), + Atom("onDOMDocElementInserted", "onDOMDocElementInserted"), + Atom("onDOMFormBeforeSubmit", "onDOMFormBeforeSubmit"), + Atom("onDOMFormHasPassword", "onDOMFormHasPassword"), + Atom("onDOMFrameContentLoaded", "onDOMFrameContentLoaded"), + Atom("onDOMHeadElementParsed", "onDOMHeadElementParsed"), + Atom("onDOMInputPasswordAdded", "onDOMInputPasswordAdded"), + Atom("onDOMLinkAdded", "onDOMLinkAdded"), + Atom("onDOMLinkChanged", "onDOMLinkChanged"), + Atom("onDOMMetaAdded", "onDOMMetaAdded"), + Atom("onDOMMetaChanged", "onDOMMetaChanged"), + Atom("onDOMMetaRemoved", "onDOMMetaRemoved"), + Atom("onDOMPopupBlocked", "onDOMPopupBlocked"), + Atom("onDOMTitleChanged", "onDOMTitleChanged"), + Atom("onDOMWindowClose", "onDOMWindowClose"), + Atom("onDOMWindowCreated", "onDOMWindowCreated"), + Atom("onDOMWindowFocus", "onDOMWindowFocus"), + Atom("onFullZoomChange", "onFullZoomChange"), + Atom("onGloballyAutoplayBlocked", "onGloballyAutoplayBlocked"), + Atom("onMozDOMFullscreen_Entered", "onMozDOMFullscreen:Entered"), + Atom("onMozDOMFullscreen_Exit", "onMozDOMFullscreen:Exit"), + Atom("onMozDOMFullscreen_Exited", "onMozDOMFullscreen:Exited"), + Atom("onMozDOMFullscreen_NewOrigin", "onMozDOMFullscreen:NewOrigin"), + Atom("onMozDOMFullscreen_Request", "onMozDOMFullscreen:Request"), + Atom("onMozDOMPointerLock_Entered", "onMozDOMPointerLock:Entered"), + Atom("onMozDOMPointerLock_Exited", "onMozDOMPointerLock:Exited"), + Atom("onMozInvalidForm", "onMozInvalidForm"), + Atom("onMozLocalStorageChanged", "onMozLocalStorageChanged"), + Atom("onMozOpenDateTimePicker", "onMozOpenDateTimePicker"), + Atom("onMozSessionStorageChanged", "onMozSessionStorageChanged"), + Atom("onMozTogglePictureInPicture", "onMozTogglePictureInPicture"), + Atom("onPluginCrashed", "onPluginCrashed"), + Atom("onPrintingError", "onPrintingError"), + Atom("onTextZoomChange", "onTextZoomChange"), + Atom("onUAWidgetSetupOrChange", "onUAWidgetSetupOrChange"), + Atom("onUAWidgetTeardown", "onUAWidgetTeardown"), + Atom("onUnselectedTabHover_Disable", "onUnselectedTabHover:Disable"), + Atom("onUnselectedTabHover_Enable", "onUnselectedTabHover:Enable"), + Atom("onmozshowdropdown", "onmozshowdropdown"), + Atom("onmozshowdropdown_sourcetouch", "onmozshowdropdown-sourcetouch"), + Atom("onprintPreviewUpdate", "onprintPreviewUpdate"), + Atom("onscrollend", "onscrollend"), + Atom("onbeforetoggle", "onbeforetoggle"), + # WebExtensions + Atom("moz_extension", "moz-extension"), + Atom("all_urlsPermission", ""), + Atom("clipboardRead", "clipboardRead"), + Atom("clipboardWrite", "clipboardWrite"), + Atom("debugger", "debugger"), + Atom("mozillaAddons", "mozillaAddons"), + Atom("tabs", "tabs"), + Atom("webRequestBlocking", "webRequestBlocking"), + Atom("webRequestFilterResponse_serviceWorkerScript", "webRequestFilterResponse.serviceWorkerScript"), + Atom("http", "http"), + Atom("https", "https"), + Atom("ws", "ws"), + Atom("wss", "wss"), + Atom("ftp", "ftp"), + Atom("chrome", "chrome"), + Atom("moz", "moz"), + Atom("moz_icon", "moz-icon"), + Atom("moz_gio", "moz-gio"), + Atom("proxy", "proxy"), + Atom("privateBrowsingAllowedPermission", "internal:privateBrowsingAllowed"), + Atom("svgContextPropertiesAllowedPermission", "internal:svgContextPropertiesAllowed"), + Atom("theme", "theme"), + # CSS Counter Styles + Atom("decimal_leading_zero", "decimal-leading-zero"), + Atom("arabic_indic", "arabic-indic"), + Atom("armenian", "armenian"), + Atom("upper_armenian", "upper-armenian"), + Atom("lower_armenian", "lower-armenian"), + Atom("bengali", "bengali"), + Atom("cambodian", "cambodian"), + Atom("khmer", "khmer"), + Atom("cjk_decimal", "cjk-decimal"), + Atom("devanagari", "devanagari"), + Atom("georgian", "georgian"), + Atom("gujarati", "gujarati"), + Atom("gurmukhi", "gurmukhi"), + Atom("kannada", "kannada"), + Atom("lao", "lao"), + Atom("malayalam", "malayalam"), + Atom("mongolian", "mongolian"), + Atom("myanmar", "myanmar"), + Atom("oriya", "oriya"), + Atom("persian", "persian"), + Atom("lower_roman", "lower-roman"), + Atom("upper_roman", "upper-roman"), + Atom("tamil", "tamil"), + Atom("telugu", "telugu"), + Atom("thai", "thai"), + Atom("tibetan", "tibetan"), + Atom("lower_alpha", "lower-alpha"), + Atom("lower_latin", "lower-latin"), + Atom("upper_alpha", "upper-alpha"), + Atom("upper_latin", "upper-latin"), + Atom("cjk_heavenly_stem", "cjk-heavenly-stem"), + Atom("cjk_earthly_branch", "cjk-earthly-branch"), + Atom("lower_greek", "lower-greek"), + Atom("hiragana", "hiragana"), + Atom("hiragana_iroha", "hiragana-iroha"), + Atom("katakana", "katakana"), + Atom("katakana_iroha", "katakana-iroha"), + Atom("cjk_ideographic", "cjk-ideographic"), + Atom("_moz_arabic_indic", "-moz-arabic-indic"), + Atom("_moz_persian", "-moz-persian"), + Atom("_moz_urdu", "-moz-urdu"), + Atom("_moz_devanagari", "-moz-devanagari"), + Atom("_moz_bengali", "-moz-bengali"), + Atom("_moz_gurmukhi", "-moz-gurmukhi"), + Atom("_moz_gujarati", "-moz-gujarati"), + Atom("_moz_oriya", "-moz-oriya"), + Atom("_moz_tamil", "-moz-tamil"), + Atom("_moz_telugu", "-moz-telugu"), + Atom("_moz_kannada", "-moz-kannada"), + Atom("_moz_malayalam", "-moz-malayalam"), + Atom("_moz_thai", "-moz-thai"), + Atom("_moz_lao", "-moz-lao"), + Atom("_moz_myanmar", "-moz-myanmar"), + Atom("_moz_khmer", "-moz-khmer"), + Atom("_moz_cjk_heavenly_stem", "-moz-cjk-heavenly-stem"), + Atom("_moz_cjk_earthly_branch", "-moz-cjk-earthly-branch"), + Atom("_moz_hangul", "-moz-hangul"), + Atom("_moz_hangul_consonant", "-moz-hangul-consonant"), + Atom("_moz_ethiopic_halehame", "-moz-ethiopic-halehame"), + Atom("_moz_ethiopic_halehame_am", "-moz-ethiopic-halehame-am"), + Atom("_moz_ethiopic_halehame_ti_er", "-moz-ethiopic-halehame-ti-er"), + Atom("_moz_ethiopic_halehame_ti_et", "-moz-ethiopic-halehame-ti-et"), + Atom("_moz_trad_chinese_informal", "-moz-trad-chinese-informal"), + Atom("_moz_trad_chinese_formal", "-moz-trad-chinese-formal"), + Atom("_moz_simp_chinese_informal", "-moz-simp-chinese-informal"), + Atom("_moz_simp_chinese_formal", "-moz-simp-chinese-formal"), + Atom("_moz_japanese_informal", "-moz-japanese-informal"), + Atom("_moz_japanese_formal", "-moz-japanese-formal"), + Atom("_moz_ethiopic_numeric", "-moz-ethiopic-numeric"), + # -------------------------------------------------------------------------- + # Special atoms + # -------------------------------------------------------------------------- + # Node types + Atom("cdataTagName", "#cdata-section"), + Atom("commentTagName", "#comment"), + Atom("documentNodeName", "#document"), + Atom("documentFragmentNodeName", "#document-fragment"), + Atom("documentTypeNodeName", "#document-type"), + Atom("processingInstructionTagName", "#processing-instruction"), + Atom("textTagName", "#text"), + # Frame types + # + # TODO(emilio): Rename this? This is only used now to mark the style context of + # the placeholder with a dummy pseudo. + Atom("placeholderFrame", "PlaceholderFrame"), + Atom("onloadend", "onloadend"), + Atom("onloadstart", "onloadstart"), + Atom("onprogress", "onprogress"), + Atom("onsuspend", "onsuspend"), + Atom("onemptied", "onemptied"), + Atom("onstalled", "onstalled"), + Atom("onplay", "onplay"), + Atom("onpause", "onpause"), + Atom("onloadedmetadata", "onloadedmetadata"), + Atom("onloadeddata", "onloadeddata"), + Atom("onwaiting", "onwaiting"), + Atom("onplaying", "onplaying"), + Atom("oncanplay", "oncanplay"), + Atom("oncanplaythrough", "oncanplaythrough"), + Atom("onseeking", "onseeking"), + Atom("onseeked", "onseeked"), + Atom("ontimeout", "ontimeout"), + Atom("ontimeupdate", "ontimeupdate"), + Atom("onended", "onended"), + Atom("onformdata", "onformdata"), + Atom("onratechange", "onratechange"), + Atom("ondurationchange", "ondurationchange"), + Atom("onvolumechange", "onvolumechange"), + Atom("onaddtrack", "onaddtrack"), + Atom("oncontrollerchange", "oncontrollerchange"), + Atom("oncuechange", "oncuechange"), + Atom("onenter", "onenter"), + Atom("onexit", "onexit"), + Atom("onencrypted", "onencrypted"), + Atom("onwaitingforkey", "onwaitingforkey"), + Atom("onkeystatuseschange", "onkeystatuseschange"), + Atom("onremovetrack", "onremovetrack"), + Atom("loadstart", "loadstart"), + Atom("suspend", "suspend"), + Atom("emptied", "emptied"), + Atom("play", "play"), + Atom("pause", "pause"), + Atom("loadedmetadata", "loadedmetadata"), + Atom("loadeddata", "loadeddata"), + Atom("waiting", "waiting"), + Atom("playing", "playing"), + Atom("timeupdate", "timeupdate"), + Atom("canplay", "canplay"), + Atom("canplaythrough", "canplaythrough"), + Atom("ondataavailable", "ondataavailable"), + Atom("onwarning", "onwarning"), + Atom("onstart", "onstart"), + Atom("onstop", "onstop"), + Atom("onphoto", "onphoto"), + Atom("ongamepadbuttondown", "ongamepadbuttondown"), + Atom("ongamepadbuttonup", "ongamepadbuttonup"), + Atom("ongamepadaxismove", "ongamepadaxismove"), + Atom("ongamepadconnected", "ongamepadconnected"), + Atom("ongamepaddisconnected", "ongamepaddisconnected"), + Atom("onfetch", "onfetch"), + # Content property names + Atom("afterPseudoProperty", "afterPseudoProperty"), # nsXMLElement* + Atom("beforePseudoProperty", "beforePseudoProperty"), # nsXMLElement* + Atom("cssPseudoElementBeforeProperty", "CSSPseudoElementBeforeProperty"), # CSSPseudoElement* + Atom("cssPseudoElementAfterProperty", "CSSPseudoElementAfterProperty"), # CSSPseudoElement* + Atom("cssPseudoElementMarkerProperty", "CSSPseudoElementMarkerProperty"), # CSSPseudoElement* + Atom("genConInitializerProperty", "QuoteNodeProperty"), + Atom("labelMouseDownPtProperty", "LabelMouseDownPtProperty"), + Atom("lockedStyleStates", "lockedStyleStates"), + Atom("apzCallbackTransform", "apzCallbackTransform"), + Atom("apzDisabled", "ApzDisabled"), # bool + Atom("restylableAnonymousNode", "restylableAnonymousNode"), # bool + Atom("docLevelNativeAnonymousContent", "docLevelNativeAnonymousContent"), # bool + Atom("paintRequestTime", "PaintRequestTime"), + Atom("pseudoProperty", "PseudoProperty"), # PseudoStyleType + Atom("manualNACProperty", "ManualNACProperty"), # ManualNAC* + Atom("markerPseudoProperty", "markerPseudoProperty"), # nsXMLElement* + # Languages for lang-specific transforms + Atom("Japanese", "ja"), + Atom("Chinese", "zh-CN"), + Atom("Taiwanese", "zh-TW"), + Atom("HongKongChinese", "zh-HK"), + Atom("Unicode", "x-unicode"), + # language codes specifically referenced in the gfx code + Atom("ko", "ko"), + Atom("zh_cn", "zh-cn"), + Atom("zh_tw", "zh-tw"), + # additional codes used in nsUnicodeRange.cpp + Atom("x_cyrillic", "x-cyrillic"), + Atom("he", "he"), + Atom("ar", "ar"), + Atom("x_devanagari", "x-devanagari"), + Atom("x_tamil", "x-tamil"), + Atom("x_armn", "x-armn"), + Atom("x_beng", "x-beng"), + Atom("x_cans", "x-cans"), + Atom("x_ethi", "x-ethi"), + Atom("x_geor", "x-geor"), + Atom("x_gujr", "x-gujr"), + Atom("x_guru", "x-guru"), + Atom("x_khmr", "x-khmr"), + Atom("x_knda", "x-knda"), + Atom("x_mlym", "x-mlym"), + Atom("x_orya", "x-orya"), + Atom("x_sinh", "x-sinh"), + Atom("x_telu", "x-telu"), + Atom("x_tibt", "x-tibt"), + # additional languages that have special case transformations + Atom("az", "az"), + Atom("ba", "ba"), + Atom("crh", "crh"), + # Atom("el", "el"), # "el" is present above + Atom("ga", "ga"), + # Atom("lt", "lt"), # "lt" is present above (atom name "lt_") + Atom("nl", "nl"), + # mathematical language, used for MathML + Atom("x_math", "x-math"), + # other languages mentioned in :lang() rules in UA style sheets + Atom("zh", "zh"), + # Names for editor transactions + Atom("TypingTxnName", "Typing"), + Atom("IMETxnName", "IME"), + Atom("DeleteTxnName", "Deleting"), + # Font families + Atom("serif", "serif"), + Atom("sans_serif", "sans-serif"), + Atom("cursive", "cursive"), + Atom("fantasy", "fantasy"), + Atom("monospace", "monospace"), + Atom("mozfixed", "-moz-fixed"), + Atom("moz_fixed_pos_containing_block", "-moz-fixed-pos-containing-block"), + # Standard font-palette identifiers + Atom("light", "light"), + Atom("dark", "dark"), + # IPC stuff + # Atom("Remote", "remote"), # "remote" is present above + Atom("RemoteId", "_remote_id"), + Atom("RemoteType", "remoteType"), + Atom("DisplayPort", "_displayport"), + Atom("DisplayPortMargins", "_displayportmargins"), + Atom("DisplayPortBase", "_displayportbase"), + Atom("MinimalDisplayPort", "_minimaldisplayport"), + Atom("forceMousewheelAutodir", "_force_mousewheel_autodir"), + Atom("forceMousewheelAutodirHonourRoot", "_force_mousewheel_autodir_honourroot"), + Atom("forcemessagemanager", "forcemessagemanager"), + Atom("initialBrowsingContextGroupId", "initialBrowsingContextGroupId"), + Atom("initiallyactive", "initiallyactive"), + # windows media query names + Atom("windows_win7", "windows-win7"), + Atom("windows_win8", "windows-win8"), + Atom("windows_win10", "windows-win10"), + # Names for system metrics. + Atom("_moz_scrollbar_start_backward", "-moz-scrollbar-start-backward"), + Atom("_moz_scrollbar_start_forward", "-moz-scrollbar-start-forward"), + Atom("_moz_scrollbar_end_backward", "-moz-scrollbar-end-backward"), + Atom("_moz_scrollbar_end_forward", "-moz-scrollbar-end-forward"), + Atom("_moz_overlay_scrollbars", "-moz-overlay-scrollbars"), + Atom("_moz_windows_accent_color_in_titlebar", "-moz-windows-accent-color-in-titlebar"), + Atom("_moz_windows_default_theme", "-moz-windows-default-theme"), + Atom("_moz_mac_graphite_theme", "-moz-mac-graphite-theme"), + Atom("_moz_mac_big_sur_theme", "-moz-mac-big-sur-theme"), + Atom("_moz_mac_rtl", "-moz-mac-rtl"), + Atom("_moz_platform", "-moz-platform"), + Atom("_moz_windows_compositor", "-moz-windows-compositor"), + Atom("_moz_windows_classic", "-moz-windows-classic"), + Atom("_moz_windows_glass", "-moz-windows-glass"), + Atom("_moz_windows_non_native_menus", "-moz-windows-non-native-menus"), + Atom("_moz_menubar_drag", "-moz-menubar-drag"), + Atom("_moz_device_pixel_ratio", "-moz-device-pixel-ratio"), + Atom("_moz_device_orientation", "-moz-device-orientation"), + Atom("_moz_is_resource_document", "-moz-is-resource-document"), + Atom("_moz_swipe_animation_enabled", "-moz-swipe-animation-enabled"), + Atom("_moz_gtk_csd_available", "-moz-gtk-csd-available"), + Atom("_moz_gtk_csd_titlebar_radius", "-moz-gtk-csd-titlebar-radius"), + Atom("_moz_gtk_csd_minimize_button", "-moz-gtk-csd-minimize-button"), + Atom("_moz_gtk_csd_minimize_button_position", "-moz-gtk-csd-minimize-button-position"), + Atom("_moz_gtk_csd_maximize_button", "-moz-gtk-csd-maximize-button"), + Atom("_moz_gtk_csd_maximize_button_position", "-moz-gtk-csd-maximize-button-position"), + Atom("_moz_gtk_csd_close_button", "-moz-gtk-csd-close-button"), + Atom("_moz_gtk_csd_close_button_position", "-moz-gtk-csd-close-button-position"), + Atom("_moz_gtk_csd_reversed_placement", "-moz-gtk-csd-reversed-placement"), + Atom("_moz_content_prefers_color_scheme", "-moz-content-prefers-color-scheme"), + Atom("_moz_content_preferred_color_scheme", "-moz-content-preferred-color-scheme"), + Atom("_moz_system_dark_theme", "-moz-system-dark-theme"), + Atom("_moz_panel_animations", "-moz-panel-animations"), + # application commands + Atom("Back", "Back"), + Atom("Forward", "Forward"), + Atom("Reload", "Reload"), + Atom("Stop", "Stop"), + Atom("Search", "Search"), + Atom("Bookmarks", "Bookmarks"), + Atom("Home", "Home"), + Atom("NextTrack", "NextTrack"), + Atom("PreviousTrack", "PreviousTrack"), + Atom("MediaStop", "MediaStop"), + Atom("PlayPause", "PlayPause"), + Atom("New", "New"), + Atom("Open", "Open"), + Atom("Close", "Close"), + Atom("Save", "Save"), + Atom("Find", "Find"), + Atom("Help", "Help"), + Atom("Print", "Print"), + Atom("SendMail", "SendMail"), + Atom("ForwardMail", "ForwardMail"), + Atom("ReplyToMail", "ReplyToMail"), + Atom("alert", "alert"), + Atom("alertdialog", "alertdialog"), + Atom("application", "application"), + Atom("aria_colcount", "aria-colcount"), + Atom("aria_colindex", "aria-colindex"), + Atom("aria_colindextext", "aria-colindextext"), + Atom("aria_colspan", "aria-colspan"), + Atom("aria_details", "aria-details"), + Atom("aria_errormessage", "aria-errormessage"), + Atom("aria_grabbed", "aria-grabbed"), + Atom("aria_keyshortcuts", "aria-keyshortcuts"), + Atom("aria_label", "aria-label"), + Atom("aria_modal", "aria-modal"), + Atom("aria_orientation", "aria-orientation"), + Atom("aria_placeholder", "aria-placeholder"), + Atom("aria_roledescription", "aria-roledescription"), + Atom("aria_rowcount", "aria-rowcount"), + Atom("aria_rowindex", "aria-rowindex"), + Atom("aria_rowindextext", "aria-rowindextext"), + Atom("aria_rowspan", "aria-rowspan"), + Atom("aria_valuetext", "aria-valuetext"), + Atom("assertive", "assertive"), + Atom("auto_generated", "auto-generated"), + Atom("banner", "banner"), + Atom("checkable", "checkable"), + Atom("columnheader", "columnheader"), + Atom("complementary", "complementary"), + Atom("containerAtomic", "container-atomic"), + Atom("containerBusy", "container-busy"), + Atom("containerLive", "container-live"), + Atom("containerLiveRole", "container-live-role"), + Atom("containerRelevant", "container-relevant"), + Atom("contentinfo", "contentinfo"), + Atom("cycles", "cycles"), + Atom("datatable", "datatable"), + Atom("eventFromInput", "event-from-input"), + Atom("feed", "feed"), + Atom("grammar", "grammar"), + Atom("gridcell", "gridcell"), + Atom("heading", "heading"), + Atom("inlinevalue", "inline"), + Atom("inline_size", "inline-size"), + Atom("invalid", "invalid"), + Atom("lineNumber", "line-number"), + Atom("menuitemcheckbox", "menuitemcheckbox"), + Atom("menuitemradio", "menuitemradio"), + # Atom("mixed", "mixed"), # "mixed" is present above + Atom("navigation", "navigation"), + Atom("polite", "polite"), + Atom("posinset", "posinset"), + Atom("presentation", "presentation"), + Atom("progressbar", "progressbar"), + Atom("region", "region"), + Atom("rowgroup", "rowgroup"), + Atom("rowheader", "rowheader"), + Atom("search", "search"), + Atom("searchbox", "searchbox"), + Atom("setsize", "setsize"), + Atom("spelling", "spelling"), + Atom("spinbutton", "spinbutton"), + Atom("status", "status"), + Atom("tableCellIndex", "table-cell-index"), + Atom("tablist", "tablist"), + Atom("textIndent", "text-indent"), + Atom("textInputType", "text-input-type"), + Atom("textLineThroughColor", "text-line-through-color"), + Atom("textLineThroughStyle", "text-line-through-style"), + Atom("textPosition", "text-position"), + Atom("textUnderlineColor", "text-underline-color"), + Atom("textUnderlineStyle", "text-underline-style"), + Atom("timer", "timer"), + Atom("toolbarname", "toolbarname"), + Atom("toolbarseparator", "toolbarseparator"), + Atom("toolbarspacer", "toolbarspacer"), + Atom("toolbarspring", "toolbarspring"), + Atom("treegrid", "treegrid"), + Atom("_undefined", "undefined"), + Atom("xmlroles", "xml-roles"), + # MathML xml roles + Atom("close_fence", "close-fence"), + Atom("denominator", "denominator"), + Atom("numerator", "numerator"), + Atom("open_fence", "open-fence"), + Atom("overscript", "overscript"), + Atom("presubscript", "presubscript"), + Atom("presuperscript", "presuperscript"), + Atom("root_index", "root-index"), + Atom("subscript", "subscript"), + Atom("superscript", "superscript"), + Atom("underscript", "underscript"), + Atom("onaudiostart", "onaudiostart"), + Atom("onaudioend", "onaudioend"), + Atom("onsoundstart", "onsoundstart"), + Atom("onsoundend", "onsoundend"), + Atom("onspeechstart", "onspeechstart"), + Atom("onspeechend", "onspeechend"), + Atom("onresult", "onresult"), + Atom("onnomatch", "onnomatch"), + Atom("onresume", "onresume"), + Atom("onmark", "onmark"), + Atom("onboundary", "onboundary"), + # Media Controller + Atom("onactivated", "onactivated"), + Atom("ondeactivated", "ondeactivated"), + Atom("onmetadatachange", "onmetadatachange"), + Atom("onplaybackstatechange", "onplaybackstatechange"), + Atom("onpositionstatechange", "onpositionstatechange"), + Atom("onsupportedkeyschange", "onsupportedkeyschange"), + # media query for MathML Core's implementation of maction/semantics + Atom("_moz_mathml_core_maction_and_semantics", "-moz-mathml-core-maction-and-semantics"), + # media query for MathML Core's implementation of ms + Atom("_moz_mathml_core_ms", "-moz-mathml-core-ms"), + Atom("_moz_popover_enabled", "-moz-popover-enabled"), + # Contextual Identity / Containers + Atom("usercontextid", "usercontextid"), + Atom("geckoViewSessionContextId", "geckoViewSessionContextId"), + # Namespaces + Atom("nsuri_xmlns", "http://www.w3.org/2000/xmlns/"), + Atom("nsuri_xml", "http://www.w3.org/XML/1998/namespace"), + Atom("nsuri_xhtml", "http://www.w3.org/1999/xhtml"), + Atom("nsuri_xlink", "http://www.w3.org/1999/xlink"), + Atom("nsuri_xslt", "http://www.w3.org/1999/XSL/Transform"), + Atom("nsuri_mathml", "http://www.w3.org/1998/Math/MathML"), + Atom("nsuri_rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"), + Atom("nsuri_xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"), + Atom("nsuri_svg", "http://www.w3.org/2000/svg"), + Atom("nsuri_parsererror", "http://www.mozilla.org/newlayout/xml/parsererror.xml"), + # MSE + Atom("onsourceopen", "onsourceopen"), + Atom("onsourceended", "onsourceended"), + Atom("onsourceclose", "onsourceclose"), + Atom("onupdatestart", "onupdatestart"), + Atom("onupdate", "onupdate"), + Atom("onupdateend", "onupdateend"), + Atom("onaddsourcebuffer", "onaddsourcebuffer"), + Atom("onremovesourcebuffer", "onremovesourcebuffer"), + # RDF (not used by mozilla-central, but still used by comm-central) + Atom("about", "about"), + Atom("ID", "ID"), + Atom("nodeID", "nodeID"), + Atom("aboutEach", "aboutEach"), + Atom("resource", "resource"), + Atom("RDF", "RDF"), + Atom("Description", "Description"), + Atom("Bag", "Bag"), + Atom("Seq", "Seq"), + Atom("Alt", "Alt"), + # Atom("kLiAtom", "li"), # "li" is present above + # Atom("kXMLNSAtom", "xmlns"), # "xmlns" is present above + Atom("parseType", "parseType"), + # Directory service + Atom("DirectoryService_CurrentProcess", "XCurProcD"), + Atom("DirectoryService_GRE_Directory", "GreD"), + Atom("DirectoryService_GRE_BinDirectory", "GreBinD"), + Atom("DirectoryService_OS_TemporaryDirectory", "TmpD"), + Atom("DirectoryService_OS_CurrentProcessDirectory", "CurProcD"), + Atom("DirectoryService_OS_CurrentWorkingDirectory", "CurWorkD"), + Atom("DirectoryService_OS_SystemConfigDir", "SysConfD"), + # Atom("DirectoryService_OS_HomeDirectory", "Home"), # "Home" is present above + Atom("DirectoryService_OS_DesktopDirectory", "Desk"), + Atom("DirectoryService_InitCurrentProcess_dummy", "MozBinD"), + Atom("DirectoryService_SystemDirectory", "SysD"), + Atom("DirectoryService_UserLibDirectory", "ULibDir"), + Atom("DirectoryService_DefaultDownloadDirectory", "DfltDwnld"), + Atom("DirectoryService_LocalApplicationsDirectory", "LocApp"), + Atom("DirectoryService_UserPreferencesDirectory", "UsrPrfs"), + Atom("DirectoryService_PictureDocumentsDirectory", "Pct"), + Atom("DirectoryService_DefaultScreenshotDirectory", "Scrnshts"), + Atom("DirectoryService_WindowsDirectory", "WinD"), + Atom("DirectoryService_WindowsProgramFiles", "ProgF"), + Atom("DirectoryService_Programs", "Progs"), + Atom("DirectoryService_Favorites", "Favs"), + Atom("DirectoryService_Appdata", "AppData"), + Atom("DirectoryService_LocalAppdata", "LocalAppData"), + Atom("DirectoryService_WinCookiesDirectory", "CookD"), + # CSS pseudo-elements -- these must appear in the same order as + # in nsCSSPseudoElementList.h + PseudoElementAtom("PseudoElement_after", ":after"), + PseudoElementAtom("PseudoElement_before", ":before"), + PseudoElementAtom("PseudoElement_marker", ":marker"), + PseudoElementAtom("PseudoElement_backdrop", ":backdrop"), + PseudoElementAtom("PseudoElement_cue", ":cue"), + PseudoElementAtom("PseudoElement_firstLetter", ":first-letter"), + PseudoElementAtom("PseudoElement_firstLine", ":first-line"), + PseudoElementAtom("PseudoElement_highlight", ":highlight"), + PseudoElementAtom("PseudoElement_selection", ":selection"), + PseudoElementAtom("PseudoElement_mozFocusInner", ":-moz-focus-inner"), + PseudoElementAtom("PseudoElement_mozNumberSpinBox", ":-moz-number-spin-box"), + PseudoElementAtom("PseudoElement_mozNumberSpinUp", ":-moz-number-spin-up"), + PseudoElementAtom("PseudoElement_mozNumberSpinDown", ":-moz-number-spin-down"), + PseudoElementAtom("PseudoElement_mozSearchClearButton", ":-moz-search-clear-button"), + PseudoElementAtom("PseudoElement_mozProgressBar", ":-moz-progress-bar"), + PseudoElementAtom("PseudoElement_mozRangeTrack", ":-moz-range-track"), + PseudoElementAtom("PseudoElement_mozRangeProgress", ":-moz-range-progress"), + PseudoElementAtom("PseudoElement_mozRangeThumb", ":-moz-range-thumb"), + PseudoElementAtom("PseudoElement_mozMeterBar", ":-moz-meter-bar"), + PseudoElementAtom("PseudoElement_placeholder", ":placeholder"), + PseudoElementAtom("PseudoElement_mozColorSwatch", ":-moz-color-swatch"), + PseudoElementAtom("PseudoElement_mozTextControlEditingRoot", ":-moz-text-control-editing-root"), + PseudoElementAtom("PseudoElement_mozTextControlPreview", ":-moz-text-control-preview"), + PseudoElementAtom("PseudoElement_mozReveal", ":-moz-reveal"), + PseudoElementAtom("PseudoElement_fileSelectorButton", ":file-selector-button"), + # CSS anonymous boxes -- these must appear in the same order as + # in nsCSSAnonBoxList.h + NonInheritingAnonBoxAtom("AnonBox_oofPlaceholder", ":-moz-oof-placeholder"), + NonInheritingAnonBoxAtom("AnonBox_horizontalFramesetBorder", ":-moz-hframeset-border"), + NonInheritingAnonBoxAtom("AnonBox_verticalFramesetBorder", ":-moz-vframeset-border"), + NonInheritingAnonBoxAtom("AnonBox_framesetBlank", ":-moz-frameset-blank"), + NonInheritingAnonBoxAtom("AnonBox_tableColGroup", ":-moz-table-column-group"), + NonInheritingAnonBoxAtom("AnonBox_tableCol", ":-moz-table-column"), + NonInheritingAnonBoxAtom("AnonBox_page", ":-moz-page"), + NonInheritingAnonBoxAtom("AnonBox_pageBreak", ":-moz-page-break"), + NonInheritingAnonBoxAtom("AnonBox_pageContent", ":-moz-page-content"), + NonInheritingAnonBoxAtom("AnonBox_printedSheet", ":-moz-printed-sheet"), + NonInheritingAnonBoxAtom("AnonBox_columnSpanWrapper", ":-moz-column-span-wrapper"), + InheritingAnonBoxAtom("AnonBox_mozText", ":-moz-text"), + InheritingAnonBoxAtom("AnonBox_firstLetterContinuation", ":-moz-first-letter-continuation"), + InheritingAnonBoxAtom("AnonBox_mozBlockInsideInlineWrapper", ":-moz-block-inside-inline-wrapper"), + InheritingAnonBoxAtom("AnonBox_mozMathMLAnonymousBlock", ":-moz-mathml-anonymous-block"), + InheritingAnonBoxAtom("AnonBox_mozLineFrame", ":-moz-line-frame"), + InheritingAnonBoxAtom("AnonBox_buttonContent", ":-moz-button-content"), + InheritingAnonBoxAtom("AnonBox_cellContent", ":-moz-cell-content"), + InheritingAnonBoxAtom("AnonBox_dropDownList", ":-moz-dropdown-list"), + InheritingAnonBoxAtom("AnonBox_fieldsetContent", ":-moz-fieldset-content"), + InheritingAnonBoxAtom("AnonBox_mozDisplayComboboxControlFrame", ":-moz-display-comboboxcontrol-frame"), + InheritingAnonBoxAtom("AnonBox_htmlCanvasContent", ":-moz-html-canvas-content"), + InheritingAnonBoxAtom("AnonBox_inlineTable", ":-moz-inline-table"), + InheritingAnonBoxAtom("AnonBox_table", ":-moz-table"), + InheritingAnonBoxAtom("AnonBox_tableCell", ":-moz-table-cell"), + InheritingAnonBoxAtom("AnonBox_tableWrapper", ":-moz-table-wrapper"), + InheritingAnonBoxAtom("AnonBox_tableRowGroup", ":-moz-table-row-group"), + InheritingAnonBoxAtom("AnonBox_tableRow", ":-moz-table-row"), + InheritingAnonBoxAtom("AnonBox_canvas", ":-moz-canvas"), + InheritingAnonBoxAtom("AnonBox_pageSequence", ":-moz-page-sequence"), + InheritingAnonBoxAtom("AnonBox_scrolledContent", ":-moz-scrolled-content"), + InheritingAnonBoxAtom("AnonBox_scrolledCanvas", ":-moz-scrolled-canvas"), + InheritingAnonBoxAtom("AnonBox_columnSet", ":-moz-column-set"), + InheritingAnonBoxAtom("AnonBox_columnContent", ":-moz-column-content"), + InheritingAnonBoxAtom("AnonBox_viewport", ":-moz-viewport"), + InheritingAnonBoxAtom("AnonBox_viewportScroll", ":-moz-viewport-scroll"), + InheritingAnonBoxAtom("AnonBox_anonymousItem", ":-moz-anonymous-item"), + InheritingAnonBoxAtom("AnonBox_blockRubyContent", ":-moz-block-ruby-content"), + InheritingAnonBoxAtom("AnonBox_ruby", ":-moz-ruby"), + InheritingAnonBoxAtom("AnonBox_rubyBase", ":-moz-ruby-base"), + InheritingAnonBoxAtom("AnonBox_rubyBaseContainer", ":-moz-ruby-base-container"), + InheritingAnonBoxAtom("AnonBox_rubyText", ":-moz-ruby-text"), + InheritingAnonBoxAtom("AnonBox_rubyTextContainer", ":-moz-ruby-text-container"), + InheritingAnonBoxAtom("AnonBox_mozTreeColumn", ":-moz-tree-column"), + InheritingAnonBoxAtom("AnonBox_mozTreeRow", ":-moz-tree-row"), + InheritingAnonBoxAtom("AnonBox_mozTreeSeparator", ":-moz-tree-separator"), + InheritingAnonBoxAtom("AnonBox_mozTreeCell", ":-moz-tree-cell"), + InheritingAnonBoxAtom("AnonBox_mozTreeIndentation", ":-moz-tree-indentation"), + InheritingAnonBoxAtom("AnonBox_mozTreeLine", ":-moz-tree-line"), + InheritingAnonBoxAtom("AnonBox_mozTreeTwisty", ":-moz-tree-twisty"), + InheritingAnonBoxAtom("AnonBox_mozTreeImage", ":-moz-tree-image"), + InheritingAnonBoxAtom("AnonBox_mozTreeCellText", ":-moz-tree-cell-text"), + InheritingAnonBoxAtom("AnonBox_mozTreeCheckbox", ":-moz-tree-checkbox"), + InheritingAnonBoxAtom("AnonBox_mozTreeDropFeedback", ":-moz-tree-drop-feedback"), + InheritingAnonBoxAtom("AnonBox_mozSVGMarkerAnonChild", ":-moz-svg-marker-anon-child"), + InheritingAnonBoxAtom("AnonBox_mozSVGOuterSVGAnonChild", ":-moz-svg-outer-svg-anon-child"), + InheritingAnonBoxAtom("AnonBox_mozSVGForeignContent", ":-moz-svg-foreign-content"), + InheritingAnonBoxAtom("AnonBox_mozSVGText", ":-moz-svg-text"), + # END ATOMS +] + HTML_PARSER_ATOMS +# fmt: on + + +def verify(): + idents = set() + strings = set() + failed = False + for atom in STATIC_ATOMS: + if atom.ident in idents: + print("StaticAtoms.py: duplicate static atom ident: %s" % atom.ident) + failed = True + if atom.string in strings: + print('StaticAtoms.py: duplicate static atom string: "%s"' % atom.string) + failed = True + idents.add(atom.ident) + strings.add(atom.string) + if failed: + sys.exit(1) + + +def generate_nsgkatomlist_h(output, *ignore): + verify() + output.write( + "/* THIS FILE IS AUTOGENERATED BY StaticAtoms.py. DO NOT EDIT */\n\n" + "#ifdef small\n" + "#undef small\n" + "#endif\n\n" + "// GK_ATOM(identifier, string, hash, is_ascii_lower, gecko_type, atom_type)\n" + + "".join( + [ + 'GK_ATOM(%s, "%s", 0x%08x, %s, %s, %s)\n' + % ( + a.ident, + a.string, + a.hash, + str(a.is_ascii_lowercase).lower(), + a.ty, + a.atom_type, + ) + for a in STATIC_ATOMS + ] + ) + ) + + +def generate_nsgkatomconsts_h(output, *ignore): + pseudo_index = None + anon_box_index = None + pseudo_count = 0 + anon_box_count = 0 + for i, atom in enumerate(STATIC_ATOMS): + if atom.atom_type == "PseudoElementAtom": + if pseudo_index is None: + pseudo_index = i + pseudo_count += 1 + elif ( + atom.atom_type == "NonInheritingAnonBoxAtom" + or atom.atom_type == "InheritingAnonBoxAtom" + ): + if anon_box_index is None: + anon_box_index = i + anon_box_count += 1 + output.write( + "/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n" + "#ifndef nsGkAtomConsts_h\n" + "#define nsGkAtomConsts_h\n\n" + "namespace mozilla {\n" + " constexpr uint32_t kAtomIndex_PseudoElements = %d;\n" + " constexpr uint32_t kAtomCount_PseudoElements = %d;\n" + " constexpr uint32_t kAtomIndex_AnonBoxes = %d;\n" + " constexpr uint32_t kAtomCount_AnonBoxes = %d;\n" + "}\n\n" + "#endif\n" % (pseudo_index, pseudo_count, anon_box_index, anon_box_count) + ) + + +if __name__ == "__main__": + generate_nsgkatomlist_h(sys.stdout) diff --git a/xpcom/ds/StickyTimeDuration.h b/xpcom/ds/StickyTimeDuration.h new file mode 100644 index 0000000000..ce4e8c1e69 --- /dev/null +++ b/xpcom/ds/StickyTimeDuration.h @@ -0,0 +1,239 @@ +/* -*- 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/. */ + +#ifndef mozilla_StickyTimeDuration_h +#define mozilla_StickyTimeDuration_h + +#include +#include "mozilla/TimeStamp.h" +#include "mozilla/FloatingPoint.h" + +namespace mozilla { + +/** + * A ValueCalculator class that performs additional checks before performing + * arithmetic operations such that if either operand is Forever (or the + * negative equivalent) the result remains Forever (or the negative equivalent + * as appropriate). + * + * Currently this only checks if either argument to each operation is + * Forever/-Forever. However, it is possible that, for example, + * aA + aB > INT64_MAX (or < INT64_MIN). + * + * We currently don't check for that case since we don't expect that to + * happen often except under test conditions in which case the wrapping + * behavior is probably acceptable. + */ +class StickyTimeDurationValueCalculator { + public: + static int64_t Add(int64_t aA, int64_t aB) { + MOZ_ASSERT((aA != INT64_MAX || aB != INT64_MIN) && + (aA != INT64_MIN || aB != INT64_MAX), + "'Infinity + -Infinity' and '-Infinity + Infinity'" + " are undefined"); + + // Forever + x = Forever + // x + Forever = Forever + if (aA == INT64_MAX || aB == INT64_MAX) { + return INT64_MAX; + } + // -Forever + x = -Forever + // x + -Forever = -Forever + if (aA == INT64_MIN || aB == INT64_MIN) { + return INT64_MIN; + } + + return aA + aB; + } + + // Note that we can't just define Add and have BaseTimeDuration call Add with + // negative arguments since INT64_MAX != -INT64_MIN so the saturating logic + // won't work. + static int64_t Subtract(int64_t aA, int64_t aB) { + MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || aA != aB, + "'Infinity - Infinity' and '-Infinity - -Infinity'" + " are undefined"); + + // Forever - x = Forever + // x - -Forever = Forever + if (aA == INT64_MAX || aB == INT64_MIN) { + return INT64_MAX; + } + // -Forever - x = -Forever + // x - Forever = -Forever + if (aA == INT64_MIN || aB == INT64_MAX) { + return INT64_MIN; + } + + return aA - aB; + } + + template + static int64_t Multiply(int64_t aA, T aB) { + // Specializations for double, float, and int64_t are provided following. + return Multiply(aA, static_cast(aB)); + } + + static int64_t Divide(int64_t aA, int64_t aB) { + MOZ_ASSERT(aB != 0, "Division by zero"); + MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || + (aB != INT64_MAX && aB != INT64_MIN), + "Dividing +/-Infinity by +/-Infinity is undefined"); + + // Forever / +x = Forever + // Forever / -x = -Forever + // -Forever / +x = -Forever + // -Forever / -x = Forever + if (aA == INT64_MAX || aA == INT64_MIN) { + return (aA >= 0) ^ (aB >= 0) ? INT64_MIN : INT64_MAX; + } + // x / Forever = 0 + // x / -Forever = 0 + if (aB == INT64_MAX || aB == INT64_MIN) { + return 0; + } + + return aA / aB; + } + + static double DivideDouble(int64_t aA, int64_t aB) { + MOZ_ASSERT(aB != 0, "Division by zero"); + MOZ_ASSERT((aA != INT64_MAX && aA != INT64_MIN) || + (aB != INT64_MAX && aB != INT64_MIN), + "Dividing +/-Infinity by +/-Infinity is undefined"); + + // Forever / +x = Forever + // Forever / -x = -Forever + // -Forever / +x = -Forever + // -Forever / -x = Forever + if (aA == INT64_MAX || aA == INT64_MIN) { + return (aA >= 0) ^ (aB >= 0) ? NegativeInfinity() + : PositiveInfinity(); + } + // x / Forever = 0 + // x / -Forever = 0 + if (aB == INT64_MAX || aB == INT64_MIN) { + return 0.0; + } + + return static_cast(aA) / aB; + } + + static int64_t Modulo(int64_t aA, int64_t aB) { + MOZ_ASSERT(aA != INT64_MAX && aA != INT64_MIN, + "Infinity modulo x is undefined"); + + return aA % aB; + } +}; + +template <> +inline int64_t StickyTimeDurationValueCalculator::Multiply( + int64_t aA, int64_t aB) { + MOZ_ASSERT((aA != 0 || (aB != INT64_MIN && aB != INT64_MAX)) && + ((aA != INT64_MIN && aA != INT64_MAX) || aB != 0), + "Multiplication of infinity by zero"); + + // Forever * +x = Forever + // Forever * -x = -Forever + // -Forever * +x = -Forever + // -Forever * -x = Forever + // + // i.e. If one or more of the arguments is +/-Forever, then + // return -Forever if the signs differ, or +Forever otherwise. + if (aA == INT64_MAX || aA == INT64_MIN || aB == INT64_MAX || + aB == INT64_MIN) { + return (aA >= 0) ^ (aB >= 0) ? INT64_MIN : INT64_MAX; + } + + return aA * aB; +} + +template <> +inline int64_t StickyTimeDurationValueCalculator::Multiply(int64_t aA, + double aB) { + MOZ_ASSERT((aA != 0 || (!std::isinf(aB))) && + ((aA != INT64_MIN && aA != INT64_MAX) || aB != 0.0), + "Multiplication of infinity by zero"); + + // As with Multiply, if one or more of the arguments is + // +/-Forever or +/-Infinity, then return -Forever if the signs differ, + // or +Forever otherwise. + if (aA == INT64_MAX || aA == INT64_MIN || std::isinf(aB)) { + return (aA >= 0) ^ (aB >= 0.0) ? INT64_MIN : INT64_MAX; + } + + return aA * aB; +} + +template <> +inline int64_t StickyTimeDurationValueCalculator::Multiply(int64_t aA, + float aB) { + MOZ_ASSERT(std::isinf(aB) == std::isinf(static_cast(aB)), + "Casting to float loses infinite-ness"); + + return Multiply(aA, static_cast(aB)); +} + +/** + * Specialization of BaseTimeDuration that uses + * StickyTimeDurationValueCalculator for arithmetic on the mValue member. + * + * Use this class when you need a time duration that is expected to hold values + * of Forever (or the negative equivalent) *and* when you expect that + * time duration to be used in arithmetic operations (and not just value + * comparisons). + */ +typedef BaseTimeDuration StickyTimeDuration; + +// Template specializations to allow arithmetic between StickyTimeDuration +// and TimeDuration objects by falling back to the safe behavior. +inline StickyTimeDuration operator+(const TimeDuration& aA, + const StickyTimeDuration& aB) { + return StickyTimeDuration(aA) + aB; +} +inline StickyTimeDuration operator+(const StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA + StickyTimeDuration(aB); +} + +inline StickyTimeDuration operator-(const TimeDuration& aA, + const StickyTimeDuration& aB) { + return StickyTimeDuration(aA) - aB; +} +inline StickyTimeDuration operator-(const StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA - StickyTimeDuration(aB); +} + +inline StickyTimeDuration& operator+=(StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA += StickyTimeDuration(aB); +} +inline StickyTimeDuration& operator-=(StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA -= StickyTimeDuration(aB); +} + +inline double operator/(const TimeDuration& aA, const StickyTimeDuration& aB) { + return StickyTimeDuration(aA) / aB; +} +inline double operator/(const StickyTimeDuration& aA, const TimeDuration& aB) { + return aA / StickyTimeDuration(aB); +} + +inline StickyTimeDuration operator%(const TimeDuration& aA, + const StickyTimeDuration& aB) { + return StickyTimeDuration(aA) % aB; +} +inline StickyTimeDuration operator%(const StickyTimeDuration& aA, + const TimeDuration& aB) { + return aA % StickyTimeDuration(aB); +} + +} // namespace mozilla + +#endif /* mozilla_StickyTimeDuration_h */ diff --git a/xpcom/ds/Tokenizer.cpp b/xpcom/ds/Tokenizer.cpp new file mode 100644 index 0000000000..3b0f6b02dd --- /dev/null +++ b/xpcom/ds/Tokenizer.cpp @@ -0,0 +1,805 @@ +/* -*- 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 "Tokenizer.h" + +#include "nsUnicharUtils.h" +#include + +namespace mozilla { + +template <> +char const TokenizerBase::sWhitespaces[] = {' ', '\t', 0}; +template <> +char16_t const TokenizerBase::sWhitespaces[3] = {' ', '\t', 0}; + +template +static bool contains(TChar const* const list, TChar const needle) { + for (TChar const* c = list; *c; ++c) { + if (needle == *c) { + return true; + } + } + return false; +} + +template +TTokenizer::TTokenizer(const typename base::TAString& aSource, + const TChar* aWhitespaces, + const TChar* aAdditionalWordChars) + : TokenizerBase(aWhitespaces, aAdditionalWordChars) { + base::mInputFinished = true; + aSource.BeginReading(base::mCursor); + mRecord = mRollback = base::mCursor; + aSource.EndReading(base::mEnd); +} + +template +TTokenizer::TTokenizer(const TChar* aSource, const TChar* aWhitespaces, + const TChar* aAdditionalWordChars) + : TTokenizer(typename base::TDependentString(aSource), aWhitespaces, + aAdditionalWordChars) {} + +template +bool TTokenizer::Next(typename base::Token& aToken) { + if (!base::HasInput()) { + base::mHasFailed = true; + return false; + } + + mRollback = base::mCursor; + base::mCursor = base::Parse(aToken); + + base::AssignFragment(aToken, mRollback, base::mCursor); + + base::mPastEof = aToken.Type() == base::TOKEN_EOF; + base::mHasFailed = false; + return true; +} + +template +bool TTokenizer::Check(const typename base::TokenType aTokenType, + typename base::Token& aResult) { + if (!base::HasInput()) { + base::mHasFailed = true; + return false; + } + + typename base::TAString::const_char_iterator next = base::Parse(aResult); + if (aTokenType != aResult.Type()) { + base::mHasFailed = true; + return false; + } + + mRollback = base::mCursor; + base::mCursor = next; + + base::AssignFragment(aResult, mRollback, base::mCursor); + + base::mPastEof = aResult.Type() == base::TOKEN_EOF; + base::mHasFailed = false; + return true; +} + +template +bool TTokenizer::Check(const typename base::Token& aToken) { +#ifdef DEBUG + base::Validate(aToken); +#endif + + if (!base::HasInput()) { + base::mHasFailed = true; + return false; + } + + typename base::Token parsed; + typename base::TAString::const_char_iterator next = base::Parse(parsed); + if (!aToken.Equals(parsed)) { + base::mHasFailed = true; + return false; + } + + mRollback = base::mCursor; + base::mCursor = next; + base::mPastEof = parsed.Type() == base::TOKEN_EOF; + base::mHasFailed = false; + return true; +} + +template +void TTokenizer::SkipWhites(WhiteSkipping aIncludeNewLines) { + if (!CheckWhite() && + (aIncludeNewLines == DONT_INCLUDE_NEW_LINE || !CheckEOL())) { + return; + } + + typename base::TAString::const_char_iterator rollback = mRollback; + while (CheckWhite() || (aIncludeNewLines == INCLUDE_NEW_LINE && CheckEOL())) { + } + + base::mHasFailed = false; + mRollback = rollback; +} + +template +void TTokenizer::SkipUntil(typename base::Token const& aToken) { + typename base::TAString::const_char_iterator rollback = base::mCursor; + const typename base::Token eof = base::Token::EndOfFile(); + + typename base::Token t; + while (Next(t)) { + if (aToken.Equals(t) || eof.Equals(t)) { + Rollback(); + break; + } + } + + mRollback = rollback; +} + +template +bool TTokenizer::CheckChar(bool (*aClassifier)(const TChar aChar)) { + if (!aClassifier) { + MOZ_ASSERT(false); + return false; + } + + if (!base::HasInput() || base::mCursor == base::mEnd) { + base::mHasFailed = true; + return false; + } + + if (!aClassifier(*base::mCursor)) { + base::mHasFailed = true; + return false; + } + + mRollback = base::mCursor; + ++base::mCursor; + base::mHasFailed = false; + return true; +} + +template +bool TTokenizer::CheckPhrase(const typename base::TAString& aPhrase) { + if (!base::HasInput()) { + return false; + } + + typedef typename base::TAString::const_char_iterator Cursor; + + TTokenizer pattern(aPhrase); + MOZ_ASSERT(!pattern.CheckEOF(), + "This will return true but won't shift the Tokenizer's cursor"); + + return [&](Cursor cursor, Cursor rollback) mutable { + while (true) { + if (pattern.CheckEOF()) { + base::mHasFailed = false; + mRollback = cursor; + return true; + } + + typename base::Token t1, t2; + Unused << Next(t1); + Unused << pattern.Next(t2); + if (t1.Type() == t2.Type() && t1.Fragment().Equals(t2.Fragment())) { + continue; + } + + break; + } + + base::mHasFailed = true; + base::mPastEof = false; + base::mCursor = cursor; + mRollback = rollback; + return false; + }(base::mCursor, mRollback); +} + +template +bool TTokenizer::ReadChar(TChar* aValue) { + MOZ_RELEASE_ASSERT(aValue); + + typename base::Token t; + if (!Check(base::TOKEN_CHAR, t)) { + return false; + } + + *aValue = t.AsChar(); + return true; +} + +template +bool TTokenizer::ReadChar(bool (*aClassifier)(const TChar aChar), + TChar* aValue) { + MOZ_RELEASE_ASSERT(aValue); + + if (!CheckChar(aClassifier)) { + return false; + } + + *aValue = *mRollback; + return true; +} + +template +bool TTokenizer::ReadWord(typename base::TAString& aValue) { + typename base::Token t; + if (!Check(base::TOKEN_WORD, t)) { + return false; + } + + aValue.Assign(t.AsString()); + return true; +} + +template +bool TTokenizer::ReadWord(typename base::TDependentSubstring& aValue) { + typename base::Token t; + if (!Check(base::TOKEN_WORD, t)) { + return false; + } + + aValue.Rebind(t.AsString().BeginReading(), t.AsString().Length()); + return true; +} + +template +bool TTokenizer::ReadUntil(typename base::Token const& aToken, + typename base::TAString& aResult, + ClaimInclusion aInclude) { + typename base::TDependentSubstring substring; + bool rv = ReadUntil(aToken, substring, aInclude); + aResult.Assign(substring); + return rv; +} + +template +bool TTokenizer::ReadUntil(typename base::Token const& aToken, + typename base::TDependentSubstring& aResult, + ClaimInclusion aInclude) { + typename base::TAString::const_char_iterator record = mRecord; + Record(); + typename base::TAString::const_char_iterator rollback = mRollback = + base::mCursor; + + bool found = false; + typename base::Token t; + while (Next(t)) { + if (aToken.Equals(t)) { + found = true; + break; + } + if (t.Equals(base::Token::EndOfFile())) { + // We don't want to eat it. + Rollback(); + break; + } + } + + Claim(aResult, aInclude); + mRollback = rollback; + mRecord = record; + return found; +} + +template +void TTokenizer::Rollback() { + MOZ_ASSERT(base::mCursor > mRollback || base::mPastEof, "TODO!!!"); + + base::mPastEof = false; + base::mHasFailed = false; + base::mCursor = mRollback; +} + +template +void TTokenizer::Record(ClaimInclusion aInclude) { + mRecord = aInclude == INCLUDE_LAST ? mRollback : base::mCursor; +} + +template +void TTokenizer::Claim(typename base::TAString& aResult, + ClaimInclusion aInclusion) { + typename base::TAString::const_char_iterator close = + aInclusion == EXCLUDE_LAST ? mRollback : base::mCursor; + aResult.Assign(Substring(mRecord, close)); +} + +template +void TTokenizer::Claim(typename base::TDependentSubstring& aResult, + ClaimInclusion aInclusion) { + typename base::TAString::const_char_iterator close = + aInclusion == EXCLUDE_LAST ? mRollback : base::mCursor; + + MOZ_RELEASE_ASSERT(close >= mRecord, "Overflow!"); + aResult.Rebind(mRecord, close - mRecord); +} + +// TokenizerBase + +template +TokenizerBase::TokenizerBase(const TChar* aWhitespaces, + const TChar* aAdditionalWordChars) + : mPastEof(false), + mHasFailed(false), + mInputFinished(true), + mMode(Mode::FULL), + mMinRawDelivery(1024), + mWhitespaces(aWhitespaces ? aWhitespaces : sWhitespaces), + mAdditionalWordChars(aAdditionalWordChars), + mCursor(nullptr), + mEnd(nullptr), + mNextCustomTokenID(TOKEN_CUSTOM0) {} + +template +auto TokenizerBase::AddCustomToken(const TAString& aValue, + ECaseSensitivity aCaseInsensitivity, + bool aEnabled) -> Token { + MOZ_ASSERT(!aValue.IsEmpty()); + + UniquePtr& t = *mCustomTokens.AppendElement(); + t = MakeUnique(); + + t->mType = static_cast(++mNextCustomTokenID); + t->mCustomCaseInsensitivity = aCaseInsensitivity; + t->mCustomEnabled = aEnabled; + t->mCustom.Assign(aValue); + return *t; +} + +template +void TokenizerBase::RemoveCustomToken(Token& aToken) { + if (aToken.mType == TOKEN_UNKNOWN) { + // Already removed + return; + } + + for (UniquePtr const& custom : mCustomTokens) { + if (custom->mType == aToken.mType) { + mCustomTokens.RemoveElement(custom); + aToken.mType = TOKEN_UNKNOWN; + return; + } + } + + MOZ_ASSERT(false, "Token to remove not found"); +} + +template +void TokenizerBase::EnableCustomToken(Token const& aToken, + bool aEnabled) { + if (aToken.mType == TOKEN_UNKNOWN) { + // Already removed + return; + } + + for (UniquePtr const& custom : mCustomTokens) { + if (custom->Type() == aToken.Type()) { + // This effectively destroys the token instance. + custom->mCustomEnabled = aEnabled; + return; + } + } + + MOZ_ASSERT(false, "Token to change not found"); +} + +template +void TokenizerBase::SetTokenizingMode(Mode aMode) { + mMode = aMode; +} + +template +bool TokenizerBase::HasFailed() const { + return mHasFailed; +} + +template +bool TokenizerBase::HasInput() const { + return !mPastEof; +} + +template +auto TokenizerBase::Parse(Token& aToken) const -> + typename TAString::const_char_iterator { + if (mCursor == mEnd) { + if (!mInputFinished) { + return mCursor; + } + + aToken = Token::EndOfFile(); + return mEnd; + } + + MOZ_RELEASE_ASSERT(mEnd >= mCursor, "Overflow!"); + typename TAString::size_type available = mEnd - mCursor; + + uint32_t longestCustom = 0; + for (UniquePtr const& custom : mCustomTokens) { + if (IsCustom(mCursor, *custom, &longestCustom)) { + aToken = *custom; + return mCursor + custom->mCustom.Length(); + } + } + + if (!mInputFinished && available < longestCustom) { + // Not enough data to deterministically decide. + return mCursor; + } + + typename TAString::const_char_iterator next = mCursor; + + if (mMode == Mode::CUSTOM_ONLY) { + // We have to do a brute-force search for all of the enabled custom + // tokens. + while (next < mEnd) { + ++next; + for (UniquePtr const& custom : mCustomTokens) { + if (IsCustom(next, *custom)) { + aToken = Token::Raw(); + return next; + } + } + } + + if (mInputFinished) { + // End of the data reached. + aToken = Token::Raw(); + return next; + } + + if (longestCustom < available && available > mMinRawDelivery) { + // We can return some data w/o waiting for either a custom token + // or call to FinishData() when we leave the tail where all the + // custom tokens potentially fit, so we can't lose only partially + // delivered tokens. This preserves reasonable granularity. + aToken = Token::Raw(); + return mEnd - longestCustom + 1; + } + + // Not enough data to deterministically decide. + return mCursor; + } + + enum State { + PARSE_INTEGER, + PARSE_WORD, + PARSE_CRLF, + PARSE_LF, + PARSE_WS, + PARSE_CHAR, + } state; + + if (IsWordFirst(*next)) { + state = PARSE_WORD; + } else if (IsNumber(*next)) { + state = PARSE_INTEGER; + } else if (contains(mWhitespaces, *next)) { // not UTF-8 friendly? + state = PARSE_WS; + } else if (*next == '\r') { + state = PARSE_CRLF; + } else if (*next == '\n') { + state = PARSE_LF; + } else { + state = PARSE_CHAR; + } + + mozilla::CheckedUint64 resultingNumber = 0; + + while (next < mEnd) { + switch (state) { + case PARSE_INTEGER: + // Keep it simple for now + resultingNumber *= 10; + resultingNumber += static_cast(*next - '0'); + + ++next; + if (IsPending(next)) { + break; + } + if (IsEnd(next) || !IsNumber(*next)) { + if (!resultingNumber.isValid()) { + aToken = Token::Error(); + } else { + aToken = Token::Number(resultingNumber.value()); + } + return next; + } + break; + + case PARSE_WORD: + ++next; + if (IsPending(next)) { + break; + } + if (IsEnd(next) || !IsWord(*next)) { + aToken = Token::Word(Substring(mCursor, next)); + return next; + } + break; + + case PARSE_CRLF: + ++next; + if (IsPending(next)) { + break; + } + if (!IsEnd(next) && *next == '\n') { // LF is optional + ++next; + } + aToken = Token::NewLine(); + return next; + + case PARSE_LF: + ++next; + aToken = Token::NewLine(); + return next; + + case PARSE_WS: + ++next; + aToken = Token::Whitespace(); + return next; + + case PARSE_CHAR: + ++next; + aToken = Token::Char(*mCursor); + return next; + } // switch (state) + } // while (next < end) + + MOZ_ASSERT(!mInputFinished); + return mCursor; +} + +template +bool TokenizerBase::IsEnd( + const typename TAString::const_char_iterator& caret) const { + return caret == mEnd; +} + +template +bool TokenizerBase::IsPending( + const typename TAString::const_char_iterator& caret) const { + return IsEnd(caret) && !mInputFinished; +} + +template +bool TokenizerBase::IsWordFirst(const TChar aInput) const { + // TODO: make this fully work with unicode + return (ToLowerCase(static_cast(aInput)) != + ToUpperCase(static_cast(aInput))) || + '_' == aInput || + (mAdditionalWordChars ? contains(mAdditionalWordChars, aInput) + : false); +} + +template +bool TokenizerBase::IsWord(const TChar aInput) const { + return IsWordFirst(aInput) || IsNumber(aInput); +} + +template +bool TokenizerBase::IsNumber(const TChar aInput) const { + // TODO: are there unicode numbers? + return aInput >= '0' && aInput <= '9'; +} + +template +bool TokenizerBase::IsCustom( + const typename TAString::const_char_iterator& caret, + const Token& aCustomToken, uint32_t* aLongest) const { + MOZ_ASSERT(aCustomToken.mType > TOKEN_CUSTOM0); + if (!aCustomToken.mCustomEnabled) { + return false; + } + + if (aLongest) { + *aLongest = std::max(*aLongest, aCustomToken.mCustom.Length()); + } + + // This is not very likely to happen according to how we call this method + // and since it's on a hot path, it's just a diagnostic assert, + // not a release assert. + MOZ_DIAGNOSTIC_ASSERT(mEnd >= caret, "Overflow?"); + uint32_t inputLength = mEnd - caret; + if (aCustomToken.mCustom.Length() > inputLength) { + return false; + } + + TDependentSubstring inputFragment(caret, aCustomToken.mCustom.Length()); + if (aCustomToken.mCustomCaseInsensitivity == CASE_INSENSITIVE) { + if constexpr (std::is_same_v) { + return inputFragment.Equals(aCustomToken.mCustom, + nsCaseInsensitiveUTF8StringComparator); + } else { + return inputFragment.Equals(aCustomToken.mCustom, + nsCaseInsensitiveStringComparator); + } + } + return inputFragment.Equals(aCustomToken.mCustom); +} + +template +void TokenizerBase::AssignFragment( + Token& aToken, typename TAString::const_char_iterator begin, + typename TAString::const_char_iterator end) { + aToken.AssignFragment(begin, end); +} + +#ifdef DEBUG + +template +void TokenizerBase::Validate(Token const& aToken) { + if (aToken.Type() == TOKEN_WORD) { + typename TAString::const_char_iterator c = aToken.AsString().BeginReading(); + typename TAString::const_char_iterator e = aToken.AsString().EndReading(); + + if (c < e) { + MOZ_ASSERT(IsWordFirst(*c)); + while (++c < e) { + MOZ_ASSERT(IsWord(*c)); + } + } + } +} + +#endif + +// TokenizerBase::Token + +template +TokenizerBase::Token::Token() + : mType(TOKEN_UNKNOWN), + mChar(0), + mInteger(0), + mCustomCaseInsensitivity(CASE_SENSITIVE), + mCustomEnabled(false) {} + +template +TokenizerBase::Token::Token(const Token& aOther) + : mType(aOther.mType), + mCustom(aOther.mCustom), + mChar(aOther.mChar), + mInteger(aOther.mInteger), + mCustomCaseInsensitivity(aOther.mCustomCaseInsensitivity), + mCustomEnabled(aOther.mCustomEnabled) { + if (mType == TOKEN_WORD || mType > TOKEN_CUSTOM0) { + mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length()); + } +} + +template +auto TokenizerBase::Token::operator=(const Token& aOther) -> Token& { + mType = aOther.mType; + mCustom = aOther.mCustom; + mChar = aOther.mChar; + mWord.Rebind(aOther.mWord.BeginReading(), aOther.mWord.Length()); + mInteger = aOther.mInteger; + mCustomCaseInsensitivity = aOther.mCustomCaseInsensitivity; + mCustomEnabled = aOther.mCustomEnabled; + return *this; +} + +template +void TokenizerBase::Token::AssignFragment( + typename TAString::const_char_iterator begin, + typename TAString::const_char_iterator end) { + MOZ_RELEASE_ASSERT(end >= begin, "Overflow!"); + mFragment.Rebind(begin, end - begin); +} + +// static +template +auto TokenizerBase::Token::Raw() -> Token { + Token t; + t.mType = TOKEN_RAW; + return t; +} + +// static +template +auto TokenizerBase::Token::Word(TAString const& aValue) -> Token { + Token t; + t.mType = TOKEN_WORD; + t.mWord.Rebind(aValue.BeginReading(), aValue.Length()); + return t; +} + +// static +template +auto TokenizerBase::Token::Char(TChar const aValue) -> Token { + Token t; + t.mType = TOKEN_CHAR; + t.mChar = aValue; + return t; +} + +// static +template +auto TokenizerBase::Token::Number(uint64_t const aValue) -> Token { + Token t; + t.mType = TOKEN_INTEGER; + t.mInteger = aValue; + return t; +} + +// static +template +auto TokenizerBase::Token::Whitespace() -> Token { + Token t; + t.mType = TOKEN_WS; + t.mChar = '\0'; + return t; +} + +// static +template +auto TokenizerBase::Token::NewLine() -> Token { + Token t; + t.mType = TOKEN_EOL; + return t; +} + +// static +template +auto TokenizerBase::Token::EndOfFile() -> Token { + Token t; + t.mType = TOKEN_EOF; + return t; +} + +// static +template +auto TokenizerBase::Token::Error() -> Token { + Token t; + t.mType = TOKEN_ERROR; + return t; +} + +template +bool TokenizerBase::Token::Equals(const Token& aOther) const { + if (mType != aOther.mType) { + return false; + } + + switch (mType) { + case TOKEN_INTEGER: + return AsInteger() == aOther.AsInteger(); + case TOKEN_WORD: + return AsString() == aOther.AsString(); + case TOKEN_CHAR: + return AsChar() == aOther.AsChar(); + default: + return true; + } +} + +template +TChar TokenizerBase::Token::AsChar() const { + MOZ_ASSERT(mType == TOKEN_CHAR || mType == TOKEN_WS); + return mChar; +} + +template +auto TokenizerBase::Token::AsString() const -> TDependentSubstring { + MOZ_ASSERT(mType == TOKEN_WORD); + return mWord; +} + +template +uint64_t TokenizerBase::Token::AsInteger() const { + MOZ_ASSERT(mType == TOKEN_INTEGER); + return mInteger; +} + +template class TokenizerBase; +template class TokenizerBase; + +template class TTokenizer; +template class TTokenizer; + +} // namespace mozilla diff --git a/xpcom/ds/Tokenizer.h b/xpcom/ds/Tokenizer.h new file mode 100644 index 0000000000..713b63f269 --- /dev/null +++ b/xpcom/ds/Tokenizer.h @@ -0,0 +1,524 @@ +/* -*- 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/. */ + +#ifndef Tokenizer_h__ +#define Tokenizer_h__ + +#include + +#include "nsString.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/UniquePtr.h" +#include "nsTArray.h" + +namespace mozilla { + +template +class TokenizerBase { + public: + typedef nsTSubstring TAString; + typedef nsTString TString; + typedef nsTDependentString TDependentString; + typedef nsTDependentSubstring TDependentSubstring; + + static TChar const sWhitespaces[]; + + /** + * The analyzer works with elements in the input cut to a sequence of token + * where each token has an elementary type + */ + enum TokenType : uint32_t { + TOKEN_UNKNOWN, + TOKEN_RAW, + TOKEN_ERROR, + TOKEN_INTEGER, + TOKEN_WORD, + TOKEN_CHAR, + TOKEN_WS, + TOKEN_EOL, + TOKEN_EOF, + TOKEN_CUSTOM0 = 1000 + }; + + enum ECaseSensitivity { CASE_SENSITIVE, CASE_INSENSITIVE }; + + /** + * Class holding the type and the value of a token. It can be manually + * created to allow checks against it via methods of TTokenizer or are results + * of some of the TTokenizer's methods. + */ + class Token { + TokenType mType; + TDependentSubstring mWord; + TString mCustom; + TChar mChar; + uint64_t mInteger; + ECaseSensitivity mCustomCaseInsensitivity; + bool mCustomEnabled; + + // If this token is a result of the parsing process, this member is + // referencing a sub-string in the input buffer. If this is externally + // created Token this member is left an empty string. + TDependentSubstring mFragment; + + friend class TokenizerBase; + void AssignFragment(typename TAString::const_char_iterator begin, + typename TAString::const_char_iterator end); + + static Token Raw(); + + public: + Token(); + Token(const Token& aOther); + Token& operator=(const Token& aOther); + + // Static constructors of tokens by type and value + static Token Word(TAString const& aWord); + static Token Char(TChar const aChar); + static Token Number(uint64_t const aNumber); + static Token Whitespace(); + static Token NewLine(); + static Token EndOfFile(); + static Token Error(); + + // Compares the two tokens, type must be identical and value + // of one of the tokens must be 'any' or equal. + bool Equals(const Token& aOther) const; + + TokenType Type() const { return mType; } + TChar AsChar() const; + TDependentSubstring AsString() const; + uint64_t AsInteger() const; + + TDependentSubstring Fragment() const { return mFragment; } + }; + + /** + * Consumers may register a custom string that, when found in the input, is + * considered a token and returned by Next*() and accepted by Check*() + * methods. AddCustomToken() returns a reference to a token that can then be + * comapred using Token::Equals() againts the output from Next*() or be passed + * to Check*(). + */ + Token AddCustomToken(const TAString& aValue, + ECaseSensitivity aCaseInsensitivity, + bool aEnabled = true); + template + Token AddCustomToken(const TChar (&aValue)[N], + ECaseSensitivity aCaseInsensitivity, + bool aEnabled = true) { + return AddCustomToken(TDependentSubstring(aValue, N - 1), + aCaseInsensitivity, aEnabled); + } + void RemoveCustomToken(Token& aToken); + /** + * Only applies to a custom type of a Token (see AddCustomToken above.) + * This turns on and off token recognition. When a custom token is disabled, + * it's ignored as never added as a custom token. + */ + void EnableCustomToken(Token const& aToken, bool aEnable); + + /** + * Mode of tokenization. + * FULL tokenization, the default, recognizes built-in tokens and any custom + * tokens, if added. CUSTOM_ONLY will only recognize custom tokens, the rest + * is seen as 'raw'. This mode can be understood as a 'binary' mode. + */ + enum class Mode { FULL, CUSTOM_ONLY }; + void SetTokenizingMode(Mode aMode); + + /** + * Return false iff the last Check*() call has returned false or when we've + * read past the end of the input string. + */ + [[nodiscard]] bool HasFailed() const; + + protected: + explicit TokenizerBase(const TChar* aWhitespaces = nullptr, + const TChar* aAdditionalWordChars = nullptr); + + // false if we have already read the EOF token. + bool HasInput() const; + // Main parsing function, it doesn't shift the read cursor, just returns the + // next token position. + typename TAString::const_char_iterator Parse(Token& aToken) const; + // Is read cursor at the end? + bool IsEnd(const typename TAString::const_char_iterator& caret) const; + // True, when we are at the end of the input data, but it has not been marked + // as complete yet. In that case we cannot proceed with providing a + // multi-TChar token. + bool IsPending(const typename TAString::const_char_iterator& caret) const; + // Is read cursor on a character that is a word start? + bool IsWordFirst(const TChar aInput) const; + // Is read cursor on a character that is an in-word letter? + bool IsWord(const TChar aInput) const; + // Is read cursor on a character that is a valid number? + // TODO - support multiple radix + bool IsNumber(const TChar aInput) const; + // Is equal to the given custom token? + bool IsCustom(const typename TAString::const_char_iterator& caret, + const Token& aCustomToken, uint32_t* aLongest = nullptr) const; + + // Friendly helper to assign a fragment on a Token + static void AssignFragment(Token& aToken, + typename TAString::const_char_iterator begin, + typename TAString::const_char_iterator end); + +#ifdef DEBUG + // This is called from inside Tokenizer methods to make sure the token is + // valid. + void Validate(Token const& aToken); +#endif + + // true iff we have already read the EOF token + bool mPastEof; + // true iff the last Check*() call has returned false, reverts to true on + // Rollback() call + bool mHasFailed; + // true if the input string is final (finished), false when we expect more + // data yet to be fed to the tokenizer (see IncrementalTokenizer derived + // class). + bool mInputFinished; + // custom only vs full tokenizing mode, see the Parse() method + Mode mMode; + // minimal raw data chunked delivery during incremental feed + uint32_t mMinRawDelivery; + + // Customizable list of whitespaces + const TChar* mWhitespaces; + // Additinal custom word characters + const TChar* mAdditionalWordChars; + + // All these point to the original buffer passed to the constructor or to the + // incremental buffer after FeedInput. + typename TAString::const_char_iterator + mCursor; // Position of the current (actually next to read) token start + typename TAString::const_char_iterator mEnd; // End of the input position + + // This is the list of tokens user has registered with AddCustomToken() + nsTArray> mCustomTokens; + uint32_t mNextCustomTokenID; + + private: + TokenizerBase() = delete; + TokenizerBase(const TokenizerBase&) = delete; + TokenizerBase(TokenizerBase&&) = delete; + TokenizerBase(const TokenizerBase&&) = delete; + TokenizerBase& operator=(const TokenizerBase&) = delete; +}; + +/** + * This is a simple implementation of a lexical analyzer or maybe better + * called a tokenizer. + * + * Please use Tokenizer or Tokenizer16 classes, that are specializations + * of this template class. Tokenizer is for ASCII input, Tokenizer16 may + * handle char16_t input, but doesn't recognize whitespaces or numbers + * other than standard `char` specialized Tokenizer class. + */ +template +class TTokenizer : public TokenizerBase { + public: + typedef TokenizerBase base; + + /** + * @param aSource + * The string to parse. + * IMPORTANT NOTE: TTokenizer doesn't ensure the input string buffer + * lifetime. It's up to the consumer to make sure the string's buffer outlives + * the TTokenizer! + * @param aWhitespaces + * If non-null TTokenizer will use this custom set of whitespaces for + * CheckWhite() and SkipWhites() calls. By default the list consists of space + * and tab. + * @param aAdditionalWordChars + * If non-null it will be added to the list of characters that consist a + * word. This is useful when you want to accept e.g. '-' in HTTP headers. By + * default a word character is consider any character for which upper case + * is different from lower case. + * + * If there is an overlap between aWhitespaces and aAdditionalWordChars, the + * check for word characters is made first. + */ + explicit TTokenizer(const typename base::TAString& aSource, + const TChar* aWhitespaces = nullptr, + const TChar* aAdditionalWordChars = nullptr); + explicit TTokenizer(const TChar* aSource, const TChar* aWhitespaces = nullptr, + const TChar* aAdditionalWordChars = nullptr); + + /** + * When there is still anything to read from the input, tokenize it, store the + * token type and value to aToken result and shift the cursor past this just + * parsed token. Each call to Next() reads another token from the input and + * shifts the cursor. Returns false if we have passed the end of the input. + */ + [[nodiscard]] bool Next(typename base::Token& aToken); + + /** + * Parse the token on the input read cursor position, check its type is equal + * to aTokenType and if so, put it into aResult, shift the cursor and return + * true. Otherwise, leave the input read cursor position intact and return + * false. + */ + [[nodiscard]] bool Check(const typename base::TokenType aTokenType, + typename base::Token& aResult); + /** + * Same as above method, just compares both token type and token value passed + * in aToken. When both the type and the value equals, shift the cursor and + * return true. Otherwise return false. + */ + [[nodiscard]] bool Check(const typename base::Token& aToken); + + /** + * SkipWhites method (below) may also skip new line characters automatically. + */ + enum WhiteSkipping { + /** + * SkipWhites will only skip what is defined as a white space (default). + */ + DONT_INCLUDE_NEW_LINE = 0, + /** + * SkipWhites will skip definited white spaces as well as new lines + * automatically. + */ + INCLUDE_NEW_LINE = 1 + }; + + /** + * Skips any occurence of whitespaces specified in mWhitespaces member, + * optionally skip also new lines. + */ + void SkipWhites(WhiteSkipping aIncludeNewLines = DONT_INCLUDE_NEW_LINE); + + /** + * Skips all tokens until the given one is found or EOF is hit. The token + * or EOF are next to read. + */ + void SkipUntil(typename base::Token const& aToken); + + // These are mostly shortcuts for the Check() methods above. + + /** + * Check whitespace character is present. + */ + [[nodiscard]] bool CheckWhite() { return Check(base::Token::Whitespace()); } + /** + * Check there is a single character on the read cursor position. If so, + * shift the read cursor position and return true. Otherwise false. + */ + [[nodiscard]] bool CheckChar(const TChar aChar) { + return Check(base::Token::Char(aChar)); + } + /** + * This is a customizable version of CheckChar. aClassifier is a function + * called with value of the character on the current input read position. If + * this user function returns true, read cursor is shifted and true returned. + * Otherwise false. The user classifiction function is not called when we are + * at or past the end and false is immediately returned. + */ + [[nodiscard]] bool CheckChar(bool (*aClassifier)(const TChar aChar)); + /** + * Check for a whole expected word. + */ + [[nodiscard]] bool CheckWord(const typename base::TAString& aWord) { + return Check(base::Token::Word(aWord)); + } + /** + * Shortcut for literal const word check with compile time length calculation. + */ + template + [[nodiscard]] bool CheckWord(const TChar (&aWord)[N]) { + return Check( + base::Token::Word(typename base::TDependentString(aWord, N - 1))); + } + /** + * Helper to check for a string compound of multiple tokens like "foo bar". + * The match is binary-exact, a white space or a delimiter character in the + * phrase must match exactly the characters in the input. + */ + [[nodiscard]] bool CheckPhrase(const typename base::TAString& aPhrase); + template + [[nodiscard]] bool CheckPhrase(const TChar (&aPhrase)[N]) { + return CheckPhrase(typename base::TDependentString(aPhrase, N - 1)); + } + /** + * Checks \r, \n or \r\n. + */ + [[nodiscard]] bool CheckEOL() { return Check(base::Token::NewLine()); } + /** + * Checks we are at the end of the input string reading. If so, shift past + * the end and returns true. Otherwise does nothing and returns false. + */ + [[nodiscard]] bool CheckEOF() { return Check(base::Token::EndOfFile()); } + + /** + * These are shortcuts to obtain the value immediately when the token type + * matches. + */ + [[nodiscard]] bool ReadChar(TChar* aValue); + [[nodiscard]] bool ReadChar(bool (*aClassifier)(const TChar aChar), + TChar* aValue); + [[nodiscard]] bool ReadWord(typename base::TAString& aValue); + [[nodiscard]] bool ReadWord(typename base::TDependentSubstring& aValue); + + /** + * This is an integer read helper. It returns false and doesn't move the read + * cursor when any of the following happens: + * - the token at the read cursor is not an integer + * - the final number doesn't fit the T type + * Otherwise true is returned, aValue is filled with the integral number + * and the cursor is moved forward. + */ + template + [[nodiscard]] bool ReadInteger(T* aValue) { + MOZ_RELEASE_ASSERT(aValue); + + typename base::TAString::const_char_iterator rollback = mRollback; + typename base::TAString::const_char_iterator cursor = base::mCursor; + typename base::Token t; + if (!Check(base::TOKEN_INTEGER, t)) { + return false; + } + + mozilla::CheckedInt checked(t.AsInteger()); + if (!checked.isValid()) { + // Move to a state as if Check() call has failed + mRollback = rollback; + base::mCursor = cursor; + base::mHasFailed = true; + return false; + } + + *aValue = checked.value(); + return true; + } + + /** + * Same as above, but accepts an integer with an optional minus sign. + */ + template >, + std::remove_pointer_t>> + [[nodiscard]] bool ReadSignedInteger(T* aValue) { + MOZ_RELEASE_ASSERT(aValue); + + typename base::TAString::const_char_iterator rollback = mRollback; + typename base::TAString::const_char_iterator cursor = base::mCursor; + auto revert = MakeScopeExit([&] { + // Move to a state as if Check() call has failed + mRollback = rollback; + base::mCursor = cursor; + base::mHasFailed = true; + }); + + // Using functional raw access because '-' could be part of the word set + // making CheckChar('-') not work. + bool minus = CheckChar([](const TChar aChar) { return aChar == '-'; }); + + typename base::Token t; + if (!Check(base::TOKEN_INTEGER, t)) { + return false; + } + + mozilla::CheckedInt checked(t.AsInteger()); + if (minus) { + checked *= -1; + } + + if (!checked.isValid()) { + return false; + } + + *aValue = checked.value(); + revert.release(); + return true; + } + + /** + * Returns the read cursor position back as it was before the last call of any + * parsing method of TTokenizer (Next, Check*, Skip*, Read*) so that the last + * operation can be repeated. Rollback cannot be used multiple times, it only + * reverts the last successfull parse operation. It also cannot be used + * before any parsing operation has been called on the TTokenizer. + */ + void Rollback(); + + /** + * Record() and Claim() are collecting the input as it is being parsed to + * obtain a substring between particular syntax bounderies defined by any + * recursive descent parser or simple parser the TTokenizer is used to read + * the input for. Inlucsion of a token that has just been parsed can be + * controlled using an arguemnt. + */ + enum ClaimInclusion { + /** + * Include resulting (or passed) token of the last lexical analyzer + * operation in the result. + */ + INCLUDE_LAST, + /** + * Do not include it. + */ + EXCLUDE_LAST + }; + + /** + * Start the process of recording. Based on aInclude value the begining of + * the recorded sub-string is at the current position (EXCLUDE_LAST) or at the + * position before the last parsed token (INCLUDE_LAST). + */ + void Record(ClaimInclusion aInclude = EXCLUDE_LAST); + /** + * Claim result of the record started with Record() call before. Depending on + * aInclude the ending of the sub-string result includes or excludes the last + * parsed or checked token. + */ + void Claim(typename base::TAString& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + void Claim(typename base::TDependentSubstring& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + + /** + * If aToken is found, aResult is set to the substring between the current + * position and the position of aToken, potentially including aToken depending + * on aInclude. + * If aToken isn't found aResult is set to the substring between the current + * position and the end of the string. + * If aToken is found, the method returns true. Otherwise it returns false. + * + * Calling Rollback() after ReadUntil() will return the read cursor to the + * position it had before ReadUntil was called. + */ + [[nodiscard]] bool ReadUntil(typename base::Token const& aToken, + typename base::TDependentSubstring& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + [[nodiscard]] bool ReadUntil(typename base::Token const& aToken, + typename base::TAString& aResult, + ClaimInclusion aInclude = EXCLUDE_LAST); + + protected: + // All these point to the original buffer passed to the TTokenizer's + // constructor + typename base::TAString::const_char_iterator + mRecord; // Position where the recorded sub-string for Claim() is + typename base::TAString::const_char_iterator + mRollback; // Position of the previous token start + + private: + TTokenizer() = delete; + TTokenizer(const TTokenizer&) = delete; + TTokenizer(TTokenizer&&) = delete; + TTokenizer(const TTokenizer&&) = delete; + TTokenizer& operator=(const TTokenizer&) = delete; +}; + +typedef TTokenizer Tokenizer; +typedef TTokenizer Tokenizer16; + +} // namespace mozilla + +#endif // Tokenizer_h__ diff --git a/xpcom/ds/components.conf b/xpcom/ds/components.conf new file mode 100644 index 0000000000..1b600352b3 --- /dev/null +++ b/xpcom/ds/components.conf @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{35c66fd1-95e9-4e0a-80c5-c3bd2b375481}', + 'contract_ids': ['@mozilla.org/array;1'], + 'legacy_constructor': 'nsArrayBase::XPCOMConstructor', + 'headers': ['nsArray.h'], + }, + { + 'name': 'Observer', + 'js_name': 'obs', + 'cid': '{d07f5195-e3d1-11d2-8acd-00105a1b8860}', + 'contract_ids': ['@mozilla.org/observer-service;1'], + 'interfaces': ['nsIObserverService'], + 'legacy_constructor': 'nsObserverService::Create', + 'headers': ['/xpcom/ds/nsObserverService.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_AND_UTILITY_PROCESS, + }, +] diff --git a/xpcom/ds/moz.build b/xpcom/ds/moz.build new file mode 100644 index 0000000000..e861f7bd8d --- /dev/null +++ b/xpcom/ds/moz.build @@ -0,0 +1,156 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + "nsIArray.idl", + "nsIArrayExtensions.idl", + "nsIINIParser.idl", + "nsIMutableArray.idl", + "nsIObserver.idl", + "nsIObserverService.idl", + "nsIPersistentProperties2.idl", + "nsIProperties.idl", + "nsIProperty.idl", + "nsIPropertyBag.idl", + "nsIPropertyBag2.idl", + "nsISerializable.idl", + "nsISimpleEnumerator.idl", + "nsIStringEnumerator.idl", + "nsISupportsIterators.idl", + "nsISupportsPrimitives.idl", + "nsIVariant.idl", + "nsIWritablePropertyBag.idl", + "nsIWritablePropertyBag2.idl", +] + +if CONFIG["OS_ARCH"] == "WINNT": + XPIDL_SOURCES += [ + "nsIWindowsRegKey.idl", + ] + EXPORTS += ["nsWindowsRegKey.h"] + SOURCES += ["nsWindowsRegKey.cpp"] + +XPIDL_MODULE = "xpcom_ds" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "!nsGkAtomConsts.h", + "!nsGkAtomList.h", + "nsArray.h", + "nsArrayEnumerator.h", + "nsArrayUtils.h", + "nsAtom.h", + "nsBaseHashtable.h", + "nsCharSeparatedTokenizer.h", + "nsCheapSets.h", + "nsClassHashtable.h", + "nsCOMArray.h", + "nsCRT.h", + "nsDeque.h", + "nsEnumeratorUtils.h", + "nsExpirationTracker.h", + "nsGkAtoms.h", + "nsHashKeys.h", + "nsHashPropertyBag.h", + "nsHashtablesFwd.h", + "nsInterfaceHashtable.h", + "nsMathUtils.h", + "nsPersistentProperties.h", + "nsPointerHashKeys.h", + "nsProperties.h", + "nsQuickSort.h", + "nsRefCountedHashtable.h", + "nsRefPtrHashtable.h", + "nsSimpleEnumerator.h", + "nsStaticAtomUtils.h", + "nsStaticNameTable.h", + "nsStringEnumerator.h", + "nsSupportsPrimitives.h", + "nsTArray-inl.h", + "nsTArray.h", + "nsTArrayForwardDeclare.h", + "nsTHashMap.h", + "nsTHashSet.h", + "nsTHashtable.h", + "nsTObserverArray.h", + "nsTPriorityQueue.h", + "nsVariant.h", + "nsWhitespaceTokenizer.h", + "PLDHashTable.h", +] + +EXPORTS.mozilla += [ + "ArenaAllocator.h", + "ArenaAllocatorExtensions.h", + "ArrayAlgorithm.h", + "ArrayIterator.h", + "AtomArray.h", + "Dafsa.h", + "IncrementalTokenizer.h", + "Observer.h", + "PerfectHash.h", + "SimpleEnumerator.h", + "StickyTimeDuration.h", + "Tokenizer.h", +] + +UNIFIED_SOURCES += [ + "Dafsa.cpp", + "IncrementalTokenizer.cpp", + "nsArray.cpp", + "nsArrayEnumerator.cpp", + "nsArrayUtils.cpp", + "nsAtomTable.cpp", + "nsCharSeparatedTokenizer.cpp", + "nsCOMArray.cpp", + "nsCRT.cpp", + "nsDeque.cpp", + "nsEnumeratorUtils.cpp", + "nsGkAtoms.cpp", + "nsHashPropertyBag.cpp", + "nsINIParserImpl.cpp", + "nsObserverList.cpp", + "nsObserverService.cpp", + "nsPersistentProperties.cpp", + "nsProperties.cpp", + "nsQuickSort.cpp", + "nsSimpleEnumerator.cpp", + "nsStaticNameTable.cpp", + "nsStringEnumerator.cpp", + "nsSupportsPrimitives.cpp", + "nsTArray.cpp", + "nsTObserverArray.cpp", + "nsVariant.cpp", + "PLDHashTable.cpp", + "Tokenizer.cpp", +] + +LOCAL_INCLUDES += [ + "../io", +] + +GeneratedFile( + "nsGkAtomList.h", + script="StaticAtoms.py", + entry_point="generate_nsgkatomlist_h", + inputs=["Atom.py", "HTMLAtoms.py"], +) + +GeneratedFile( + "nsGkAtomConsts.h", + script="StaticAtoms.py", + entry_point="generate_nsgkatomconsts_h", + inputs=["Atom.py", "HTMLAtoms.py"], +) + +FINAL_LIBRARY = "xul" + +PYTHON_UNITTEST_MANIFESTS += [ + "test/python.ini", +] diff --git a/xpcom/ds/nsArray.cpp b/xpcom/ds/nsArray.cpp new file mode 100644 index 0000000000..60758133d9 --- /dev/null +++ b/xpcom/ds/nsArray.cpp @@ -0,0 +1,146 @@ +/* -*- 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 "nsArray.h" +#include "nsArrayEnumerator.h" +#include "nsThreadUtils.h" + +NS_INTERFACE_MAP_BEGIN(nsArray) + NS_INTERFACE_MAP_ENTRY(nsIArray) + NS_INTERFACE_MAP_ENTRY(nsIArrayExtensions) + NS_INTERFACE_MAP_ENTRY(nsIMutableArray) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMutableArray) +NS_INTERFACE_MAP_END + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsArrayCC) + NS_INTERFACE_MAP_ENTRY(nsIArray) + NS_INTERFACE_MAP_ENTRY(nsIArrayExtensions) + NS_INTERFACE_MAP_ENTRY(nsIMutableArray) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMutableArray) +NS_INTERFACE_MAP_END + +nsArrayBase::~nsArrayBase() { Clear(); } + +NS_IMPL_ADDREF(nsArray) +NS_IMPL_RELEASE(nsArray) + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsArrayCC) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsArrayCC) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsArrayCC) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsArrayCC) + tmp->Clear(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsArrayCC) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArray) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMETHODIMP +nsArrayBase::GetLength(uint32_t* aLength) { + *aLength = mArray.Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::QueryElementAt(uint32_t aIndex, const nsIID& aIID, + void** aResult) { + nsISupports* obj = mArray.SafeObjectAt(aIndex); + if (!obj) { + return NS_ERROR_ILLEGAL_VALUE; + } + + // no need to worry about a leak here, because SafeObjectAt() + // doesn't addref its result + return obj->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsArrayBase::IndexOf(uint32_t aStartIndex, nsISupports* aElement, + uint32_t* aResult) { + int32_t idx = mArray.IndexOf(aElement, aStartIndex); + if (idx == -1) { + return NS_ERROR_FAILURE; + } + + *aResult = static_cast(idx); + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::ScriptedEnumerate(const nsIID& aElemIID, uint8_t aArgc, + nsISimpleEnumerator** aResult) { + if (aArgc > 0) { + return NS_NewArrayEnumerator(aResult, static_cast(this), + aElemIID); + } + return NS_NewArrayEnumerator(aResult, static_cast(this)); +} + +NS_IMETHODIMP +nsArrayBase::EnumerateImpl(const nsID& aElemIID, + nsISimpleEnumerator** aResult) { + return NS_NewArrayEnumerator(aResult, static_cast(this), aElemIID); +} + +// nsIMutableArray implementation + +NS_IMETHODIMP +nsArrayBase::AppendElement(nsISupports* aElement) { + bool result = mArray.AppendObject(aElement); + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::RemoveElementAt(uint32_t aIndex) { + bool result = mArray.RemoveObjectAt(aIndex); + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::InsertElementAt(nsISupports* aElement, uint32_t aIndex) { + bool result = mArray.InsertObjectAt(aElement, aIndex); + return result ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsArrayBase::ReplaceElementAt(nsISupports* aElement, uint32_t aIndex) { + mArray.ReplaceObjectAt(aElement, aIndex); + return NS_OK; +} + +NS_IMETHODIMP +nsArrayBase::Clear() { + mArray.Clear(); + return NS_OK; +} + +// nsIArrayExtensions implementation. + +NS_IMETHODIMP +nsArrayBase::Count(uint32_t* aResult) { return GetLength(aResult); } + +NS_IMETHODIMP +nsArrayBase::GetElementAt(uint32_t aIndex, nsISupports** aResult) { + nsCOMPtr obj = mArray.SafeObjectAt(aIndex); + obj.forget(aResult); + return NS_OK; +} + +nsresult nsArrayBase::XPCOMConstructor(const nsIID& aIID, void** aResult) { + nsCOMPtr inst = Create(); + return inst->QueryInterface(aIID, aResult); +} + +already_AddRefed nsArrayBase::Create() { + nsCOMPtr inst; + if (NS_IsMainThread()) { + inst = new nsArrayCC; + } else { + inst = new nsArray; + } + return inst.forget(); +} diff --git a/xpcom/ds/nsArray.h b/xpcom/ds/nsArray.h new file mode 100644 index 0000000000..49d701d466 --- /dev/null +++ b/xpcom/ds/nsArray.h @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +#ifndef nsArray_h__ +#define nsArray_h__ + +#include "nsIMutableArray.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsCycleCollectionParticipant.h" +#include "mozilla/Attributes.h" + +// {35C66FD1-95E9-4e0a-80C5-C3BD2B375481} +#define NS_ARRAY_CID \ + { \ + 0x35c66fd1, 0x95e9, 0x4e0a, { \ + 0x80, 0xc5, 0xc3, 0xbd, 0x2b, 0x37, 0x54, 0x81 \ + } \ + } + +// nsArray without any refcounting declarations +class nsArrayBase : public nsIMutableArray { + public: + NS_DECL_NSIARRAY + NS_DECL_NSIARRAYEXTENSIONS + NS_DECL_NSIMUTABLEARRAY + + /* Both of these factory functions create a cycle-collectable array + on the main thread and a non-cycle-collectable array on other + threads. */ + static already_AddRefed Create(); + /* Only for the benefit of the XPCOM module system, use Create() + instead. */ + static nsresult XPCOMConstructor(const nsIID& aIID, void** aResult); + + protected: + nsArrayBase() = default; + nsArrayBase(const nsArrayBase& aOther); + explicit nsArrayBase(const nsCOMArray_base& aBaseArray) + : mArray(aBaseArray) {} + virtual ~nsArrayBase(); + + nsCOMArray_base mArray; +}; + +class nsArray final : public nsArrayBase { + friend class nsArrayBase; + + public: + NS_DECL_ISUPPORTS + + private: + nsArray() : nsArrayBase() {} + nsArray(const nsArray& aOther); + explicit nsArray(const nsCOMArray_base& aBaseArray) + : nsArrayBase(aBaseArray) {} + ~nsArray() = default; +}; + +class nsArrayCC final : public nsArrayBase { + friend class nsArrayBase; + + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsArrayCC, nsIMutableArray) + + private: + nsArrayCC() : nsArrayBase() {} + nsArrayCC(const nsArrayCC& aOther); + explicit nsArrayCC(const nsCOMArray_base& aBaseArray) + : nsArrayBase(aBaseArray) {} + ~nsArrayCC() = default; +}; + +#endif diff --git a/xpcom/ds/nsArrayEnumerator.cpp b/xpcom/ds/nsArrayEnumerator.cpp new file mode 100644 index 0000000000..ec475f9e5b --- /dev/null +++ b/xpcom/ds/nsArrayEnumerator.cpp @@ -0,0 +1,213 @@ +/* -*- 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/Attributes.h" + +#include "nsArrayEnumerator.h" + +#include "nsIArray.h" +#include "nsSimpleEnumerator.h" + +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/RefPtr.h" + +class nsSimpleArrayEnumerator final : public nsSimpleEnumerator { + public: + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // nsSimpleArrayEnumerator methods + explicit nsSimpleArrayEnumerator(nsIArray* aValueArray, const nsID& aEntryIID) + : mValueArray(aValueArray), mEntryIID(aEntryIID), mIndex(0) {} + + const nsID& DefaultInterface() override { return mEntryIID; } + + private: + ~nsSimpleArrayEnumerator() override = default; + + protected: + nsCOMPtr mValueArray; + const nsID mEntryIID; + uint32_t mIndex; +}; + +NS_IMETHODIMP +nsSimpleArrayEnumerator::HasMoreElements(bool* aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (!mValueArray) { + *aResult = false; + return NS_OK; + } + + uint32_t cnt; + nsresult rv = mValueArray->GetLength(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + *aResult = (mIndex < cnt); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleArrayEnumerator::GetNext(nsISupports** aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (!mValueArray) { + *aResult = nullptr; + return NS_OK; + } + + uint32_t cnt; + nsresult rv = mValueArray->GetLength(&cnt); + if (NS_FAILED(rv)) { + return rv; + } + if (mIndex >= cnt) { + return NS_ERROR_UNEXPECTED; + } + + return mValueArray->QueryElementAt(mIndex++, NS_GET_IID(nsISupports), + (void**)aResult); +} + +nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, nsIArray* aArray, + const nsID& aEntryIID) { + RefPtr enumer = + new nsSimpleArrayEnumerator(aArray, aEntryIID); + enumer.forget(aResult); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +// enumerator implementation for nsCOMArray +// creates a snapshot of the array in question +// you MUST use NS_NewArrayEnumerator to create this, so that +// allocation is done correctly +class nsCOMArrayEnumerator final : public nsSimpleEnumerator { + public: + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // Use this instead of `new`. + static nsCOMArrayEnumerator* Allocate(const nsCOMArray_base& aArray, + const nsID& aEntryIID); + + // specialized operator to make sure we make room for mValues + void operator delete(void* aPtr) { free(aPtr); } + + const nsID& DefaultInterface() override { return mEntryIID; } + + private: + // nsSimpleArrayEnumerator methods + explicit nsCOMArrayEnumerator(const nsID& aEntryIID) + : mIndex(0), mArraySize(0), mEntryIID(aEntryIID) { + mValueArray[0] = nullptr; + } + + ~nsCOMArrayEnumerator(void) override; + + protected: + uint32_t mIndex; // current position + uint32_t mArraySize; // size of the array + + const nsID& mEntryIID; + + // this is actually bigger + nsISupports* mValueArray[1]; +}; + +nsCOMArrayEnumerator::~nsCOMArrayEnumerator() { + // only release the entries that we haven't visited yet + for (; mIndex < mArraySize; ++mIndex) { + NS_IF_RELEASE(mValueArray[mIndex]); + } +} + +NS_IMETHODIMP +nsCOMArrayEnumerator::HasMoreElements(bool* aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + *aResult = (mIndex < mArraySize); + return NS_OK; +} + +NS_IMETHODIMP +nsCOMArrayEnumerator::GetNext(nsISupports** aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mIndex >= mArraySize) { + return NS_ERROR_UNEXPECTED; + } + + // pass the ownership of the reference to the caller. Since + // we AddRef'ed during creation of |this|, there is no need + // to AddRef here + *aResult = mValueArray[mIndex++]; + + // this really isn't necessary. just pretend this happens, since + // we'll never visit this value again! + // mValueArray[(mIndex-1)] = nullptr; + + return NS_OK; +} + +nsCOMArrayEnumerator* nsCOMArrayEnumerator::Allocate( + const nsCOMArray_base& aArray, const nsID& aEntryIID) { + // create enough space such that mValueArray points to a large + // enough value. Note that the initial value of aSize gives us + // space for mValueArray[0], so we must subtract + size_t size = sizeof(nsCOMArrayEnumerator); + uint32_t count; + if (aArray.Count() > 0) { + count = static_cast(aArray.Count()); + size += (count - 1) * sizeof(aArray[0]); + } else { + count = 0; + } + + // Allocate a buffer large enough to contain our object and its array. + void* mem = moz_xmalloc(size); + auto result = + new (mozilla::KnownNotNull, mem) nsCOMArrayEnumerator(aEntryIID); + + result->mArraySize = count; + + // now need to copy over the values, and addref each one + // now this might seem like a lot of work, but we're actually just + // doing all our AddRef's ahead of time since GetNext() doesn't + // need to AddRef() on the way out + for (uint32_t i = 0; i < count; ++i) { + result->mValueArray[i] = aArray[i]; + NS_IF_ADDREF(result->mValueArray[i]); + } + + return result; +} + +nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, + const nsCOMArray_base& aArray, + const nsID& aEntryIID) { + RefPtr enumerator = + nsCOMArrayEnumerator::Allocate(aArray, aEntryIID); + enumerator.forget(aResult); + return NS_OK; +} diff --git a/xpcom/ds/nsArrayEnumerator.h b/xpcom/ds/nsArrayEnumerator.h new file mode 100644 index 0000000000..a406a9ea66 --- /dev/null +++ b/xpcom/ds/nsArrayEnumerator.h @@ -0,0 +1,31 @@ +/* -*- 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/. */ + +#ifndef nsArrayEnumerator_h__ +#define nsArrayEnumerator_h__ + +// enumerator implementation for nsIArray + +#include "nsISupports.h" + +class nsISimpleEnumerator; +class nsIArray; +class nsCOMArray_base; + +// Create an enumerator for an existing nsIArray implementation +// The enumerator holds an owning reference to the array. +nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, nsIArray* aArray, + const nsID& aEntryIID = NS_GET_IID(nsISupports)); + +// create an enumerator for an existing nsCOMArray implementation +// The enumerator will hold an owning reference to each ELEMENT in +// the array. This means that the nsCOMArray can safely go away +// without its objects going away. +nsresult NS_NewArrayEnumerator(nsISimpleEnumerator** aResult, + const nsCOMArray_base& aArray, + const nsID& aEntryIID = NS_GET_IID(nsISupports)); + +#endif diff --git a/xpcom/ds/nsArrayUtils.cpp b/xpcom/ds/nsArrayUtils.cpp new file mode 100644 index 0000000000..e8ea8bd0f2 --- /dev/null +++ b/xpcom/ds/nsArrayUtils.cpp @@ -0,0 +1,22 @@ +/* -*- 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 "nsArrayUtils.h" + +// +// do_QueryElementAt helper stuff +// +nsresult nsQueryArrayElementAt::operator()(const nsIID& aIID, + void** aResult) const { + nsresult status = mArray ? mArray->QueryElementAt(mIndex, aIID, aResult) + : NS_ERROR_NULL_POINTER; + + if (mErrorPtr) { + *mErrorPtr = status; + } + + return status; +} diff --git a/xpcom/ds/nsArrayUtils.h b/xpcom/ds/nsArrayUtils.h new file mode 100644 index 0000000000..6adad119c7 --- /dev/null +++ b/xpcom/ds/nsArrayUtils.h @@ -0,0 +1,34 @@ +/* -*- 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/. */ + +#ifndef nsArrayUtils_h__ +#define nsArrayUtils_h__ + +#include "nsCOMPtr.h" +#include "nsIArray.h" + +// helper class for do_QueryElementAt +class MOZ_STACK_CLASS nsQueryArrayElementAt final : public nsCOMPtr_helper { + public: + nsQueryArrayElementAt(nsIArray* aArray, uint32_t aIndex, nsresult* aErrorPtr) + : mArray(aArray), mIndex(aIndex), mErrorPtr(aErrorPtr) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID& aIID, + void**) const override; + + private: + nsIArray* MOZ_NON_OWNING_REF mArray; + uint32_t mIndex; + nsresult* mErrorPtr; +}; + +inline const nsQueryArrayElementAt do_QueryElementAt(nsIArray* aArray, + uint32_t aIndex, + nsresult* aErrorPtr = 0) { + return nsQueryArrayElementAt(aArray, aIndex, aErrorPtr); +} + +#endif // nsArrayUtils_h__ diff --git a/xpcom/ds/nsAtom.h b/xpcom/ds/nsAtom.h new file mode 100644 index 0000000000..1b53b7be2e --- /dev/null +++ b/xpcom/ds/nsAtom.h @@ -0,0 +1,309 @@ +/* -*- 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/. */ + +#ifndef nsAtom_h +#define nsAtom_h + +#include + +#include "mozilla/Atomics.h" +#include "mozilla/Char16.h" +#include "mozilla/MemoryReporting.h" +#include "nsISupports.h" +#include "nsString.h" + +namespace mozilla { +struct AtomsSizes; +} // namespace mozilla + +class nsStaticAtom; +class nsDynamicAtom; + +// This class encompasses both static and dynamic atoms. +// +// - In places where static and dynamic atoms can be used, use RefPtr. +// This is by far the most common case. +// +// - In places where only static atoms can appear, use nsStaticAtom* to avoid +// unnecessary refcounting. This is a moderately common case. +// +// - In places where only dynamic atoms can appear, it doesn't matter much +// whether you use RefPtr or RefPtr. This is an +// extremely rare case. +// +class nsAtom { + public: + void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf, + mozilla::AtomsSizes& aSizes) const; + + bool Equals(char16ptr_t aString, uint32_t aLength) const { + return mLength == aLength && + memcmp(GetUTF16String(), aString, mLength * sizeof(char16_t)) == 0; + } + + bool Equals(const nsAString& aString) const { + return Equals(aString.BeginReading(), aString.Length()); + } + + bool IsStatic() const { return mIsStatic; } + bool IsDynamic() const { return !IsStatic(); } + + inline const nsStaticAtom* AsStatic() const; + inline const nsDynamicAtom* AsDynamic() const; + inline nsDynamicAtom* AsDynamic(); + + char16ptr_t GetUTF16String() const; + + uint32_t GetLength() const { return mLength; } + + operator mozilla::Span() const { + // Explicitly specify template argument here to avoid instantiating + // Span first and then implicitly converting to Span + return mozilla::Span{GetUTF16String(), GetLength()}; + } + + void ToString(nsAString& aString) const; + void ToUTF8String(nsACString& aString) const; + + // A hashcode that is better distributed than the actual atom pointer, for + // use in situations that need a well-distributed hashcode. It's called hash() + // rather than Hash() so we can use mozilla::BloomFilter, because + // BloomFilter requires elements to implement a function called hash(). + // + uint32_t hash() const { return mHash; } + + // This function returns true if ToLowercaseASCII would return the string + // unchanged. + bool IsAsciiLowercase() const { return mIsAsciiLowercase; } + + // This function returns true if this is the empty atom. This is exactly + // equivalent to `this == nsGkAtoms::_empty`, but it's a bit less foot-gunny, + // since we also have `nsGkAtoms::empty`. + // + // Defined in nsGkAtoms.h + inline bool IsEmpty() const; + + // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting + // of this type is special. + inline MozExternalRefCountType AddRef(); + inline MozExternalRefCountType Release(); + + using HasThreadSafeRefCnt = std::true_type; + + protected: + // Used by nsStaticAtom. + constexpr nsAtom(uint32_t aLength, uint32_t aHash, bool aIsAsciiLowercase) + : mLength(aLength), + mIsStatic(true), + mIsAsciiLowercase(aIsAsciiLowercase), + mHash(aHash) {} + + // Used by nsDynamicAtom. + nsAtom(const nsAString& aString, uint32_t aHash, bool aIsAsciiLowercase) + : mLength(aString.Length()), + mIsStatic(false), + mIsAsciiLowercase(aIsAsciiLowercase), + mHash(aHash) {} + + ~nsAtom() = default; + + const uint32_t mLength : 30; + const uint32_t mIsStatic : 1; + const uint32_t mIsAsciiLowercase : 1; + const uint32_t mHash; +}; + +// This class would be |final| if it wasn't for nsCSSAnonBoxPseudoStaticAtom +// and nsCSSPseudoElementStaticAtom, which are trivial subclasses used to +// ensure only certain static atoms are passed to certain functions. +class nsStaticAtom : public nsAtom { + public: + // These are deleted so it's impossible to RefPtr. Raw + // nsStaticAtom pointers should be used instead. + MozExternalRefCountType AddRef() = delete; + MozExternalRefCountType Release() = delete; + + // The static atom's precomputed hash value is an argument here, but it + // must be the same as would be computed by mozilla::HashString(aStr), + // which is what we use when atomizing strings. We compute this hash in + // Atom.py and assert in nsAtomTable::RegisterStaticAtoms that the two + // hashes match. + constexpr nsStaticAtom(uint32_t aLength, uint32_t aHash, + uint32_t aStringOffset, bool aIsAsciiLowercase) + : nsAtom(aLength, aHash, aIsAsciiLowercase), + mStringOffset(aStringOffset) {} + + const char16_t* String() const { + return reinterpret_cast(uintptr_t(this) - mStringOffset); + } + + already_AddRefed ToAddRefed() { + return already_AddRefed(static_cast(this)); + } + + private: + // This is an offset to the string chars, which must be at a lower address in + // memory. + uint32_t mStringOffset; +}; + +class nsDynamicAtom : public nsAtom { + public: + // We can't use NS_INLINE_DECL_THREADSAFE_REFCOUNTING because the refcounting + // of this type is special. + MozExternalRefCountType AddRef() { + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsrefcnt count = ++mRefCnt; + if (count == 1) { + gUnusedAtomCount--; + } + return count; + } + + MozExternalRefCountType Release() { +#ifdef DEBUG + // We set a lower GC threshold for atoms in debug builds so that we exercise + // the GC machinery more often. + static const int32_t kAtomGCThreshold = 20; +#else + static const int32_t kAtomGCThreshold = 10000; +#endif + + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + nsrefcnt count = --mRefCnt; + if (count == 0) { + if (++gUnusedAtomCount >= kAtomGCThreshold) { + GCAtomTable(); + } + } + + return count; + } + + const char16_t* String() const { + return reinterpret_cast(this + 1); + } + + static nsDynamicAtom* FromChars(char16_t* chars) { + return reinterpret_cast(chars) - 1; + } + + private: + friend class nsAtomTable; + friend class nsAtomSubTable; + friend int32_t NS_GetUnusedAtomCount(); + + static mozilla::Atomic gUnusedAtomCount; + static void GCAtomTable(); + + // These shouldn't be used directly, even by friend classes. The + // Create()/Destroy() methods use them. + nsDynamicAtom(const nsAString& aString, uint32_t aHash, + bool aIsAsciiLowercase); + ~nsDynamicAtom() = default; + + static nsDynamicAtom* Create(const nsAString& aString, uint32_t aHash); + static void Destroy(nsDynamicAtom* aAtom); + + mozilla::ThreadSafeAutoRefCnt mRefCnt; + + // The atom's chars are stored at the end of the struct. +}; + +const nsStaticAtom* nsAtom::AsStatic() const { + MOZ_ASSERT(IsStatic()); + return static_cast(this); +} + +const nsDynamicAtom* nsAtom::AsDynamic() const { + MOZ_ASSERT(IsDynamic()); + return static_cast(this); +} + +nsDynamicAtom* nsAtom::AsDynamic() { + MOZ_ASSERT(IsDynamic()); + return static_cast(this); +} + +MozExternalRefCountType nsAtom::AddRef() { + return IsStatic() ? 2 : AsDynamic()->AddRef(); +} + +MozExternalRefCountType nsAtom::Release() { + return IsStatic() ? 1 : AsDynamic()->Release(); +} + +// The four forms of NS_Atomize (for use with |RefPtr|) return the +// atom for the string given. At any given time there will always be one atom +// representing a given string. Atoms are intended to make string comparison +// cheaper by simplifying it to pointer equality. A pointer to the atom that +// does not own a reference is not guaranteed to be valid. + +// Find an atom that matches the given UTF-8 string. The string is assumed to +// be zero terminated. Never returns null. +already_AddRefed NS_Atomize(const char* aUTF8String); + +// Find an atom that matches the given UTF-8 string. Never returns null. +already_AddRefed NS_Atomize(const nsACString& aUTF8String); + +// Find an atom that matches the given UTF-16 string. The string is assumed to +// be zero terminated. Never returns null. +already_AddRefed NS_Atomize(const char16_t* aUTF16String); + +// Find an atom that matches the given UTF-16 string. Never returns null. +already_AddRefed NS_Atomize(const nsAString& aUTF16String); + +// An optimized version of the method above for the main thread. +already_AddRefed NS_AtomizeMainThread(const nsAString& aUTF16String); + +// Return a count of the total number of atoms currently alive in the system. +// +// Note that the result is imprecise and racy if other threads are currently +// operating on atoms. It's also slow, since it triggers a GC before counting. +// Currently this function is only used in tests, which should probably remain +// the case. +nsrefcnt NS_GetNumberOfAtoms(); + +// Return a pointer for a static atom for the string or null if there's no +// static atom for this string. +nsStaticAtom* NS_GetStaticAtom(const nsAString& aUTF16String); + +class nsAtomString : public nsString { + public: + explicit nsAtomString(const nsAtom* aAtom) { aAtom->ToString(*this); } +}; + +class nsAutoAtomString : public nsAutoString { + public: + explicit nsAutoAtomString(const nsAtom* aAtom) { aAtom->ToString(*this); } +}; + +class nsAtomCString : public nsCString { + public: + explicit nsAtomCString(const nsAtom* aAtom) { aAtom->ToUTF8String(*this); } +}; + +class nsAutoAtomCString : public nsAutoCString { + public: + explicit nsAutoAtomCString(const nsAtom* aAtom) { + aAtom->ToUTF8String(*this); + } +}; + +class nsDependentAtomString : public nsDependentString { + public: + explicit nsDependentAtomString(const nsAtom* aAtom) + : nsDependentString(aAtom->GetUTF16String(), aAtom->GetLength()) {} +}; + +// Checks if the ascii chars in a given atom are already lowercase. +// If they are, no-op. Otherwise, converts all the ascii uppercase +// chars to lowercase and atomizes, storing the result in the inout +// param. +void ToLowerCaseASCII(RefPtr& aAtom); + +#endif // nsAtom_h diff --git a/xpcom/ds/nsAtomTable.cpp b/xpcom/ds/nsAtomTable.cpp new file mode 100644 index 0000000000..56618cd46c --- /dev/null +++ b/xpcom/ds/nsAtomTable.cpp @@ -0,0 +1,670 @@ +/* -*- 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/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/MruCache.h" +#include "mozilla/Mutex.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Sprintf.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Unused.h" + +#include "nsAtom.h" +#include "nsAtomTable.h" +#include "nsCRT.h" +#include "nsGkAtoms.h" +#include "nsHashKeys.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsUnicharUtils.h" +#include "PLDHashTable.h" +#include "prenv.h" + +// There are two kinds of atoms handled by this module. +// +// - Dynamic: the atom itself is heap allocated, as is the char buffer it +// points to. |gAtomTable| holds weak references to dynamic atoms. When the +// refcount of a dynamic atom drops to zero, we increment a static counter. +// When that counter reaches a certain threshold, we iterate over the atom +// table, removing and deleting dynamic atoms with refcount zero. This allows +// us to avoid acquiring the atom table lock during normal refcounting. +// +// - Static: both the atom and its chars are statically allocated and +// immutable, so it ignores all AddRef/Release calls. +// +// Note that gAtomTable is used on multiple threads, and has internal +// synchronization. + +using namespace mozilla; + +//---------------------------------------------------------------------- + +enum class GCKind { + RegularOperation, + Shutdown, +}; + +//---------------------------------------------------------------------- + +// gUnusedAtomCount is incremented when an atom loses its last reference +// (and thus turned into unused state), and decremented when an unused +// atom gets a reference again. The atom table relies on this value to +// schedule GC. This value can temporarily go below zero when multiple +// threads are operating the same atom, so it has to be signed so that +// we wouldn't use overflow value for comparison. +// See nsAtom::AddRef() and nsAtom::Release(). +// This atomic can be accessed during the GC and other places where recorded +// events are not allowed, so its value is not preserved when recording or +// replaying. +Atomic nsDynamicAtom::gUnusedAtomCount; + +nsDynamicAtom::nsDynamicAtom(const nsAString& aString, uint32_t aHash, + bool aIsAsciiLowercase) + : nsAtom(aString, aHash, aIsAsciiLowercase), mRefCnt(1) {} + +// Returns true if ToLowercaseASCII would return the string unchanged. +static bool IsAsciiLowercase(const char16_t* aString, const uint32_t aLength) { + for (uint32_t i = 0; i < aLength; ++i) { + if (IS_ASCII_UPPER(aString[i])) { + return false; + } + } + + return true; +} + +nsDynamicAtom* nsDynamicAtom::Create(const nsAString& aString, uint32_t aHash) { + // We tack the chars onto the end of the nsDynamicAtom object. + size_t numCharBytes = (aString.Length() + 1) * sizeof(char16_t); + size_t numTotalBytes = sizeof(nsDynamicAtom) + numCharBytes; + + bool isAsciiLower = ::IsAsciiLowercase(aString.Data(), aString.Length()); + + nsDynamicAtom* atom = (nsDynamicAtom*)moz_xmalloc(numTotalBytes); + new (atom) nsDynamicAtom(aString, aHash, isAsciiLower); + memcpy(const_cast(atom->String()), + PromiseFlatString(aString).get(), numCharBytes); + + MOZ_ASSERT(atom->String()[atom->GetLength()] == char16_t(0)); + MOZ_ASSERT(atom->Equals(aString)); + MOZ_ASSERT(atom->mHash == HashString(atom->String(), atom->GetLength())); + MOZ_ASSERT(atom->mIsAsciiLowercase == isAsciiLower); + + return atom; +} + +void nsDynamicAtom::Destroy(nsDynamicAtom* aAtom) { + aAtom->~nsDynamicAtom(); + free(aAtom); +} + +void nsAtom::ToString(nsAString& aString) const { + // See the comment on |mString|'s declaration. + if (IsStatic()) { + // AssignLiteral() lets us assign without copying. This isn't a string + // literal, but it's a static atom and thus has an unbounded lifetime, + // which is what's important. + aString.AssignLiteral(AsStatic()->String(), mLength); + } else { + aString.Assign(AsDynamic()->String(), mLength); + } +} + +void nsAtom::ToUTF8String(nsACString& aBuf) const { + CopyUTF16toUTF8(nsDependentString(GetUTF16String(), mLength), aBuf); +} + +void nsAtom::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + AtomsSizes& aSizes) const { + // Static atoms are in static memory, and so are not measured here. + if (IsDynamic()) { + aSizes.mDynamicAtoms += aMallocSizeOf(this); + } +} + +char16ptr_t nsAtom::GetUTF16String() const { + return IsStatic() ? AsStatic()->String() : AsDynamic()->String(); +} + +//---------------------------------------------------------------------- + +struct AtomTableKey { + explicit AtomTableKey(const nsStaticAtom* aAtom) + : mUTF16String(aAtom->String()), + mUTF8String(nullptr), + mLength(aAtom->GetLength()), + mHash(aAtom->hash()) { + MOZ_ASSERT(HashString(mUTF16String, mLength) == mHash); + } + + AtomTableKey(const char16_t* aUTF16String, uint32_t aLength) + : mUTF16String(aUTF16String), mUTF8String(nullptr), mLength(aLength) { + mHash = HashString(mUTF16String, mLength); + } + + AtomTableKey(const char* aUTF8String, uint32_t aLength, bool* aErr) + : mUTF16String(nullptr), mUTF8String(aUTF8String), mLength(aLength) { + mHash = HashUTF8AsUTF16(mUTF8String, mLength, aErr); + } + + const char16_t* mUTF16String; + const char* mUTF8String; + uint32_t mLength; + uint32_t mHash; +}; + +struct AtomTableEntry : public PLDHashEntryHdr { + // These references are either to dynamic atoms, in which case they are + // non-owning, or they are to static atoms, which aren't really refcounted. + // See the comment at the top of this file for more details. + nsAtom* MOZ_NON_OWNING_REF mAtom; +}; + +struct AtomCache : public MruCache { + static HashNumber Hash(const AtomTableKey& aKey) { return aKey.mHash; } + static bool Match(const AtomTableKey& aKey, const nsAtom* aVal) { + MOZ_ASSERT(aKey.mUTF16String); + return aVal->Equals(aKey.mUTF16String, aKey.mLength); + } +}; + +static AtomCache sRecentlyUsedMainThreadAtoms; + +// In order to reduce locking contention for concurrent atomization, we segment +// the atom table into N subtables, each with a separate lock. If the hash +// values we use to select the subtable are evenly distributed, this reduces the +// probability of contention by a factor of N. See bug 1440824. +// +// NB: This is somewhat similar to the technique used by Java's +// ConcurrentHashTable. +class nsAtomSubTable { + friend class nsAtomTable; + Mutex mLock; + PLDHashTable mTable; + nsAtomSubTable(); + void GCLocked(GCKind aKind) MOZ_REQUIRES(mLock); + void AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf, + AtomsSizes& aSizes) MOZ_REQUIRES(mLock); + + AtomTableEntry* Search(AtomTableKey& aKey) const MOZ_REQUIRES(mLock) { + mLock.AssertCurrentThreadOwns(); + return static_cast(mTable.Search(&aKey)); + } + + AtomTableEntry* Add(AtomTableKey& aKey) MOZ_REQUIRES(mLock) { + mLock.AssertCurrentThreadOwns(); + return static_cast(mTable.Add(&aKey)); // Infallible + } +}; + +// The outer atom table, which coordinates access to the inner array of +// subtables. +class nsAtomTable { + public: + nsAtomSubTable& SelectSubTable(AtomTableKey& aKey); + void AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes); + void GC(GCKind aKind); + already_AddRefed Atomize(const nsAString& aUTF16String); + already_AddRefed Atomize(const nsACString& aUTF8String); + already_AddRefed AtomizeMainThread(const nsAString& aUTF16String); + nsStaticAtom* GetStaticAtom(const nsAString& aUTF16String); + void RegisterStaticAtoms(const nsStaticAtom* aAtoms, size_t aAtomsLen); + + // The result of this function may be imprecise if other threads are operating + // on atoms concurrently. It's also slow, since it triggers a GC before + // counting. + size_t RacySlowCount(); + + // This hash table op is a static member of this class so that it can take + // advantage of |friend| declarations. + static void AtomTableClearEntry(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry); + + // We achieve measurable reduction in locking contention in parallel CSS + // parsing by increasing the number of subtables up to 128. This has been + // measured to have neglible impact on the performance of initialization, GC, + // and shutdown. + // + // Another important consideration is memory, since we're adding fixed + // overhead per content process, which we try to avoid. Measuring a + // mostly-empty page [1] with various numbers of subtables, we get the + // following deep sizes for the atom table: + // 1 subtable: 278K + // 8 subtables: 279K + // 16 subtables: 282K + // 64 subtables: 286K + // 128 subtables: 290K + // + // So 128 subtables costs us 12K relative to a single table, and 4K relative + // to 64 subtables. Conversely, measuring parallel (6 thread) CSS parsing on + // tp6-facebook, a single table provides ~150ms of locking overhead per + // thread, 64 subtables provides ~2-3ms of overhead, and 128 subtables + // provides <1ms. And so while either 64 or 128 subtables would probably be + // acceptable, achieving a measurable reduction in contention for 4k of fixed + // memory overhead is probably worth it. + // + // [1] The numbers will look different for content processes with complex + // pages loaded, but in those cases the actual atoms will dominate memory + // usage and the overhead of extra tables will be negligible. We're mostly + // interested in the fixed cost for nearly-empty content processes. + const static size_t kNumSubTables = 128; // Must be power of two. + + private: + nsAtomSubTable mSubTables[kNumSubTables]; +}; + +// Static singleton instance for the atom table. +static nsAtomTable* gAtomTable; + +static PLDHashNumber AtomTableGetHash(const void* aKey) { + const AtomTableKey* k = static_cast(aKey); + return k->mHash; +} + +static bool AtomTableMatchKey(const PLDHashEntryHdr* aEntry, const void* aKey) { + const AtomTableEntry* he = static_cast(aEntry); + const AtomTableKey* k = static_cast(aKey); + + if (k->mUTF8String) { + bool err = false; + return (CompareUTF8toUTF16(nsDependentCSubstring( + k->mUTF8String, k->mUTF8String + k->mLength), + nsDependentAtomString(he->mAtom), &err) == 0) && + !err; + } + + return he->mAtom->Equals(k->mUTF16String, k->mLength); +} + +void nsAtomTable::AtomTableClearEntry(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry) { + auto entry = static_cast(aEntry); + entry->mAtom = nullptr; +} + +static void AtomTableInitEntry(PLDHashEntryHdr* aEntry, const void* aKey) { + static_cast(aEntry)->mAtom = nullptr; +} + +static const PLDHashTableOps AtomTableOps = { + AtomTableGetHash, AtomTableMatchKey, PLDHashTable::MoveEntryStub, + nsAtomTable::AtomTableClearEntry, AtomTableInitEntry}; + +// The atom table very quickly gets 10,000+ entries in it (or even 100,000+). +// But choosing the best initial subtable length has some subtleties: we add +// ~2700 static atoms at start-up, and then we start adding and removing +// dynamic atoms. If we make the tables too big to start with, when the first +// dynamic atom gets removed from a given table the load factor will be < 25% +// and we will shrink it. +// +// So we first make the simplifying assumption that the atoms are more or less +// evenly-distributed across the subtables (which is the case empirically). +// Then, we take the total atom count when the first dynamic atom is removed +// (~2700), divide that across the N subtables, and the largest capacity that +// will allow each subtable to be > 25% full with that count. +// +// So want an initial subtable capacity less than (2700 / N) * 4 = 10800 / N. +// Rounding down to the nearest power of two gives us 8192 / N. Since the +// capacity is double the initial length, we end up with (4096 / N) per +// subtable. +#define INITIAL_SUBTABLE_LENGTH (4096 / nsAtomTable::kNumSubTables) + +nsAtomSubTable& nsAtomTable::SelectSubTable(AtomTableKey& aKey) { + // There are a few considerations around how we select subtables. + // + // First, we want entries to be evenly distributed across the subtables. This + // can be achieved by using any bits in the hash key, assuming the key itself + // is evenly-distributed. Empirical measurements indicate that this method + // produces a roughly-even distribution across subtables. + // + // Second, we want to use the hash bits that are least likely to influence an + // entry's position within the subtable. If we used the exact same bits used + // by the subtables, then each subtable would compute the same position for + // every entry it observes, leading to pessimal performance. In this case, + // we're using PLDHashTable, whose primary hash function uses the N leftmost + // bits of the hash value (where N is the log2 capacity of the table). This + // means we should prefer the rightmost bits here. + // + // Note that the below is equivalent to mHash % kNumSubTables, a replacement + // which an optimizing compiler should make, but let's avoid any doubt. + static_assert((kNumSubTables & (kNumSubTables - 1)) == 0, + "must be power of two"); + return mSubTables[aKey.mHash & (kNumSubTables - 1)]; +} + +void nsAtomTable::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, + AtomsSizes& aSizes) { + MOZ_ASSERT(NS_IsMainThread()); + aSizes.mTable += aMallocSizeOf(this); + for (auto& table : mSubTables) { + MutexAutoLock lock(table.mLock); + table.AddSizeOfExcludingThisLocked(aMallocSizeOf, aSizes); + } +} + +void nsAtomTable::GC(GCKind aKind) { + MOZ_ASSERT(NS_IsMainThread()); + sRecentlyUsedMainThreadAtoms.Clear(); + + // Note that this is effectively an incremental GC, since only one subtable + // is locked at a time. + for (auto& table : mSubTables) { + MutexAutoLock lock(table.mLock); + table.GCLocked(aKind); + } + + // We would like to assert that gUnusedAtomCount matches the number of atoms + // we found in the table which we removed. However, there are two problems + // with this: + // * We have multiple subtables, each with their own lock. For optimal + // performance we only want to hold one lock at a time, but this means + // that atoms can be added and removed between GC slices. + // * Even if we held all the locks and performed all GC slices atomically, + // the locks are not acquired for AddRef() and Release() calls. This means + // we might see a gUnusedAtomCount value in between, say, AddRef() + // incrementing mRefCnt and it decrementing gUnusedAtomCount. + // + // So, we don't bother asserting that there are no unused atoms at the end of + // a regular GC. But we can (and do) assert this just after the last GC at + // shutdown. + // + // Note that, barring refcounting bugs, an atom can only go from a zero + // refcount to a non-zero refcount while the atom table lock is held, so + // so we won't try to resurrect a zero refcount atom while trying to delete + // it. + + MOZ_ASSERT_IF(aKind == GCKind::Shutdown, + nsDynamicAtom::gUnusedAtomCount == 0); +} + +size_t nsAtomTable::RacySlowCount() { + // Trigger a GC so that the result is deterministic modulo other threads. + GC(GCKind::RegularOperation); + size_t count = 0; + for (auto& table : mSubTables) { + MutexAutoLock lock(table.mLock); + count += table.mTable.EntryCount(); + } + + return count; +} + +nsAtomSubTable::nsAtomSubTable() + : mLock("Atom Sub-Table Lock"), + mTable(&AtomTableOps, sizeof(AtomTableEntry), INITIAL_SUBTABLE_LENGTH) {} + +void nsAtomSubTable::GCLocked(GCKind aKind) { + MOZ_ASSERT(NS_IsMainThread()); + mLock.AssertCurrentThreadOwns(); + + int32_t removedCount = 0; // A non-atomic temporary for cheaper increments. + nsAutoCString nonZeroRefcountAtoms; + uint32_t nonZeroRefcountAtomsCount = 0; + for (auto i = mTable.Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + if (entry->mAtom->IsStatic()) { + continue; + } + + nsAtom* atom = entry->mAtom; + if (atom->IsDynamic() && atom->AsDynamic()->mRefCnt == 0) { + i.Remove(); + nsDynamicAtom::Destroy(atom->AsDynamic()); + ++removedCount; + } +#ifdef NS_FREE_PERMANENT_DATA + else if (aKind == GCKind::Shutdown && PR_GetEnv("XPCOM_MEM_BLOAT_LOG")) { + // Only report leaking atoms in leak-checking builds in a run where we + // are checking for leaks, during shutdown. If something is anomalous, + // then we'll assert later in this function. + nsAutoCString name; + atom->ToUTF8String(name); + if (nonZeroRefcountAtomsCount == 0) { + nonZeroRefcountAtoms = name; + } else if (nonZeroRefcountAtomsCount < 20) { + nonZeroRefcountAtoms += ","_ns + name; + } else if (nonZeroRefcountAtomsCount == 20) { + nonZeroRefcountAtoms += ",..."_ns; + } + nonZeroRefcountAtomsCount++; + } +#endif + } + if (nonZeroRefcountAtomsCount) { + nsPrintfCString msg("%d dynamic atom(s) with non-zero refcount: %s", + nonZeroRefcountAtomsCount, nonZeroRefcountAtoms.get()); + NS_ASSERTION(nonZeroRefcountAtomsCount == 0, msg.get()); + } + + nsDynamicAtom::gUnusedAtomCount -= removedCount; +} + +void nsDynamicAtom::GCAtomTable() { + MOZ_ASSERT(gAtomTable); + if (NS_IsMainThread()) { + gAtomTable->GC(GCKind::RegularOperation); + } +} + +//---------------------------------------------------------------------- + +// Have the static atoms been inserted into the table? +static bool gStaticAtomsDone = false; + +void NS_InitAtomTable() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!gAtomTable); + + // We register static atoms immediately so they're available for use as early + // as possible. + gAtomTable = new nsAtomTable(); + gAtomTable->RegisterStaticAtoms(nsGkAtoms::sAtoms, nsGkAtoms::sAtomsLen); + gStaticAtomsDone = true; +} + +void NS_ShutdownAtomTable() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gAtomTable); + +#ifdef NS_FREE_PERMANENT_DATA + // Do a final GC to satisfy leak checking. We skip this step in release + // builds. + gAtomTable->GC(GCKind::Shutdown); +#endif + + delete gAtomTable; + gAtomTable = nullptr; +} + +void NS_AddSizeOfAtoms(MallocSizeOf aMallocSizeOf, AtomsSizes& aSizes) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(gAtomTable); + return gAtomTable->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); +} + +void nsAtomSubTable::AddSizeOfExcludingThisLocked(MallocSizeOf aMallocSizeOf, + AtomsSizes& aSizes) { + mLock.AssertCurrentThreadOwns(); + aSizes.mTable += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + entry->mAtom->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); + } +} + +void nsAtomTable::RegisterStaticAtoms(const nsStaticAtom* aAtoms, + size_t aAtomsLen) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_RELEASE_ASSERT(!gStaticAtomsDone, "Static atom insertion is finished!"); + + for (uint32_t i = 0; i < aAtomsLen; ++i) { + const nsStaticAtom* atom = &aAtoms[i]; + MOZ_ASSERT(IsAsciiNullTerminated(atom->String())); + MOZ_ASSERT(NS_strlen(atom->String()) == atom->GetLength()); + MOZ_ASSERT(atom->IsAsciiLowercase() == + ::IsAsciiLowercase(atom->String(), atom->GetLength())); + + // This assertion ensures the static atom's precomputed hash value matches + // what would be computed by mozilla::HashString(aStr), which is what we use + // when atomizing strings. We compute this hash in Atom.py. + MOZ_ASSERT(HashString(atom->String()) == atom->hash()); + + AtomTableKey key(atom); + nsAtomSubTable& table = SelectSubTable(key); + MutexAutoLock lock(table.mLock); + AtomTableEntry* he = table.Add(key); + + if (he->mAtom) { + // There are two ways we could get here. + // - Register two static atoms with the same string. + // - Create a dynamic atom and then register a static atom with the same + // string while the dynamic atom is alive. + // Both cases can cause subtle bugs, and are disallowed. We're + // programming in C++ here, not Smalltalk. + nsAutoCString name; + he->mAtom->ToUTF8String(name); + MOZ_CRASH_UNSAFE_PRINTF("Atom for '%s' already exists", name.get()); + } + he->mAtom = const_cast(atom); + } +} + +already_AddRefed NS_Atomize(const char* aUTF8String) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->Atomize(nsDependentCString(aUTF8String)); +} + +already_AddRefed nsAtomTable::Atomize(const nsACString& aUTF8String) { + bool err; + AtomTableKey key(aUTF8String.Data(), aUTF8String.Length(), &err); + if (MOZ_UNLIKELY(err)) { + MOZ_ASSERT_UNREACHABLE("Tried to atomize invalid UTF-8."); + // The input was invalid UTF-8. Let's replace the errors with U+FFFD + // and atomize the result. + nsString str; + CopyUTF8toUTF16(aUTF8String, str); + return Atomize(str); + } + nsAtomSubTable& table = SelectSubTable(key); + MutexAutoLock lock(table.mLock); + AtomTableEntry* he = table.Add(key); + + if (he->mAtom) { + RefPtr atom = he->mAtom; + return atom.forget(); + } + + nsString str; + CopyUTF8toUTF16(aUTF8String, str); + RefPtr atom = dont_AddRef(nsDynamicAtom::Create(str, key.mHash)); + + he->mAtom = atom; + + return atom.forget(); +} + +already_AddRefed NS_Atomize(const nsACString& aUTF8String) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->Atomize(aUTF8String); +} + +already_AddRefed NS_Atomize(const char16_t* aUTF16String) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->Atomize(nsDependentString(aUTF16String)); +} + +already_AddRefed nsAtomTable::Atomize(const nsAString& aUTF16String) { + AtomTableKey key(aUTF16String.Data(), aUTF16String.Length()); + nsAtomSubTable& table = SelectSubTable(key); + MutexAutoLock lock(table.mLock); + AtomTableEntry* he = table.Add(key); + + if (he->mAtom) { + RefPtr atom = he->mAtom; + return atom.forget(); + } + + RefPtr atom = + dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash)); + he->mAtom = atom; + + return atom.forget(); +} + +already_AddRefed NS_Atomize(const nsAString& aUTF16String) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->Atomize(aUTF16String); +} + +already_AddRefed nsAtomTable::AtomizeMainThread( + const nsAString& aUTF16String) { + MOZ_ASSERT(NS_IsMainThread()); + RefPtr retVal; + AtomTableKey key(aUTF16String.Data(), aUTF16String.Length()); + auto p = sRecentlyUsedMainThreadAtoms.Lookup(key); + if (p) { + retVal = p.Data(); + return retVal.forget(); + } + + nsAtomSubTable& table = SelectSubTable(key); + MutexAutoLock lock(table.mLock); + AtomTableEntry* he = table.Add(key); + + if (he->mAtom) { + retVal = he->mAtom; + } else { + RefPtr newAtom = + dont_AddRef(nsDynamicAtom::Create(aUTF16String, key.mHash)); + he->mAtom = newAtom; + retVal = std::move(newAtom); + } + + p.Set(retVal); + return retVal.forget(); +} + +already_AddRefed NS_AtomizeMainThread(const nsAString& aUTF16String) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->AtomizeMainThread(aUTF16String); +} + +nsrefcnt NS_GetNumberOfAtoms(void) { + MOZ_ASSERT(gAtomTable); + return gAtomTable->RacySlowCount(); +} + +int32_t NS_GetUnusedAtomCount(void) { return nsDynamicAtom::gUnusedAtomCount; } + +nsStaticAtom* NS_GetStaticAtom(const nsAString& aUTF16String) { + MOZ_ASSERT(gStaticAtomsDone, "Static atom setup not yet done."); + MOZ_ASSERT(gAtomTable); + return gAtomTable->GetStaticAtom(aUTF16String); +} + +nsStaticAtom* nsAtomTable::GetStaticAtom(const nsAString& aUTF16String) { + AtomTableKey key(aUTF16String.Data(), aUTF16String.Length()); + nsAtomSubTable& table = SelectSubTable(key); + MutexAutoLock lock(table.mLock); + AtomTableEntry* he = table.Search(key); + return he && he->mAtom->IsStatic() ? static_cast(he->mAtom) + : nullptr; +} + +void ToLowerCaseASCII(RefPtr& aAtom) { + // Assume the common case is that the atom is already ASCII lowercase. + if (aAtom->IsAsciiLowercase()) { + return; + } + + nsAutoString lowercased; + ToLowerCaseASCII(nsDependentAtomString(aAtom), lowercased); + aAtom = NS_Atomize(lowercased); +} diff --git a/xpcom/ds/nsAtomTable.h b/xpcom/ds/nsAtomTable.h new file mode 100644 index 0000000000..ac69daea9f --- /dev/null +++ b/xpcom/ds/nsAtomTable.h @@ -0,0 +1,30 @@ +/* -*- 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/. */ + +#ifndef nsAtomTable_h__ +#define nsAtomTable_h__ + +#include "mozilla/MemoryReporting.h" +#include "nsAtom.h" + +#include + +void NS_InitAtomTable(); +void NS_ShutdownAtomTable(); + +namespace mozilla { +struct AtomsSizes { + size_t mTable; + size_t mDynamicAtoms; + + AtomsSizes() : mTable(0), mDynamicAtoms(0) {} +}; +} // namespace mozilla + +void NS_AddSizeOfAtoms(mozilla::MallocSizeOf aMallocSizeOf, + mozilla::AtomsSizes& aSizes); + +#endif // nsAtomTable_h__ diff --git a/xpcom/ds/nsBaseHashtable.h b/xpcom/ds/nsBaseHashtable.h new file mode 100644 index 0000000000..7b57ef4666 --- /dev/null +++ b/xpcom/ds/nsBaseHashtable.h @@ -0,0 +1,1029 @@ +/* -*- 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/. */ + +#ifndef nsBaseHashtable_h__ +#define nsBaseHashtable_h__ + +#include +#include + +#include "mozilla/dom/SafeRefPtr.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Result.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsHashtablesFwd.h" +#include "nsTHashtable.h" + +namespace mozilla::detail { + +template +struct SmartPtrTraits { + static constexpr bool IsSmartPointer = false; + static constexpr bool IsRefCounted = false; +}; + +template +struct SmartPtrTraits> { + static constexpr bool IsSmartPointer = true; + static constexpr bool IsRefCounted = false; + using SmartPointerType = UniquePtr; + using PointeeType = Pointee; + using RawPointerType = Pointee*; + template + using OtherSmartPtrType = UniquePtr; + + template + static SmartPointerType NewObject(Args&&... aConstructionArgs) { + return mozilla::MakeUnique(std::forward(aConstructionArgs)...); + } +}; + +template +struct SmartPtrTraits> { + static constexpr bool IsSmartPointer = true; + static constexpr bool IsRefCounted = true; + using SmartPointerType = RefPtr; + using PointeeType = Pointee; + using RawPointerType = Pointee*; + template + using OtherSmartPtrType = RefPtr; + + template + static SmartPointerType NewObject(Args&&... aConstructionArgs) { + return MakeRefPtr(std::forward(aConstructionArgs)...); + } +}; + +template +struct SmartPtrTraits> { + static constexpr bool IsSmartPointer = true; + static constexpr bool IsRefCounted = true; + using SmartPointerType = SafeRefPtr; + using PointeeType = Pointee; + using RawPointerType = Pointee*; + template + using OtherSmartPtrType = SafeRefPtr; + + template + static SmartPointerType NewObject(Args&&... aConstructionArgs) { + return MakeSafeRefPtr(std::forward(aConstructionArgs)...); + } +}; + +template +struct SmartPtrTraits> { + static constexpr bool IsSmartPointer = true; + static constexpr bool IsRefCounted = true; + using SmartPointerType = nsCOMPtr; + using PointeeType = Pointee; + using RawPointerType = Pointee*; + template + using OtherSmartPtrType = nsCOMPtr; + + template + static SmartPointerType NewObject(Args&&... aConstructionArgs) { + return MakeRefPtr(std::forward(aConstructionArgs)...); + } +}; + +template +T* PtrGetWeak(T* aPtr) { + return aPtr; +} + +template +T* PtrGetWeak(const RefPtr& aPtr) { + return aPtr.get(); +} + +template +T* PtrGetWeak(const SafeRefPtr& aPtr) { + return aPtr.unsafeGetRawPtr(); +} + +template +T* PtrGetWeak(const nsCOMPtr& aPtr) { + return aPtr.get(); +} + +template +T* PtrGetWeak(const UniquePtr& aPtr) { + return aPtr.get(); +} + +template +class nsBaseHashtableValueIterator : public ::detail::nsTHashtableIteratorBase { + // friend class nsTHashtable; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const std::decay_t; + using difference_type = int32_t; + using pointer = value_type*; + using reference = value_type&; + + using iterator_type = nsBaseHashtableValueIterator; + using const_iterator_type = nsBaseHashtableValueIterator; + + using nsTHashtableIteratorBase::nsTHashtableIteratorBase; + + value_type* operator->() const { + return &static_cast(mIterator.Get())->GetData(); + } + decltype(auto) operator*() const { + return static_cast(mIterator.Get())->GetData(); + } + + iterator_type& operator++() { + mIterator.Next(); + return *this; + } + iterator_type operator++(int) { + iterator_type it = *this; + ++*this; + return it; + } +}; + +template +class nsBaseHashtableValueRange { + public: + using IteratorType = nsBaseHashtableValueIterator; + using iterator = IteratorType; + + explicit nsBaseHashtableValueRange(const PLDHashTable& aHashtable) + : mHashtable{aHashtable} {} + + auto begin() const { return IteratorType{mHashtable}; } + auto end() const { + return IteratorType{mHashtable, typename IteratorType::EndIteratorTag{}}; + } + auto cbegin() const { return begin(); } + auto cend() const { return end(); } + + uint32_t Count() const { return mHashtable.EntryCount(); } + + private: + const PLDHashTable& mHashtable; +}; + +template +auto RangeSize(const detail::nsBaseHashtableValueRange& aRange) { + return aRange.Count(); +} + +} // namespace mozilla::detail + +/** + * Data type conversion helper that is used to wrap and unwrap the specified + * DataType. + */ +template +class nsDefaultConverter { + public: + /** + * Maps the storage DataType to the exposed UserDataType. + */ + static UserDataType Unwrap(DataType& src) { return UserDataType(src); } + + /** + * Const ref variant used for example with nsCOMPtr wrappers. + */ + static DataType Wrap(const UserDataType& src) { return DataType(src); } + + /** + * Generic conversion, this is useful for things like already_AddRefed. + */ + template + static DataType Wrap(U&& src) { + return std::forward(src); + } + + template + static UserDataType Unwrap(U&& src) { + return std::forward(src); + } +}; + +/** + * the private nsTHashtable::EntryType class used by nsBaseHashtable + * @see nsTHashtable for the specification of this class + * @see nsBaseHashtable for template parameters + */ +template +class nsBaseHashtableET : public KeyClass { + public: + using DataType = TDataType; + + const DataType& GetData() const { return mData; } + DataType* GetModifiableData() { return &mData; } + template + void SetData(U&& aData) { + mData = std::forward(aData); + } + + decltype(auto) GetWeak() const { + return mozilla::detail::PtrGetWeak(GetData()); + } + + private: + DataType mData; + friend class nsTHashtable>; + template + friend class nsBaseHashtable; + friend class ::detail::nsTHashtableKeyIterator< + nsBaseHashtableET>; + + typedef typename KeyClass::KeyType KeyType; + typedef typename KeyClass::KeyTypePointer KeyTypePointer; + + template + explicit nsBaseHashtableET(KeyTypePointer aKey, Args&&... aArgs); + nsBaseHashtableET(nsBaseHashtableET&& aToMove) = default; + ~nsBaseHashtableET() = default; +}; + +/** + * Templated hashtable. Usually, this isn't instantiated directly but through + * its sub-class templates nsInterfaceHashtable, nsClassHashtable, + * nsRefPtrHashtable and nsTHashMap. + * + * Originally, UserDataType used to be the only type exposed to the user in the + * public member function signatures (hence its name), but this has proven to + * inadequate over time. Now, UserDataType is only exposed in by-value + * getter member functions that are called *Get*. Member functions that provide + * access to the DataType are called Lookup rather than Get. Note that this rule + * does not apply to nsRefPtrHashtable and nsInterfaceHashtable, as they are + * provide a similar interface, but are no genuine sub-classes of + * nsBaseHashtable. + * + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param DataType the datatype stored in the hashtable, + * for example, uint32_t or nsCOMPtr. + * @param UserDataType the datatype returned from the by-value getter member + * functions (named *Get*), for example uint32_t or nsISupports* + * @param Converter that is used to map from DataType to UserDataType. A + * default converter is provided that assumes implicit conversion is an + * option. + */ +template +class nsBaseHashtable + : protected nsTHashtable> { + using Base = nsTHashtable>; + typedef mozilla::fallible_t fallible_t; + + public: + typedef typename KeyClass::KeyType KeyType; + typedef nsBaseHashtableET EntryType; + + using nsTHashtable::Contains; + using nsTHashtable::GetGeneration; + using nsTHashtable::SizeOfExcludingThis; + using nsTHashtable::SizeOfIncludingThis; + + nsBaseHashtable() = default; + explicit nsBaseHashtable(uint32_t aInitLength) + : nsTHashtable(aInitLength) {} + + /** + * Return the number of entries in the table. + * @return number of entries + */ + [[nodiscard]] uint32_t Count() const { + return nsTHashtable::Count(); + } + + /** + * Return whether the table is empty. + * @return whether empty + */ + [[nodiscard]] bool IsEmpty() const { + return nsTHashtable::IsEmpty(); + } + + /** + * Get the value, returning a flag indicating the presence of the entry in + * the table. + * + * @param aKey the key to retrieve + * @param aData data associated with this key will be placed at this pointer. + * If you only need to check if the key exists, aData may be null. + * @return true if the key exists. If key does not exist, aData is not + * modified. + * + * @attention As opposed to Remove, this does not assign a value to *aData if + * no entry is present! (And also as opposed to the member function Get with + * the same signature that nsClassHashtable defines and hides this one.) + */ + [[nodiscard]] bool Get(KeyType aKey, UserDataType* aData) const { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return false; + } + + if (aData) { + *aData = Converter::Unwrap(ent->mData); + } + + return true; + } + + /** + * Get the value, returning a zero-initialized POD or a default-initialized + * object if the entry is not present in the table. + * + * This overload can only be used if UserDataType is default-constructible. + * Use the double-argument Get or MaybeGet with non-default-constructible + * UserDataType. + * + * @param aKey the key to retrieve + * @return The found value, or UserDataType{} if no entry was found with the + * given key. + * @note If zero/default-initialized values are stored in the table, it is + * not possible to distinguish between such a value and a missing entry. + */ + [[nodiscard]] UserDataType Get(KeyType aKey) const { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return UserDataType{}; + } + + return Converter::Unwrap(ent->mData); + } + + /** + * Get the value, returning Nothing if the entry is not present in the table. + * + * @param aKey the key to retrieve + * @return The found value wrapped in a Maybe, or Nothing if no entry was + * found with the given key. + */ + [[nodiscard]] mozilla::Maybe MaybeGet(KeyType aKey) const { + EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return mozilla::Nothing(); + } + + return mozilla::Some(Converter::Unwrap(ent->mData)); + } + + using SmartPtrTraits = mozilla::detail::SmartPtrTraits; + + /** + * Looks up aKey in the hash table. If it doesn't exist a new object of + * SmartPtrTraits::PointeeType will be created (using the arguments provided) + * and then returned. + * + * \note This can only be instantiated if DataType is a smart pointer. + */ + template + auto GetOrInsertNew(KeyType aKey, Args&&... aConstructionArgs) { + static_assert( + SmartPtrTraits::IsSmartPointer, + "GetOrInsertNew can only be used with smart pointer data types"); + return mozilla::detail::PtrGetWeak(LookupOrInsertWith(std::move(aKey), [&] { + return SmartPtrTraits::template NewObject< + typename SmartPtrTraits::PointeeType>( + std::forward(aConstructionArgs)...); + })); + } + + /** + * Add aKey to the table if not already present, and return a reference to its + * value. If aKey is not already in the table then the a default-constructed + * or the provided value aData is used. + * + * If the arguments are non-trivial to provide, consider using + * LookupOrInsertWith instead. + */ + template + DataType& LookupOrInsert(const KeyType& aKey, Args&&... aArgs) { + return WithEntryHandle(aKey, [&](auto entryHandle) -> DataType& { + return entryHandle.OrInsert(std::forward(aArgs)...); + }); + } + + /** + * Add aKey to the table if not already present, and return a reference to its + * value. If aKey is not already in the table then the value is + * constructed using the given factory. + */ + template + DataType& LookupOrInsertWith(const KeyType& aKey, F&& aFunc) { + return WithEntryHandle(aKey, [&aFunc](auto entryHandle) -> DataType& { + return entryHandle.OrInsertWith(std::forward(aFunc)); + }); + } + + /** + * Add aKey to the table if not already present, and return a reference to its + * value. If aKey is not already in the table then the value is + * constructed using the given factory. + */ + template + [[nodiscard]] auto TryLookupOrInsertWith(const KeyType& aKey, F&& aFunc) { + return WithEntryHandle( + aKey, + [&aFunc](auto entryHandle) + -> mozilla::Result, + typename std::invoke_result_t::err_type> { + if (entryHandle) { + return std::ref(entryHandle.Data()); + } + + // XXX Use MOZ_TRY after generalizing QM_TRY to mfbt. + auto res = std::forward(aFunc)(); + if (res.isErr()) { + return res.propagateErr(); + } + return std::ref(entryHandle.Insert(res.unwrap())); + }); + } + + /** + * If it does not yet, inserts a new entry with the handle's key and the + * value passed to this function. Otherwise, it updates the entry by the + * value passed to this function. + * + * \tparam U DataType must be implicitly convertible (and assignable) from U + * \post HasEntry() + * \param aKey the key to put + * \param aData the new data + */ + template + DataType& InsertOrUpdate(KeyType aKey, U&& aData) { + return WithEntryHandle(aKey, [&aData](auto entryHandle) -> DataType& { + return entryHandle.InsertOrUpdate(std::forward(aData)); + }); + } + + template + [[nodiscard]] bool InsertOrUpdate(KeyType aKey, U&& aData, + const fallible_t& aFallible) { + return WithEntryHandle(aKey, aFallible, [&aData](auto maybeEntryHandle) { + if (!maybeEntryHandle) { + return false; + } + maybeEntryHandle->InsertOrUpdate(std::forward(aData)); + return true; + }); + } + + /** + * Remove the entry associated with aKey (if any), _moving_ its current value + * into *aData. Return true if found. + * + * This overload can only be used if DataType is default-constructible. Use + * the single-argument Remove or Extract with non-default-constructible + * DataType. + * + * @param aKey the key to remove from the hashtable + * @param aData where to move the value. If an entry is not found, *aData + * will be assigned a default-constructed value (i.e. reset to + * zero or nullptr for primitive types). + * @return true if an entry for aKey was found (and removed) + */ + // XXX This should also better be marked nodiscard, but due to + // nsClassHashtable not guaranteeing non-nullness of entries, it is usually + // only checked if aData is nullptr in such cases. + // [[nodiscard]] + bool Remove(KeyType aKey, DataType* aData) { + if (auto* ent = this->GetEntry(aKey)) { + if (aData) { + *aData = std::move(ent->mData); + } + this->RemoveEntry(ent); + return true; + } + if (aData) { + *aData = std::move(DataType()); + } + return false; + } + + /** + * Remove the entry associated with aKey (if any). Return true if found. + * + * @param aKey the key to remove from the hashtable + * @return true if an entry for aKey was found (and removed) + */ + bool Remove(KeyType aKey) { + if (auto* ent = this->GetEntry(aKey)) { + this->RemoveEntry(ent); + return true; + } + + return false; + } + + /** + * Retrieve the value for a key and remove the corresponding entry at + * the same time. + * + * @param aKey the key to retrieve and remove + * @return the found value, or Nothing if no entry was found with the + * given key. + */ + [[nodiscard]] mozilla::Maybe Extract(KeyType aKey) { + mozilla::Maybe value; + if (EntryType* ent = this->GetEntry(aKey)) { + value.emplace(std::move(ent->mData)); + this->RemoveEntry(ent); + } + return value; + } + + template + struct LookupResult { + private: + EntryType* mEntry; + HashtableRef mTable; +#ifdef DEBUG + uint32_t mTableGeneration; +#endif + + public: + LookupResult(EntryType* aEntry, HashtableRef aTable) + : mEntry(aEntry), + mTable(aTable) +#ifdef DEBUG + , + mTableGeneration(aTable.GetGeneration()) +#endif + { + } + + // Is there something stored in the table? + explicit operator bool() const { + MOZ_ASSERT(mTableGeneration == mTable.GetGeneration()); + return mEntry; + } + + void Remove() { + if (!*this) { + return; + } + mTable.RemoveEntry(mEntry); + mEntry = nullptr; + } + + [[nodiscard]] DataType& Data() { + MOZ_ASSERT(!!*this, "must have an entry to access its value"); + return mEntry->mData; + } + + [[nodiscard]] const DataType& Data() const { + MOZ_ASSERT(!!*this, "must have an entry to access its value"); + return mEntry->mData; + } + + [[nodiscard]] DataType* DataPtrOrNull() { + return static_cast(*this) ? &mEntry->mData : nullptr; + } + + [[nodiscard]] const DataType* DataPtrOrNull() const { + return static_cast(*this) ? &mEntry->mData : nullptr; + } + + [[nodiscard]] DataType* operator->() { return &Data(); } + [[nodiscard]] const DataType* operator->() const { return &Data(); } + + [[nodiscard]] DataType& operator*() { return Data(); } + [[nodiscard]] const DataType& operator*() const { return Data(); } + }; + + /** + * Removes all entries matching a predicate. + * + * The predicate must be compatible with signature bool (const Iterator &). + */ + template + void RemoveIf(Pred&& aPred) { + for (auto iter = Iter(); !iter.Done(); iter.Next()) { + if (aPred(const_cast&>(iter))) { + iter.Remove(); + } + } + } + + /** + * Looks up aKey in the hashtable and returns an object that allows you to + * read/modify the value of the entry, or remove the entry (if found). + * + * A typical usage of this API looks like this: + * + * if (auto entry = hashtable.Lookup(key)) { + * DoSomething(entry.Data()); + * if (entry.Data() > 42) { + * entry.Remove(); + * } + * } // else - an entry with the given key doesn't exist + * + * This is useful for cases where you want to read/write the value of an entry + * and (optionally) remove the entry without having to do multiple hashtable + * lookups. If you want to insert a new entry if one does not exist, then use + * WithEntryHandle instead, see below. + */ + [[nodiscard]] auto Lookup(KeyType aKey) { + return LookupResult(this->GetEntry(aKey), *this); + } + + [[nodiscard]] auto Lookup(KeyType aKey) const { + return LookupResult(this->GetEntry(aKey), *this); + } + + /** + * Used by WithEntryHandle as the argument type to its functor. It is + * associated with the Key passed to WithEntryHandle and manages only the + * potential entry with that key. Note that in case no modifying operations + * are called on the handle, the state of the hashtable remains unchanged, + * i.e. WithEntryHandle does not modify the hashtable itself. + * + * Provides query functions (Key, HasEntry/operator bool, Data) and + * modifying operations for inserting new entries (Insert), updating existing + * entries (Update) and removing existing entries (Remove). They have + * debug-only assertion that fail when the state of the entry doesn't match + * the expectation. There are variants prefixed with "Or" (OrInsert, OrUpdate, + * OrRemove) that are a no-op in case the entry does already exist resp. does + * not exist. There are also variants OrInsertWith and OrUpdateWith that don't + * accept a value, but a functor, which is only called if the operation takes + * place, which should be used if the provision of the value is not trivial + * (e.g. allocates a heap object). Finally, there's InsertOrUpdate that + * handles both existing and non-existing entries. + * + * Note that all functions of EntryHandle only deal with DataType, not with + * UserDataType. + */ + class EntryHandle : protected nsTHashtable::EntryHandle { + public: + using Base = typename nsTHashtable::EntryHandle; + + EntryHandle(EntryHandle&& aOther) = default; + ~EntryHandle() = default; + + EntryHandle(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&&) = delete; + + using Base::Key; + + using Base::HasEntry; + + using Base::operator bool; + + using Base::Entry; + + /** + * Inserts a new entry with the handle's key and the value passed to this + * function. + * + * \tparam Args DataType must be constructible from Args + * \pre !HasEntry() + * \post HasEntry() + */ + template + DataType& Insert(Args&&... aArgs) { + Base::InsertInternal(std::forward(aArgs)...); + return Data(); + } + + /** + * If it doesn't yet exist, inserts a new entry with the handle's key and + * the value passed to this function. The value is not consumed if no insert + * takes place. + * + * \tparam Args DataType must be constructible from Args + * \post HasEntry() + */ + template + DataType& OrInsert(Args&&... aArgs) { + if (!HasEntry()) { + return Insert(std::forward(aArgs)...); + } + return Data(); + } + + /** + * If it doesn't yet exist, inserts a new entry with the handle's key and + * the result of the functor passed to this function. The functor is not + * called if no insert takes place. + * + * \tparam F must return a value that is implicitly convertible to DataType + * \post HasEntry() + */ + template + DataType& OrInsertWith(F&& aFunc) { + if (!HasEntry()) { + return Insert(std::forward(aFunc)()); + } + return Data(); + } + + /** + * Updates the entry with the handle's key by the value passed to this + * function. + * + * \tparam U DataType must be assignable from U + * \pre HasEntry() + */ + template + DataType& Update(U&& aData) { + MOZ_RELEASE_ASSERT(HasEntry()); + Data() = std::forward(aData); + return Data(); + } + + /** + * If an entry with the handle's key already exists, updates its value by + * the value passed to this function. The value is not consumed if no update + * takes place. + * + * \tparam U DataType must be assignable from U + */ + template + void OrUpdate(U&& aData) { + if (HasEntry()) { + Update(std::forward(aData)); + } + } + + /** + * If an entry with the handle's key already exists, updates its value by + * the the result of the functor passed to this function. The functor is not + * called if no update takes place. + * + * \tparam F must return a value that DataType is assignable from + */ + template + void OrUpdateWith(F&& aFunc) { + if (HasEntry()) { + Update(std::forward(aFunc)()); + } + } + + /** + * If it does not yet, inserts a new entry with the handle's key and the + * value passed to this function. Otherwise, it updates the entry by the + * value passed to this function. + * + * \tparam U DataType must be implicitly convertible (and assignable) from U + * \post HasEntry() + */ + template + DataType& InsertOrUpdate(U&& aData) { + if (!HasEntry()) { + Insert(std::forward(aData)); + } else { + Update(std::forward(aData)); + } + return Data(); + } + + using Base::Remove; + + using Base::OrRemove; + + /** + * Returns a reference to the value of the entry. + * + * \pre HasEntry() + */ + [[nodiscard]] DataType& Data() { return Entry()->mData; } + + [[nodiscard]] DataType* DataPtrOrNull() { + return static_cast(*this) ? &Data() : nullptr; + } + + [[nodiscard]] DataType* operator->() { return &Data(); } + + [[nodiscard]] DataType& operator*() { return Data(); } + + private: + friend class nsBaseHashtable; + + explicit EntryHandle(Base&& aBase) : Base(std::move(aBase)) {} + }; + + /** + * Performs a scoped operation on the entry for aKey, which may or may not + * exist when the function is called. It calls aFunc with an EntryHandle. The + * result of aFunc is returned as the result of this function. Its return type + * may be void. See the documentation of EntryHandle for the query and + * modifying operations it offers. + * + * A simple use of this function is, e.g., + * + * hashtable.WithEntryHandle(key, [](auto&& entry) { entry.OrInsert(42); }); + * + * \attention It is not safe to perform modifying operations on the hashtable + * other than through the EntryHandle within aFunc, and trying to do so will + * trigger debug assertions, and result in undefined behaviour otherwise. + */ + template + [[nodiscard]] auto WithEntryHandle(KeyType aKey, F&& aFunc) + -> std::invoke_result_t { + return Base::WithEntryHandle( + aKey, [&aFunc](auto entryHandle) -> decltype(auto) { + return std::forward(aFunc)(EntryHandle{std::move(entryHandle)}); + }); + } + + /** + * Fallible variant of WithEntryHandle, with the following differences: + * - The functor aFunc must accept a Maybe (instead of an + * EntryHandle). + * - In case allocation of the slot for the entry fails, Nothing is passed to + * the functor. + * + * For more details, see the explanation on the non-fallible overload above. + */ + template + [[nodiscard]] auto WithEntryHandle(KeyType aKey, const fallible_t& aFallible, + F&& aFunc) + -> std::invoke_result_t&&> { + return Base::WithEntryHandle( + aKey, aFallible, [&aFunc](auto maybeEntryHandle) { + return std::forward(aFunc)( + maybeEntryHandle + ? mozilla::Some(EntryHandle{maybeEntryHandle.extract()}) + : mozilla::Nothing()); + }); + } + + public: + class ConstIterator { + public: + explicit ConstIterator(nsBaseHashtable* aTable) + : mBaseIterator(&aTable->mTable) {} + ~ConstIterator() = default; + + KeyType Key() const { + return static_cast(mBaseIterator.Get())->GetKey(); + } + UserDataType UserData() const { + return Converter::Unwrap( + static_cast(mBaseIterator.Get())->mData); + } + const DataType& Data() const { + return static_cast(mBaseIterator.Get())->mData; + } + + bool Done() const { return mBaseIterator.Done(); } + void Next() { mBaseIterator.Next(); } + + ConstIterator() = delete; + ConstIterator(const ConstIterator&) = delete; + ConstIterator(ConstIterator&& aOther) = delete; + ConstIterator& operator=(const ConstIterator&) = delete; + ConstIterator& operator=(ConstIterator&&) = delete; + + protected: + PLDHashTable::Iterator mBaseIterator; + }; + + // This is an iterator that also allows entry removal. Example usage: + // + // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // const KeyType key = iter.Key(); + // const UserDataType data = iter.UserData(); + // // or + // const DataType& data = iter.Data(); + // // ... do stuff with |key| and/or |data| ... + // // ... possibly call iter.Remove() once ... + // } + // + class Iterator final : public ConstIterator { + public: + using ConstIterator::ConstIterator; + + using ConstIterator::Data; + DataType& Data() { + return static_cast(this->mBaseIterator.Get())->mData; + } + + void Remove() { this->mBaseIterator.Remove(); } + }; + + Iterator Iter() { return Iterator(this); } + + ConstIterator ConstIter() const { + return ConstIterator(const_cast(this)); + } + + using nsTHashtable::Remove; + + /** + * Remove the entry associated with aIter. + * + * @param aIter the iterator pointing to the entry + * @pre !aIter.Done() + */ + void Remove(ConstIterator& aIter) { aIter.mBaseIterator.Remove(); } + + using typename nsTHashtable::iterator; + using typename nsTHashtable::const_iterator; + + using nsTHashtable::begin; + using nsTHashtable::end; + using nsTHashtable::cbegin; + using nsTHashtable::cend; + + using nsTHashtable::Keys; + + /** + * Return a range of the values (of DataType). Note this range iterates over + * the values in place, so modifications to the nsTHashtable invalidate the + * range while it's iterated, except when calling Remove() with a value + * iterator derived from that range. + */ + auto Values() const { + return mozilla::detail::nsBaseHashtableValueRange{this->mTable}; + } + + /** + * Remove an entry from a value range, specified via a value iterator, e.g. + * + * for (auto it = hash.Values().begin(), end = hash.Values().end(); + * it != end; * ++it) { + * if (*it > 42) { hash.Remove(it); } + * } + * + * You might also consider using RemoveIf though. + */ + void Remove(mozilla::detail::nsBaseHashtableValueIterator& aIter) { + aIter.mIterator.Remove(); + } + + /** + * reset the hashtable, removing all entries + */ + void Clear() { nsTHashtable::Clear(); } + + /** + * Measure the size of the table's entry storage. The size of things pointed + * to by entries must be measured separately; hence the "Shallow" prefix. + * + * @param aMallocSizeOf the function used to measure heap-allocated blocks + * @return the summed size of the table's storage + */ + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return this->mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Like ShallowSizeOfExcludingThis, but includes sizeof(*this). + */ + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Swap the elements in this hashtable with the elements in aOther. + */ + void SwapElements(nsBaseHashtable& aOther) { + nsTHashtable::SwapElements(aOther); + } + + using nsTHashtable::MarkImmutable; + + /** + * Makes a clone of this hashtable by copying all entries. This requires + * KeyType and DataType to be copy-constructible. + */ + nsBaseHashtable Clone() const { return CloneAs(); } + + protected: + template + T CloneAs() const { + static_assert(std::is_base_of_v); + // XXX This can probably be optimized, see Bug 1694368. + T result(Count()); + for (const auto& srcEntry : *this) { + result.WithEntryHandle(srcEntry.GetKey(), [&](auto&& dstEntry) { + dstEntry.Insert(srcEntry.GetData()); + }); + } + return result; + } +}; + +// +// nsBaseHashtableET definitions +// + +template +template +nsBaseHashtableET::nsBaseHashtableET(KeyTypePointer aKey, + Args&&... aArgs) + : KeyClass(aKey), mData(std::forward(aArgs)...) {} + +#endif // nsBaseHashtable_h__ diff --git a/xpcom/ds/nsCOMArray.cpp b/xpcom/ds/nsCOMArray.cpp new file mode 100644 index 0000000000..d388857573 --- /dev/null +++ b/xpcom/ds/nsCOMArray.cpp @@ -0,0 +1,252 @@ +/* -*- 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 "nsCOMArray.h" + +#include "mozilla/MemoryReporting.h" +#include "mozilla/OperatorNewExtensions.h" +#include "nsQuickSort.h" + +#include "nsCOMPtr.h" + +// This specialization is private to nsCOMArray. +// It exists solely to automatically zero-out newly created array elements. +template <> +class nsTArrayElementTraits { + typedef nsISupports* E; + + public: + // Zero out the value + static inline void Construct(E* aE) { + new (mozilla::KnownNotNull, static_cast(aE)) E(); + } + // Invoke the copy-constructor in place. + template + static inline void Construct(E* aE, const A& aArg) { + new (mozilla::KnownNotNull, static_cast(aE)) E(aArg); + } + // Construct in place. + template + static inline void Emplace(E* aE, Args&&... aArgs) { + new (mozilla::KnownNotNull, static_cast(aE)) + E(std::forward(aArgs)...); + } + // Invoke the destructor in place. + static inline void Destruct(E* aE) { aE->~E(); } +}; + +static void ReleaseObjects(nsTArray& aArray); + +// implementations of non-trivial methods in nsCOMArray_base + +nsCOMArray_base::nsCOMArray_base(const nsCOMArray_base& aOther) { + // make sure we do only one allocation + mArray.SetCapacity(aOther.Count()); + AppendObjects(aOther); +} + +nsCOMArray_base::~nsCOMArray_base() { Clear(); } + +int32_t nsCOMArray_base::IndexOf(nsISupports* aObject, + uint32_t aStartIndex) const { + return mArray.IndexOf(aObject, aStartIndex); +} + +int32_t nsCOMArray_base::IndexOfObject(nsISupports* aObject) const { + nsCOMPtr supports = do_QueryInterface(aObject); + if (NS_WARN_IF(!supports)) { + return -1; + } + + uint32_t i, count; + int32_t retval = -1; + count = mArray.Length(); + for (i = 0; i < count; ++i) { + nsCOMPtr arrayItem = do_QueryInterface(mArray[i]); + if (arrayItem == supports) { + retval = i; + break; + } + } + return retval; +} + +bool nsCOMArray_base::EnumerateForwards(nsBaseArrayEnumFunc aFunc, + void* aData) const { + for (uint32_t index = 0; index < mArray.Length(); ++index) { + if (!(*aFunc)(mArray[index], aData)) { + return false; + } + } + + return true; +} + +bool nsCOMArray_base::EnumerateBackwards(nsBaseArrayEnumFunc aFunc, + void* aData) const { + for (uint32_t index = mArray.Length(); index--;) { + if (!(*aFunc)(mArray[index], aData)) { + return false; + } + } + + return true; +} + +int nsCOMArray_base::VoidStarComparator(const void* aElement1, + const void* aElement2, void* aData) { + auto ctx = static_cast(aData); + return (*ctx->mComparatorFunc)(*static_cast(aElement1), + *static_cast(aElement2), + ctx->mData); +} + +void nsCOMArray_base::Sort(nsISupportsComparatorFunc aFunc, void* aData) { + if (mArray.Length() > 1) { + nsISupportsComparatorContext ctx = {aFunc, aData}; + NS_QuickSort(mArray.Elements(), mArray.Length(), sizeof(nsISupports*), + VoidStarComparator, &ctx); + } +} + +bool nsCOMArray_base::InsertObjectAt(nsISupports* aObject, int32_t aIndex) { + if ((uint32_t)aIndex > mArray.Length()) { + return false; + } + + mArray.InsertElementAt(aIndex, aObject); + + NS_IF_ADDREF(aObject); + return true; +} + +void nsCOMArray_base::InsertElementAt(uint32_t aIndex, nsISupports* aElement) { + mArray.InsertElementAt(aIndex, aElement); + NS_IF_ADDREF(aElement); +} + +void nsCOMArray_base::InsertElementAt(uint32_t aIndex, + already_AddRefed aElement) { + mArray.InsertElementAt(aIndex, aElement.take()); +} + +bool nsCOMArray_base::InsertObjectsAt(const nsCOMArray_base& aObjects, + int32_t aIndex) { + if ((uint32_t)aIndex > mArray.Length()) { + return false; + } + + mArray.InsertElementsAt(aIndex, aObjects.mArray); + + // need to addref all these + uint32_t count = aObjects.Length(); + for (uint32_t i = 0; i < count; ++i) { + NS_IF_ADDREF(aObjects[i]); + } + + return true; +} + +void nsCOMArray_base::InsertElementsAt(uint32_t aIndex, + const nsCOMArray_base& aElements) { + mArray.InsertElementsAt(aIndex, aElements.mArray); + + // need to addref all these + uint32_t count = aElements.Length(); + for (uint32_t i = 0; i < count; ++i) { + NS_IF_ADDREF(aElements[i]); + } +} + +void nsCOMArray_base::InsertElementsAt(uint32_t aIndex, + nsISupports* const* aElements, + uint32_t aCount) { + mArray.InsertElementsAt(aIndex, aElements, aCount); + + // need to addref all these + for (uint32_t i = 0; i < aCount; ++i) { + NS_IF_ADDREF(aElements[i]); + } +} + +void nsCOMArray_base::ReplaceObjectAt(nsISupports* aObject, int32_t aIndex) { + mArray.EnsureLengthAtLeast(aIndex + 1); + nsISupports* oldObject = mArray[aIndex]; + // Make sure to addref first, in case aObject == oldObject + NS_IF_ADDREF(mArray[aIndex] = aObject); + NS_IF_RELEASE(oldObject); +} + +bool nsCOMArray_base::RemoveObject(nsISupports* aObject) { + bool result = mArray.RemoveElement(aObject); + if (result) { + NS_IF_RELEASE(aObject); + } + return result; +} + +bool nsCOMArray_base::RemoveObjectAt(int32_t aIndex) { + if (uint32_t(aIndex) < mArray.Length()) { + nsISupports* element = mArray[aIndex]; + + mArray.RemoveElementAt(aIndex); + NS_IF_RELEASE(element); + return true; + } + + return false; +} + +void nsCOMArray_base::RemoveElementAt(uint32_t aIndex) { + nsISupports* element = mArray[aIndex]; + mArray.RemoveElementAt(aIndex); + NS_IF_RELEASE(element); +} + +bool nsCOMArray_base::RemoveObjectsAt(int32_t aIndex, int32_t aCount) { + if (uint32_t(aIndex) + uint32_t(aCount) <= mArray.Length()) { + nsTArray elementsToDestroy(aCount); + elementsToDestroy.AppendElements(mArray.Elements() + aIndex, aCount); + mArray.RemoveElementsAt(aIndex, aCount); + ReleaseObjects(elementsToDestroy); + return true; + } + + return false; +} + +void nsCOMArray_base::RemoveElementsAt(uint32_t aIndex, uint32_t aCount) { + nsTArray elementsToDestroy(aCount); + elementsToDestroy.AppendElements(mArray.Elements() + aIndex, aCount); + mArray.RemoveElementsAt(aIndex, aCount); + ReleaseObjects(elementsToDestroy); +} + +// useful for destructors +void ReleaseObjects(nsTArray& aArray) { + for (uint32_t i = 0; i < aArray.Length(); ++i) { + NS_IF_RELEASE(aArray[i]); + } +} + +void nsCOMArray_base::Clear() { + nsTArray objects = std::move(mArray); + ReleaseObjects(objects); +} + +bool nsCOMArray_base::SetCount(int32_t aNewCount) { + NS_ASSERTION(aNewCount >= 0, "SetCount(negative index)"); + if (aNewCount < 0) { + return false; + } + + int32_t count = mArray.Length(); + if (count > aNewCount) { + RemoveObjectsAt(aNewCount, mArray.Length() - aNewCount); + } + mArray.SetLength(aNewCount); + return true; +} diff --git a/xpcom/ds/nsCOMArray.h b/xpcom/ds/nsCOMArray.h new file mode 100644 index 0000000000..0ea1437f25 --- /dev/null +++ b/xpcom/ds/nsCOMArray.h @@ -0,0 +1,398 @@ +/* -*- 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/. */ + +#ifndef nsCOMArray_h__ +#define nsCOMArray_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/ArrayIterator.h" +#include "mozilla/MemoryReporting.h" + +#include "nsCycleCollectionNoteChild.h" +#include "nsTArray.h" +#include "nsISupports.h" + +#include + +// See below for the definition of nsCOMArray + +// a class that's nsISupports-specific, so that we can contain the +// work of this class in the XPCOM dll +class nsCOMArray_base { + friend class nsArrayBase; + + protected: + nsCOMArray_base() = default; + explicit nsCOMArray_base(int32_t aCount) : mArray(aCount) {} + nsCOMArray_base(const nsCOMArray_base& aOther); + nsCOMArray_base(nsCOMArray_base&& aOther) = default; + nsCOMArray_base& operator=(nsCOMArray_base&& aOther) = default; + ~nsCOMArray_base(); + + int32_t IndexOf(nsISupports* aObject, uint32_t aStartIndex = 0) const; + bool Contains(nsISupports* aObject) const { return IndexOf(aObject) != -1; } + + int32_t IndexOfObject(nsISupports* aObject) const; + bool ContainsObject(nsISupports* aObject) const { + return IndexOfObject(aObject) != -1; + } + + typedef bool (*nsBaseArrayEnumFunc)(void* aElement, void* aData); + + // enumerate through the array with a callback. + bool EnumerateForwards(nsBaseArrayEnumFunc aFunc, void* aData) const; + + bool EnumerateBackwards(nsBaseArrayEnumFunc aFunc, void* aData) const; + + typedef int (*nsISupportsComparatorFunc)(nsISupports* aElement1, + nsISupports* aElement2, void* aData); + + struct nsISupportsComparatorContext { + nsISupportsComparatorFunc mComparatorFunc; + void* mData; + }; + + static int VoidStarComparator(const void* aElement1, const void* aElement2, + void* aData); + void Sort(nsISupportsComparatorFunc aFunc, void* aData); + + bool InsertObjectAt(nsISupports* aObject, int32_t aIndex); + void InsertElementAt(uint32_t aIndex, nsISupports* aElement); + void InsertElementAt(uint32_t aIndex, already_AddRefed aElement); + bool InsertObjectsAt(const nsCOMArray_base& aObjects, int32_t aIndex); + void InsertElementsAt(uint32_t aIndex, const nsCOMArray_base& aElements); + void InsertElementsAt(uint32_t aIndex, nsISupports* const* aElements, + uint32_t aCount); + void ReplaceObjectAt(nsISupports* aObject, int32_t aIndex); + void ReplaceElementAt(uint32_t aIndex, nsISupports* aElement) { + nsISupports* oldElement = mArray[aIndex]; + NS_IF_ADDREF(mArray[aIndex] = aElement); + NS_IF_RELEASE(oldElement); + } + bool AppendObject(nsISupports* aObject) { + return InsertObjectAt(aObject, Count()); + } + void AppendElement(nsISupports* aElement) { + InsertElementAt(Length(), aElement); + } + void AppendElement(already_AddRefed aElement) { + InsertElementAt(Length(), std::move(aElement)); + } + + bool AppendObjects(const nsCOMArray_base& aObjects) { + return InsertObjectsAt(aObjects, Count()); + } + void AppendElements(const nsCOMArray_base& aElements) { + return InsertElementsAt(Length(), aElements); + } + void AppendElements(nsISupports* const* aElements, uint32_t aCount) { + return InsertElementsAt(Length(), aElements, aCount); + } + bool RemoveObject(nsISupports* aObject); + nsISupports** Elements() { return mArray.Elements(); } + void SwapElements(nsCOMArray_base& aOther) { + mArray.SwapElements(aOther.mArray); + } + + public: + // elements in the array (including null elements!) + int32_t Count() const { return mArray.Length(); } + // nsTArray-compatible version + uint32_t Length() const { return mArray.Length(); } + bool IsEmpty() const { return mArray.IsEmpty(); } + + // If the array grows, the newly created entries will all be null; + // if the array shrinks, the excess entries will all be released. + bool SetCount(int32_t aNewCount); + // nsTArray-compatible version + void TruncateLength(uint32_t aNewLength) { + if (mArray.Length() > aNewLength) { + RemoveElementsAt(aNewLength, mArray.Length() - aNewLength); + } + } + + // remove all elements in the array, and call NS_RELEASE on each one + void Clear(); + + nsISupports* ObjectAt(int32_t aIndex) const { return mArray[aIndex]; } + // nsTArray-compatible version + nsISupports* ElementAt(uint32_t aIndex) const { return mArray[aIndex]; } + + nsISupports* SafeObjectAt(int32_t aIndex) const { + return mArray.SafeElementAt(aIndex, nullptr); + } + // nsTArray-compatible version + nsISupports* SafeElementAt(uint32_t aIndex) const { + return mArray.SafeElementAt(aIndex, nullptr); + } + + nsISupports* operator[](int32_t aIndex) const { return mArray[aIndex]; } + + // remove an element at a specific position, shrinking the array + // as necessary + bool RemoveObjectAt(int32_t aIndex); + // nsTArray-compatible version + void RemoveElementAt(uint32_t aIndex); + + // remove a range of elements at a specific position, shrinking the array + // as necessary + bool RemoveObjectsAt(int32_t aIndex, int32_t aCount); + // nsTArray-compatible version + void RemoveElementsAt(uint32_t aIndex, uint32_t aCount); + + void SwapElementsAt(uint32_t aIndex1, uint32_t aIndex2) { + nsISupports* tmp = mArray[aIndex1]; + mArray[aIndex1] = mArray[aIndex2]; + mArray[aIndex2] = tmp; + } + + // Ensures there is enough space to store a total of aCapacity objects. + // This method never deletes any objects. + void SetCapacity(uint32_t aCapacity) { mArray.SetCapacity(aCapacity); } + uint32_t Capacity() { return mArray.Capacity(); } + + // Measures the size of the array's element storage. If you want to measure + // anything hanging off the array, you must iterate over the elements and + // measure them individually; hence the "Shallow" prefix. Note that because + // each element in an nsCOMArray is actually a T* any such iteration + // should use a SizeOfIncludingThis() function on each element rather than a + // SizeOfExcludingThis() function, so that the memory taken by the T itself + // is included as well as anything it points to. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + private: + // the actual storage + nsTArray mArray; + + // don't implement these, defaults will muck with refcounts! + nsCOMArray_base& operator=(const nsCOMArray_base& aOther) = delete; +}; + +inline void ImplCycleCollectionUnlink(nsCOMArray_base& aField) { + aField.Clear(); +} + +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, nsCOMArray_base& aField, + const char* aName, uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + int32_t length = aField.Count(); + for (int32_t i = 0; i < length; ++i) { + CycleCollectionNoteChild(aCallback, aField[i], aName, aFlags); + } +} + +// a non-XPCOM, refcounting array of XPCOM objects +// used as a member variable or stack variable - this object is NOT +// refcounted, but the objects that it holds are +// +// most of the read-only accessors like ObjectAt()/etc do NOT refcount +// on the way out. This means that you can do one of two things: +// +// * does an addref, but holds onto a reference +// nsCOMPtr foo = array[i]; +// +// * avoids the refcount, but foo might go stale if array[i] is ever +// * modified/removed. Be careful not to NS_RELEASE(foo)! +// T* foo = array[i]; +// +// This array will accept null as an argument for any object, and will store +// null in the array. But that also means that methods like ObjectAt() may +// return null when referring to an existing, but null entry in the array. +template +class nsCOMArray : public nsCOMArray_base { + public: + typedef int32_t index_type; + typedef mozilla::ArrayIterator iterator; + typedef mozilla::ArrayIterator const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + nsCOMArray() = default; + explicit nsCOMArray(int32_t aCount) : nsCOMArray_base(aCount) {} + explicit nsCOMArray(const nsCOMArray& aOther) : nsCOMArray_base(aOther) {} + nsCOMArray(nsCOMArray&& aOther) = default; + ~nsCOMArray() = default; + + // We have a move assignment operator, but no copy assignment operator. + nsCOMArray& operator=(nsCOMArray&& aOther) = default; + + // these do NOT refcount on the way out, for speed + T* ObjectAt(int32_t aIndex) const { + return static_cast(nsCOMArray_base::ObjectAt(aIndex)); + } + // nsTArray-compatible version + T* ElementAt(uint32_t aIndex) const { + return static_cast(nsCOMArray_base::ElementAt(aIndex)); + } + + // these do NOT refcount on the way out, for speed + T* SafeObjectAt(int32_t aIndex) const { + return static_cast(nsCOMArray_base::SafeObjectAt(aIndex)); + } + // nsTArray-compatible version + T* SafeElementAt(uint32_t aIndex) const { + return static_cast(nsCOMArray_base::SafeElementAt(aIndex)); + } + + // indexing operator for syntactic sugar + T* operator[](int32_t aIndex) const { return ObjectAt(aIndex); } + + // index of the element in question.. does NOT refcount + // note: this does not check COM object identity. Use + // IndexOfObject() for that purpose + int32_t IndexOf(T* aObject, uint32_t aStartIndex = 0) const { + return nsCOMArray_base::IndexOf(aObject, aStartIndex); + } + bool Contains(T* aObject) const { return nsCOMArray_base::Contains(aObject); } + + // index of the element in question.. be careful! + // this is much slower than IndexOf() because it uses + // QueryInterface to determine actual COM identity of the object + // if you need to do this frequently then consider enforcing + // COM object identity before adding/comparing elements + int32_t IndexOfObject(T* aObject) const { + return nsCOMArray_base::IndexOfObject(aObject); + } + bool ContainsObject(nsISupports* aObject) const { + return nsCOMArray_base::ContainsObject(aObject); + } + + // inserts aObject at aIndex, shifting the objects at aIndex and + // later to make space + bool InsertObjectAt(T* aObject, int32_t aIndex) { + return nsCOMArray_base::InsertObjectAt(aObject, aIndex); + } + // nsTArray-compatible version + void InsertElementAt(uint32_t aIndex, T* aElement) { + nsCOMArray_base::InsertElementAt(aIndex, aElement); + } + + // inserts the objects from aObject at aIndex, shifting the + // objects at aIndex and later to make space + bool InsertObjectsAt(const nsCOMArray& aObjects, int32_t aIndex) { + return nsCOMArray_base::InsertObjectsAt(aObjects, aIndex); + } + // nsTArray-compatible version + void InsertElementsAt(uint32_t aIndex, const nsCOMArray& aElements) { + nsCOMArray_base::InsertElementsAt(aIndex, aElements); + } + void InsertElementsAt(uint32_t aIndex, T* const* aElements, uint32_t aCount) { + nsCOMArray_base::InsertElementsAt( + aIndex, reinterpret_cast(aElements), aCount); + } + + // replaces an existing element. Warning: if the array grows, + // the newly created entries will all be null + void ReplaceObjectAt(T* aObject, int32_t aIndex) { + nsCOMArray_base::ReplaceObjectAt(aObject, aIndex); + } + // nsTArray-compatible version + void ReplaceElementAt(uint32_t aIndex, T* aElement) { + nsCOMArray_base::ReplaceElementAt(aIndex, aElement); + } + + typedef int (*TComparatorFunc)(T* aElement1, T* aElement2, void* aData); + + struct TComparatorContext { + TComparatorFunc mComparatorFunc; + void* mData; + }; + + static int nsISupportsComparator(nsISupports* aElement1, + nsISupports* aElement2, void* aData) { + auto ctx = static_cast(aData); + return (*ctx->mComparatorFunc)(static_cast(aElement1), + static_cast(aElement2), ctx->mData); + } + + void Sort(TComparatorFunc aFunc, void* aData) { + TComparatorContext ctx = {aFunc, aData}; + nsCOMArray_base::Sort(nsISupportsComparator, &ctx); + } + + // append an object, growing the array as necessary + bool AppendObject(T* aObject) { + return nsCOMArray_base::AppendObject(aObject); + } + // nsTArray-compatible version + void AppendElement(T* aElement) { nsCOMArray_base::AppendElement(aElement); } + void AppendElement(already_AddRefed aElement) { + nsCOMArray_base::AppendElement(std::move(aElement)); + } + + // append objects, growing the array as necessary + bool AppendObjects(const nsCOMArray& aObjects) { + return nsCOMArray_base::AppendObjects(aObjects); + } + // nsTArray-compatible version + void AppendElements(const nsCOMArray& aElements) { + return nsCOMArray_base::AppendElements(aElements); + } + void AppendElements(T* const* aElements, uint32_t aCount) { + InsertElementsAt(Length(), aElements, aCount); + } + + // remove the first instance of the given object and shrink the + // array as necessary + // Warning: if you pass null here, it will remove the first null element + bool RemoveObject(T* aObject) { + return nsCOMArray_base::RemoveObject(aObject); + } + // nsTArray-compatible version + bool RemoveElement(T* aElement) { + return nsCOMArray_base::RemoveObject(aElement); + } + + T** Elements() { return reinterpret_cast(nsCOMArray_base::Elements()); } + void SwapElements(nsCOMArray& aOther) { + nsCOMArray_base::SwapElements(aOther); + } + + // Methods for range-based for loops. + iterator begin() { return iterator(*this, 0); } + const_iterator begin() const { return const_iterator(*this, 0); } + const_iterator cbegin() const { return begin(); } + iterator end() { return iterator(*this, Length()); } + const_iterator end() const { return const_iterator(*this, Length()); } + const_iterator cend() const { return end(); } + + // Methods for reverse iterating. + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(end()); + } + const_reverse_iterator crbegin() const { return rbegin(); } + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { + return const_reverse_iterator(begin()); + } + const_reverse_iterator crend() const { return rend(); } + + private: + // don't implement these! + nsCOMArray& operator=(const nsCOMArray& aOther) = delete; +}; + +template +inline void ImplCycleCollectionUnlink(nsCOMArray& aField) { + aField.Clear(); +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, nsCOMArray& aField, + const char* aName, uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + int32_t length = aField.Count(); + for (int32_t i = 0; i < length; ++i) { + CycleCollectionNoteChild(aCallback, aField[i], aName, aFlags); + } +} + +#endif diff --git a/xpcom/ds/nsCRT.cpp b/xpcom/ds/nsCRT.cpp new file mode 100644 index 0000000000..9d2fcb7120 --- /dev/null +++ b/xpcom/ds/nsCRT.cpp @@ -0,0 +1,123 @@ +/* -*- 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/. */ + +/** + * MODULE NOTES: + * @update gess7/30/98 + * + * Much as I hate to do it, we were using string compares wrong. + * Often, programmers call functions like strcmp(s1,s2), and pass + * one or more null strings. Rather than blow up on these, I've + * added quick checks to ensure that cases like this don't cause + * us to fail. + * + * In general, if you pass a null into any of these string compare + * routines, we simply return 0. + */ + +#include "nsCRT.h" +#include "nsDebug.h" + +//---------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +// My lovely strtok routine + +#define IS_DELIM(m, c) ((m)[(c) >> 3] & (1 << ((c)&7))) +#define SET_DELIM(m, c) ((m)[(c) >> 3] |= (1 << ((c)&7))) +#define DELIM_TABLE_SIZE 32 + +char* nsCRT::strtok(char* aString, const char* aDelims, char** aNewStr) { + NS_ASSERTION(aString, + "Unlike regular strtok, the first argument cannot be null."); + + char delimTable[DELIM_TABLE_SIZE]; + uint32_t i; + char* result; + char* str = aString; + + for (i = 0; i < DELIM_TABLE_SIZE; ++i) { + delimTable[i] = '\0'; + } + + for (i = 0; aDelims[i]; i++) { + SET_DELIM(delimTable, static_cast(aDelims[i])); + } + NS_ASSERTION(aDelims[i] == '\0', "too many delimiters"); + + // skip to beginning + while (*str && IS_DELIM(delimTable, static_cast(*str))) { + str++; + } + result = str; + + // fix up the end of the token + while (*str) { + if (IS_DELIM(delimTable, static_cast(*str))) { + *str++ = '\0'; + break; + } + str++; + } + *aNewStr = str; + + return str == result ? nullptr : result; +} + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Compare unichar string ptrs, stopping at the 1st null + * NOTE: If both are null, we return 0. + * NOTE: We terminate the search upon encountering a nullptr + * + * @update gess 11/10/99 + * @param s1 and s2 both point to unichar strings + * @return 0 if they match, -1 if s1s2 + */ +int32_t nsCRT::strcmp(const char16_t* aStr1, const char16_t* aStr2) { + if (aStr1 && aStr2) { + for (;;) { + char16_t c1 = *aStr1++; + char16_t c2 = *aStr2++; + if (c1 != c2) { + if (c1 < c2) { + return -1; + } + return 1; + } + if (c1 == 0 || c2 == 0) { + break; + } + } + } else { + if (aStr1) { // aStr2 must have been null + return -1; + } + if (aStr2) { // aStr1 must have been null + return 1; + } + } + return 0; +} + +// This should use NSPR but NSPR isn't exporting its PR_strtoll function +// Until then... +int64_t nsCRT::atoll(const char* aStr) { + if (!aStr) { + return 0; + } + + int64_t ll = 0; + + while (*aStr && *aStr >= '0' && *aStr <= '9') { + ll *= 10; + ll += *aStr - '0'; + aStr++; + } + + return ll; +} diff --git a/xpcom/ds/nsCRT.h b/xpcom/ds/nsCRT.h new file mode 100644 index 0000000000..14b46c5059 --- /dev/null +++ b/xpcom/ds/nsCRT.h @@ -0,0 +1,119 @@ +/* -*- 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/. */ +#ifndef nsCRT_h___ +#define nsCRT_h___ + +#include +#include +#include "plstr.h" +#include "nscore.h" +#include "nsCRTGlue.h" + +#if defined(XP_WIN) +# define NS_LINEBREAK "\015\012" +# define NS_ULINEBREAK u"\015\012" +# define NS_LINEBREAK_LEN 2 +#else +# ifdef XP_UNIX +# define NS_LINEBREAK "\012" +# define NS_ULINEBREAK u"\012" +# define NS_LINEBREAK_LEN 1 +# endif /* XP_UNIX */ +#endif /* XP_WIN */ + +/// This is a wrapper class around all the C runtime functions. + +class nsCRT { + public: + enum { + LF = '\n' /* Line Feed */, + VTAB = '\v' /* Vertical Tab */, + CR = '\r' /* Carriage Return */ + }; + + /// String comparison. + static int32_t strcmp(const char* aStr1, const char* aStr2) { + return int32_t(PL_strcmp(aStr1, aStr2)); + } + + /// Case-insensitive string comparison. + static int32_t strcasecmp(const char* aStr1, const char* aStr2) { + /* Some functions like `PL_strcasecmp` are reimplementations + * of the their native POSIX counterparts, which breaks libFuzzer. + * For this purpose, we use the natives instead when fuzzing. + */ +#if defined(LIBFUZZER) && defined(LINUX) + return int32_t(::strcasecmp(aStr1, aStr2)); +#else + return int32_t(PL_strcasecmp(aStr1, aStr2)); +#endif + } + + /// Case-insensitive string comparison with length + static int32_t strncasecmp(const char* aStr1, const char* aStr2, + uint32_t aMaxLen) { +#if defined(LIBFUZZER) && defined(LINUX) + int32_t result = int32_t(::strncasecmp(aStr1, aStr2, aMaxLen)); +#else + int32_t result = int32_t(PL_strncasecmp(aStr1, aStr2, aMaxLen)); +#endif + // Egads. PL_strncasecmp is returning *very* negative numbers. + // Some folks expect -1,0,1, so let's temper its enthusiasm. + if (result < 0) { + result = -1; + } + return result; + } + + /// Case-insensitive substring search. + static char* strcasestr(const char* aStr1, const char* aStr2) { +#if defined(LIBFUZZER) && defined(LINUX) + return const_cast(::strcasestr(aStr1, aStr2)); +#else + return PL_strcasestr(aStr1, aStr2); +#endif + } + + /** + + How to use this fancy (thread-safe) version of strtok: + + void main(void) { + printf("%s\n\nTokens:\n", string); + // Establish string and get the first token: + char* newStr; + token = nsCRT::strtok(string, seps, &newStr); + while (token != nullptr) { + // While there are tokens in "string" + printf(" %s\n", token); + // Get next token: + token = nsCRT::strtok(newStr, seps, &newStr); + } + } + * WARNING - STRTOK WHACKS str THE FIRST TIME IT IS CALLED * + * MAKE A COPY OF str IF YOU NEED TO USE IT AFTER strtok() * + */ + static char* strtok(char* aStr, const char* aDelims, char** aNewStr); + + /// Like strcmp except for ucs2 strings + static int32_t strcmp(const char16_t* aStr1, const char16_t* aStr2); + + // String to longlong + static int64_t atoll(const char* aStr); + + static char ToUpper(char aChar) { return NS_ToUpper(aChar); } + static char ToLower(char aChar) { return NS_ToLower(aChar); } + + static bool IsAsciiSpace(char16_t aChar) { + return NS_IsAsciiWhitespace(aChar); + } +}; + +inline bool NS_IS_SPACE(char16_t aChar) { + return ((int(aChar) & 0x7f) == int(aChar)) && isspace(int(aChar)); +} + +#endif /* nsCRT_h___ */ diff --git a/xpcom/ds/nsCharSeparatedTokenizer.cpp b/xpcom/ds/nsCharSeparatedTokenizer.cpp new file mode 100644 index 0000000000..7288ed070f --- /dev/null +++ b/xpcom/ds/nsCharSeparatedTokenizer.cpp @@ -0,0 +1,10 @@ +/* -*- 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 "nsCharSeparatedTokenizer.h" + +template class nsTSubstringSplitter; +template class nsTSubstringSplitter; diff --git a/xpcom/ds/nsCharSeparatedTokenizer.h b/xpcom/ds/nsCharSeparatedTokenizer.h new file mode 100644 index 0000000000..5cf6992e3e --- /dev/null +++ b/xpcom/ds/nsCharSeparatedTokenizer.h @@ -0,0 +1,274 @@ +/* -*- 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/. */ + +#ifndef __nsCharSeparatedTokenizer_h +#define __nsCharSeparatedTokenizer_h + +#include "mozilla/Maybe.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/TypedEnumBits.h" + +#include "nsCRTGlue.h" +#include "nsTDependentSubstring.h" + +// Flags -- only one for now. If we need more, they should be defined to +// be 1 << 1, 1 << 2, etc. (They're masks, and aFlags is a bitfield.) +enum class nsTokenizerFlags { + Default = 0, + SeparatorOptional = 1 << 0, + IncludeEmptyTokenAtEnd = 1 << 1 +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsTokenizerFlags) + +/** + * This parses a SeparatorChar-separated string into tokens. + * Whitespace surrounding tokens is not treated as part of tokens, however + * whitespace inside a token is. If the final token is the empty string, it is + * not returned by default. + * + * Some examples, with SeparatorChar = ',': + * + * "foo, bar, baz" -> "foo" "bar" "baz" + * "foo,bar,baz" -> "foo" "bar" "baz" + * "foo , bar hi , baz" -> "foo" "bar hi" "baz" + * "foo, ,bar,baz" -> "foo" "" "bar" "baz" + * "foo,,bar,baz" -> "foo" "" "bar" "baz" + * "foo,bar,baz," -> "foo" "bar" "baz" + * + * The function used for whitespace detection is a template argument. + * By default, it is NS_IsAsciiWhitespace. + */ +template +class nsTCharSeparatedTokenizer { + using CharType = typename TDependentSubstringType::char_type; + using SubstringType = typename TDependentSubstringType::substring_type; + + public: + using DependentSubstringType = TDependentSubstringType; + + nsTCharSeparatedTokenizer(const SubstringType& aSource, + CharType aSeparatorChar) + : mIter(aSource.Data(), aSource.Length()), + mEnd(aSource.Data() + aSource.Length(), aSource.Data(), + aSource.Length()), + mSeparatorChar(aSeparatorChar), + mWhitespaceBeforeFirstToken(false), + mWhitespaceAfterCurrentToken(false), + mSeparatorAfterCurrentToken(false) { + // Skip initial whitespace + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceBeforeFirstToken = true; + ++mIter; + } + } + + /** + * Checks if any more tokens are available. + */ + bool hasMoreTokens() const { + MOZ_ASSERT(mIter == mEnd || !IsWhitespace(*mIter), + "Should be at beginning of token if there is one"); + + if constexpr (Flags & nsTokenizerFlags::IncludeEmptyTokenAtEnd) { + return mIter < mEnd || (mIter == mEnd && mSeparatorAfterCurrentToken); + } else { + return mIter < mEnd; + } + } + + /* + * Returns true if there is whitespace prior to the first token. + */ + bool whitespaceBeforeFirstToken() const { + return mWhitespaceBeforeFirstToken; + } + + /* + * Returns true if there is a separator after the current token. + * Useful if you want to check whether the last token has a separator + * after it which may not be valid. + */ + bool separatorAfterCurrentToken() const { + return mSeparatorAfterCurrentToken; + } + + /* + * Returns true if there is any whitespace after the current token. + */ + bool whitespaceAfterCurrentToken() const { + return mWhitespaceAfterCurrentToken; + } + + /** + * Returns the next token. + */ + const DependentSubstringType nextToken() { + mozilla::RangedPtr tokenStart = mIter; + mozilla::RangedPtr tokenEnd = mIter; + + MOZ_ASSERT(mIter == mEnd || !IsWhitespace(*mIter), + "Should be at beginning of token if there is one"); + + // Search until we hit separator or end (or whitespace, if a separator + // isn't required -- see clause with 'break' below). + while (mIter < mEnd && *mIter != mSeparatorChar) { + // Skip to end of the current word. + while (mIter < mEnd && !IsWhitespace(*mIter) && + *mIter != mSeparatorChar) { + ++mIter; + } + tokenEnd = mIter; + + // Skip whitespace after the current word. + mWhitespaceAfterCurrentToken = false; + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceAfterCurrentToken = true; + ++mIter; + } + if constexpr (Flags & nsTokenizerFlags::SeparatorOptional) { + // We've hit (and skipped) whitespace, and that's sufficient to end + // our token, regardless of whether we've reached a SeparatorChar. + break; + } // (else, we'll keep looping until we hit mEnd or SeparatorChar) + } + + mSeparatorAfterCurrentToken = (mIter != mEnd && *mIter == mSeparatorChar); + MOZ_ASSERT((Flags & nsTokenizerFlags::SeparatorOptional) || + (mSeparatorAfterCurrentToken == (mIter < mEnd)), + "If we require a separator and haven't hit the end of " + "our string, then we shouldn't have left the loop " + "unless we hit a separator"); + + // Skip separator (and any whitespace after it), if we're at one. + if (mSeparatorAfterCurrentToken) { + ++mIter; + + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceAfterCurrentToken = true; + ++mIter; + } + } + + return Substring(tokenStart.get(), tokenEnd.get()); + } + + auto ToRange() const; + + private: + mozilla::RangedPtr mIter; + const mozilla::RangedPtr mEnd; + const CharType mSeparatorChar; + bool mWhitespaceBeforeFirstToken; + bool mWhitespaceAfterCurrentToken; + bool mSeparatorAfterCurrentToken; +}; + +constexpr bool NS_TokenizerIgnoreNothing(char16_t) { return false; } + +template +using nsTCharSeparatedTokenizerTemplate = + nsTCharSeparatedTokenizer, IsWhitespace, + Flags>; + +template +using nsCharSeparatedTokenizerTemplate = + nsTCharSeparatedTokenizerTemplate; + +using nsCharSeparatedTokenizer = + nsCharSeparatedTokenizerTemplate; + +template +using nsCCharSeparatedTokenizerTemplate = + nsTCharSeparatedTokenizerTemplate; + +using nsCCharSeparatedTokenizer = + nsCCharSeparatedTokenizerTemplate; + +/** + * Adapts a char separated tokenizer for use in a range-based for loop. + * + * Use this typically only indirectly, e.g. like + * + * for (const auto& token : nsCharSeparatedTokenizer(aText, ' ').ToRange()) { + * // ... + * } + */ +template +class nsTokenizedRange { + public: + using DependentSubstringType = typename Tokenizer::DependentSubstringType; + + explicit nsTokenizedRange(Tokenizer&& aTokenizer) + : mTokenizer(std::move(aTokenizer)) {} + + struct EndSentinel {}; + struct Iterator { + explicit Iterator(const Tokenizer& aTokenizer) : mTokenizer(aTokenizer) { + Next(); + } + + const DependentSubstringType& operator*() const { return *mCurrentToken; } + + Iterator& operator++() { + Next(); + return *this; + } + + bool operator==(const EndSentinel&) const { + return mCurrentToken.isNothing(); + } + + bool operator!=(const EndSentinel&) const { return mCurrentToken.isSome(); } + + private: + void Next() { + mCurrentToken.reset(); + + if (mTokenizer.hasMoreTokens()) { + mCurrentToken.emplace(mTokenizer.nextToken()); + } + } + + Tokenizer mTokenizer; + mozilla::Maybe mCurrentToken; + }; + + auto begin() const { return Iterator{mTokenizer}; } + auto end() const { return EndSentinel{}; } + + private: + const Tokenizer mTokenizer; +}; + +template +auto nsTCharSeparatedTokenizer::ToRange() const { + return nsTokenizedRange{nsTCharSeparatedTokenizer{*this}}; +} + +// You should not need to instantiate this class directly. +// Use nsTSubstring::Split instead. +template +class nsTSubstringSplitter + : public nsTokenizedRange> { + public: + using nsTokenizedRange>::nsTokenizedRange; +}; + +extern template class nsTSubstringSplitter; +extern template class nsTSubstringSplitter; + +#endif /* __nsCharSeparatedTokenizer_h */ diff --git a/xpcom/ds/nsCheapSets.h b/xpcom/ds/nsCheapSets.h new file mode 100644 index 0000000000..3d09ccfdb7 --- /dev/null +++ b/xpcom/ds/nsCheapSets.h @@ -0,0 +1,155 @@ +/* -*- 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/. */ + +#ifndef __nsCheapSets_h__ +#define __nsCheapSets_h__ + +#include "nsTHashtable.h" +#include + +enum nsCheapSetOperator { + OpNext = 0, // enumerator says continue + OpRemove = 1, // enumerator says remove and continue +}; + +/** + * A set that takes up minimal size when there are 0 or 1 entries in the set. + * Use for cases where sizes of 0 and 1 are even slightly common. + */ +template +class nsCheapSet { + public: + typedef typename EntryType::KeyType KeyType; + typedef nsCheapSetOperator (*Enumerator)(EntryType* aEntry, void* userArg); + + nsCheapSet() : mState(ZERO) { mUnion.table = nullptr; } + ~nsCheapSet() { Clear(); } + + /** + * Remove all entries. + */ + void Clear() { + switch (mState) { + case ZERO: + break; + case ONE: + GetSingleEntry()->~EntryType(); + break; + case MANY: + delete mUnion.table; + break; + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + break; + } + mState = ZERO; + } + + void Put(const KeyType aVal); + + void Remove(const KeyType aVal); + + bool Contains(const KeyType aVal) { + switch (mState) { + case ZERO: + return false; + case ONE: + return GetSingleEntry()->KeyEquals(EntryType::KeyToPointer(aVal)); + case MANY: + return !!mUnion.table->GetEntry(aVal); + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + return false; + } + } + + uint32_t EnumerateEntries(Enumerator aEnumFunc, void* aUserArg) { + switch (mState) { + case ZERO: + return 0; + case ONE: + if (aEnumFunc(GetSingleEntry(), aUserArg) == OpRemove) { + GetSingleEntry()->~EntryType(); + mState = ZERO; + } + return 1; + case MANY: { + uint32_t n = mUnion.table->Count(); + for (auto iter = mUnion.table->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + if (aEnumFunc(entry, aUserArg) == OpRemove) { + iter.Remove(); + } + } + return n; + } + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + return 0; + } + } + + private: + EntryType* GetSingleEntry() { + return reinterpret_cast(&mUnion.singleEntry[0]); + } + + enum SetState { ZERO, ONE, MANY }; + + union { + nsTHashtable* table; + char singleEntry[sizeof(EntryType)]; + } mUnion; + enum SetState mState; +}; + +template +void nsCheapSet::Put(const KeyType aVal) { + switch (mState) { + case ZERO: + new (GetSingleEntry()) EntryType(EntryType::KeyToPointer(aVal)); + mState = ONE; + return; + case ONE: { + nsTHashtable* table = new nsTHashtable(); + EntryType* entry = GetSingleEntry(); + table->PutEntry(entry->GetKey()); + entry->~EntryType(); + mUnion.table = table; + mState = MANY; + } + [[fallthrough]]; + + case MANY: + mUnion.table->PutEntry(aVal); + return; + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + return; + } +} + +template +void nsCheapSet::Remove(const KeyType aVal) { + switch (mState) { + case ZERO: + break; + case ONE: + if (Contains(aVal)) { + GetSingleEntry()->~EntryType(); + mState = ZERO; + } + break; + case MANY: + mUnion.table->RemoveEntry(aVal); + break; + default: + MOZ_ASSERT_UNREACHABLE("bogus state"); + break; + } +} + +#endif diff --git a/xpcom/ds/nsClassHashtable.h b/xpcom/ds/nsClassHashtable.h new file mode 100644 index 0000000000..a1a2384f7e --- /dev/null +++ b/xpcom/ds/nsClassHashtable.h @@ -0,0 +1,115 @@ +/* -*- 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/. */ + +#ifndef nsClassHashtable_h__ +#define nsClassHashtable_h__ + +#include + +#include "mozilla/UniquePtr.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" + +/** + * Helper class that provides methods to wrap and unwrap the UserDataType. + */ +template +class nsUniquePtrConverter { + public: + using UserDataType = T*; + using DataType = mozilla::UniquePtr; + + static UserDataType Unwrap(DataType& src) { return src.get(); } + static DataType Wrap(UserDataType&& src) { return DataType(std::move(src)); } + static DataType Wrap(const UserDataType& src) { return DataType(src); } +}; + +/** + * templated hashtable class maps keys to C++ object pointers. + * See nsBaseHashtable for complete declaration. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param Class the class-type being wrapped + * @see nsInterfaceHashtable, nsClassHashtable + */ +template +class nsClassHashtable : public nsBaseHashtable, + T*, nsUniquePtrConverter> { + public: + typedef typename KeyClass::KeyType KeyType; + typedef T* UserDataType; + typedef nsBaseHashtable, T*, + nsUniquePtrConverter> + base_type; + + using base_type::IsEmpty; + using base_type::Remove; + + nsClassHashtable() = default; + explicit nsClassHashtable(uint32_t aInitLength) : base_type(aInitLength) {} + + /** + * @copydoc nsBaseHashtable::Get + * @param aData if the key doesn't exist, pData will be set to nullptr. + */ + bool Get(KeyType aKey, UserDataType* aData) const; + + /** + * @copydoc nsBaseHashtable::Get + * @returns nullptr if the key is not present. + */ + [[nodiscard]] UserDataType Get(KeyType aKey) const; +}; + +template +inline void ImplCycleCollectionUnlink(nsClassHashtable& aField) { + aField.Clear(); +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + const nsClassHashtable& aField, const char* aName, + uint32_t aFlags = 0) { + for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) { + ImplCycleCollectionTraverse(aCallback, *iter.UserData(), aName, aFlags); + } +} + +// +// nsClassHashtable definitions +// + +template +bool nsClassHashtable::Get(KeyType aKey, T** aRetVal) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRetVal) { + *aRetVal = ent->GetData().get(); + } + + return true; + } + + if (aRetVal) { + *aRetVal = nullptr; + } + + return false; +} + +template +T* nsClassHashtable::Get(KeyType aKey) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return nullptr; + } + + return ent->GetData().get(); +} + +#endif // nsClassHashtable_h__ diff --git a/xpcom/ds/nsDeque.cpp b/xpcom/ds/nsDeque.cpp new file mode 100644 index 0000000000..07f61fa471 --- /dev/null +++ b/xpcom/ds/nsDeque.cpp @@ -0,0 +1,265 @@ +/* -*- 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 "nsDeque.h" +#include "nsISupportsImpl.h" +#include + +#include "mozilla/CheckedInt.h" + +#define modulus(x, y) ((x) % (y)) + +namespace mozilla { +namespace detail { + +/** + * Standard constructor + * @param deallocator, called by Erase and ~nsDequeBase + */ +nsDequeBase::nsDequeBase() { + MOZ_COUNT_CTOR(nsDequeBase); + mOrigin = mSize = 0; + mData = mBuffer; // don't allocate space until you must + mCapacity = sizeof(mBuffer) / sizeof(mBuffer[0]); + memset(mData, 0, mCapacity * sizeof(mBuffer[0])); +} + +/** + * Destructor + */ +nsDequeBase::~nsDequeBase() { + MOZ_COUNT_DTOR(nsDequeBase); + + if (mData && mData != mBuffer) { + free(mData); + } + mData = nullptr; +} + +size_t nsDequeBase::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t size = 0; + if (mData != mBuffer) { + size += aMallocSizeOf(mData); + } + + return size; +} + +/** + * Remove all items from container without destroying them. + */ +void nsDequeBase::Empty() { + if (mSize && mData) { + memset(mData, 0, mCapacity * sizeof(*mData)); + } + mSize = 0; + mOrigin = 0; +} + +/** + * This method quadruples the size of the deque + * Elements in the deque are resequenced so that elements + * in the deque are stored sequentially + * + * @return whether growing succeeded + */ +bool nsDequeBase::GrowCapacity() { + mozilla::CheckedInt newCapacity = mCapacity; + newCapacity *= 4; + + NS_ASSERTION(newCapacity.isValid(), "Overflow"); + if (!newCapacity.isValid()) { + return false; + } + + // Sanity check the new byte size. + mozilla::CheckedInt newByteSize = newCapacity; + newByteSize *= sizeof(void*); + + NS_ASSERTION(newByteSize.isValid(), "Overflow"); + if (!newByteSize.isValid()) { + return false; + } + + void** temp = (void**)malloc(newByteSize.value()); + if (!temp) { + return false; + } + + // Here's the interesting part: You can't just move the elements + // directly (in situ) from the old buffer to the new one. + // Since capacity has changed, the old origin doesn't make + // sense anymore. It's better to resequence the elements now. + + memcpy(temp, mData + mOrigin, sizeof(void*) * (mCapacity - mOrigin)); + memcpy(temp + (mCapacity - mOrigin), mData, sizeof(void*) * mOrigin); + + if (mData != mBuffer) { + free(mData); + } + + mCapacity = newCapacity.value(); + mOrigin = 0; // now realign the origin... + mData = temp; + + return true; +} + +/** + * This method adds an item to the end of the deque. + * This operation has the potential to cause the + * underlying buffer to resize. + * + * @param aItem: new item to be added to deque + */ +bool nsDequeBase::Push(void* aItem, const fallible_t&) { + if (mSize == mCapacity && !GrowCapacity()) { + return false; + } + mData[modulus(mOrigin + mSize, mCapacity)] = aItem; + mSize++; + return true; +} + +/** + * This method adds an item to the front of the deque. + * This operation has the potential to cause the + * underlying buffer to resize. + * + * --Commments for GrowCapacity() case + * We've grown and shifted which means that the old + * final element in the deque is now the first element + * in the deque. This is temporary. + * We haven't inserted the new element at the front. + * + * To continue with the idea of having the front at zero + * after a grow, we move the old final item (which through + * the voodoo of mOrigin-- is now the first) to its final + * position which is conveniently the old length. + * + * Note that this case only happens when the deque is full. + * [And that pieces of this magic only work if the deque is full.] + * picture: + * [ABCDEFGH] @[mOrigin:3]:D. + * Task: PushFront("Z") + * shift mOrigin so, @[mOrigin:2]:C + * stretch and rearrange: (mOrigin:0) + * [CDEFGHAB ________ ________ ________] + * copy: (The second C is currently out of bounds) + * [CDEFGHAB C_______ ________ ________] + * later we will insert Z: + * [ZDEFGHAB C_______ ________ ________] + * and increment size: 9. (C is no longer out of bounds) + * -- + * @param aItem: new item to be added to deque + */ +bool nsDequeBase::PushFront(void* aItem, const fallible_t&) { + if (mOrigin == 0) { + mOrigin = mCapacity - 1; + } else { + mOrigin--; + } + + if (mSize == mCapacity) { + if (!GrowCapacity()) { + return false; + } + /* Comments explaining this are above*/ + mData[mSize] = mData[mOrigin]; + } + mData[mOrigin] = aItem; + mSize++; + return true; +} + +/** + * Remove and return the last item in the container. + * + * @return ptr to last item in container + */ +void* nsDequeBase::Pop() { + void* result = nullptr; + if (mSize > 0) { + --mSize; + size_t offset = modulus(mSize + mOrigin, mCapacity); + result = mData[offset]; + mData[offset] = nullptr; + if (!mSize) { + mOrigin = 0; + } + } + return result; +} + +/** + * This method gets called you want to remove and return + * the first member in the container. + * + * @return last item in container + */ +void* nsDequeBase::PopFront() { + void* result = nullptr; + if (mSize > 0) { + NS_ASSERTION(mOrigin < mCapacity, "Error: Bad origin"); + result = mData[mOrigin]; + mData[mOrigin++] = nullptr; // zero it out for debugging purposes. + mSize--; + // Cycle around if we pop off the end + // and reset origin if when we pop the last element + if (mCapacity == mOrigin || !mSize) { + mOrigin = 0; + } + } + return result; +} + +/** + * This method gets called you want to peek at the bottom + * member without removing it. + * + * @return last item in container + */ +void* nsDequeBase::Peek() const { + void* result = nullptr; + if (mSize > 0) { + result = mData[modulus(mSize - 1 + mOrigin, mCapacity)]; + } + return result; +} + +/** + * This method gets called you want to peek at the topmost + * member without removing it. + * + * @return last item in container + */ +void* nsDequeBase::PeekFront() const { + void* result = nullptr; + if (mSize > 0) { + result = mData[mOrigin]; + } + return result; +} + +/** + * Call this to retrieve the ith element from this container. + * Keep in mind that accessing the underlying elements is + * done in a relative fashion. Object 0 is not necessarily + * the first element (the first element is at mOrigin). + * + * @param aIndex : 0 relative offset of item you want + * @return void* or null + */ +void* nsDequeBase::ObjectAt(size_t aIndex) const { + void* result = nullptr; + if (aIndex < mSize) { + result = mData[modulus(mOrigin + aIndex, mCapacity)]; + } + return result; +} +} // namespace detail +} // namespace mozilla diff --git a/xpcom/ds/nsDeque.h b/xpcom/ds/nsDeque.h new file mode 100644 index 0000000000..4f0bf27037 --- /dev/null +++ b/xpcom/ds/nsDeque.h @@ -0,0 +1,538 @@ +/* -*- 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/. */ + +/** + * MODULE NOTES: + * + * The Deque is a very small, very efficient container object + * than can hold items of type T*, offering the following features: + * - Its interface supports pushing, popping, and peeking of items at the back + * or front, and retrieval from any position. + * - It can iterate over items via a ForEach method, range-for, or an iterator + * class. + * - When full, it can efficiently resize dynamically. + * + * NOTE: The only bit of trickery here is that this deque is + * built upon a ring-buffer. Like all ring buffers, the first + * item may not be at index[0]. The mOrigin member determines + * where the first child is. This point is quietly hidden from + * customers of this class. + */ + +#ifndef _NSDEQUE +#define _NSDEQUE +#include + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/fallible.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsISupports.h" + +namespace mozilla { + +namespace detail { +/** + * The deque (double-ended queue) class is a common container type, + * whose behavior mimics a line in your favorite checkout stand. + * Classic CS describes the common behavior of a queue as FIFO. + * A deque allows insertion and removal at both ends of + * the container. + * + * nsDequeBase implements the untyped deque data structure while + * nsDeque provides the typed implementation and iterators. + */ +class nsDequeBase { + public: + /** + * Returns the number of items currently stored in + * this deque. + * + * @return number of items currently in the deque + */ + inline size_t GetSize() const { return mSize; } + + protected: + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + /** + * Constructs an empty deque. + * + * @param aDeallocator Optional deallocator functor that will be called from + * Erase() and the destructor on any remaining item. + * The deallocator is owned by the deque and will be + * deleted at destruction time. + */ + explicit nsDequeBase(); + + /** + * Deque destructor. Erases all items, deletes the deallocator. + */ + ~nsDequeBase(); + + /** + * Appends new member at the end of the deque. + * + * @param aItem item to store in deque + * @return true if succeeded, false if failed to resize deque as needed + */ + [[nodiscard]] bool Push(void* aItem, const fallible_t&); + + /** + * Inserts new member at the front of the deque. + * + * @param aItem item to store in deque + * @return true if succeeded, false if failed to resize deque as needed + */ + [[nodiscard]] bool PushFront(void* aItem, const fallible_t&); + + /** + * Remove and return the last item in the container. + * + * @return the item that was the last item in container + */ + void* Pop(); + + /** + * Remove and return the first item in the container. + * + * @return the item that was first item in container + */ + void* PopFront(); + + /** + * Retrieve the last item without removing it. + * + * @return the last item in container + */ + void* Peek() const; + + /** + * Retrieve the first item without removing it. + * + * @return the first item in container + */ + void* PeekFront() const; + + /** + * Retrieve a member from the deque without removing it. + * + * @param index of desired item + * @return item in list, or nullptr if index is outside the deque + */ + void* ObjectAt(size_t aIndex) const; + + bool GrowCapacity(); + + /** + * Remove all items from container without destroying them. + */ + void Empty(); + + size_t mSize; + size_t mCapacity; + size_t mOrigin; + void* mBuffer[8]; + void** mData; + + nsDequeBase& operator=(const nsDequeBase& aOther) = delete; + nsDequeBase(const nsDequeBase& aOther) = delete; +}; + +// This iterator assumes that the deque itself is const, i.e., it cannot be +// modified while the iterator is used. +// Also it is a 'const' iterator in that it provides copies of the deque's +// elements, and therefore it is not possible to modify the deque's contents +// by assigning to a dereference of this iterator. +template +class ConstDequeIterator { + public: + ConstDequeIterator(const Deque& aDeque, size_t aIndex) + : mDeque(aDeque), mIndex(aIndex) {} + ConstDequeIterator& operator++() { + ++mIndex; + return *this; + } + bool operator==(const ConstDequeIterator& aOther) const { + return mIndex == aOther.mIndex; + } + bool operator!=(const ConstDequeIterator& aOther) const { + return mIndex != aOther.mIndex; + } + typename Deque::PointerType operator*() const { + // Don't allow out-of-deque dereferences. + MOZ_RELEASE_ASSERT(mIndex < mDeque.GetSize()); + return mDeque.ObjectAt(mIndex); + } + + private: + const Deque& mDeque; + size_t mIndex; +}; + +// It is a 'const' iterator in that it provides copies of the deque's +// elements, and therefore it is not possible to modify the deque's contents +// by assigning to a dereference of this iterator. +// If the deque is modified in other ways, this iterator will stay at the same +// index, and will handle past-the-end comparisons, but not dereferencing. +template +class ConstIterator { + public: + // Special index for the end iterator, to track the possibly-shifting + // deque size. + static const size_t EndIteratorIndex = size_t(-1); + + ConstIterator(const Deque& aDeque, size_t aIndex) + : mDeque(aDeque), mIndex(aIndex) {} + ConstIterator& operator++() { + // End-iterator shouldn't be modified. + MOZ_ASSERT(mIndex != EndIteratorIndex); + ++mIndex; + return *this; + } + bool operator==(const ConstIterator& aOther) const { + return EffectiveIndex() == aOther.EffectiveIndex(); + } + bool operator!=(const ConstIterator& aOther) const { + return EffectiveIndex() != aOther.EffectiveIndex(); + } + typename Deque::PointerType operator*() const { + // Don't allow out-of-deque dereferences. + MOZ_RELEASE_ASSERT(mIndex < mDeque.GetSize()); + return mDeque.ObjectAt(mIndex); + } + + private: + // 0 <= index < deque.GetSize() inside the deque, deque.GetSize() otherwise. + // Only used when comparing indices, not to actually access items. + size_t EffectiveIndex() const { + return (mIndex < mDeque.GetSize()) ? mIndex : mDeque.GetSize(); + } + + const Deque& mDeque; + size_t mIndex; // May point outside the deque! +}; + +} // namespace detail +} // namespace mozilla + +/** + * The nsDequeFunctor class is used when you want to create + * callbacks between the deque and your generic code. + * Use these objects in a call to ForEach(), and as custom deallocators. + */ +template +class nsDequeFunctor { + public: + virtual void operator()(T* aObject) = 0; + virtual ~nsDequeFunctor() = default; +}; + +/****************************************************** + * Here comes the nsDeque class itself... + ******************************************************/ + +/** + * The deque (double-ended queue) class is a common container type, + * whose behavior mimics a line in your favorite checkout stand. + * Classic CS describes the common behavior of a queue as FIFO. + * A deque allows insertion and removal at both ends of + * the container. + * + * The deque stores pointers to items. + */ +template +class nsDeque : public mozilla::detail::nsDequeBase { + typedef mozilla::fallible_t fallible_t; + + public: + using PointerType = T*; + using ConstDequeIterator = mozilla::detail::ConstDequeIterator>; + using ConstIterator = mozilla::detail::ConstIterator>; + + /** + * Constructs an empty deque. + * + * @param aDeallocator Optional deallocator functor that will be called from + * Erase() and the destructor on any remaining item. + * The deallocator is owned by the deque and will be + * deleted at destruction time. + */ + explicit nsDeque(nsDequeFunctor* aDeallocator = nullptr) { + MOZ_COUNT_CTOR(nsDeque); + mDeallocator = aDeallocator; + } + + /** + * Deque destructor. Erases all items, deletes the deallocator. + */ + ~nsDeque() { + MOZ_COUNT_DTOR(nsDeque); + + Erase(); + SetDeallocator(nullptr); + } + + /** + * Appends new member at the end of the deque. + * + * @param aItem item to store in deque + */ + inline void Push(T* aItem) { + if (!nsDequeBase::Push(aItem, mozilla::fallible)) { + NS_ABORT_OOM(mSize * sizeof(T*)); + } + } + + /** + * Appends new member at the end of the deque. + * + * @param aItem item to store in deque + * @return true if succeeded, false if failed to resize deque as needed + */ + [[nodiscard]] inline bool Push(T* aItem, const fallible_t& aFaillible) { + return nsDequeBase::Push(aItem, aFaillible); + } + + /** + * Inserts new member at the front of the deque. + * + * @param aItem item to store in deque + */ + inline void PushFront(T* aItem) { + if (!nsDequeBase::PushFront(aItem, mozilla::fallible)) { + NS_ABORT_OOM(mSize * sizeof(T*)); + } + } + + /** + * Inserts new member at the front of the deque. + * + * @param aItem item to store in deque + * @return true if succeeded, false if failed to resize deque as needed + */ + [[nodiscard]] bool PushFront(T* aItem, const fallible_t& aFallible) { + return nsDequeBase::PushFront(aItem, aFallible); + } + + /** + * Remove and return the last item in the container. + * + * @return the item that was the last item in container + */ + inline T* Pop() { return static_cast(nsDequeBase::Pop()); } + + /** + * Remove and return the first item in the container. + * + * @return the item that was first item in container + */ + inline T* PopFront() { return static_cast(nsDequeBase::PopFront()); } + + /** + * Retrieve the last item without removing it. + * + * @return the last item in container + */ + inline T* Peek() const { return static_cast(nsDequeBase::Peek()); } + + /** + * Retrieve the first item without removing it. + * + * @return the first item in container + */ + inline T* PeekFront() const { + return static_cast(nsDequeBase::PeekFront()); + } + + /** + * Retrieve a member from the deque without removing it. + * + * @param index of desired item + * @return item in list, or nullptr if index is outside the deque + */ + inline T* ObjectAt(size_t aIndex) const { + if (NS_WARN_IF(aIndex >= GetSize())) { + return nullptr; + } + return static_cast(nsDequeBase::ObjectAt(aIndex)); + } + + /** + * Remove and delete all items from container. + * Deletes are handled by the deallocator nsDequeFunctor + * which is specified at deque construction. + */ + void Erase() { + if (mDeallocator && mSize) { + ForEach(*mDeallocator); + } + Empty(); + } + + /** + * Call this method when you want to iterate through all + * items in the container, passing a functor along + * to call your code. + * If the deque is modified during ForEach, iteration will continue based on + * item indices; meaning that front operations may effectively skip over + * items or visit some items multiple times. + * + * @param aFunctor object to call for each member + */ + void ForEach(nsDequeFunctor& aFunctor) const { + for (size_t i = 0; i < mSize; ++i) { + aFunctor(ObjectAt(i)); + } + } + + // If this deque is const, we can provide ConstDequeIterator's. + ConstDequeIterator begin() const { return ConstDequeIterator(*this, 0); } + ConstDequeIterator end() const { return ConstDequeIterator(*this, mSize); } + + // If this deque is *not* const, we provide ConstIterator's that can handle + // deque size changes. + ConstIterator begin() { return ConstIterator(*this, 0); } + ConstIterator end() { + return ConstIterator(*this, ConstIterator::EndIteratorIndex); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t size = nsDequeBase::SizeOfExcludingThis(aMallocSizeOf); + if (mDeallocator) { + size += aMallocSizeOf(mDeallocator); + } + return size; + } + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + protected: + nsDequeFunctor* mDeallocator; + + private: + /** + * Copy constructor (deleted) + * + * @param aOther another deque + */ + nsDeque(const nsDeque& aOther) = delete; + + /** + * Deque assignment operator (deleted) + * + * @param aOther another deque + * @return *this + */ + nsDeque& operator=(const nsDeque& aOther) = delete; + + void SetDeallocator(nsDequeFunctor* aDeallocator) { + delete mDeallocator; + mDeallocator = aDeallocator; + } +}; + +/** + * Specialization of nsDeque for reference counted pointers. + * + * Provides builtin reference handling as part of Push/Peek/Pop + */ +template +class nsRefPtrDeque : private nsDeque { + typedef mozilla::fallible_t fallible_t; + + class RefPtrDeallocator : public nsDequeFunctor { + public: + virtual void operator()(T* aObject) override { + RefPtr releaseMe = dont_AddRef(aObject); + } + }; + + public: + using PointerType = RefPtr; + using ConstDequeIterator = + mozilla::detail::ConstDequeIterator>; + using ConstIterator = mozilla::detail::ConstIterator>; + + explicit nsRefPtrDeque() : nsDeque(new RefPtrDeallocator()) {} + + inline void PushFront(already_AddRefed aItem) { + T* item = aItem.take(); + nsDeque::PushFront(item); + } + + inline void PushFront(T* aItem) { PushFront(do_AddRef(aItem)); } + + inline void Push(T* aItem) { Push(do_AddRef(aItem)); } + + inline void Push(already_AddRefed aItem) { + T* item = aItem.take(); + nsDeque::Push(item); + } + + inline already_AddRefed PopFront() { + return dont_AddRef(nsDeque::PopFront()); + } + + inline already_AddRefed Pop() { return dont_AddRef(nsDeque::Pop()); } + + inline T* PeekFront() const { return nsDeque::PeekFront(); } + + inline T* Peek() const { return nsDeque::Peek(); } + + inline T* ObjectAt(size_t aIndex) const { + return nsDeque::ObjectAt(aIndex); + } + + inline void Erase() { nsDeque::Erase(); } + + // If this deque is const, we can provide ConstDequeIterator's. + ConstDequeIterator begin() const { return ConstDequeIterator(*this, 0); } + ConstDequeIterator end() const { + return ConstDequeIterator(*this, GetSize()); + } + + // If this deque is *not* const, we provide ConstIterator's that can handle + // deque size changes. + ConstIterator begin() { return ConstIterator(*this, 0); } + ConstIterator end() { + return ConstIterator(*this, ConstIterator::EndIteratorIndex); + } + + inline size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return nsDeque::SizeOfExcludingThis(aMallocSizeOf); + } + + inline size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return nsDeque::SizeOfIncludingThis(aMallocSizeOf); + } + + inline size_t GetSize() const { return nsDeque::GetSize(); } + + /** + * Call this method when you want to iterate through all + * items in the container, passing a functor along + * to call your code. + * If the deque is modified during ForEach, iteration will continue based on + * item indices; meaning that front operations may effectively skip over + * items or visit some items multiple times. + * + * @param aFunctor object to call for each member + */ + void ForEach(nsDequeFunctor& aFunctor) const { + size_t size = GetSize(); + for (size_t i = 0; i < size; ++i) { + aFunctor(ObjectAt(i)); + } + } +}; + +#endif diff --git a/xpcom/ds/nsEnumeratorUtils.cpp b/xpcom/ds/nsEnumeratorUtils.cpp new file mode 100644 index 0000000000..69631818a3 --- /dev/null +++ b/xpcom/ds/nsEnumeratorUtils.cpp @@ -0,0 +1,248 @@ +/* -*- 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/Attributes.h" + +#include "nsEnumeratorUtils.h" + +#include "nsIStringEnumerator.h" +#include "nsSimpleEnumerator.h" + +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" + +class EmptyEnumeratorImpl : public nsSimpleEnumerator, + public nsIUTF8StringEnumerator, + public nsIStringEnumerator { + public: + EmptyEnumeratorImpl() = default; + + // nsISupports interface. Not really inherited, but no mRefCnt. + NS_DECL_ISUPPORTS_INHERITED + + // nsISimpleEnumerator + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIUTF8STRINGENUMERATOR + NS_DECL_NSISTRINGENUMERATORBASE + // can't use NS_DECL_NSISTRINGENUMERATOR because they share the + // HasMore() signature + + NS_IMETHOD GetNext(nsAString& aResult) override; + + static EmptyEnumeratorImpl* GetInstance() { + static const EmptyEnumeratorImpl kInstance; + return const_cast(&kInstance); + } +}; + +// nsISupports interface +NS_IMETHODIMP_(MozExternalRefCountType) +EmptyEnumeratorImpl::AddRef(void) { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +EmptyEnumeratorImpl::Release(void) { return 1; } + +NS_IMPL_QUERY_INTERFACE_INHERITED(EmptyEnumeratorImpl, nsSimpleEnumerator, + nsIUTF8StringEnumerator, nsIStringEnumerator) + +// nsISimpleEnumerator interface +NS_IMETHODIMP +EmptyEnumeratorImpl::HasMoreElements(bool* aResult) { + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::HasMore(bool* aResult) { + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsISupports** aResult) { return NS_ERROR_FAILURE; } + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsACString& aResult) { + return NS_ERROR_UNEXPECTED; +} + +NS_IMETHODIMP +EmptyEnumeratorImpl::GetNext(nsAString& aResult) { return NS_ERROR_UNEXPECTED; } + +NS_IMETHODIMP +EmptyEnumeratorImpl::StringIterator(nsIJSEnumerator** aRetVal) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult NS_NewEmptyEnumerator(nsISimpleEnumerator** aResult) { + *aResult = EmptyEnumeratorImpl::GetInstance(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsSingletonEnumerator final : public nsSimpleEnumerator { + public: + // nsISimpleEnumerator methods + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + explicit nsSingletonEnumerator(nsISupports* aValue); + + private: + ~nsSingletonEnumerator() override; + + protected: + nsCOMPtr mValue; + bool mConsumed; +}; + +nsSingletonEnumerator::nsSingletonEnumerator(nsISupports* aValue) + : mValue(aValue) { + mConsumed = (mValue ? false : true); +} + +nsSingletonEnumerator::~nsSingletonEnumerator() = default; + +NS_IMETHODIMP +nsSingletonEnumerator::HasMoreElements(bool* aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + *aResult = !mConsumed; + return NS_OK; +} + +NS_IMETHODIMP +nsSingletonEnumerator::GetNext(nsISupports** aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mConsumed) { + return NS_ERROR_FAILURE; + } + + mConsumed = true; + + *aResult = mValue; + NS_ADDREF(*aResult); + return NS_OK; +} + +nsresult NS_NewSingletonEnumerator(nsISimpleEnumerator** aResult, + nsISupports* aSingleton) { + RefPtr enumer = new nsSingletonEnumerator(aSingleton); + enumer.forget(aResult); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsUnionEnumerator final : public nsSimpleEnumerator { + public: + // nsISimpleEnumerator methods + NS_IMETHOD HasMoreElements(bool* aResult) override; + NS_IMETHOD GetNext(nsISupports** aResult) override; + + nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator); + + private: + ~nsUnionEnumerator() override; + + protected: + nsCOMPtr mFirstEnumerator, mSecondEnumerator; + bool mConsumed; + bool mAtSecond; +}; + +nsUnionEnumerator::nsUnionEnumerator(nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator) + : mFirstEnumerator(aFirstEnumerator), + mSecondEnumerator(aSecondEnumerator), + mConsumed(false), + mAtSecond(false) {} + +nsUnionEnumerator::~nsUnionEnumerator() = default; + +NS_IMETHODIMP +nsUnionEnumerator::HasMoreElements(bool* aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + nsresult rv; + + if (mConsumed) { + *aResult = false; + return NS_OK; + } + + if (!mAtSecond) { + rv = mFirstEnumerator->HasMoreElements(aResult); + if (NS_FAILED(rv)) { + return rv; + } + + if (*aResult) { + return NS_OK; + } + + mAtSecond = true; + } + + rv = mSecondEnumerator->HasMoreElements(aResult); + if (NS_FAILED(rv)) { + return rv; + } + + if (*aResult) { + return NS_OK; + } + + *aResult = false; + mConsumed = true; + return NS_OK; +} + +NS_IMETHODIMP +nsUnionEnumerator::GetNext(nsISupports** aResult) { + MOZ_ASSERT(aResult != 0, "null ptr"); + if (!aResult) { + return NS_ERROR_NULL_POINTER; + } + + if (mConsumed) { + return NS_ERROR_FAILURE; + } + + if (!mAtSecond) { + return mFirstEnumerator->GetNext(aResult); + } + + return mSecondEnumerator->GetNext(aResult); +} + +nsresult NS_NewUnionEnumerator(nsISimpleEnumerator** aResult, + nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator) { + *aResult = nullptr; + if (!aFirstEnumerator) { + *aResult = aSecondEnumerator; + } else if (!aSecondEnumerator) { + *aResult = aFirstEnumerator; + } else { + auto* enumer = new nsUnionEnumerator(aFirstEnumerator, aSecondEnumerator); + *aResult = enumer; + } + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/xpcom/ds/nsEnumeratorUtils.h b/xpcom/ds/nsEnumeratorUtils.h new file mode 100644 index 0000000000..f7a0db099e --- /dev/null +++ b/xpcom/ds/nsEnumeratorUtils.h @@ -0,0 +1,24 @@ +/* -*- 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/. */ + +#ifndef nsEnumeratorUtils_h__ +#define nsEnumeratorUtils_h__ + +#include "nscore.h" + +class nsISupports; +class nsISimpleEnumerator; + +nsresult NS_NewEmptyEnumerator(nsISimpleEnumerator** aResult); + +nsresult NS_NewSingletonEnumerator(nsISimpleEnumerator** aResult, + nsISupports* aSingleton); + +nsresult NS_NewUnionEnumerator(nsISimpleEnumerator** aResult, + nsISimpleEnumerator* aFirstEnumerator, + nsISimpleEnumerator* aSecondEnumerator); + +#endif /* nsEnumeratorUtils_h__ */ diff --git a/xpcom/ds/nsExpirationTracker.h b/xpcom/ds/nsExpirationTracker.h new file mode 100644 index 0000000000..ec2a25f67d --- /dev/null +++ b/xpcom/ds/nsExpirationTracker.h @@ -0,0 +1,618 @@ +/* -*- 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/. */ + +#ifndef NSEXPIRATIONTRACKER_H_ +#define NSEXPIRATIONTRACKER_H_ + +#include +#include "MainThreadUtils.h" +#include "nsAlgorithm.h" +#include "nsDebug.h" +#include "nsTArray.h" +#include "nsITimer.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsISupports.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "nscore.h" +#include "mozilla/Assertions.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/RefCountType.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" + +/** + * Data used to track the expiration state of an object. We promise that this + * is 32 bits so that objects that includes this as a field can pad and align + * efficiently. + */ +struct nsExpirationState { + enum { + NOT_TRACKED = (1U << 4) - 1, + MAX_INDEX_IN_GENERATION = (1U << 28) - 1 + }; + + nsExpirationState() : mGeneration(NOT_TRACKED), mIndexInGeneration(0) {} + bool IsTracked() { return mGeneration != NOT_TRACKED; } + + /** + * The generation that this object belongs to, or NOT_TRACKED. + */ + uint32_t mGeneration : 4; + uint32_t mIndexInGeneration : 28; +}; + +/** + * ExpirationTracker classes: + * - ExpirationTrackerImpl (Thread-safe class) + * - nsExpirationTracker (Main-thread only class) + * + * These classes can track the lifetimes and usage of a large number of + * objects, and send a notification some window of time after a live object was + * last used. This is very useful when you manage a large number of objects + * and want to flush some after they haven't been used for a while. + * nsExpirationTracker is designed to be very space and time efficient. + * + * The type parameter T is the object type that we will track pointers to. T + * must include an accessible method GetExpirationState() that returns a + * pointer to an nsExpirationState associated with the object (preferably, + * stored in a field of the object). + * + * The parameter K is the number of generations that will be used. Increasing + * the number of generations narrows the window within which we promise + * to fire notifications, at a slight increase in space cost for the tracker. + * We require 2 <= K <= nsExpirationState::NOT_TRACKED (currently 15). + * + * To use this class, you need to inherit from it and override the + * NotifyExpired() method. + * + * The approach is to track objects in K generations. When an object is accessed + * it moves from its current generation to the newest generation. Generations + * are stored in a cyclic array; when a timer interrupt fires, we advance + * the current generation pointer to effectively age all objects very + * efficiently. By storing information in each object about its generation and + * index within its generation array, we make removal of objects from a + * generation very cheap. + * + * Future work: + * -- Add a method to change the timer period? + */ + +/** + * Base class for ExiprationTracker implementations. + * + * nsExpirationTracker class below is a specialized class to be inherited by the + * instances to be accessed only on main-thread. + * + * For creating a thread-safe tracker, you can define a subclass inheriting this + * base class and specialize the Mutex and AutoLock to be used. + * + * For an example of using ExpirationTrackerImpl with a DataMutex + * @see mozilla::gfx::GradientCache. + * + */ +template +class ExpirationTrackerImpl { + public: + /** + * Initialize the tracker. + * @param aTimerPeriod the timer period in milliseconds. The guarantees + * provided by the tracker are defined in terms of this period. If the + * period is zero, then we don't use a timer and rely on someone calling + * AgeOneGenerationLocked explicitly. + * @param aName the name of the subclass for telemetry. + * @param aEventTarget the optional event target on main thread to label the + * runnable of the asynchronous invocation to NotifyExpired(). + + */ + ExpirationTrackerImpl(uint32_t aTimerPeriod, const char* aName, + nsIEventTarget* aEventTarget = nullptr) + : mTimerPeriod(aTimerPeriod), + mNewestGeneration(0), + mInAgeOneGeneration(false), + mName(aName), + mEventTarget(aEventTarget) { + static_assert(K >= 2 && K <= nsExpirationState::NOT_TRACKED, + "Unsupported number of generations (must be 2 <= K <= 15)"); + MOZ_ASSERT(NS_IsMainThread()); + if (mEventTarget) { + bool current = false; + // NOTE: The following check+crash could be condensed into a + // MOZ_RELEASE_ASSERT, but that triggers a segfault during compilation in + // clang 3.8. Once we don't have to care about clang 3.8 anymore, though, + // we can convert to MOZ_RELEASE_ASSERT here. + if (MOZ_UNLIKELY(NS_FAILED(mEventTarget->IsOnCurrentThread(¤t)) || + !current)) { + MOZ_CRASH("Provided event target must be on the main thread"); + } + } + mObserver = new ExpirationTrackerObserver(); + mObserver->Init(this); + } + + virtual ~ExpirationTrackerImpl() { + MOZ_ASSERT(NS_IsMainThread()); + if (mTimer) { + mTimer->Cancel(); + } + mObserver->Destroy(); + } + + /** + * Add an object to be tracked. It must not already be tracked. It will + * be added to the newest generation, i.e., as if it was just used. + * @return an error on out-of-memory + */ + nsresult AddObjectLocked(T* aObj, const AutoLock& aAutoLock) { + if (NS_WARN_IF(!aObj)) { + MOZ_DIAGNOSTIC_ASSERT(false, "Invalid object to add"); + return NS_ERROR_UNEXPECTED; + } + nsExpirationState* state = aObj->GetExpirationState(); + if (NS_WARN_IF(state->IsTracked())) { + MOZ_DIAGNOSTIC_ASSERT(false, + "Tried to add an object that's already tracked"); + return NS_ERROR_UNEXPECTED; + } + nsTArray& generation = mGenerations[mNewestGeneration]; + uint32_t index = generation.Length(); + if (index > nsExpirationState::MAX_INDEX_IN_GENERATION) { + NS_WARNING("More than 256M elements tracked, this is probably a problem"); + return NS_ERROR_OUT_OF_MEMORY; + } + if (index == 0) { + // We might need to start the timer + nsresult rv = CheckStartTimerLocked(aAutoLock); + if (NS_FAILED(rv)) { + return rv; + } + } + // XXX(Bug 1631371) Check if this should use a fallible operation as it + // pretended earlier. + generation.AppendElement(aObj); + state->mGeneration = mNewestGeneration; + state->mIndexInGeneration = index; + return NS_OK; + } + + /** + * Remove an object from the tracker. It must currently be tracked. + */ + void RemoveObjectLocked(T* aObj, const AutoLock& aAutoLock) { + if (NS_WARN_IF(!aObj)) { + MOZ_DIAGNOSTIC_ASSERT(false, "Invalid object to remove"); + return; + } + nsExpirationState* state = aObj->GetExpirationState(); + if (NS_WARN_IF(!state->IsTracked())) { + MOZ_DIAGNOSTIC_ASSERT(false, + "Tried to remove an object that's not tracked"); + return; + } + nsTArray& generation = mGenerations[state->mGeneration]; + uint32_t index = state->mIndexInGeneration; + MOZ_ASSERT(generation.Length() > index && generation[index] == aObj, + "Object is lying about its index"); + // Move the last object to fill the hole created by removing aObj + T* lastObj = generation.PopLastElement(); + // XXX It looks weird that index might point to the element that was just + // removed. Is that really correct? + if (index < generation.Length()) { + generation[index] = lastObj; + } + lastObj->GetExpirationState()->mIndexInGeneration = index; + state->mGeneration = nsExpirationState::NOT_TRACKED; + // We do not check whether we need to stop the timer here. The timer + // will check that itself next time it fires. Checking here would not + // be efficient since we'd need to track all generations. Also we could + // thrash by incessantly creating and destroying timers if someone + // kept adding and removing an object from the tracker. + } + + /** + * Notify that an object has been used. + * @return an error if we lost the object from the tracker... + */ + nsresult MarkUsedLocked(T* aObj, const AutoLock& aAutoLock) { + nsExpirationState* state = aObj->GetExpirationState(); + if (mNewestGeneration == state->mGeneration) { + return NS_OK; + } + RemoveObjectLocked(aObj, aAutoLock); + return AddObjectLocked(aObj, aAutoLock); + } + + /** + * The timer calls this, but it can also be manually called if you want + * to age objects "artifically". This can result in calls to + * NotifyExpiredLocked. + */ + void AgeOneGenerationLocked(const AutoLock& aAutoLock) { + if (mInAgeOneGeneration) { + NS_WARNING("Can't reenter AgeOneGeneration from NotifyExpired"); + return; + } + + mInAgeOneGeneration = true; + uint32_t reapGeneration = + mNewestGeneration > 0 ? mNewestGeneration - 1 : K - 1; + nsTArray& generation = mGenerations[reapGeneration]; + // The following is rather tricky. We have to cope with objects being + // removed from this generation either because of a call to RemoveObject + // (or indirectly via MarkUsedLocked) inside NotifyExpiredLocked. + // Fortunately no objects can be added to this generation because it's not + // the newest generation. We depend on the fact that RemoveObject can only + // cause the indexes of objects in this generation to *decrease*, not + // increase. So if we start from the end and work our way backwards we are + // guaranteed to see each object at least once. + size_t index = generation.Length(); + for (;;) { + // Objects could have been removed so index could be outside + // the array + index = XPCOM_MIN(index, generation.Length()); + if (index == 0) { + break; + } + --index; + NotifyExpiredLocked(generation[index], aAutoLock); + } + // Any leftover objects from reapGeneration just end up in the new + // newest-generation. This is bad form, though, so warn if there are any. + if (!generation.IsEmpty()) { + NS_WARNING("Expired objects were not removed or marked used"); + } + // Free excess memory used by the generation array, since we probably + // just removed most or all of its elements. + generation.Compact(); + mNewestGeneration = reapGeneration; + mInAgeOneGeneration = false; + } + + /** + * This just calls AgeOneGenerationLocked K times. Under normal circumstances + * this will result in all objects getting NotifyExpiredLocked called on them, + * but if NotifyExpiredLocked itself marks some objects as used, then those + * objects might not expire. This would be a good thing to call if we get into + * a critically-low memory situation. + */ + void AgeAllGenerationsLocked(const AutoLock& aAutoLock) { + uint32_t i; + for (i = 0; i < K; ++i) { + AgeOneGenerationLocked(aAutoLock); + } + } + + class Iterator { + private: + ExpirationTrackerImpl* mTracker; + uint32_t mGeneration; + uint32_t mIndex; + + public: + Iterator(ExpirationTrackerImpl* aTracker, + AutoLock& aAutoLock) + : mTracker(aTracker), mGeneration(0), mIndex(0) {} + + T* Next() { + while (mGeneration < K) { + nsTArray* generation = &mTracker->mGenerations[mGeneration]; + if (mIndex < generation->Length()) { + ++mIndex; + return (*generation)[mIndex - 1]; + } + ++mGeneration; + mIndex = 0; + } + return nullptr; + } + }; + + friend class Iterator; + + bool IsEmptyLocked(const AutoLock& aAutoLock) const { + for (uint32_t i = 0; i < K; ++i) { + if (!mGenerations[i].IsEmpty()) { + return false; + } + } + return true; + } + + size_t Length(const AutoLock& aAutoLock) const { + size_t len = 0; + for (uint32_t i = 0; i < K; ++i) { + len += mGenerations[i].Length(); + } + return len; + } + + // @return The amount of memory used by this ExpirationTrackerImpl, excluding + // sizeof(*this). If you want to measure anything hanging off the mGenerations + // array, you must iterate over the elements and measure them individually; + // hence the "Shallow" prefix. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t bytes = 0; + for (uint32_t i = 0; i < K; ++i) { + bytes += mGenerations[i].ShallowSizeOfExcludingThis(aMallocSizeOf); + } + return bytes; + } + + protected: + /** + * This must be overridden to catch notifications. It is called whenever + * we detect that an object has not been used for at least (K-1)*mTimerPeriod + * milliseconds. If timer events are not delayed, it will be called within + * roughly K*mTimerPeriod milliseconds after the last use. + * (Unless AgeOneGenerationLocked or AgeAllGenerationsLocked have been called + * to accelerate the aging process.) + * + * NOTE: These bounds ignore delays in timer firings due to actual work being + * performed by the browser. We use a slack timer so there is always at least + * mTimerPeriod milliseconds between firings, which gives us + * (K-1)*mTimerPeriod as a pretty solid lower bound. The upper bound is rather + * loose, however. If the maximum amount by which any given timer firing is + * delayed is D, then the upper bound before NotifyExpiredLocked is called is + * K*(mTimerPeriod + D). + * + * The NotifyExpiredLocked call is expected to remove the object from the + * tracker, but it need not. The object (or other objects) could be + * "resurrected" by calling MarkUsedLocked() on them, or they might just not + * be removed. Any objects left over that have not been resurrected or removed + * are placed in the new newest-generation, but this is considered "bad form" + * and should be avoided (we'll issue a warning). (This recycling counts + * as "a use" for the purposes of the expiry guarantee above...) + * + * For robustness and simplicity, we allow objects to be notified more than + * once here in the same timer tick. + */ + virtual void NotifyExpiredLocked(T*, const AutoLock&) = 0; + + /** + * This may be overridden to perform any post-aging work that needs to be + * done while still holding the lock. It will be called once after each timer + * event, and each low memory event has been handled. + */ + virtual void NotifyHandlerEndLocked(const AutoLock&){}; + + /** + * This may be overridden to perform any post-aging work that needs to be + * done outside the lock. It will be called once after each + * NotifyEndTransactionLocked call. + */ + virtual void NotifyHandlerEnd(){}; + + virtual Mutex& GetMutex() = 0; + + private: + class ExpirationTrackerObserver; + RefPtr mObserver; + nsTArray mGenerations[K]; + nsCOMPtr mTimer; + uint32_t mTimerPeriod; + uint32_t mNewestGeneration; + bool mInAgeOneGeneration; + const char* const mName; // Used for timer firing profiling. + const nsCOMPtr mEventTarget; + + /** + * Whenever "memory-pressure" is observed, it calls AgeAllGenerationsLocked() + * to minimize memory usage. + */ + class ExpirationTrackerObserver final : public nsIObserver { + public: + void Init(ExpirationTrackerImpl* aObj) { + mOwner = aObj; + nsCOMPtr obs = + mozilla::services::GetObserverService(); + if (obs) { + obs->AddObserver(this, "memory-pressure", false); + } + } + void Destroy() { + mOwner = nullptr; + nsCOMPtr obs = + mozilla::services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "memory-pressure"); + } + } + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + private: + ExpirationTrackerImpl* mOwner; + }; + + void HandleLowMemory() { + { + AutoLock lock(GetMutex()); + AgeAllGenerationsLocked(lock); + NotifyHandlerEndLocked(lock); + } + NotifyHandlerEnd(); + } + + void HandleTimeout() { + { + AutoLock lock(GetMutex()); + AgeOneGenerationLocked(lock); + // Cancel the timer if we have no objects to track + if (IsEmptyLocked(lock)) { + mTimer->Cancel(); + mTimer = nullptr; + } + NotifyHandlerEndLocked(lock); + } + NotifyHandlerEnd(); + } + + static void TimerCallback(nsITimer* aTimer, void* aThis) { + ExpirationTrackerImpl* tracker = static_cast(aThis); + tracker->HandleTimeout(); + } + + nsresult CheckStartTimerLocked(const AutoLock& aAutoLock) { + if (mTimer || !mTimerPeriod) { + return NS_OK; + } + nsCOMPtr target = mEventTarget; + if (!target && !NS_IsMainThread()) { + // TimerCallback should always be run on the main thread to prevent races + // to the destruction of the tracker. + target = do_GetMainThread(); + NS_ENSURE_STATE(target); + } + + return NS_NewTimerWithFuncCallback( + getter_AddRefs(mTimer), TimerCallback, this, mTimerPeriod, + nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY, mName, target); + } +}; + +namespace detail { + +class PlaceholderLock { + public: + void Lock() {} + void Unlock() {} +}; + +class PlaceholderAutoLock { + public: + explicit PlaceholderAutoLock(PlaceholderLock&) {} + ~PlaceholderAutoLock() = default; +}; + +template +using SingleThreadedExpirationTracker = + ExpirationTrackerImpl; + +} // namespace detail + +template +class nsExpirationTracker + : protected ::detail::SingleThreadedExpirationTracker { + typedef ::detail::PlaceholderLock Lock; + typedef ::detail::PlaceholderAutoLock AutoLock; + + Lock mLock; + + AutoLock FakeLock() { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + return AutoLock(mLock); + } + + Lock& GetMutex() override { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread()); + return mLock; + } + + void NotifyExpiredLocked(T* aObject, const AutoLock&) override { + NotifyExpired(aObject); + } + + /** + * Since there are no users of these callbacks in the single threaded case, + * we mark them as final with the hope that the compiler can optimize the + * method calls out entirely. + */ + void NotifyHandlerEndLocked(const AutoLock&) final {} + void NotifyHandlerEnd() final {} + + protected: + virtual void NotifyExpired(T* aObj) = 0; + + public: + nsExpirationTracker(uint32_t aTimerPeriod, const char* aName, + nsIEventTarget* aEventTarget = nullptr) + : ::detail::SingleThreadedExpirationTracker(aTimerPeriod, aName, + aEventTarget) {} + + virtual ~nsExpirationTracker() = default; + + nsresult AddObject(T* aObj) { + return this->AddObjectLocked(aObj, FakeLock()); + } + + void RemoveObject(T* aObj) { this->RemoveObjectLocked(aObj, FakeLock()); } + + nsresult MarkUsed(T* aObj) { return this->MarkUsedLocked(aObj, FakeLock()); } + + void AgeOneGeneration() { this->AgeOneGenerationLocked(FakeLock()); } + + void AgeAllGenerations() { this->AgeAllGenerationsLocked(FakeLock()); } + + class Iterator { + private: + AutoLock mAutoLock; + typename ExpirationTrackerImpl::Iterator mIterator; + + public: + explicit Iterator(nsExpirationTracker* aTracker) + : mAutoLock(aTracker->GetMutex()), mIterator(aTracker, mAutoLock) {} + + T* Next() { return mIterator.Next(); } + }; + + friend class Iterator; + + bool IsEmpty() { return this->IsEmptyLocked(FakeLock()); } +}; + +template +NS_IMETHODIMP ExpirationTrackerImpl:: + ExpirationTrackerObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, "memory-pressure") && mOwner) { + mOwner->HandleLowMemory(); + } + return NS_OK; +} + +template +NS_IMETHODIMP_(MozExternalRefCountType) +ExpirationTrackerImpl::ExpirationTrackerObserver::AddRef( + void) { + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver); + ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "ExpirationTrackerObserver", sizeof(*this)); + return mRefCnt; +} + +template +NS_IMETHODIMP_(MozExternalRefCountType) +ExpirationTrackerImpl::ExpirationTrackerObserver::Release(void) { + MOZ_ASSERT(int32_t(mRefCnt) > 0, "dup release"); + NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver); + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "ExpirationTrackerObserver"); + if (mRefCnt == 0) { + NS_ASSERT_OWNINGTHREAD(ExpirationTrackerObserver); + mRefCnt = 1; /* stabilize */ + delete (this); + return 0; + } + return mRefCnt; +} + +template +NS_IMETHODIMP ExpirationTrackerImpl:: + ExpirationTrackerObserver::QueryInterface(REFNSIID aIID, + void** aInstancePtr) { + NS_ASSERTION(aInstancePtr, "QueryInterface requires a non-NULL destination!"); + nsresult rv = NS_ERROR_FAILURE; + NS_INTERFACE_TABLE(ExpirationTrackerObserver, nsIObserver) + return rv; +} + +#endif /*NSEXPIRATIONTRACKER_H_*/ diff --git a/xpcom/ds/nsGkAtoms.cpp b/xpcom/ds/nsGkAtoms.cpp new file mode 100644 index 0000000000..402e0eb779 --- /dev/null +++ b/xpcom/ds/nsGkAtoms.cpp @@ -0,0 +1,70 @@ +/* -*- 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 "nsGkAtoms.h" + +namespace mozilla { +namespace detail { + +// Because this is `constexpr` it ends up in read-only memory where it can be +// shared between processes. +extern constexpr GkAtoms gGkAtoms = { +// The initialization of each atom's string. +// +// Expansion of the example GK_ATOM entries in nsGkAtoms.h: +// +// u"a", +// u"bb", +// u"Ccc", +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \ + u"" value_, +#include "nsGkAtomList.h" +#undef GK_ATOM + { +// The initialization of the atoms themselves. +// +// Note that |value_| is an 8-bit string, and so |sizeof(value_)| is equal +// to the number of chars (including the terminating '\0'). The |u""| prefix +// converts |value_| to a 16-bit string. +// +// Expansion of the example GK_ATOM entries in nsGkAtoms.h: +// +// nsStaticAtom( +// 1, +// 0x01234567, +// offsetof(GkAtoms, mAtoms[static_cast(GkAtoms::Atoms::a)]) - +// offsetof(GkAtoms, a_string), +// true), +// +// nsStaticAtom( +// 2, +// 0x12345678, +// offsetof(GkAtoms, mAtoms[static_cast(GkAtoms::Atoms::bb)]) - +// offsetof(GkAtoms, bb_string), +// false), +// +// nsStaticAtom( +// 3, +// 0x23456789, +// offsetof(GkAtoms, mAtoms[static_cast(GkAtoms::Atoms::Ccc)]) - +// offsetof(GkAtoms, Ccc_string), +// false), +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \ + nsStaticAtom( \ + sizeof(value_) - 1, hash_, \ + offsetof(GkAtoms, mAtoms[static_cast(GkAtoms::Atoms::name_)]) - \ + offsetof(GkAtoms, name_##_string), \ + is_ascii_lower_), +#include "nsGkAtomList.h" +#undef GK_ATOM + }}; + +} // namespace detail +} // namespace mozilla + +const nsStaticAtom* const nsGkAtoms::sAtoms = mozilla::detail::gGkAtoms.mAtoms; diff --git a/xpcom/ds/nsGkAtoms.h b/xpcom/ds/nsGkAtoms.h new file mode 100644 index 0000000000..eba33bf95c --- /dev/null +++ b/xpcom/ds/nsGkAtoms.h @@ -0,0 +1,192 @@ +/* -*- 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/. */ + +#ifndef nsGkAtoms_h___ +#define nsGkAtoms_h___ + +#include "nsAtom.h" + +// Static atoms are structured carefully to satisfy a lot of constraints. +// +// - We have ~2300 static atoms. +// +// - We want them to be constexpr so they end up in .rodata, and thus shared +// between processes, minimizing memory usage. +// +// - We need them to be in an array, so we can iterate over them (for +// registration and lookups). +// +// - Each static atom has a string literal associated with it. We can't use a +// pointer to the string literal because then the atoms won't end up in +// .rodata. Therefore the string literals and the atoms must be arranged in a +// way such that a numeric index can be used instead. This numeric index +// (nsStaticAtom::mStringOffset) must be computable at compile-time to keep +// the static atom constexpr. It should also not be too large (a uint32_t is +// reasonable). +// +// - Each static atom stores the hash value of its associated string literal; +// it's used in various ways. The hash value must be specified at +// compile-time, to keep the static atom constexpr. +// +// - As well as accessing each static atom via array indexing, we need an +// individual pointer, e.g. nsGkAtoms::foo. We want this to be constexpr so +// it doesn't take up any space in memory. +// +// - The array of static atoms can't be in a .h file, because it's a huge +// constexpr expression, which would blow out compile times. But the +// individual pointers for the static atoms must be in a .h file so they are +// public. +// +// nsGkAtoms below defines static atoms in a way that satisfies these +// constraints. It uses nsGkAtomList.h, which defines the names and values of +// the atoms. nsGkAtomList.h is generated by StaticAtoms.py and has entries +// that look like this: +// +// GK_ATOM(a, "a", 0x01234567, nsStaticAtom, Atom) +// GK_ATOM(bb, "bb", 0x12345678, nsCSSPseudoElementStaticAtom, +// PseudoElementAtom) +// GK_ATOM(Ccc, "Ccc", 0x23456789, nsCSSAnonBoxPseudoStaticAtom, +// InheritingAnonBoxAtom) +// +// Comments throughout this file and nsGkAtoms.cpp show how these entries get +// expanded by macros. + +// Trivial subclasses of nsStaticAtom so that function signatures can require +// an atom from a specific atom list. +#define DEFINE_STATIC_ATOM_SUBCLASS(name_) \ + class name_ : public nsStaticAtom { \ + public: \ + constexpr name_(uint32_t aLength, uint32_t aHash, uint32_t aOffset, \ + bool aIsAsciiLowercase) \ + : nsStaticAtom(aLength, aHash, aOffset, aIsAsciiLowercase) {} \ + }; + +DEFINE_STATIC_ATOM_SUBCLASS(nsCSSAnonBoxPseudoStaticAtom) +DEFINE_STATIC_ATOM_SUBCLASS(nsCSSPseudoElementStaticAtom) + +#undef DEFINE_STATIC_ATOM_SUBCLASS + +namespace mozilla { +namespace detail { + +// This `detail` class contains the atom strings and the atom objects. Because +// they are together in a class, the `mStringOffset` field of the atoms will be +// small and can be initialized at compile time. +// +// A `detail` namespace is used because the things within it aren't directly +// referenced by external users of these static atoms. +struct GkAtoms { +// The declaration of each atom's string. +// +// Expansion of the example GK_ATOM entries from above: +// +// const char16_t a_string[sizeof("a")]; +// const char16_t bb_string[sizeof("bb")]; +// const char16_t Ccc_string[sizeof("Ccc")]; +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \ + const char16_t name_##_string[sizeof(value_)]; +#include "nsGkAtomList.h" +#undef GK_ATOM + + // The enum value for each atom. + enum class Atoms { +// Expansion of the example GK_ATOM entries above: +// +// a, +// bb, +// Ccc, +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) name_, +#include "nsGkAtomList.h" +#undef GK_ATOM + AtomsCount + }; + + const nsStaticAtom mAtoms[static_cast(Atoms::AtomsCount)]; +}; + +// The offset from the start of the GkAtoms object to the start of the +// nsStaticAtom array inside it. This is used in Rust to avoid problems +// with lld-link.exe on Windows when rust-bindgen generates a non-opaque +// version of GkAtoms. +// +// https://bugzilla.mozilla.org/show_bug.cgi?id=1517685 +const ptrdiff_t kGkAtomsArrayOffset = offsetof(GkAtoms, mAtoms); + +// The GkAtoms instance is `extern const` so it can be defined in a .cpp file. +// +// XXX: The NS_EXTERNAL_VIS is necessary to work around an apparent GCC bug: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87494 +#if defined(__GNUC__) && !defined(__clang__) +extern NS_EXTERNAL_VIS const GkAtoms gGkAtoms; +#else +extern const GkAtoms gGkAtoms; +#endif + +} // namespace detail +} // namespace mozilla + +// This class holds the pointers to the individual atoms. +class nsGkAtoms { + private: + friend void NS_InitAtomTable(); + + // This is a useful handle to the array of atoms, used below and also + // possibly by Rust code. + static const nsStaticAtom* const sAtoms; + + // The number of atoms, used below. + static constexpr size_t sAtomsLen = + static_cast(mozilla::detail::GkAtoms::Atoms::AtomsCount); + + public: + static nsStaticAtom* GetAtomByIndex(size_t aIndex) { + MOZ_ASSERT(aIndex < sAtomsLen); + return const_cast(&sAtoms[aIndex]); + } + + static size_t IndexOf(const nsStaticAtom* atom) { + nsStaticAtom* firstAtom = GetAtomByIndex(0); + size_t ret = atom - firstAtom; + MOZ_ASSERT(ret < sAtomsLen); + return ret; + } + +// The definition of the pointer to each static atom. +// +// These types are not `static constexpr * const` -- even though these +// atoms are immutable -- because they are often passed to functions with +// `nsAtom*` parameters that can be passed both dynamic and static atoms. +// +// Expansion of the example GK_ATOM entries above: +// +// static constexpr nsStaticAtom* a = +// const_cast( +// &mozilla::detail::gGkAtoms.mAtoms[ +// static_cast(mozilla::detail::GkAtoms::Atoms::a)]); +// +// static constexpr nsStaticAtom* bb = +// const_cast( +// &mozilla::detail::gGkAtoms.mAtoms[ +// static_cast(mozilla::detail::GkAtoms::Atoms::bb)]); +// +// static constexpr nsStaticAtom* Ccc = +// const_cast( +// &mozilla::detail::gGkAtoms.mAtoms[ +// static_cast(mozilla::detail::GkAtoms::Atoms::Ccc)]); +// +#define GK_ATOM(name_, value_, hash_, is_ascii_lower_, type_, atom_type_) \ + static constexpr nsStaticAtom* name_ = const_cast( \ + &mozilla::detail::gGkAtoms.mAtoms[static_cast( \ + mozilla::detail::GkAtoms::Atoms::name_)]); +#include "nsGkAtomList.h" +#undef GK_ATOM +}; + +inline bool nsAtom::IsEmpty() const { return this == nsGkAtoms::_empty; } + +#endif /* nsGkAtoms_h___ */ diff --git a/xpcom/ds/nsHashKeys.h b/xpcom/ds/nsHashKeys.h new file mode 100644 index 0000000000..fb0a8244c4 --- /dev/null +++ b/xpcom/ds/nsHashKeys.h @@ -0,0 +1,636 @@ +/* -*- 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/. */ + +#ifndef nsTHashKeys_h__ +#define nsTHashKeys_h__ + +#include "nsID.h" +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "PLDHashTable.h" +#include + +#include "nsString.h" +#include "nsCRTGlue.h" +#include "nsUnicharUtils.h" +#include "nsPointerHashKeys.h" + +#include +#include +#include + +#include +#include + +#include "mozilla/HashFunctions.h" + +namespace mozilla { + +// These are defined analogously to the HashString overloads in mfbt. + +inline uint32_t HashString(const nsAString& aStr) { + return HashString(aStr.BeginReading(), aStr.Length()); +} + +inline uint32_t HashString(const nsACString& aStr) { + return HashString(aStr.BeginReading(), aStr.Length()); +} + +} // namespace mozilla + +/** @file nsHashKeys.h + * standard HashKey classes for nsBaseHashtable and relatives. Each of these + * classes follows the nsTHashtable::EntryType specification + * + * Lightweight keytypes provided here: + * nsStringHashKey + * nsCStringHashKey + * nsUint32HashKey + * nsUint64HashKey + * nsFloatHashKey + * IntPtrHashKey + * nsPtrHashKey + * nsClearingPtrHashKey + * nsVoidPtrHashKey + * nsClearingVoidPtrHashKey + * nsISupportsHashKey + * nsIDHashKey + * nsDepCharHashKey + * nsCharPtrHashKey + * nsUnicharPtrHashKey + * nsGenericHashKey + */ + +/** + * hashkey wrapper using nsAString KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsStringHashKey : public PLDHashEntryHdr { + public: + typedef const nsAString& KeyType; + typedef const nsAString* KeyTypePointer; + + explicit nsStringHashKey(KeyTypePointer aStr) : mStr(*aStr) {} + nsStringHashKey(const nsStringHashKey&) = delete; + nsStringHashKey(nsStringHashKey&& aToMove) + : PLDHashEntryHdr(std::move(aToMove)), mStr(std::move(aToMove.mStr)) {} + ~nsStringHashKey() = default; + + KeyType GetKey() const { return mStr; } + bool KeyEquals(const KeyTypePointer aKey) const { return mStr.Equals(*aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + return mozilla::HashString(*aKey); + } + +#ifdef MOZILLA_INTERNAL_API + // To avoid double-counting, only measure the string if it is unshared. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +#endif + + enum { ALLOW_MEMMOVE = true }; + + private: + nsString mStr; +}; + +#ifdef MOZILLA_INTERNAL_API + +namespace mozilla::detail { + +template +struct comparatorTraits {}; + +template <> +struct comparatorTraits { + static int caseInsensitiveCompare(const char* aLhs, const char* aRhs, + size_t aLhsLength, size_t aRhsLength) { + return nsCaseInsensitiveCStringComparator(aLhs, aRhs, aLhsLength, + aRhsLength); + }; +}; + +template <> +struct comparatorTraits { + static int caseInsensitiveCompare(const char* aLhs, const char* aRhs, + size_t aLhsLength, size_t aRhsLength) { + return nsCaseInsensitiveUTF8StringComparator(aLhs, aRhs, aLhsLength, + aRhsLength); + }; +}; + +template <> +struct comparatorTraits { + static int caseInsensitiveCompare(const char16_t* aLhs, const char16_t* aRhs, + size_t aLhsLength, size_t aRhsLength) { + return nsCaseInsensitiveStringComparator(aLhs, aRhs, aLhsLength, + aRhsLength); + }; +}; + +} // namespace mozilla::detail + +/** + * This is internal-API only because nsCaseInsensitive{C}StringComparator is + * internal-only. + * + * @see nsTHashtable::EntryType for specification + */ + +template +class nsTStringCaseInsensitiveHashKey : public PLDHashEntryHdr { + public: + typedef const nsTSubstring& KeyType; + typedef const nsTSubstring* KeyTypePointer; + + explicit nsTStringCaseInsensitiveHashKey(KeyTypePointer aStr) : mStr(*aStr) { + // take it easy just deal HashKey + } + + nsTStringCaseInsensitiveHashKey(const nsTStringCaseInsensitiveHashKey&) = + delete; + nsTStringCaseInsensitiveHashKey(nsTStringCaseInsensitiveHashKey&& aToMove) + : PLDHashEntryHdr(std::move(aToMove)), mStr(std::move(aToMove.mStr)) {} + ~nsTStringCaseInsensitiveHashKey() = default; + + KeyType GetKey() const { return mStr; } + bool KeyEquals(const KeyTypePointer aKey) const { + using comparator = typename mozilla::detail::comparatorTraits; + return mStr.Equals(*aKey, comparator::caseInsensitiveCompare); + } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + nsTAutoString tmKey(*aKey); + ToLowerCase(tmKey); + return mozilla::HashString(tmKey); + } + enum { ALLOW_MEMMOVE = true }; + + // To avoid double-counting, only measure the string if it is unshared. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + + private: + const nsTString mStr; +}; + +using nsStringCaseInsensitiveHashKey = + nsTStringCaseInsensitiveHashKey; +using nsCStringASCIICaseInsensitiveHashKey = + nsTStringCaseInsensitiveHashKey; +using nsCStringUTF8CaseInsensitiveHashKey = + nsTStringCaseInsensitiveHashKey; + +#endif // MOZILLA_INTERNAL_API + +/** + * hashkey wrapper using nsACString KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsCStringHashKey : public PLDHashEntryHdr { + public: + typedef const nsACString& KeyType; + typedef const nsACString* KeyTypePointer; + + explicit nsCStringHashKey(const nsACString* aStr) : mStr(*aStr) {} + nsCStringHashKey(nsCStringHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mStr(std::move(aOther.mStr)) {} + ~nsCStringHashKey() = default; + + KeyType GetKey() const { return mStr; } + bool KeyEquals(KeyTypePointer aKey) const { return mStr.Equals(*aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashString(*aKey); + } + +#ifdef MOZILLA_INTERNAL_API + // To avoid double-counting, only measure the string if it is unshared. + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return GetKey().SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } +#endif + + enum { ALLOW_MEMMOVE = true }; + + private: + const nsCString mStr; +}; + +/** + * hashkey wrapper using integral or enum KeyTypes + * + * @see nsTHashtable::EntryType for specification + */ +template || std::is_enum_v, int> = 0> +class nsIntegralHashKey : public PLDHashEntryHdr { + public: + using KeyType = const T&; + using KeyTypePointer = const T*; + + explicit nsIntegralHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + nsIntegralHashKey(nsIntegralHashKey&& aOther) noexcept + : PLDHashEntryHdr(std::move(aOther)), mValue(aOther.mValue) {} + ~nsIntegralHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashGeneric(*aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const T mValue; +}; + +/** + * hashkey wrapper using uint32_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +using nsUint32HashKey = nsIntegralHashKey; + +/** + * hashkey wrapper using uint64_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +using nsUint64HashKey = nsIntegralHashKey; + +/** + * hashkey wrapper using float KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsFloatHashKey : public PLDHashEntryHdr { + public: + typedef const float& KeyType; + typedef const float* KeyTypePointer; + + explicit nsFloatHashKey(KeyTypePointer aKey) : mValue(*aKey) {} + nsFloatHashKey(nsFloatHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mValue(std::move(aOther.mValue)) {} + ~nsFloatHashKey() = default; + + KeyType GetKey() const { return mValue; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mValue; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return *reinterpret_cast(aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const float mValue; +}; + +/** + * hashkey wrapper using intptr_t KeyType + * + * @see nsTHashtable::EntryType for specification + */ +using IntPtrHashKey = nsIntegralHashKey; + +/** + * hashkey wrapper using nsISupports* KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsISupportsHashKey : public PLDHashEntryHdr { + public: + typedef nsISupports* KeyType; + typedef const nsISupports* KeyTypePointer; + + explicit nsISupportsHashKey(const nsISupports* aKey) + : mSupports(const_cast(aKey)) {} + nsISupportsHashKey(nsISupportsHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), + mSupports(std::move(aOther.mSupports)) {} + ~nsISupportsHashKey() = default; + + KeyType GetKey() const { return mSupports; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey == mSupports; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return NS_PTR_TO_UINT32(aKey) >> 2; + } + enum { ALLOW_MEMMOVE = true }; + + private: + nsCOMPtr mSupports; +}; + +/** + * hashkey wrapper using refcounted * KeyType + * + * @see nsTHashtable::EntryType for specification + */ +template +class nsRefPtrHashKey : public PLDHashEntryHdr { + public: + typedef T* KeyType; + typedef const T* KeyTypePointer; + + explicit nsRefPtrHashKey(const T* aKey) : mKey(const_cast(aKey)) {} + nsRefPtrHashKey(nsRefPtrHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {} + ~nsRefPtrHashKey() = default; + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return NS_PTR_TO_UINT32(aKey) >> 2; + } + enum { ALLOW_MEMMOVE = true }; + + private: + RefPtr mKey; +}; + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, nsRefPtrHashKey& aField, + const char* aName, uint32_t aFlags = 0) { + CycleCollectionNoteChild(aCallback, aField.GetKey(), aName, aFlags); +} + +/** + * hashkey wrapper using T* KeyType that sets key to nullptr upon + * destruction. Relevant only in cases where a memory pointer-scanner + * like valgrind might get confused about stale references. + * + * @see nsTHashtable::EntryType for specification + */ + +template +class nsClearingPtrHashKey : public nsPtrHashKey { + public: + explicit nsClearingPtrHashKey(const T* aKey) : nsPtrHashKey(aKey) {} + nsClearingPtrHashKey(nsClearingPtrHashKey&& aToMove) + : nsPtrHashKey(std::move(aToMove)) {} + ~nsClearingPtrHashKey() { nsPtrHashKey::mKey = nullptr; } +}; + +typedef nsClearingPtrHashKey nsClearingVoidPtrHashKey; + +/** + * hashkey wrapper using a function pointer KeyType + * + * @see nsTHashtable::EntryType for specification + */ +template +class nsFuncPtrHashKey : public PLDHashEntryHdr { + public: + typedef T& KeyType; + typedef const T* KeyTypePointer; + + explicit nsFuncPtrHashKey(const T* aKey) : mKey(*const_cast(aKey)) {} + nsFuncPtrHashKey(const nsFuncPtrHashKey& aToCopy) : mKey(aToCopy.mKey) {} + ~nsFuncPtrHashKey() = default; + + KeyType GetKey() const { return const_cast(mKey); } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return NS_PTR_TO_UINT32(*aKey) >> 2; + } + enum { ALLOW_MEMMOVE = true }; + + protected: + T mKey; +}; + +/** + * hashkey wrapper using nsID KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsIDHashKey : public PLDHashEntryHdr { + public: + typedef const nsID& KeyType; + typedef const nsID* KeyTypePointer; + + explicit nsIDHashKey(const nsID* aInID) : mID(*aInID) {} + nsIDHashKey(nsIDHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mID(std::move(aOther.mID)) {} + ~nsIDHashKey() = default; + + KeyType GetKey() const { return mID; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey->Equals(mID); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + // Hash the nsID object's raw bytes. + return mozilla::HashBytes(aKey, sizeof(KeyType)); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + nsID mID; +}; + +/** + * hashkey wrapper using nsID* KeyType + * + * @see nsTHashtable::EntryType for specification + */ +class nsIDPointerHashKey : public PLDHashEntryHdr { + public: + typedef const nsID* KeyType; + typedef const nsID* KeyTypePointer; + + explicit nsIDPointerHashKey(const nsID* aInID) : mID(aInID) {} + nsIDPointerHashKey(nsIDPointerHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mID(aOther.mID) {} + ~nsIDPointerHashKey() = default; + + KeyType GetKey() const { return mID; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey->Equals(*mID); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + // Hash the nsID object's raw bytes. + return mozilla::HashBytes(aKey, sizeof(*aKey)); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + const nsID* mID; +}; + +/** + * hashkey wrapper for "dependent" const char*; this class does not "own" + * its string pointer. + * + * This class must only be used if the strings have a lifetime longer than + * the hashtable they occupy. This normally occurs only for static + * strings or strings that have been arena-allocated. + * + * @see nsTHashtable::EntryType for specification + */ +class nsDepCharHashKey : public PLDHashEntryHdr { + public: + typedef const char* KeyType; + typedef const char* KeyTypePointer; + + explicit nsDepCharHashKey(const char* aKey) : mKey(aKey) {} + nsDepCharHashKey(nsDepCharHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {} + ~nsDepCharHashKey() = default; + + const char* GetKey() const { return mKey; } + bool KeyEquals(const char* aKey) const { return !strcmp(mKey, aKey); } + + static const char* KeyToPointer(const char* aKey) { return aKey; } + static PLDHashNumber HashKey(const char* aKey) { + return mozilla::HashString(aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + const char* mKey; +}; + +/** + * hashkey wrapper for const char*; at construction, this class duplicates + * a string pointed to by the pointer so that it doesn't matter whether or not + * the string lives longer than the hash table. + */ +class nsCharPtrHashKey : public PLDHashEntryHdr { + public: + typedef const char* KeyType; + typedef const char* KeyTypePointer; + + explicit nsCharPtrHashKey(const char* aKey) : mKey(strdup(aKey)) {} + + nsCharPtrHashKey(const nsCharPtrHashKey&) = delete; + nsCharPtrHashKey(nsCharPtrHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mKey(aOther.mKey) { + aOther.mKey = nullptr; + } + + ~nsCharPtrHashKey() { + if (mKey) { + free(const_cast(mKey)); + } + } + + const char* GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return !strcmp(mKey, aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashString(aKey); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(mKey); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + const char* mKey; +}; + +/** + * hashkey wrapper for const char16_t*; at construction, this class duplicates + * a string pointed to by the pointer so that it doesn't matter whether or not + * the string lives longer than the hash table. + */ +class nsUnicharPtrHashKey : public PLDHashEntryHdr { + public: + typedef const char16_t* KeyType; + typedef const char16_t* KeyTypePointer; + + explicit nsUnicharPtrHashKey(const char16_t* aKey) : mKey(NS_xstrdup(aKey)) {} + nsUnicharPtrHashKey(const nsUnicharPtrHashKey& aToCopy) = delete; + nsUnicharPtrHashKey(nsUnicharPtrHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mKey(aOther.mKey) { + aOther.mKey = nullptr; + } + + ~nsUnicharPtrHashKey() { + if (mKey) { + free(const_cast(mKey)); + } + } + + const char16_t* GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return !NS_strcmp(mKey, aKey); } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashString(aKey); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(mKey); + } + + enum { ALLOW_MEMMOVE = true }; + + private: + const char16_t* mKey; +}; + +namespace mozilla { + +template +PLDHashNumber Hash(const T& aValue) { + return aValue.Hash(); +} + +} // namespace mozilla + +/** + * Hashtable key class to use with objects for which Hash() and operator==() + * are defined. + */ +template +class nsGenericHashKey : public PLDHashEntryHdr { + public: + typedef const T& KeyType; + typedef const T* KeyTypePointer; + + explicit nsGenericHashKey(KeyTypePointer aKey) : mKey(*aKey) {} + nsGenericHashKey(const nsGenericHashKey&) = delete; + nsGenericHashKey(nsGenericHashKey&& aOther) + : PLDHashEntryHdr(std::move(aOther)), mKey(std::move(aOther.mKey)) {} + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return *aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return ::mozilla::Hash(*aKey); + } + enum { ALLOW_MEMMOVE = true }; + + private: + T mKey; +}; + +#endif // nsTHashKeys_h__ diff --git a/xpcom/ds/nsHashPropertyBag.cpp b/xpcom/ds/nsHashPropertyBag.cpp new file mode 100644 index 0000000000..68dd612c57 --- /dev/null +++ b/xpcom/ds/nsHashPropertyBag.cpp @@ -0,0 +1,366 @@ +/* -*- 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 "nsHashPropertyBag.h" + +#include + +#include "mozilla/Attributes.h" +#include "mozilla/SimpleEnumerator.h" +#include "nsArray.h" +#include "nsArrayEnumerator.h" +#include "nsIProperty.h" +#include "nsIVariant.h" +#include "nsThreadUtils.h" +#include "nsVariant.h" + +using mozilla::MakeRefPtr; +using mozilla::SimpleEnumerator; +using mozilla::Unused; + +extern "C" { + +// This function uses C linkage because it's exposed to Rust to support the +// `HashPropertyBag` wrapper in the `storage_variant` crate. +void NS_NewHashPropertyBag(nsIWritablePropertyBag** aBag) { + MakeRefPtr().forget(aBag); +} + +} // extern "C" + +/* + * nsHashPropertyBagBase implementation. + */ + +NS_IMETHODIMP +nsHashPropertyBagBase::HasKey(const nsAString& aName, bool* aResult) { + *aResult = mPropertyHash.Get(aName, nullptr); + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::Get(const nsAString& aName, nsIVariant** aResult) { + if (!mPropertyHash.Get(aName, aResult)) { + *aResult = nullptr; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetProperty(const nsAString& aName, + nsIVariant** aResult) { + bool isFound = mPropertyHash.Get(aName, aResult); + if (!isFound) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetProperty(const nsAString& aName, nsIVariant* aValue) { + if (NS_WARN_IF(!aValue)) { + return NS_ERROR_INVALID_ARG; + } + + mPropertyHash.InsertOrUpdate(aName, aValue); + + return NS_OK; +} + +NS_IMETHODIMP +nsHashPropertyBagBase::DeleteProperty(const nsAString& aName) { + return mPropertyHash.Remove(aName) ? NS_OK : NS_ERROR_FAILURE; +} + +// +// nsSimpleProperty class and impl; used for GetEnumerator +// + +class nsSimpleProperty final : public nsIProperty { + ~nsSimpleProperty() = default; + + public: + nsSimpleProperty(const nsAString& aName, nsIVariant* aValue) + : mName(aName), mValue(aValue) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTY + protected: + nsString mName; + nsCOMPtr mValue; +}; + +NS_IMPL_ISUPPORTS(nsSimpleProperty, nsIProperty) + +NS_IMETHODIMP +nsSimpleProperty::GetName(nsAString& aName) { + aName.Assign(mName); + return NS_OK; +} + +NS_IMETHODIMP +nsSimpleProperty::GetValue(nsIVariant** aValue) { + NS_IF_ADDREF(*aValue = mValue); + return NS_OK; +} + +// end nsSimpleProperty + +NS_IMETHODIMP +nsHashPropertyBagBase::GetEnumerator(nsISimpleEnumerator** aResult) { + nsCOMPtr propertyArray = nsArray::Create(); + if (!propertyArray) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (auto iter = mPropertyHash.Iter(); !iter.Done(); iter.Next()) { + const nsAString& key = iter.Key(); + nsIVariant* data = iter.UserData(); + nsSimpleProperty* sprop = new nsSimpleProperty(key, data); + propertyArray->AppendElement(sprop); + } + + return NS_NewArrayEnumerator(aResult, propertyArray, NS_GET_IID(nsIProperty)); +} + +#define IMPL_GETSETPROPERTY_AS(Name, Type) \ + NS_IMETHODIMP \ + nsHashPropertyBagBase::GetPropertyAs##Name(const nsAString& prop, \ + Type* _retval) { \ + nsIVariant* v = mPropertyHash.GetWeak(prop); \ + if (!v) return NS_ERROR_NOT_AVAILABLE; \ + return v->GetAs##Name(_retval); \ + } \ + \ + NS_IMETHODIMP \ + nsHashPropertyBagBase::SetPropertyAs##Name(const nsAString& prop, \ + Type value) { \ + nsCOMPtr var = new nsVariant(); \ + var->SetAs##Name(value); \ + return SetProperty(prop, var); \ + } + +IMPL_GETSETPROPERTY_AS(Int32, int32_t) +IMPL_GETSETPROPERTY_AS(Uint32, uint32_t) +IMPL_GETSETPROPERTY_AS(Int64, int64_t) +IMPL_GETSETPROPERTY_AS(Uint64, uint64_t) +IMPL_GETSETPROPERTY_AS(Double, double) +IMPL_GETSETPROPERTY_AS(Bool, bool) + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsAString(const nsAString& aProp, + nsAString& aResult) { + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + return v->GetAsAString(aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsACString(const nsAString& aProp, + nsACString& aResult) { + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + return v->GetAsACString(aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsAUTF8String(const nsAString& aProp, + nsACString& aResult) { + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + return v->GetAsAUTF8String(aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::GetPropertyAsInterface(const nsAString& aProp, + const nsIID& aIID, + void** aResult) { + nsIVariant* v = mPropertyHash.GetWeak(aProp); + if (!v) { + return NS_ERROR_NOT_AVAILABLE; + } + nsCOMPtr val; + nsresult rv = v->GetAsISupports(getter_AddRefs(val)); + if (NS_FAILED(rv)) { + return rv; + } + if (!val) { + // We have a value, but it's null + *aResult = nullptr; + return NS_OK; + } + return val->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsAString(const nsAString& aProp, + const nsAString& aValue) { + nsCOMPtr var = new nsVariant(); + var->SetAsAString(aValue); + return SetProperty(aProp, var); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsACString(const nsAString& aProp, + const nsACString& aValue) { + nsCOMPtr var = new nsVariant(); + var->SetAsACString(aValue); + return SetProperty(aProp, var); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsAUTF8String(const nsAString& aProp, + const nsACString& aValue) { + nsCOMPtr var = new nsVariant(); + var->SetAsAUTF8String(aValue); + return SetProperty(aProp, var); +} + +NS_IMETHODIMP +nsHashPropertyBagBase::SetPropertyAsInterface(const nsAString& aProp, + nsISupports* aValue) { + nsCOMPtr var = new nsVariant(); + var->SetAsISupports(aValue); + return SetProperty(aProp, var); +} + +void nsHashPropertyBagBase::CopyFrom(const nsHashPropertyBagBase* aOther) { + for (const auto& entry : aOther->mPropertyHash) { + SetProperty(entry.GetKey(), entry.GetWeak()); + } +} + +void nsHashPropertyBagBase::CopyFrom(nsIPropertyBag* aOther) { + CopyFrom(this, aOther); +} + +/* static */ void nsHashPropertyBagBase::CopyFrom(nsIWritablePropertyBag* aTo, + nsIPropertyBag* aFrom) { + if (aTo && aFrom) { + nsCOMPtr enumerator; + if (NS_SUCCEEDED(aFrom->GetEnumerator(getter_AddRefs(enumerator)))) { + for (auto& property : SimpleEnumerator(enumerator)) { + nsString name; + nsCOMPtr value; + Unused << NS_WARN_IF(NS_FAILED(property->GetName(name))); + Unused << NS_WARN_IF( + NS_FAILED(property->GetValue(getter_AddRefs(value)))); + Unused << NS_WARN_IF( + NS_FAILED(aTo->SetProperty(std::move(name), value))); + } + } else { + NS_WARNING("Unable to copy nsIPropertyBag"); + } + } +} + +nsresult nsGetProperty::operator()(const nsIID& aIID, + void** aInstancePtr) const { + nsresult rv; + + if (mPropBag) { + rv = mPropBag->GetPropertyAsInterface(mPropName, aIID, aInstancePtr); + } else { + rv = NS_ERROR_NULL_POINTER; + *aInstancePtr = 0; + } + + if (mErrorPtr) { + *mErrorPtr = rv; + } + return rv; +} + +/* + * nsHashPropertyBag implementation. + */ + +NS_IMPL_ADDREF(nsHashPropertyBag) +NS_IMPL_RELEASE(nsHashPropertyBag) + +NS_INTERFACE_MAP_BEGIN(nsHashPropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2) +NS_INTERFACE_MAP_END + +/* + * We need to ensure that the hashtable is destroyed on the main thread, as + * the nsIVariant values are main-thread only objects. + */ +class ProxyHashtableDestructor final : public mozilla::Runnable { + public: + using HashtableType = nsInterfaceHashtable; + explicit ProxyHashtableDestructor(HashtableType&& aTable) + : mozilla::Runnable("ProxyHashtableDestructor"), + mPropertyHash(std::move(aTable)) {} + + NS_IMETHODIMP + Run() override { + MOZ_ASSERT(NS_IsMainThread()); + HashtableType table(std::move(mPropertyHash)); + return NS_OK; + } + + private: + HashtableType mPropertyHash; +}; + +nsHashPropertyBag::~nsHashPropertyBag() { + if (!NS_IsMainThread()) { + RefPtr runnable = + new ProxyHashtableDestructor(std::move(mPropertyHash)); + MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable)); + } +} + +/* + * nsHashPropertyBagOMT implementation + */ +NS_IMPL_ADDREF(nsHashPropertyBagOMT) +NS_IMPL_RELEASE(nsHashPropertyBagOMT) + +NS_INTERFACE_MAP_BEGIN(nsHashPropertyBagOMT) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2) +NS_INTERFACE_MAP_END + +nsHashPropertyBagOMT::nsHashPropertyBagOMT() { + // nsHashPropertyBagOMT is supposed to be used off-main thread. If you need a + // single threaded property bag on the main thread, you should consider using + // nsHashPropertyBagCC instead, to prevent leaks. + MOZ_ASSERT(!NS_IsMainThread()); +} + +/* + * nsHashPropertyBagCC implementation. + */ + +NS_IMPL_CYCLE_COLLECTION(nsHashPropertyBagCC, mPropertyHash) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHashPropertyBagCC) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHashPropertyBagCC) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsHashPropertyBagCC) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIPropertyBag, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWritablePropertyBag) + NS_INTERFACE_MAP_ENTRY(nsIPropertyBag2) + NS_INTERFACE_MAP_ENTRY(nsIWritablePropertyBag2) +NS_INTERFACE_MAP_END diff --git a/xpcom/ds/nsHashPropertyBag.h b/xpcom/ds/nsHashPropertyBag.h new file mode 100644 index 0000000000..7a2e6b0ac8 --- /dev/null +++ b/xpcom/ds/nsHashPropertyBag.h @@ -0,0 +1,80 @@ +/* -*- 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/. */ + +#ifndef nsHashPropertyBag_h___ +#define nsHashPropertyBag_h___ + +#include "nsIVariant.h" +#include "nsIWritablePropertyBag.h" +#include "nsIWritablePropertyBag2.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsInterfaceHashtable.h" + +class nsHashPropertyBagBase : public nsIWritablePropertyBag, + public nsIWritablePropertyBag2 { + public: + nsHashPropertyBagBase() = default; + + void CopyFrom(const nsHashPropertyBagBase* aOther); + void CopyFrom(nsIPropertyBag* aOther); + static void CopyFrom(nsIWritablePropertyBag* aTo, nsIPropertyBag* aFrom); + + NS_DECL_NSIPROPERTYBAG + NS_DECL_NSIPROPERTYBAG2 + + NS_DECL_NSIWRITABLEPROPERTYBAG + NS_DECL_NSIWRITABLEPROPERTYBAG2 + + protected: + // a hash table of string -> nsIVariant + nsInterfaceHashtable mPropertyHash; +}; + +class nsHashPropertyBag : public nsHashPropertyBagBase { + public: + nsHashPropertyBag() = default; + NS_DECL_THREADSAFE_ISUPPORTS + + protected: + virtual ~nsHashPropertyBag(); +}; + +/* + * Off Main Thread variant of nsHashPropertyBag. Instances of this class + * should not be created on a main thread, nor should it contain main thread + * only objects, such as XPCVariants. The purpose of this class is to provide a + * way to use the property bag off main thread. + * Note: this class needs to be created and destroyed on the same thread and + * should be used single threaded. + */ +class nsHashPropertyBagOMT final : public nsHashPropertyBagBase { + public: + nsHashPropertyBagOMT(); + NS_DECL_ISUPPORTS + + protected: + // Doesn't need to dispatch to main thread because it cannot contain + // XPCVariants + virtual ~nsHashPropertyBagOMT() = default; +}; + +/* A cycle collected nsHashPropertyBag for main-thread-only use. */ +class nsHashPropertyBagCC final : public nsHashPropertyBagBase { + public: + nsHashPropertyBagCC() = default; + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsHashPropertyBagCC, + nsIWritablePropertyBag) + protected: + virtual ~nsHashPropertyBagCC() = default; +}; + +inline nsISupports* ToSupports(nsHashPropertyBagBase* aPropertyBag) { + return static_cast(aPropertyBag); +} + +#endif /* nsHashPropertyBag_h___ */ diff --git a/xpcom/ds/nsHashtablesFwd.h b/xpcom/ds/nsHashtablesFwd.h new file mode 100644 index 0000000000..080f79887d --- /dev/null +++ b/xpcom/ds/nsHashtablesFwd.h @@ -0,0 +1,94 @@ +/* -*- 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/. */ + +#ifndef XPCOM_DS_NSHASHTABLESFWD_H_ +#define XPCOM_DS_NSHASHTABLESFWD_H_ + +#include "mozilla/Attributes.h" + +struct PLDHashEntryHdr; + +template +class MOZ_IS_REFPTR nsCOMPtr; + +template +class MOZ_IS_REFPTR RefPtr; + +template +class MOZ_NEEDS_NO_VTABLE_TYPE nsTHashtable; + +template +class nsDefaultConverter; + +template > +class nsBaseHashtable; + +template +class nsClassHashtable; + +template +class nsRefCountedHashtable; + +/** + * templated hashtable class maps keys to interface pointers. + * See nsBaseHashtable for complete declaration. + * @deprecated This is going to be removed. Use nsTHashMap instead. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param Interface the interface-type being wrapped + * @see nsClassHashtable, nsTHashMap + */ +template +using nsInterfaceHashtable = + nsRefCountedHashtable>; + +/** + * templated hashtable class maps keys to reference pointers. + * See nsBaseHashtable for complete declaration. + * @deprecated This is going to be removed. Use nsTHashMap instead. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param PtrType the reference-type being wrapped + * @see nsClassHashtable, nsTHashMap + */ +template +using nsRefPtrHashtable = nsRefCountedHashtable>; + +namespace mozilla::detail { +template +struct nsKeyClass; +} // namespace mozilla::detail + +/** + * A universal hash map that maps some KeyType to some DataType. It can be used + * for any DataType, including RefPtr, nsCOMPtr and UniquePtr. + * + * For the default hash keys types, the appropriate hash key class is determined + * automatically, so you can just specify `nsTHashMap>`, for example. + * + * If you require custom hash behaviour (e.g. case insensitive string handling), + * you can still specify a hash key class derived from PLDHashEntryHdr + * explicitly. + * + * If you need to use a custom UserDataType, use nsBaseHashtable (or + * nsTHashtable) directly. However, you should double-check if that's really + * necessary. + */ +template +using nsTHashMap = + nsBaseHashtable::type, + DataType, DataType>; + +template +class nsTBaseHashSet; + +template +using nsTHashSet = + nsTBaseHashSet::type>; + +#endif // XPCOM_DS_NSHASHTABLESFWD_H_ diff --git a/xpcom/ds/nsIArray.idl b/xpcom/ds/nsIArray.idl new file mode 100644 index 0000000000..7e514649cb --- /dev/null +++ b/xpcom/ds/nsIArray.idl @@ -0,0 +1,103 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsISimpleEnumerator; + +/** + * nsIArray + * + * An indexed collection of elements. Provides basic functionality for + * retrieving elements at a specific position, searching for + * elements. Indexes are zero-based, such that the last element in the + * array is stored at the index length-1. + * + * For an array which can be modified, see nsIMutableArray below. + * + * Neither interface makes any attempt to protect the individual + * elements from modification. The convention is that the elements of + * the array should not be modified. Documentation within a specific + * interface should describe variations from this convention. + * + * It is also convention that if an interface provides access to an + * nsIArray, that the array should not be QueryInterfaced to an + * nsIMutableArray for modification. If the interface in question had + * intended the array to be modified, it would have returned an + * nsIMutableArray! + * + * null is a valid entry in the array, and as such any nsISupports + * parameters may be null, except where noted. + */ +[scriptable, builtinclass, uuid(114744d9-c369-456e-b55a-52fe52880d2d)] +interface nsIArray : nsISupports +{ + /** + * length + * + * number of elements in the array. + */ + readonly attribute unsigned long length; + + /** + * queryElementAt() + * + * Retrieve a specific element of the array, and QueryInterface it + * to the specified interface. null is a valid result for + * this method, but exceptions are thrown in other circumstances + * + * @param index position of element + * @param uuid the IID of the requested interface + * @param result the object, QI'd to the requested interface + * + * @throws NS_ERROR_NO_INTERFACE when an entry exists at the + * specified index, but the requested interface is not + * available. + * @throws NS_ERROR_ILLEGAL_VALUE when index > length-1 + * + */ + void queryElementAt(in unsigned long index, + in nsIIDRef uuid, + [iid_is(uuid), retval] out nsQIResult result); + + /** + * indexOf() + * + * Get the position of a specific element. Note that since null is + * a valid input, exceptions are used to indicate that an element + * is not found. + * + * @param startIndex The initial element to search in the array + * To start at the beginning, use 0 as the + * startIndex + * @param element The element you are looking for + * @returns a number >= startIndex which is the position of the + * element in the array. + * @throws NS_ERROR_FAILURE if the element was not in the array. + */ + unsigned long indexOf(in unsigned long startIndex, + in nsISupports element); + + /** + * enumerate the array + * + * @returns a new enumerator positioned at the start of the array + * @throws NS_ERROR_FAILURE if the array is empty (to make it easy + * to detect errors), or NS_ERROR_OUT_OF_MEMORY if out of memory. + */ + [binaryname(ScriptedEnumerate), optional_argc] + nsISimpleEnumerator enumerate([optional] in nsIIDRef aElemIID); + + [noscript] + nsISimpleEnumerator enumerateImpl(in nsIDRef aElemIID); + + %{C++ + nsresult + Enumerate(nsISimpleEnumerator** aRetVal, const nsID& aElemIID = NS_GET_IID(nsISupports)) + { + return EnumerateImpl(aElemIID, aRetVal); + } + %} +}; diff --git a/xpcom/ds/nsIArrayExtensions.idl b/xpcom/ds/nsIArrayExtensions.idl new file mode 100644 index 0000000000..872a5c018d --- /dev/null +++ b/xpcom/ds/nsIArrayExtensions.idl @@ -0,0 +1,51 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIArray.idl" + +/** + * Helper interface for allowing scripts to treat nsIArray instances as if + * they were nsISupportsArray instances while iterating. + * + * nsISupportsArray is convenient to iterate over in JavaScript: + * + * for (let i = 0; i < array.Count(); ++i) { + * let elem = array.GetElementAt(i); + * ... + * } + * + * but doing the same with nsIArray is somewhat less convenient, since + * queryElementAt is not nearly so nice to use from JavaScript. So we provide + * this extension interface so interfaces that currently return + * nsISupportsArray can start returning nsIArrayExtensions and all JavaScript + * should Just Work. Eventually we'll roll this interface into nsIArray + * itself, possibly getting rid of the Count() method, as it duplicates + * nsIArray functionality. + */ +[scriptable, builtinclass, uuid(261d442e-050c-453d-8aaa-b3f23bcc528b)] +interface nsIArrayExtensions : nsIArray +{ + /** + * Count() + * + * Retrieves the length of the array. This is an alias for the + * |nsIArray.length| attribute. + */ + uint32_t Count(); + + /** + * GetElementAt() + * + * Retrieve a specific element of the array. null is a valid result for + * this method. + * + * Note: If the index is out of bounds null will be returned. + * This differs from the behavior of nsIArray.queryElementAt() which + * will throw if an invalid index is specified. + * + * @param index position of element + */ + nsISupports GetElementAt(in uint32_t index); +}; diff --git a/xpcom/ds/nsIINIParser.idl b/xpcom/ds/nsIINIParser.idl new file mode 100644 index 0000000000..434490bf42 --- /dev/null +++ b/xpcom/ds/nsIINIParser.idl @@ -0,0 +1,61 @@ +/* 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 "nsISupports.idl" + +interface nsIUTF8StringEnumerator; +interface nsIFile; + +[scriptable, uuid(7eb955f6-3e78-4d39-b72f-c1bf12a94bce)] +interface nsIINIParser : nsISupports +{ + /** + * Initializes an INI file from string data + */ + void initFromString(in AUTF8String aData); + + /** + * Enumerates the [section]s available in the INI file. + */ + nsIUTF8StringEnumerator getSections(); + + /** + * Enumerates the keys available within a section. + */ + nsIUTF8StringEnumerator getKeys(in AUTF8String aSection); + + /** + * Get the value of a string for a particular section and key. + */ + AUTF8String getString(in AUTF8String aSection, in AUTF8String aKey); +}; + +[scriptable, uuid(b67bb24b-31a3-4a6a-a5d9-0485c9af5a04)] +interface nsIINIParserWriter : nsISupports +{ + + /** + * Set the value of a string for a particular section and key. + */ + void setString(in AUTF8String aSection, in AUTF8String aKey, in AUTF8String aValue); + + /** + * Write to the INI file. + */ + void writeFile(in nsIFile aINIFile); + + /** + * Return the formatted INI file contents + */ + AUTF8String writeToString(); +}; + +[scriptable, uuid(ccae7ea5-1218-4b51-aecb-c2d8ecd46af9)] +interface nsIINIParserFactory : nsISupports +{ + /** + * Create an iniparser instance from a local file. + */ + nsIINIParser createINIParser([optional] in nsIFile aINIFile); +}; diff --git a/xpcom/ds/nsIMutableArray.idl b/xpcom/ds/nsIMutableArray.idl new file mode 100644 index 0000000000..3f06ecbeb1 --- /dev/null +++ b/xpcom/ds/nsIMutableArray.idl @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIArrayExtensions.idl" + +/** + * nsIMutableArray + * A separate set of methods that will act on the array. Consumers of + * nsIArray should not QueryInterface to nsIMutableArray unless they + * own the array. + * + * As above, it is legal to add null elements to the array. Note also + * that null elements can be created as a side effect of + * insertElementAt(). Conversely, if insertElementAt() is never used, + * and null elements are never explicitly added to the array, then it + * is guaranteed that queryElementAt() will never return a null value. + * + * Any of these methods may throw NS_ERROR_OUT_OF_MEMORY when the + * array must grow to complete the call, but the allocation fails. + */ +[scriptable, builtinclass, uuid(af059da0-c85b-40ec-af07-ae4bfdc192cc)] +interface nsIMutableArray : nsIArrayExtensions +{ + /** + * appendElement() + * + * Append an element at the end of the array. + * + * @param element The element to append. + */ + void appendElement(in nsISupports element); + + /** + * removeElementAt() + * + * Remove an element at a specific position, moving all elements + * stored at a higher position down one. + * To remove a specific element, use indexOf() to find the index + * first, then call removeElementAt(). + * + * @param index the position of the item + * + */ + void removeElementAt(in unsigned long index); + + /** + * insertElementAt() + * + * Insert an element at the given position, moving the element + * currently located in that position, and all elements in higher + * position, up by one. + * + * @param element The element to insert + * @param index The position in the array: + * If the position is lower than the current length + * of the array, the elements at that position and + * onwards are bumped one position up. + * If the position is equal to the current length + * of the array, the new element is appended. + * An index lower than 0 or higher than the current + * length of the array is invalid and will be ignored. + */ + void insertElementAt(in nsISupports element, in unsigned long index); + + /** + * replaceElementAt() + * + * Replace the element at the given position. + * + * @param element The new element to insert + * @param index The position in the array + * If the position is lower than the current length + * of the array, an existing element will be replaced. + * If the position is equal to the current length + * of the array, the new element is appended. + * If the position is higher than the current length + * of the array, empty elements are appended followed + * by the new element at the specified position. + * An index lower than 0 is invalid and will be ignored. + */ + void replaceElementAt(in nsISupports element, in unsigned long index); + + + /** + * clear() + * + * clear the entire array, releasing all stored objects + */ + void clear(); +}; diff --git a/xpcom/ds/nsINIParserImpl.cpp b/xpcom/ds/nsINIParserImpl.cpp new file mode 100644 index 0000000000..6ed52947d8 --- /dev/null +++ b/xpcom/ds/nsINIParserImpl.cpp @@ -0,0 +1,143 @@ +/* -*- 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 "nsINIParserImpl.h" + +#include "nsINIParser.h" +#include "nsStringEnumerator.h" +#include "nsTArray.h" +#include "mozilla/Attributes.h" + +class nsINIParserImpl final : public nsIINIParser, public nsIINIParserWriter { + ~nsINIParserImpl() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINIPARSER + NS_DECL_NSIINIPARSERWRITER + + nsresult Init(nsIFile* aINIFile) { return mParser.Init(aINIFile); } + + private: + nsINIParser mParser; + bool ContainsNull(const nsACString& aStr); +}; + +NS_IMPL_ISUPPORTS(nsINIParserFactory, nsIINIParserFactory) + +NS_IMETHODIMP +nsINIParserFactory::CreateINIParser(nsIFile* aINIFile, nsIINIParser** aResult) { + *aResult = nullptr; + + RefPtr p(new nsINIParserImpl()); + + if (aINIFile) { + nsresult rv = p->Init(aINIFile); + if (NS_FAILED(rv)) { + return rv; + } + } + + p.forget(aResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsINIParserImpl, nsIINIParser, nsIINIParserWriter) + +bool nsINIParserImpl::ContainsNull(const nsACString& aStr) { + return aStr.CountChar('\0') > 0; +} + +static bool SectionCB(const char* aSection, void* aClosure) { + nsTArray* strings = static_cast*>(aClosure); + strings->AppendElement()->Assign(aSection); + return true; +} + +NS_IMETHODIMP +nsINIParserImpl::GetSections(nsIUTF8StringEnumerator** aResult) { + nsTArray* strings = new nsTArray; + + nsresult rv = mParser.GetSections(SectionCB, strings); + if (NS_SUCCEEDED(rv)) { + rv = NS_NewAdoptingUTF8StringEnumerator(aResult, strings); + } + + if (NS_FAILED(rv)) { + delete strings; + } + + return rv; +} + +static bool KeyCB(const char* aKey, const char* aValue, void* aClosure) { + nsTArray* strings = static_cast*>(aClosure); + strings->AppendElement()->Assign(aKey); + return true; +} + +NS_IMETHODIMP +nsINIParserImpl::GetKeys(const nsACString& aSection, + nsIUTF8StringEnumerator** aResult) { + if (ContainsNull(aSection)) { + return NS_ERROR_INVALID_ARG; + } + + nsTArray* strings = new nsTArray; + + nsresult rv = + mParser.GetStrings(PromiseFlatCString(aSection).get(), KeyCB, strings); + if (NS_SUCCEEDED(rv)) { + rv = NS_NewAdoptingUTF8StringEnumerator(aResult, strings); + } + + if (NS_FAILED(rv)) { + delete strings; + } + + return rv; +} + +NS_IMETHODIMP +nsINIParserImpl::GetString(const nsACString& aSection, const nsACString& aKey, + nsACString& aResult) { + if (ContainsNull(aSection) || ContainsNull(aKey)) { + return NS_ERROR_INVALID_ARG; + } + + return mParser.GetString(PromiseFlatCString(aSection).get(), + PromiseFlatCString(aKey).get(), aResult); +} + +NS_IMETHODIMP +nsINIParserImpl::InitFromString(const nsACString& aData) { + return mParser.InitFromString(nsCString(aData)); +} + +NS_IMETHODIMP +nsINIParserImpl::SetString(const nsACString& aSection, const nsACString& aKey, + const nsACString& aValue) { + if (ContainsNull(aSection) || ContainsNull(aKey) || ContainsNull(aValue)) { + return NS_ERROR_INVALID_ARG; + } + + return mParser.SetString(PromiseFlatCString(aSection).get(), + PromiseFlatCString(aKey).get(), + PromiseFlatCString(aValue).get()); +} + +NS_IMETHODIMP +nsINIParserImpl::WriteFile(nsIFile* aINIFile) { + return mParser.WriteToFile(aINIFile); +} + +NS_IMETHODIMP +nsINIParserImpl::WriteToString(nsACString& aOutput) { + aOutput.Truncate(); + mParser.WriteToString(aOutput); + + return NS_OK; +} diff --git a/xpcom/ds/nsINIParserImpl.h b/xpcom/ds/nsINIParserImpl.h new file mode 100644 index 0000000000..1a60fd1071 --- /dev/null +++ b/xpcom/ds/nsINIParserImpl.h @@ -0,0 +1,23 @@ +/* -*- 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/. */ + +#ifndef nsINIParserImpl_h__ +#define nsINIParserImpl_h__ + +#include "nsIINIParser.h" +#include "mozilla/Attributes.h" + +#define NS_INIPARSERFACTORY_CONTRACTID "@mozilla.org/xpcom/ini-parser-factory;1" + +class nsINIParserFactory final : public nsIINIParserFactory { + ~nsINIParserFactory() = default; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINIPARSERFACTORY +}; + +#endif // nsINIParserImpl_h__ diff --git a/xpcom/ds/nsIObserver.idl b/xpcom/ds/nsIObserver.idl new file mode 100644 index 0000000000..773424d0a7 --- /dev/null +++ b/xpcom/ds/nsIObserver.idl @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * This interface is implemented by an object that wants + * to observe an event corresponding to a topic. + */ + +[scriptable, function, uuid(DB242E01-E4D9-11d2-9DDE-000064657374)] +interface nsIObserver : nsISupports { + + /** + * Observe will be called when there is a notification for the + * topic |aTopic|. This assumes that the object implementing + * this interface has been registered with an observer service + * such as the nsIObserverService. + * + * If you expect multiple topics/subjects, the impl is + * responsible for filtering. + * + * You should not modify, add, remove, or enumerate + * notifications in the implemention of observe. + * + * @param aSubject : Notification specific interface pointer. + * @param aTopic : The notification topic or subject. + * @param aData : Notification specific wide string. + * subject event. + */ + void observe( in nsISupports aSubject, + in string aTopic, + in wstring aData ); + +}; diff --git a/xpcom/ds/nsIObserverService.idl b/xpcom/ds/nsIObserverService.idl new file mode 100644 index 0000000000..5ca51d5d44 --- /dev/null +++ b/xpcom/ds/nsIObserverService.idl @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIObserver; +interface nsISimpleEnumerator; + +/** + * nsIObserverService + * + * Service allows a client listener (nsIObserver) to register and unregister for + * notifications of specific string referenced topic. Service also provides a + * way to notify registered listeners and a way to enumerate registered client + * listeners. + */ + +[scriptable, builtinclass, uuid(D07F5192-E3D1-11d2-8ACD-00105A1B8860)] +interface nsIObserverService : nsISupports +{ + + /** + * AddObserver + * + * Registers a given listener for a notifications regarding the specified + * topic. + * + * @param anObserve : The interface pointer which will receive notifications. + * @param aTopic : The notification topic or subject. + * @param ownsWeak : If set to false, the nsIObserverService will hold a + * strong reference to |anObserver|. If set to true and + * |anObserver| supports the nsIWeakReference interface, + * a weak reference will be held. Otherwise an error will be + * returned. + */ + void addObserver( in nsIObserver anObserver, in string aTopic, + [optional] in boolean ownsWeak); + + /** + * removeObserver + * + * Unregisters a given listener from notifications regarding the specified + * topic. + * + * @param anObserver : The interface pointer which will stop recieving + * notifications. + * @param aTopic : The notification topic or subject. + */ + void removeObserver( in nsIObserver anObserver, in string aTopic ); + + /** + * notifyObservers + * + * Notifies all registered listeners of the given topic. + * Must not be used with shutdown topics (will assert + * on the parent process). + * + * @param aSubject : Notification specific interface pointer. + * @param aTopic : The notification topic or subject. + * @param someData : Notification specific wide string. + */ + void notifyObservers( in nsISupports aSubject, + in string aTopic, + [optional] in wstring someData ); + + /** + * hasObservers + * + * Checks to see if there are registered listeners for the given topic. + * + * Implemented in "nsObserverService.cpp". + * + * @param aTopic : The notification topic or subject. + * @param aFound : An out parameter; True if there are registered observers, + * False otherwise. + */ + [noscript, notxpcom, nostdcall] boolean hasObservers(in string aTopic); + + %{C++ + /** + * notifyWhenScriptSafe + * + * Notifies all registered listeners of the given topic once it is safe to + * run script. + * + * Implemented in "nsObserverService.cpp". + * + * @param aSubject : Notification specific interface pointer. + * @param aTopic : The notification topic or subject. + * @param someData : Notification specific wide string. + */ + nsresult NotifyWhenScriptSafe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData = nullptr); + %} + + /** + * enumerateObservers + * + * Returns an enumeration of all registered listeners. + * + * @param aTopic : The notification topic or subject. + */ + nsISimpleEnumerator enumerateObservers( in string aTopic ); + + +}; diff --git a/xpcom/ds/nsIPersistentProperties.h b/xpcom/ds/nsIPersistentProperties.h new file mode 100644 index 0000000000..eef30c2ff2 --- /dev/null +++ b/xpcom/ds/nsIPersistentProperties.h @@ -0,0 +1,13 @@ +/* -*- 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/. */ + +#ifndef __gen_nsIPersistentProperties_h__ +#define __gen_nsIPersistentProperties_h__ + +// "soft" switch over to an IDL generated header file +#include "nsIPersistentProperties2.h" + +#endif /* __gen_nsIPersistentProperties_h__ */ diff --git a/xpcom/ds/nsIPersistentProperties2.idl b/xpcom/ds/nsIPersistentProperties2.idl new file mode 100644 index 0000000000..7b07ba33ab --- /dev/null +++ b/xpcom/ds/nsIPersistentProperties2.idl @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsIProperties.idl" + +interface nsIInputStream; +interface nsIOutputStream; +interface nsISimpleEnumerator; + +%{C++ +#include "mozilla/MemoryReporting.h" +%} + +native MallocSizeOf(mozilla::MallocSizeOf); + +[scriptable, uuid(283EE646-1AEF-11D4-98B3-00C04fA0CE9A)] +interface nsIPropertyElement : nsISupports { + attribute AUTF8String key; + attribute AString value; +}; + +[scriptable, builtinclass, uuid(706867af-0400-4faa-beb1-0dae87308784)] +interface nsIPersistentProperties : nsIProperties +{ + /** + * load a set of name/value pairs from the input stream + * names and values should be in UTF8 + */ + void load(in nsIInputStream input); + + /** + * output the values to the stream - results will be in UTF8 + */ + void save(in nsIOutputStream output, in AUTF8String header); + + /** + * get an enumeration of nsIPropertyElement objects, + * which are read-only (i.e. setting properties on the element will + * not make changes back into the source nsIPersistentProperties + */ + nsISimpleEnumerator enumerate(); + + /** + * shortcut to nsIProperty's get() which retrieves a string value + * directly (and thus faster) + */ + AString getStringProperty(in AUTF8String key); + + /** + * shortcut to nsIProperty's set() which sets a string value + * directly (and thus faster). If the given property already exists, + * then the old value will be returned + */ + AString setStringProperty(in AUTF8String key, in AString value); + + [notxpcom, nostdcall] size_t sizeOfIncludingThis(in MallocSizeOf aMallocSizeOf); +}; diff --git a/xpcom/ds/nsIProperties.idl b/xpcom/ds/nsIProperties.idl new file mode 100644 index 0000000000..50d833e231 --- /dev/null +++ b/xpcom/ds/nsIProperties.idl @@ -0,0 +1,46 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +/* + * Simple mapping service interface. + */ + +[scriptable, uuid(78650582-4e93-4b60-8e85-26ebd3eb14ca)] +interface nsIProperties : nsISupports +{ + /** + * Gets a property with a given name. + * + * @throws NS_ERROR_FAILURE if a property with that name doesn't exist. + * @throws NS_ERROR_NO_INTERFACE if the found property fails to QI to the + * given iid. + */ + void get(in string prop, in nsIIDRef iid, + [iid_is(iid),retval] out nsQIResult result); + + /** + * Sets a property with a given name to a given value. + */ + void set(in string prop, in nsISupports value); + + /** + * Returns true if the property with the given name exists. + */ + boolean has(in string prop); + + /** + * Undefines a property. + * @throws NS_ERROR_FAILURE if a property with that name doesn't + * already exist. + */ + void undefine(in string prop); + + /** + * Returns an array of the keys. + */ + Array getKeys(); +}; diff --git a/xpcom/ds/nsIProperty.idl b/xpcom/ds/nsIProperty.idl new file mode 100644 index 0000000000..9e6e1e487e --- /dev/null +++ b/xpcom/ds/nsIProperty.idl @@ -0,0 +1,25 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/* nsIVariant based Property support. */ + +#include "nsISupports.idl" + +interface nsIVariant; + +[scriptable, uuid(6dcf9030-a49f-11d5-910d-0010a4e73d9a)] +interface nsIProperty : nsISupports +{ + /** + * Get the name of the property. + */ + readonly attribute AString name; + + /** + * Get the value of the property. + */ + readonly attribute nsIVariant value; +}; diff --git a/xpcom/ds/nsIPropertyBag.idl b/xpcom/ds/nsIPropertyBag.idl new file mode 100644 index 0000000000..57bd263451 --- /dev/null +++ b/xpcom/ds/nsIPropertyBag.idl @@ -0,0 +1,28 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/* nsIVariant based Property Bag support. */ + +#include "nsISupports.idl" + +interface nsIVariant; +interface nsISimpleEnumerator; + +[scriptable, uuid(bfcd37b0-a49f-11d5-910d-0010a4e73d9a)] +interface nsIPropertyBag : nsISupports +{ + /** + * Get a nsISimpleEnumerator whose elements are nsIProperty objects. + */ + readonly attribute nsISimpleEnumerator enumerator; + + /** + * Get a property value for the given name. + * @throws NS_ERROR_FAILURE if a property with that name doesn't + * exist. + */ + nsIVariant getProperty(in AString name); +}; diff --git a/xpcom/ds/nsIPropertyBag2.idl b/xpcom/ds/nsIPropertyBag2.idl new file mode 100644 index 0000000000..3232dce0ce --- /dev/null +++ b/xpcom/ds/nsIPropertyBag2.idl @@ -0,0 +1,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/. */ + +/* nsIVariant based Property Bag support. */ + +#include "nsIPropertyBag.idl" + +[scriptable, uuid(625cfd1e-da1e-4417-9ee9-dbc8e0b3fd79)] +interface nsIPropertyBag2 : nsIPropertyBag +{ + // Accessing a property as a different type may attempt conversion to the + // requested value + + int32_t getPropertyAsInt32 (in AString prop); + uint32_t getPropertyAsUint32 (in AString prop); + int64_t getPropertyAsInt64 (in AString prop); + uint64_t getPropertyAsUint64 (in AString prop); + double getPropertyAsDouble (in AString prop); + AString getPropertyAsAString (in AString prop); + ACString getPropertyAsACString (in AString prop); + AUTF8String getPropertyAsAUTF8String (in AString prop); + boolean getPropertyAsBool (in AString prop); + + /** + * This method returns null if the value exists, but is null. + * + * Note: C++ callers should not use this method. They should use the + * typesafe `do_GetProperty` wrapper instead. + */ + void getPropertyAsInterface (in AString prop, + in nsIIDRef iid, + [iid_is(iid), retval] out nsQIResult result); + + /** + * This method returns null if the value does not exist, + * or exists but is null. + */ + nsIVariant get (in AString prop); + + /** + * Check for the existence of a key. + */ + boolean hasKey (in AString prop); +}; + + +%{C++ +#include "nsCOMPtr.h" +#include "nsAString.h" + +class MOZ_STACK_CLASS nsGetProperty final : public nsCOMPtr_helper { + public: + nsGetProperty(nsIPropertyBag2* aPropBag, const nsAString& aPropName, nsresult* aError) + : mPropBag(aPropBag), mPropName(aPropName), mErrorPtr(aError) {} + + virtual nsresult NS_FASTCALL operator()(const nsIID&, void**) const override; + + private: + nsIPropertyBag2* MOZ_NON_OWNING_REF mPropBag; + const nsAString& mPropName; + nsresult* mErrorPtr; +}; + +/** + * A typesafe wrapper around nsIPropertyBag2::GetPropertyAsInterface. Similar + * to the `do_QueryInterface` family of functions, when assigned to a + * `nsCOMPtr` of a given type, attempts to query the given property to that + * type. + * + * If `aError` is passed, the return value of `GetPropertyAsInterface` is + * stored in it. + */ +inline const nsGetProperty do_GetProperty(nsIPropertyBag2* aPropBag, + const nsAString& aPropName, + nsresult* aError = 0) { + return nsGetProperty(aPropBag, aPropName, aError); +} + +%} diff --git a/xpcom/ds/nsISerializable.idl b/xpcom/ds/nsISerializable.idl new file mode 100644 index 0000000000..1920fa1b16 --- /dev/null +++ b/xpcom/ds/nsISerializable.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIObjectInputStream; +interface nsIObjectOutputStream; + +[scriptable, uuid(91cca981-c26d-44a8-bebe-d9ed4891503a)] +interface nsISerializable : nsISupports +{ + /** + * Initialize the object implementing nsISerializable, which must have + * been freshly constructed via CreateInstance. All data members that + * can't be set to default values must have been serialized by write, + * and should be read from aInputStream in the same order by this method. + */ + [must_use] void read(in nsIObjectInputStream aInputStream); + + /** + * Serialize the object implementing nsISerializable to aOutputStream, by + * writing each data member that must be recovered later to reconstitute + * a working replica of this object, in a canonical member and byte order, + * to aOutputStream. + * + * NB: a class that implements nsISerializable *must* also implement + * nsIClassInfo, in particular nsIClassInfo::GetClassID. + */ + void write(in nsIObjectOutputStream aOutputStream); +}; diff --git a/xpcom/ds/nsISimpleEnumerator.idl b/xpcom/ds/nsISimpleEnumerator.idl new file mode 100644 index 0000000000..1a2b47705d --- /dev/null +++ b/xpcom/ds/nsISimpleEnumerator.idl @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * Used to enumerate over elements defined by its implementor. + * Although hasMoreElements() can be called independently of getNext(), + * getNext() must be pre-ceeded by a call to hasMoreElements(). There is + * no way to "reset" an enumerator, once you obtain one. + * + * @version 1.0 + */ + +/** + * A wrapper for an nsISimpleEnumerator instance which implements the + * JavaScript iteration protocol. + */ +[scriptable, uuid(4432e8ae-d4d3-42a6-a4d1-829f1c29512b)] +interface nsIJSEnumerator : nsISupports { + [symbol] + nsIJSEnumerator iterator(); + + [implicit_jscontext] + jsval next(); +}; + +[scriptable, uuid(796f340d-0a2a-490b-9c60-640765e99782)] +interface nsISimpleEnumeratorBase : nsISupports { + /** + * Returns a JavaScript iterator for all remaining entries in the enumerator. + * Each entry is typically queried to the appropriate interface for the + * enumerator. + */ + [symbol] + nsIJSEnumerator iterator(); + + /** + * Returns JavaScript iterator for all remaining entries in the enumerator. + * Each entry is queried only to the supplied interface. If any element + * fails to query to that interface, the error is propagated to the caller. + */ + nsIJSEnumerator entries(in nsIIDRef aIface); +}; + +[scriptable, uuid(D1899240-F9D2-11D2-BDD6-000064657374)] +interface nsISimpleEnumerator : nsISimpleEnumeratorBase { + /** + * Called to determine whether or not the enumerator has + * any elements that can be returned via getNext(). This method + * is generally used to determine whether or not to initiate or + * continue iteration over the enumerator, though it can be + * called without subsequent getNext() calls. Does not affect + * internal state of enumerator. + * + * @see getNext() + * @return true if there are remaining elements in the enumerator. + * false if there are no more elements in the enumerator. + */ + boolean hasMoreElements(); + + /** + * Called to retrieve the next element in the enumerator. The "next" + * element is the first element upon the first call. Must be + * pre-ceeded by a call to hasMoreElements() which returns PR_TRUE. + * This method is generally called within a loop to iterate over + * the elements in the enumerator. + * + * @see hasMoreElements() + * @throws NS_ERROR_FAILURE if there are no more elements + * to enumerate. + * @return the next element in the enumeration. + */ + nsISupports getNext(); +}; diff --git a/xpcom/ds/nsIStringEnumerator.idl b/xpcom/ds/nsIStringEnumerator.idl new file mode 100644 index 0000000000..96ab966bff --- /dev/null +++ b/xpcom/ds/nsIStringEnumerator.idl @@ -0,0 +1,37 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +interface nsIJSEnumerator; + +/** + * Used to enumerate over an ordered list of strings. + */ + +/** + * Base class for C++-implemented string iterators. JS implementors need not + * be queryable to it. + */ +[scriptable, uuid(f5213d15-a4d1-4fb7-8a48-d69ccb7fb0eb)] +interface nsIStringEnumeratorBase : nsISupports +{ + [symbol, binaryname(StringIterator)] + nsIJSEnumerator iterator(); +}; + +[scriptable, uuid(50d3ef6c-9380-4f06-9fb2-95488f7d141c)] +interface nsIStringEnumerator : nsIStringEnumeratorBase +{ + boolean hasMore(); + AString getNext(); +}; + +[scriptable, uuid(9bdf1010-3695-4907-95ed-83d0410ec307)] +interface nsIUTF8StringEnumerator : nsIStringEnumeratorBase +{ + boolean hasMore(); + AUTF8String getNext(); +}; diff --git a/xpcom/ds/nsISupportsIterators.idl b/xpcom/ds/nsISupportsIterators.idl new file mode 100644 index 0000000000..8d47375d57 --- /dev/null +++ b/xpcom/ds/nsISupportsIterators.idl @@ -0,0 +1,292 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* nsISupportsIterators.idl --- IDL defining general purpose iterators */ + + +#include "nsISupports.idl" + + + /* + ... + */ + + + /** + * ... + */ +[scriptable, uuid(7330650e-1dd2-11b2-a0c2-9ff86ee97bed)] +interface nsIOutputIterator : nsISupports + { + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + }; + + /** + * ... + */ +[scriptable, uuid(85585e12-1dd2-11b2-a930-f6929058269a)] +interface nsIInputIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + + /** + * ... + */ +[scriptable, uuid(8da01646-1dd2-11b2-98a7-c7009045be7e)] +interface nsIForwardIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + + /** + * ... + */ +[scriptable, uuid(948defaa-1dd1-11b2-89f6-8ce81f5ebda9)] +interface nsIBidirectionalIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Move this iterator to the previous position in the underlying container or sequence. + */ + void stepBackward(); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + + /** + * ... + */ +[scriptable, uuid(9bd6fdb0-1dd1-11b2-9101-d15375968230)] +interface nsIRandomAccessIterator : nsISupports + { + /** + * Retrieve (and |AddRef()|) the element this iterator currently points to. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @result a new reference to the element this iterator currently points to (if any) + */ + nsISupports getElement(); + + /** + * Retrieve (and |AddRef()|) an element at some offset from where this iterator currently points. + * The offset may be negative. |getElementAt(0)| is equivalent to |getElement()|. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + * @result a new reference to the indicated element (if any) + */ + nsISupports getElementAt( in int32_t anOffset ); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElement( in nsISupports anElementToPut ); + + /** + * Put |anElementToPut| into the underlying container or sequence at the position |anOffset| away from that currently pointed to by this iterator. + * The iterator and the underlying container or sequence cooperate to |Release()| + * the replaced element, if any and if necessary, and to |AddRef()| the new element. + * |putElementAt(0, obj)| is equivalent to |putElement(obj)|. + * + * The result is undefined if this iterator currently points outside the + * useful range of the underlying container or sequence. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + * @param anElementToPut the element to place into the underlying container or sequence + */ + void putElementAt( in int32_t anOffset, in nsISupports anElementToPut ); + + /** + * Advance this iterator to the next position in the underlying container or sequence. + */ + void stepForward(); + + /** + * Move this iterator by |anOffset| positions in the underlying container or sequence. + * |anOffset| may be negative. |stepForwardBy(1)| is equivalent to |stepForward()|. + * |stepForwardBy(0)| is a no-op. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + */ + void stepForwardBy( in int32_t anOffset ); + + /** + * Move this iterator to the previous position in the underlying container or sequence. + */ + void stepBackward(); + + /** + * Move this iterator backwards by |anOffset| positions in the underlying container or sequence. + * |anOffset| may be negative. |stepBackwardBy(1)| is equivalent to |stepBackward()|. + * |stepBackwardBy(n)| is equivalent to |stepForwardBy(-n)|. |stepBackwardBy(0)| is a no-op. + * + * @param anOffset a |0|-based offset from the position to which this iterator currently points + */ + void stepBackwardBy( in int32_t anOffset ); + + /** + * Test if |anotherIterator| points to the same position in the underlying container or sequence. + * + * The result is undefined if |anotherIterator| was not created by or for the same underlying container or sequence. + * + * @param anotherIterator another iterator to compare against, created by or for the same underlying container or sequence + * @result true if |anotherIterator| points to the same position in the underlying container or sequence + */ + boolean isEqualTo( in nsISupports anotherIterator ); + + /** + * Create a new iterator pointing to the same position in the underlying container or sequence to which this iterator currently points. + * The returned iterator is suitable for use in a subsequent call to |isEqualTo()| against this iterator. + * + * @result a new iterator pointing at the same position in the same underlying container or sequence as this iterator + */ + nsISupports clone(); + }; + +%{C++ +%} diff --git a/xpcom/ds/nsISupportsPrimitives.idl b/xpcom/ds/nsISupportsPrimitives.idl new file mode 100644 index 0000000000..d661ce3b21 --- /dev/null +++ b/xpcom/ds/nsISupportsPrimitives.idl @@ -0,0 +1,222 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* nsISupports wrappers for single primitive pieces of data. */ + +#include "nsISupports.idl" + +/** + * Primitive base interface. + * + * These first three are pointer types and do data copying + * using the nsIMemory. Be careful! + */ + +[scriptable, builtinclass, uuid(d0d4b136-1dd1-11b2-9371-f0727ef827c0)] +interface nsISupportsPrimitive : nsISupports +{ + const unsigned short TYPE_ID = 1; + const unsigned short TYPE_CSTRING = 2; + const unsigned short TYPE_STRING = 3; + const unsigned short TYPE_PRBOOL = 4; + const unsigned short TYPE_PRUINT8 = 5; + const unsigned short TYPE_PRUINT16 = 6; + const unsigned short TYPE_PRUINT32 = 7; + const unsigned short TYPE_PRUINT64 = 8; + const unsigned short TYPE_PRTIME = 9; + const unsigned short TYPE_CHAR = 10; + const unsigned short TYPE_PRINT16 = 11; + const unsigned short TYPE_PRINT32 = 12; + const unsigned short TYPE_PRINT64 = 13; + const unsigned short TYPE_FLOAT = 14; + const unsigned short TYPE_DOUBLE = 15; + // 16 was for TYPE_VOID + const unsigned short TYPE_INTERFACE_POINTER = 17; + + readonly attribute unsigned short type; +}; + +/** + * Scriptable storage for nsID structures + */ + +[scriptable, builtinclass, uuid(d18290a0-4a1c-11d3-9890-006008962422)] +interface nsISupportsID : nsISupportsPrimitive +{ + attribute nsIDPtr data; + string toString(); +}; + +/** + * Scriptable storage for ASCII strings + */ + +[scriptable, builtinclass, uuid(d65ff270-4a1c-11d3-9890-006008962422)] +interface nsISupportsCString : nsISupportsPrimitive +{ + attribute ACString data; + string toString(); +}; + +/** + * Scriptable storage for Unicode strings + */ + +[scriptable, builtinclass, uuid(d79dc970-4a1c-11d3-9890-006008962422)] +interface nsISupportsString : nsISupportsPrimitive +{ + attribute AString data; + wstring toString(); +}; + +/** + * The rest are truly primitive and are passed by value + */ + +/** + * Scriptable storage for booleans + */ + +[scriptable, builtinclass, uuid(ddc3b490-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRBool : nsISupportsPrimitive +{ + attribute boolean data; + string toString(); +}; + +/** + * Scriptable storage for 8-bit integers + */ + +[scriptable, builtinclass, uuid(dec2e4e0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint8 : nsISupportsPrimitive +{ + attribute uint8_t data; + string toString(); +}; + +/** + * Scriptable storage for unsigned 16-bit integers + */ + +[scriptable, builtinclass, uuid(dfacb090-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint16 : nsISupportsPrimitive +{ + attribute uint16_t data; + string toString(); +}; + +/** + * Scriptable storage for unsigned 32-bit integers + */ + +[scriptable, builtinclass, uuid(e01dc470-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint32 : nsISupportsPrimitive +{ + attribute uint32_t data; + string toString(); +}; + +/** + * Scriptable storage for 64-bit integers + */ + +[scriptable, builtinclass, uuid(e13567c0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRUint64 : nsISupportsPrimitive +{ + attribute uint64_t data; + string toString(); +}; + +/** + * Scriptable storage for NSPR date/time values + */ + +[scriptable, builtinclass, uuid(e2563630-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRTime : nsISupportsPrimitive +{ + attribute PRTime data; + string toString(); +}; + +/** + * Scriptable storage for single character values + * (often used to store an ASCII character) + */ + +[scriptable, builtinclass, uuid(e2b05e40-4a1c-11d3-9890-006008962422)] +interface nsISupportsChar : nsISupportsPrimitive +{ + attribute char data; + string toString(); +}; + +/** + * Scriptable storage for 16-bit integers + */ + +[scriptable, builtinclass, uuid(e30d94b0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt16 : nsISupportsPrimitive +{ + attribute int16_t data; + string toString(); +}; + +/** + * Scriptable storage for 32-bit integers + */ + +[scriptable, builtinclass, uuid(e36c5250-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt32 : nsISupportsPrimitive +{ + attribute int32_t data; + string toString(); +}; + +/** + * Scriptable storage for 64-bit integers + */ + +[scriptable, builtinclass, uuid(e3cb0ff0-4a1c-11d3-9890-006008962422)] +interface nsISupportsPRInt64 : nsISupportsPrimitive +{ + attribute int64_t data; + string toString(); +}; + +/** + * Scriptable storage for floating point numbers + */ + +[scriptable, builtinclass, uuid(abeaa390-4ac0-11d3-baea-00805f8a5dd7)] +interface nsISupportsFloat : nsISupportsPrimitive +{ + attribute float data; + string toString(); +}; + +/** + * Scriptable storage for doubles + */ + +[scriptable, builtinclass, uuid(b32523a0-4ac0-11d3-baea-00805f8a5dd7)] +interface nsISupportsDouble : nsISupportsPrimitive +{ + attribute double data; + string toString(); +}; + +/** + * Scriptable storage for other XPCOM objects + */ + +[scriptable, builtinclass, uuid(995ea724-1dd1-11b2-9211-c21bdd3e7ed0)] +interface nsISupportsInterfacePointer : nsISupportsPrimitive +{ + attribute nsISupports data; + attribute nsIDPtr dataIID; + + string toString(); +}; diff --git a/xpcom/ds/nsIVariant.idl b/xpcom/ds/nsIVariant.idl new file mode 100644 index 0000000000..0d7f7e70c3 --- /dev/null +++ b/xpcom/ds/nsIVariant.idl @@ -0,0 +1,162 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/* The long avoided variant support for xpcom. */ + +#include "nsISupports.idl" + +%{C++ +#include "xptinfo.h" + +// This enum class used to be a const-only XPIDL interface, containing literal +// integer descriptions of the different fields. Instead, it now directly +// references the nsXPTTypeTag variants VTYPE_ are intended to match. +struct nsIDataType +{ + enum { + // These MUST match the declarations in xptinfo.h. + // Otherwise the world is likely to explode. + VTYPE_INT8 = TD_INT8 , + VTYPE_INT16 = TD_INT16 , + VTYPE_INT32 = TD_INT32 , + VTYPE_INT64 = TD_INT64 , + VTYPE_UINT8 = TD_UINT8 , + VTYPE_UINT16 = TD_UINT16 , + VTYPE_UINT32 = TD_UINT32 , + VTYPE_UINT64 = TD_UINT64 , + VTYPE_FLOAT = TD_FLOAT , + VTYPE_DOUBLE = TD_DOUBLE , + VTYPE_BOOL = TD_BOOL , + VTYPE_CHAR = TD_CHAR , + VTYPE_WCHAR = TD_WCHAR , + VTYPE_VOID = TD_VOID , + VTYPE_ID = TD_NSIDPTR , + VTYPE_CHAR_STR = TD_PSTRING , + VTYPE_WCHAR_STR = TD_PWSTRING , + VTYPE_INTERFACE = TD_INTERFACE_TYPE , + VTYPE_INTERFACE_IS = TD_INTERFACE_IS_TYPE, + VTYPE_ARRAY = TD_LEGACY_ARRAY , + VTYPE_STRING_SIZE_IS = TD_PSTRING_SIZE_IS , + VTYPE_WSTRING_SIZE_IS = TD_PWSTRING_SIZE_IS , + VTYPE_UTF8STRING = TD_UTF8STRING , + VTYPE_CSTRING = TD_CSTRING , + VTYPE_ASTRING = TD_ASTRING , + + // Non-xpt variant types + VTYPE_EMPTY_ARRAY = 254 , + VTYPE_EMPTY = 255 + }; +}; +%} + + +/** + * XPConnect has magic to transparently convert between nsIVariant and JS types. + * We mark the interface [scriptable] so that JS can use methods + * that refer to this interface. But we mark all the methods and attributes + * [noscript] since any nsIVariant object will be automatically converted to a + * JS type anyway. + */ + +[scriptable, builtinclass, uuid(81e4c2de-acac-4ad6-901a-b5fb1b851a0d)] +interface nsIVariant : nsISupports +{ + [notxpcom,nostdcall] readonly attribute uint16_t dataType; + + [noscript] uint8_t getAsInt8(); + [noscript] int16_t getAsInt16(); + [noscript] int32_t getAsInt32(); + [noscript] int64_t getAsInt64(); + [noscript] uint8_t getAsUint8(); + [noscript] uint16_t getAsUint16(); + [noscript] uint32_t getAsUint32(); + [noscript] uint64_t getAsUint64(); + [noscript] float getAsFloat(); + [noscript] double getAsDouble(); + [noscript] boolean getAsBool(); + [noscript] char getAsChar(); + [noscript] wchar getAsWChar(); + [notxpcom] nsresult getAsID(out nsID retval); + [noscript] AString getAsAString(); + [noscript] ACString getAsACString(); + [noscript] AUTF8String getAsAUTF8String(); + [noscript] string getAsString(); + [noscript] wstring getAsWString(); + [noscript] nsISupports getAsISupports(); + [noscript] jsval getAsJSVal(); + + [noscript] void getAsInterface(out nsIIDPtr iid, + [iid_is(iid), retval] out nsQIResult iface); + + [notxpcom] nsresult getAsArray(out uint16_t type, out nsIID iid, + out uint32_t count, out voidPtr ptr); + + [noscript] void getAsStringWithSize(out uint32_t size, + [size_is(size), retval] out string str); + + [noscript] void getAsWStringWithSize(out uint32_t size, + [size_is(size), retval] out wstring str); +}; + +/** + * An object that implements nsIVariant may or may NOT also implement this + * nsIWritableVariant. + * + * If the 'writable' attribute is false then attempts to call any of the 'set' + * methods can be expected to fail. Setting the 'writable' attribute may or + * may not succeed. + * + */ + +[scriptable, builtinclass, uuid(5586a590-8c82-11d5-90f3-0010a4e73d9a)] +interface nsIWritableVariant : nsIVariant +{ + attribute boolean writable; + + void setAsInt8(in uint8_t aValue); + void setAsInt16(in int16_t aValue); + void setAsInt32(in int32_t aValue); + void setAsInt64(in int64_t aValue); + void setAsUint8(in uint8_t aValue); + void setAsUint16(in uint16_t aValue); + void setAsUint32(in uint32_t aValue); + void setAsUint64(in uint64_t aValue); + void setAsFloat(in float aValue); + void setAsDouble(in double aValue); + void setAsBool(in boolean aValue); + void setAsChar(in char aValue); + void setAsWChar(in wchar aValue); + void setAsID(in nsIDRef aValue); + void setAsAString(in AString aValue); + void setAsACString(in ACString aValue); + void setAsAUTF8String(in AUTF8String aValue); + void setAsString(in string aValue); + void setAsWString(in wstring aValue); + void setAsISupports(in nsISupports aValue); + + void setAsInterface(in nsIIDRef iid, + [iid_is(iid)] in nsQIResult iface); + + [noscript] void setAsArray(in uint16_t type, in nsIIDPtr iid, + in uint32_t count, in voidPtr ptr); + + void setAsStringWithSize(in uint32_t size, + [size_is(size)] in string str); + + void setAsWStringWithSize(in uint32_t size, + [size_is(size)] in wstring str); + + void setAsVoid(); + void setAsEmpty(); + void setAsEmptyArray(); + + void setFromVariant(in nsIVariant aValue); +}; + +%{C++ +// The contractID for the generic implementation built in to xpcom. +#define NS_VARIANT_CONTRACTID "@mozilla.org/variant;1" +%} diff --git a/xpcom/ds/nsIWindowsRegKey.idl b/xpcom/ds/nsIWindowsRegKey.idl new file mode 100644 index 0000000000..197ba69c6d --- /dev/null +++ b/xpcom/ds/nsIWindowsRegKey.idl @@ -0,0 +1,336 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISupports.idl" + +%{C++ +#include +%} + +native HKEY(HKEY); + +/** + * This interface is designed to provide scriptable access to the Windows + * registry system ("With Great Power Comes Great Responsibility"). The + * interface represents a single key in the registry. + * + * This interface is highly Win32 specific. + */ +[scriptable, uuid(2555b930-d64f-437e-9be7-0a2cb252c1f4)] +interface nsIWindowsRegKey : nsISupports +{ + /** + * Root keys. The values for these keys correspond to the values from + * WinReg.h in the MS Platform SDK. The ROOT_KEY_ prefix corresponds to the + * HKEY_ prefix in the MS Platform SDK. + * + * This interface is not restricted to using only these root keys. + */ + const unsigned long ROOT_KEY_CLASSES_ROOT = 0x80000000; + const unsigned long ROOT_KEY_CURRENT_USER = 0x80000001; + const unsigned long ROOT_KEY_LOCAL_MACHINE = 0x80000002; + + /** + * Values for the mode parameter passed to the open and create methods. + * The values defined here correspond to the REGSAM values defined in + * WinNT.h in the MS Platform SDK, where ACCESS_ is replaced with KEY_. + * + * This interface is not restricted to using only these access types. + */ + const unsigned long ACCESS_BASIC = 0x00020000; + const unsigned long ACCESS_QUERY_VALUE = 0x00000001; + const unsigned long ACCESS_SET_VALUE = 0x00000002; + const unsigned long ACCESS_CREATE_SUB_KEY = 0x00000004; + const unsigned long ACCESS_ENUMERATE_SUB_KEYS = 0x00000008; + const unsigned long ACCESS_NOTIFY = 0x00000010; + const unsigned long ACCESS_READ = ACCESS_BASIC | + ACCESS_QUERY_VALUE | + ACCESS_ENUMERATE_SUB_KEYS | + ACCESS_NOTIFY; + const unsigned long ACCESS_WRITE = ACCESS_BASIC | + ACCESS_SET_VALUE | + ACCESS_CREATE_SUB_KEY; + const unsigned long ACCESS_ALL = ACCESS_READ | + ACCESS_WRITE; + const unsigned long WOW64_32 = 0x00000200; + const unsigned long WOW64_64 = 0x00000100; + + + /** + * Values for the type of a registry value. The numeric values of these + * constants are taken directly from WinNT.h in the MS Platform SDK. + * The Microsoft documentation should be consulted for the exact meaning of + * these value types. + * + * This interface is somewhat restricted to using only these value types. + * There is no method that is directly equivalent to RegQueryValueEx or + * RegSetValueEx. In particular, this interface does not support the + * REG_MULTI_SZ and REG_EXPAND_SZ value types. It is still possible to + * enumerate values that have other types (i.e., getValueType may return a + * type not defined below). + */ + const unsigned long TYPE_NONE = 0; // REG_NONE + const unsigned long TYPE_STRING = 1; // REG_SZ + const unsigned long TYPE_BINARY = 3; // REG_BINARY + const unsigned long TYPE_INT = 4; // REG_DWORD + const unsigned long TYPE_INT64 = 11; // REG_QWORD + + /** + * This attribute exposes the native HKEY and is available to provide C++ + * consumers with the flexibility of making other Windows registry API calls + * that are not exposed via this interface. + * + * It is possible to initialize this object by setting an HKEY on it. In + * that case, it is the responsibility of the consumer setting the HKEY to + * ensure that it is a valid HKEY. + * + * WARNING: Setting the key does not close the old key. + */ + [noscript] attribute HKEY key; + + /** + * This method closes the key. If the key is already closed, then this + * method does nothing. + */ + void close(); + + /** + * This method opens an existing key. This method fails if the key + * does not exist. + * + * NOTE: On 32-bit Windows, it is valid to pass any HKEY as the rootKey + * parameter of this function. However, for compatibility with 64-bit + * Windows, that usage should probably be avoided in favor of openChild. + * + * @param rootKey + * A root key defined above or any valid HKEY on 32-bit Windows. + * @param relPath + * A relative path from the given root key. + * @param mode + * Access mode, which is a bit-wise OR of the ACCESS_ values defined + * above. + */ + void open(in unsigned long rootKey, in AString relPath, in unsigned long mode); + + /** + * This method opens an existing key or creates a new key. + * + * NOTE: On 32-bit Windows, it is valid to pass any HKEY as the rootKey + * parameter of this function. However, for compatibility with 64-bit + * Windows, that usage should probably be avoided in favor of createChild. + * + * @param rootKey + * A root key defined above or any valid HKEY on 32-bit Windows. + * @param relPath + * A relative path from the given root key. + * @param mode + * Access mode, which is a bit-wise OR of the ACCESS_ values defined + * above. + */ + void create(in unsigned long rootKey, in AString relPath, in unsigned long mode); + + /** + * This method opens a subkey relative to this key. This method fails if the + * key does not exist. + * + * @return nsIWindowsRegKey for the newly opened subkey. + */ + nsIWindowsRegKey openChild(in AString relPath, in unsigned long mode); + + /** + * This method opens or creates a subkey relative to this key. + * + * @return nsIWindowsRegKey for the newly opened or created subkey. + */ + nsIWindowsRegKey createChild(in AString relPath, in unsigned long mode); + + /** + * This attribute returns the number of child keys. + */ + readonly attribute unsigned long childCount; + + /** + * This method returns the name of the n'th child key. + * + * @param index + * The index of the requested child key. + */ + AString getChildName(in unsigned long index); + + /** + * This method checks to see if the key has a child by the given name. + * + * @param name + * The name of the requested child key. + */ + boolean hasChild(in AString name); + + /** + * This attribute returns the number of values under this key. + */ + readonly attribute unsigned long valueCount; + + /** + * This method returns the name of the n'th value under this key. + * + * @param index + * The index of the requested value. + */ + AString getValueName(in unsigned long index); + + /** + * This method checks to see if the key has a value by the given name. + * + * @param name + * The name of the requested value. + */ + boolean hasValue(in AString name); + + /** + * This method removes a child key and all of its values. This method will + * fail if the key has any children of its own. + * + * @param relPath + * The relative path from this key to the key to be removed. + */ + void removeChild(in AString relPath); + + /** + * This method removes the value with the given name. + * + * @param name + * The name of the value to be removed. + */ + void removeValue(in AString name); + + /** + * This method returns the type of the value with the given name. The return + * value is one of the "TYPE_" constants defined above. + * + * @param name + * The name of the value to query. + */ + unsigned long getValueType(in AString name); + + /** + * This method reads the string contents of the named value as a Unicode + * string. + * + * @param name + * The name of the value to query. This parameter can be the empty + * string to request the key's default value. + */ + AString readStringValue(in AString name); + + /** + * This method reads the integer contents of the named value. + * + * @param name + * The name of the value to query. + */ + unsigned long readIntValue(in AString name); + + /** + * This method reads the 64-bit integer contents of the named value. + * + * @param name + * The name of the value to query. + */ + unsigned long long readInt64Value(in AString name); + + /** + * This method reads the binary contents of the named value under this key. + * + * JavaScript callers should take care with the result of this method since + * it will be byte-expanded to form a JS string. (The binary data will be + * treated as an ISO-Latin-1 character string, which it is not). + * + * @param name + * The name of the value to query. + */ + ACString readBinaryValue(in AString name); + + /** + * This method writes the unicode string contents of the named value. The + * value will be created if it does not already exist. + * + * @param name + * The name of the value to modify. This parameter can be the empty + * string to modify the key's default value. + * @param data + * The data for the value to modify. + */ + void writeStringValue(in AString name, in AString data); + + /** + * This method writes the integer contents of the named value. The value + * will be created if it does not already exist. + * + * @param name + * The name of the value to modify. + * @param data + * The data for the value to modify. + */ + void writeIntValue(in AString name, in unsigned long data); + + /** + * This method writes the 64-bit integer contents of the named value. The + * value will be created if it does not already exist. + * + * @param name + * The name of the value to modify. + * @param data + * The data for the value to modify. + */ + void writeInt64Value(in AString name, in unsigned long long data); + + /** + * This method writes the binary contents of the named value. The value will + * be created if it does not already exist. + * + * JavaScript callers should take care with the value passed to this method + * since it will be truncated from a JS string (unicode) to a ISO-Latin-1 + * string. (The binary data will be treated as an ISO-Latin-1 character + * string, which it is not). So, JavaScript callers should only pass + * character values in the range \u0000 to \u00FF, or else data loss will + * occur. + * + * @param name + * The name of the value to modify. + * @param data + * The data for the value to modify. + */ + void writeBinaryValue(in AString name, in ACString data); + + /** + * This method starts watching the key to see if any of its values have + * changed. The key must have been opened with mode including ACCESS_NOTIFY. + * If recurse is true, then this key and any of its descendant keys are + * watched. Otherwise, only this key is watched. + * + * @param recurse + * Indicates whether or not to also watch child keys. + */ + void startWatching(in boolean recurse); + + /** + * This method stops any watching of the key initiated by a call to + * startWatching. This method does nothing if the key is not being watched. + */ + void stopWatching(); + + /** + * This method returns true if the key is being watched for changes (i.e., + * if startWatching() was called). + */ + boolean isWatching(); + + /** + * This method returns true if the key has changed and false otherwise. + * This method will always return false if startWatching was not called. + */ + boolean hasChanged(); +}; diff --git a/xpcom/ds/nsIWritablePropertyBag.idl b/xpcom/ds/nsIWritablePropertyBag.idl new file mode 100644 index 0000000000..e916b7ccd6 --- /dev/null +++ b/xpcom/ds/nsIWritablePropertyBag.idl @@ -0,0 +1,27 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * 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/. */ + +/* nsIVariant based writable Property Bag support. */ + +#include "nsIPropertyBag.idl" + +[scriptable, uuid(96fc4671-eeb4-4823-9421-e50fb70ad353)] +interface nsIWritablePropertyBag : nsIPropertyBag +{ + /** + * Set a property with the given name to the given value. If + * a property already exists with the given name, it is + * overwritten. + */ + void setProperty(in AString name, in nsIVariant value); + + /** + * Delete a property with the given name. + * @throws NS_ERROR_FAILURE if a property with that name doesn't + * exist. + */ + void deleteProperty(in AString name); +}; diff --git a/xpcom/ds/nsIWritablePropertyBag2.idl b/xpcom/ds/nsIWritablePropertyBag2.idl new file mode 100644 index 0000000000..50f093905d --- /dev/null +++ b/xpcom/ds/nsIWritablePropertyBag2.idl @@ -0,0 +1,22 @@ +/* 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/. */ + +/* nsIVariant based Property Bag support. */ + +#include "nsIPropertyBag2.idl" + +[scriptable, uuid(9cfd1587-360e-4957-a58f-4c2b1c5e7ed9)] +interface nsIWritablePropertyBag2 : nsIPropertyBag2 +{ + void setPropertyAsInt32 (in AString prop, in int32_t value); + void setPropertyAsUint32 (in AString prop, in uint32_t value); + void setPropertyAsInt64 (in AString prop, in int64_t value); + void setPropertyAsUint64 (in AString prop, in uint64_t value); + void setPropertyAsDouble (in AString prop, in double value); + void setPropertyAsAString (in AString prop, in AString value); + void setPropertyAsACString (in AString prop, in ACString value); + void setPropertyAsAUTF8String (in AString prop, in AUTF8String value); + void setPropertyAsBool (in AString prop, in boolean value); + void setPropertyAsInterface (in AString prop, in nsISupports value); +}; diff --git a/xpcom/ds/nsInterfaceHashtable.h b/xpcom/ds/nsInterfaceHashtable.h new file mode 100644 index 0000000000..644864cf8f --- /dev/null +++ b/xpcom/ds/nsInterfaceHashtable.h @@ -0,0 +1,14 @@ +/* -*- 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/. */ + +#ifndef nsInterfaceHashtable_h__ +#define nsInterfaceHashtable_h__ + +#include "nsRefCountedHashtable.h" +#include "nsHashKeys.h" +#include "nsCOMPtr.h" + +#endif // nsInterfaceHashtable_h__ diff --git a/xpcom/ds/nsMathUtils.h b/xpcom/ds/nsMathUtils.h new file mode 100644 index 0000000000..527e0c3eb2 --- /dev/null +++ b/xpcom/ds/nsMathUtils.h @@ -0,0 +1,109 @@ +/* -*- 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/. */ + +#ifndef nsMathUtils_h__ +#define nsMathUtils_h__ + +#include "nscore.h" +#include +#include + +#if defined(XP_SOLARIS) +# include +#endif + +/* + * round + */ +inline double NS_round(double aNum) { + return aNum >= 0.0 ? floor(aNum + 0.5) : ceil(aNum - 0.5); +} +inline float NS_roundf(float aNum) { + return aNum >= 0.0f ? floorf(aNum + 0.5f) : ceilf(aNum - 0.5f); +} +inline int32_t NS_lround(double aNum) { + return aNum >= 0.0 ? int32_t(aNum + 0.5) : int32_t(aNum - 0.5); +} + +/* NS_roundup30 rounds towards infinity for positive and */ +/* negative numbers. */ + +#if defined(XP_WIN) && defined(_M_IX86) && !defined(__GNUC__) && \ + !defined(__clang__) +inline int32_t NS_lroundup30(float x) { + /* Code derived from Laurent de Soras' paper at */ + /* http://ldesoras.free.fr/doc/articles/rounding_en.pdf */ + + /* Rounding up on Windows is expensive using the float to */ + /* int conversion and the floor function. A faster */ + /* approach is to use f87 rounding while assuming the */ + /* default rounding mode of rounding to the nearest */ + /* integer. This rounding mode, however, actually rounds */ + /* to the nearest integer so we add the floating point */ + /* number to itself and add our rounding factor before */ + /* doing the conversion to an integer. We then do a right */ + /* shift of one bit on the integer to divide by two. */ + + /* This routine doesn't handle numbers larger in magnitude */ + /* than 2^30 but this is fine for NSToCoordRound because */ + /* Coords are limited to 2^30 in magnitude. */ + + static const double round_to_nearest = 0.5f; + int i; + + __asm { + fld x ; load fp argument + fadd st, st(0) ; double it + fadd round_to_nearest ; add the rounding factor + fistp dword ptr i ; convert the result to int + } + return i >> 1; /* divide by 2 */ +} +#endif /* XP_WIN && _M_IX86 && !__GNUC__ */ + +inline int32_t NS_lroundf(float aNum) { + return aNum >= 0.0f ? int32_t(aNum + 0.5f) : int32_t(aNum - 0.5f); +} + +/* + * hypot. We don't need a super accurate version of this, if a platform + * turns up with none of the possibilities below it would be okay to fall + * back to sqrt(x*x + y*y). + */ +inline double NS_hypot(double aNum1, double aNum2) { +#ifdef __GNUC__ + return __builtin_hypot(aNum1, aNum2); +#elif defined _WIN32 + return _hypot(aNum1, aNum2); +#else + return hypot(aNum1, aNum2); +#endif +} + +/** + * Check whether a floating point number is finite (not +/-infinity and not a + * NaN value). + */ +inline bool NS_finite(double aNum) { +#ifdef WIN32 + // NOTE: '!!' casts an int to bool without spamming MSVC warning C4800. + return !!_finite(aNum); +#else + return std::isfinite(aNum); +#endif +} + +/** + * Returns the result of the modulo of x by y using a floored division. + * fmod(x, y) is using a truncated division. + * The main difference is that the result of this method will have the sign of + * y while the result of fmod(x, y) will have the sign of x. + */ +inline double NS_floorModulo(double aNum1, double aNum2) { + return (aNum1 - aNum2 * floor(aNum1 / aNum2)); +} + +#endif diff --git a/xpcom/ds/nsObserverList.cpp b/xpcom/ds/nsObserverList.cpp new file mode 100644 index 0000000000..ba3eaaa2aa --- /dev/null +++ b/xpcom/ds/nsObserverList.cpp @@ -0,0 +1,92 @@ +/* -*- 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 "nsObserverList.h" + +#include "mozilla/ResultExtensions.h" +#include "nsCOMArray.h" +#include "xpcpublic.h" + +nsresult nsObserverList::AddObserver(nsIObserver* anObserver, bool ownsWeak) { + NS_ASSERTION(anObserver, "Null input"); + + MOZ_TRY(mObservers.AppendWeakElement(anObserver, ownsWeak)); + return NS_OK; +} + +nsresult nsObserverList::RemoveObserver(nsIObserver* anObserver) { + NS_ASSERTION(anObserver, "Null input"); + + MOZ_TRY(mObservers.RemoveWeakElement(anObserver)); + return NS_OK; +} + +void nsObserverList::GetObserverList(nsISimpleEnumerator** anEnumerator) { + RefPtr e(new nsObserverEnumerator(this)); + e.forget(anEnumerator); +} + +nsCOMArray nsObserverList::ReverseCloneObserverArray() { + nsCOMArray array; + array.SetCapacity(mObservers.Length()); + + // XXX This could also use RemoveElementsBy if we lifted the promise to fill + // aArray in reverse order. Although there shouldn't be anyone explicitly + // relying on the order, changing this might cause subtle problems, so we + // better leave it as it is. + + for (int32_t i = mObservers.Length() - 1; i >= 0; --i) { + nsCOMPtr observer = mObservers[i].GetValue(); + if (observer) { + array.AppendElement(observer.forget()); + } else { + // the object has gone away, remove the weakref + mObservers.RemoveElementAt(i); + } + } + + return array; +} + +void nsObserverList::AppendStrongObservers(nsCOMArray& aArray) { + aArray.SetCapacity(aArray.Length() + mObservers.Length()); + + for (int32_t i = mObservers.Length() - 1; i >= 0; --i) { + if (!mObservers[i].IsWeak()) { + nsCOMPtr observer = mObservers[i].GetValue(); + aArray.AppendObject(observer); + } + } +} + +void nsObserverList::NotifyObservers(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + const nsCOMArray observers = ReverseCloneObserverArray(); + + for (int32_t i = 0; i < observers.Count(); ++i) { + observers[i]->Observe(aSubject, aTopic, someData); + } +} + +nsObserverEnumerator::nsObserverEnumerator(nsObserverList* aObserverList) + : mIndex(0), mObservers(aObserverList->ReverseCloneObserverArray()) {} + +NS_IMETHODIMP +nsObserverEnumerator::HasMoreElements(bool* aResult) { + *aResult = (mIndex < mObservers.Count()); + return NS_OK; +} + +NS_IMETHODIMP +nsObserverEnumerator::GetNext(nsISupports** aResult) { + if (mIndex == mObservers.Count()) { + return NS_ERROR_FAILURE; + } + + NS_ADDREF(*aResult = mObservers[mIndex]); + ++mIndex; + return NS_OK; +} diff --git a/xpcom/ds/nsObserverList.h b/xpcom/ds/nsObserverList.h new file mode 100644 index 0000000000..9f209e40d6 --- /dev/null +++ b/xpcom/ds/nsObserverList.h @@ -0,0 +1,67 @@ +/* -*- 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/. */ + +#ifndef nsObserverList_h___ +#define nsObserverList_h___ + +#include "nsISupports.h" +#include "nsCOMArray.h" +#include "nsIObserver.h" +#include "nsHashKeys.h" +#include "nsMaybeWeakPtr.h" +#include "nsSimpleEnumerator.h" +#include "mozilla/Attributes.h" + +class nsObserverList : public nsCharPtrHashKey { + friend class nsObserverService; + + public: + explicit nsObserverList(const char* aKey) : nsCharPtrHashKey(aKey) { + MOZ_COUNT_CTOR(nsObserverList); + } + + nsObserverList(nsObserverList&& aOther) + : nsCharPtrHashKey(std::move(aOther)), + mObservers(std::move(aOther.mObservers)) { + MOZ_COUNT_CTOR(nsObserverList); + } + + MOZ_COUNTED_DTOR(nsObserverList) + + [[nodiscard]] nsresult AddObserver(nsIObserver* aObserver, bool aOwnsWeak); + [[nodiscard]] nsresult RemoveObserver(nsIObserver* aObserver); + + void NotifyObservers(nsISupports* aSubject, const char* aTopic, + const char16_t* aSomeData); + void GetObserverList(nsISimpleEnumerator** aEnumerator); + + // Clone an array with the observers of this category. + // The array is filled in last-added-first order. + nsCOMArray ReverseCloneObserverArray(); + + // Like FillObserverArray(), but only for strongly held observers. + void AppendStrongObservers(nsCOMArray& aArray); + + private: + nsMaybeWeakPtrArray mObservers; +}; + +class nsObserverEnumerator final : public nsSimpleEnumerator { + public: + NS_DECL_NSISIMPLEENUMERATOR + + explicit nsObserverEnumerator(nsObserverList* aObserverList); + + const nsID& DefaultInterface() override { return NS_GET_IID(nsIObserver); } + + private: + ~nsObserverEnumerator() override = default; + + int32_t mIndex; // Counts up from 0 + nsCOMArray mObservers; +}; + +#endif /* nsObserverList_h___ */ diff --git a/xpcom/ds/nsObserverService.cpp b/xpcom/ds/nsObserverService.cpp new file mode 100644 index 0000000000..5ed81b01a7 --- /dev/null +++ b/xpcom/ds/nsObserverService.cpp @@ -0,0 +1,357 @@ +/* -*- 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/Logging.h" +#include "nsComponentManagerUtils.h" +#include "nsContentUtils.h" +#include "nsIConsoleService.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsIScriptError.h" +#include "nsObserverService.h" +#include "nsObserverList.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsEnumeratorUtils.h" +#include "xpcpublic.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/net/NeckoCommon.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "nsString.h" + +// Log module for nsObserverService logging... +// +// To enable logging (see prlog.h for full details): +// +// set MOZ_LOG=ObserverService:5 +// set MOZ_LOG_FILE=service.log +// +// This enables LogLevel::Debug level information and places all output in +// the file service.log. +static mozilla::LazyLogModule sObserverServiceLog("ObserverService"); +#define LOG(x) MOZ_LOG(sObserverServiceLog, mozilla::LogLevel::Debug, x) + +using namespace mozilla; + +NS_IMETHODIMP +nsObserverService::CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) { + struct SuspectObserver { + SuspectObserver(const char* aTopic, size_t aReferentCount) + : mTopic(aTopic), mReferentCount(aReferentCount) {} + const char* mTopic; + size_t mReferentCount; + }; + + size_t totalNumStrong = 0; + size_t totalNumWeakAlive = 0; + size_t totalNumWeakDead = 0; + nsTArray suspectObservers; + + for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) { + nsObserverList* observerList = iter.Get(); + if (!observerList) { + continue; + } + + size_t topicNumStrong = 0; + size_t topicNumWeakAlive = 0; + size_t topicNumWeakDead = 0; + + nsMaybeWeakPtrArray& observers = observerList->mObservers; + for (uint32_t i = 0; i < observers.Length(); i++) { + if (observers[i].IsWeak()) { + nsCOMPtr ref = observers[i].GetValue(); + if (ref) { + topicNumWeakAlive++; + } else { + topicNumWeakDead++; + } + } else { + topicNumStrong++; + } + } + + totalNumStrong += topicNumStrong; + totalNumWeakAlive += topicNumWeakAlive; + totalNumWeakDead += topicNumWeakDead; + + // Keep track of topics that have a suspiciously large number + // of referents (symptom of leaks). + size_t topicTotal = topicNumStrong + topicNumWeakAlive + topicNumWeakDead; + if (topicTotal > kSuspectReferentCount) { + SuspectObserver suspect(observerList->GetKey(), topicTotal); + suspectObservers.AppendElement(suspect); + } + } + + // These aren't privacy-sensitive and so don't need anonymizing. + for (uint32_t i = 0; i < suspectObservers.Length(); i++) { + SuspectObserver& suspect = suspectObservers[i]; + nsPrintfCString suspectPath("observer-service-suspect/referent(topic=%s)", + suspect.mTopic); + aHandleReport->Callback( + /* process */ ""_ns, suspectPath, KIND_OTHER, UNITS_COUNT, + suspect.mReferentCount, + nsLiteralCString("A topic with a suspiciously large number of " + "referents. This may be symptomatic of a leak " + "if the number of referents is high with " + "respect to the number of windows."), + aData); + } + + MOZ_COLLECT_REPORT( + "observer-service/referent/strong", KIND_OTHER, UNITS_COUNT, + totalNumStrong, + "The number of strong references held by the observer service."); + + MOZ_COLLECT_REPORT( + "observer-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT, + totalNumWeakAlive, + "The number of weak references held by the observer service that are " + "still alive."); + + MOZ_COLLECT_REPORT( + "observer-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT, + totalNumWeakDead, + "The number of weak references held by the observer service that are " + "dead."); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsObserverService Implementation + +NS_IMPL_ISUPPORTS(nsObserverService, nsIObserverService, nsObserverService, + nsIMemoryReporter) + +nsObserverService::nsObserverService() : mShuttingDown(false) {} + +nsObserverService::~nsObserverService(void) { Shutdown(); } + +void nsObserverService::RegisterReporter() { RegisterWeakMemoryReporter(this); } + +void nsObserverService::Shutdown() { + if (mShuttingDown) { + return; + } + + mShuttingDown = true; + UnregisterWeakMemoryReporter(this); + mObserverTopicTable.Clear(); +} + +nsresult nsObserverService::Create(const nsIID& aIID, void** aInstancePtr) { + LOG(("nsObserverService::Create()")); + + RefPtr os = new nsObserverService(); + + // The memory reporter can not be immediately registered here because + // the nsMemoryReporterManager may attempt to get the nsObserverService + // during initialization, causing a recursive GetService. + NS_DispatchToCurrentThread( + NewRunnableMethod("nsObserverService::RegisterReporter", os, + &nsObserverService::RegisterReporter)); + + return os->QueryInterface(aIID, aInstancePtr); +} + +nsresult nsObserverService::EnsureValidCall() const { + if (!NS_IsMainThread()) { + MOZ_CRASH("Using observer service off the main thread!"); + return NS_ERROR_UNEXPECTED; + } + + if (mShuttingDown) { + NS_ERROR("Using observer service after XPCOM shutdown!"); + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + + return NS_OK; +} + +nsresult nsObserverService::FilterHttpOnTopics(const char* aTopic) { + // Specifically allow http-on-opening-request and http-on-stop-request in the + // child process; see bug 1269765. + if (mozilla::net::IsNeckoChild() && !strncmp(aTopic, "http-on-", 8) && + strcmp(aTopic, "http-on-failed-opening-request") && + strcmp(aTopic, "http-on-opening-request") && + strcmp(aTopic, "http-on-stop-request") && + strcmp(aTopic, "http-on-image-cache-response")) { + nsCOMPtr console( + do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + nsCOMPtr error( + do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); + error->Init(u"http-on-* observers only work in the parent process"_ns, + u""_ns, u""_ns, 0, 0, nsIScriptError::warningFlag, + "chrome javascript"_ns, false /* from private window */, + true /* from chrome context */); + console->LogMessage(error); + + return NS_ERROR_NOT_IMPLEMENTED; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsObserverService::AddObserver(nsIObserver* aObserver, const char* aTopic, + bool aOwnsWeak) { + LOG(("nsObserverService::AddObserver(%p: %s, %s)", (void*)aObserver, aTopic, + aOwnsWeak ? "weak" : "strong")); + + MOZ_TRY(EnsureValidCall()); + if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + MOZ_TRY(FilterHttpOnTopics(aTopic)); + + nsObserverList* observerList = mObserverTopicTable.PutEntry(aTopic); + if (!observerList) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return observerList->AddObserver(aObserver, aOwnsWeak); +} + +NS_IMETHODIMP +nsObserverService::RemoveObserver(nsIObserver* aObserver, const char* aTopic) { + LOG(("nsObserverService::RemoveObserver(%p: %s)", (void*)aObserver, aTopic)); + + if (mShuttingDown) { + // The service is shutting down. Let's ignore this call. + return NS_OK; + } + + MOZ_TRY(EnsureValidCall()); + if (NS_WARN_IF(!aObserver) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (!observerList) { + return NS_ERROR_FAILURE; + } + + return observerList->RemoveObserver(aObserver); +} + +NS_IMETHODIMP +nsObserverService::EnumerateObservers(const char* aTopic, + nsISimpleEnumerator** anEnumerator) { + LOG(("nsObserverService::EnumerateObservers(%s)", aTopic)); + + MOZ_TRY(EnsureValidCall()); + if (NS_WARN_IF(!anEnumerator) || NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (!observerList) { + return NS_NewEmptyEnumerator(anEnumerator); + } + + observerList->GetObserverList(anEnumerator); + return NS_OK; +} + +// Enumerate observers of aTopic and call Observe on each. +NS_IMETHODIMP nsObserverService::NotifyObservers(nsISupports* aSubject, + const char* aTopic, + const char16_t* aSomeData) { + LOG(("nsObserverService::NotifyObservers(%s)", aTopic)); + + MOZ_TRY(EnsureValidCall()); + if (NS_WARN_IF(!aTopic)) { + return NS_ERROR_INVALID_ARG; + } + + MOZ_ASSERT(AppShutdown::IsNoOrLegalShutdownTopic(aTopic)); + + AUTO_PROFILER_MARKER_TEXT("NotifyObservers", OTHER, MarkerStack::Capture(), + nsDependentCString(aTopic)); + AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE( + "nsObserverService::NotifyObservers", OTHER, aTopic); + + nsObserverList* observerList = mObserverTopicTable.GetEntry(aTopic); + if (observerList) { + observerList->NotifyObservers(aSubject, aTopic, aSomeData); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsObserverService::UnmarkGrayStrongObservers() { + MOZ_TRY(EnsureValidCall()); + + nsCOMArray strongObservers; + for (auto iter = mObserverTopicTable.Iter(); !iter.Done(); iter.Next()) { + nsObserverList* aObserverList = iter.Get(); + if (aObserverList) { + aObserverList->AppendStrongObservers(strongObservers); + } + } + + for (uint32_t i = 0; i < strongObservers.Length(); ++i) { + xpc_TryUnmarkWrappedGrayObject(strongObservers[i]); + } + + return NS_OK; +} + +bool nsObserverService::HasObservers(const char* aTopic) { + return mObserverTopicTable.Contains(aTopic); +} + +namespace { + +class NotifyWhenScriptSafeRunnable : public mozilla::Runnable { + public: + NotifyWhenScriptSafeRunnable(nsIObserverService* aObs, nsISupports* aSubject, + const char* aTopic, const char16_t* aData) + : mozilla::Runnable("NotifyWhenScriptSafeRunnable"), + mObs(aObs), + mSubject(aSubject), + mTopic(aTopic) { + if (aData) { + mData.Assign(aData); + } else { + mData.SetIsVoid(true); + } + } + + NS_IMETHOD Run() override { + const char16_t* data = mData.IsVoid() ? nullptr : mData.get(); + return mObs->NotifyObservers(mSubject, mTopic.get(), data); + } + + private: + nsCOMPtr mObs; + nsCOMPtr mSubject; + nsCString mTopic; + nsString mData; +}; + +} // namespace + +nsresult nsIObserverService::NotifyWhenScriptSafe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (nsContentUtils::IsSafeToRunScript()) { + return NotifyObservers(aSubject, aTopic, aData); + } + + nsContentUtils::AddScriptRunner(MakeAndAddRef( + this, aSubject, aTopic, aData)); + return NS_OK; +} diff --git a/xpcom/ds/nsObserverService.h b/xpcom/ds/nsObserverService.h new file mode 100644 index 0000000000..f4e445995b --- /dev/null +++ b/xpcom/ds/nsObserverService.h @@ -0,0 +1,56 @@ +/* -*- 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/. */ + +#ifndef nsObserverService_h___ +#define nsObserverService_h___ + +#include "nsIObserverService.h" +#include "nsObserverList.h" +#include "nsIMemoryReporter.h" +#include "nsTHashtable.h" +#include "mozilla/Attributes.h" + +// {D07F5195-E3D1-11d2-8ACD-00105A1B8860} +#define NS_OBSERVERSERVICE_CID \ + { \ + 0xd07f5195, 0xe3d1, 0x11d2, { \ + 0x8a, 0xcd, 0x0, 0x10, 0x5a, 0x1b, 0x88, 0x60 \ + } \ + } + +class nsObserverService final : public nsIObserverService, + public nsIMemoryReporter { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_OBSERVERSERVICE_CID) + + nsObserverService(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVERSERVICE + NS_DECL_NSIMEMORYREPORTER + + void Shutdown(); + + [[nodiscard]] static nsresult Create(const nsIID& aIID, void** aInstancePtr); + + // Unmark any strongly held observers implemented in JS so the cycle + // collector will not traverse them. + NS_IMETHOD UnmarkGrayStrongObservers(); + + private: + ~nsObserverService(void); + void RegisterReporter(); + nsresult EnsureValidCall() const; + nsresult FilterHttpOnTopics(const char* aTopic); + + static const size_t kSuspectReferentCount = 100; + bool mShuttingDown; + nsTHashtable mObserverTopicTable; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsObserverService, NS_OBSERVERSERVICE_CID) + +#endif /* nsObserverService_h___ */ diff --git a/xpcom/ds/nsPersistentProperties.cpp b/xpcom/ds/nsPersistentProperties.cpp new file mode 100644 index 0000000000..4e83870742 --- /dev/null +++ b/xpcom/ds/nsPersistentProperties.cpp @@ -0,0 +1,584 @@ +/* -*- 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 "nsArrayEnumerator.h" +#include "nsID.h" +#include "nsCOMArray.h" +#include "nsUnicharInputStream.h" +#include "nsPrintfCString.h" + +#include "nsPersistentProperties.h" +#include "nsIProperties.h" + +#include "mozilla/ArenaAllocatorExtensions.h" + +using mozilla::ArenaStrdup; + +struct PropertyTableEntry : public PLDHashEntryHdr { + // both of these are arena-allocated + const char* mKey; + const char16_t* mValue; +}; + +static const struct PLDHashTableOps property_HashTableOps = { + PLDHashTable::HashStringKey, + PLDHashTable::MatchStringKey, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr, +}; + +// +// parser stuff +// +enum EParserState { + eParserState_AwaitingKey, + eParserState_Key, + eParserState_AwaitingValue, + eParserState_Value, + eParserState_Comment +}; + +enum EParserSpecial { + eParserSpecial_None, // not parsing a special character + eParserSpecial_Escaped, // awaiting a special character + eParserSpecial_Unicode // parsing a \Uxxx value +}; + +class MOZ_STACK_CLASS nsPropertiesParser { + public: + explicit nsPropertiesParser(nsIPersistentProperties* aProps) + : mUnicodeValuesRead(0), + mUnicodeValue(u'\0'), + mHaveMultiLine(false), + mMultiLineCanSkipN(false), + mMinLength(0), + mState(eParserState_AwaitingKey), + mSpecialState(eParserSpecial_None), + mProps(aProps) {} + + void FinishValueState(nsAString& aOldValue) { + static const char trimThese[] = " \t"; + mKey.Trim(trimThese, false, true); + + // This is really ugly hack but it should be fast + char16_t backup_char; + uint32_t minLength = mMinLength; + if (minLength) { + backup_char = mValue[minLength - 1]; + mValue.SetCharAt('x', minLength - 1); + } + mValue.Trim(trimThese, false, true); + if (minLength) { + mValue.SetCharAt(backup_char, minLength - 1); + } + + mProps->SetStringProperty(NS_ConvertUTF16toUTF8(mKey), mValue, aOldValue); + mSpecialState = eParserSpecial_None; + WaitForKey(); + } + + EParserState GetState() { return mState; } + + static nsresult SegmentWriter(nsIUnicharInputStream* aStream, void* aClosure, + const char16_t* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount); + + nsresult ParseBuffer(const char16_t* aBuffer, uint32_t aBufferLength); + + private: + bool ParseValueCharacter( + char16_t aChar, // character that is just being parsed + const char16_t* aCur, // pointer to character aChar in the buffer + const char16_t*& aTokenStart, // string copying is done in blocks as big + // as possible, aTokenStart points to the + // beginning of this block + nsAString& aOldValue); // when duplicate property is found, new value + // is stored into hashtable and the old one is + // placed in this variable + + void WaitForKey() { mState = eParserState_AwaitingKey; } + + void EnterKeyState() { + mKey.Truncate(); + mState = eParserState_Key; + } + + void WaitForValue() { mState = eParserState_AwaitingValue; } + + void EnterValueState() { + mValue.Truncate(); + mMinLength = 0; + mState = eParserState_Value; + mSpecialState = eParserSpecial_None; + } + + void EnterCommentState() { mState = eParserState_Comment; } + + nsAutoString mKey; + nsAutoString mValue; + + uint32_t mUnicodeValuesRead; // should be 4! + char16_t mUnicodeValue; // currently parsed unicode value + bool mHaveMultiLine; // is TRUE when last processed characters form + // any of following sequences: + // - "\\\r" + // - "\\\n" + // - "\\\r\n" + // - any sequence above followed by any + // combination of ' ' and '\t' + bool mMultiLineCanSkipN; // TRUE if "\\\r" was detected + uint32_t mMinLength; // limit right trimming at the end to not trim + // escaped whitespaces + EParserState mState; + // if we see a '\' then we enter this special state + EParserSpecial mSpecialState; + nsCOMPtr mProps; +}; + +inline bool IsWhiteSpace(char16_t aChar) { + return (aChar == ' ') || (aChar == '\t') || (aChar == '\r') || + (aChar == '\n'); +} + +inline bool IsEOL(char16_t aChar) { return (aChar == '\r') || (aChar == '\n'); } + +bool nsPropertiesParser::ParseValueCharacter(char16_t aChar, + const char16_t* aCur, + const char16_t*& aTokenStart, + nsAString& aOldValue) { + switch (mSpecialState) { + // the normal state - look for special characters + case eParserSpecial_None: + switch (aChar) { + case '\\': + if (mHaveMultiLine) { + // there is nothing to append to mValue yet + mHaveMultiLine = false; + } else { + mValue += Substring(aTokenStart, aCur); + } + + mSpecialState = eParserSpecial_Escaped; + break; + + case '\n': + // if we detected multiline and got only "\\\r" ignore next "\n" if + // any + if (mHaveMultiLine && mMultiLineCanSkipN) { + // but don't allow another '\n' to be skipped + mMultiLineCanSkipN = false; + // Now there is nothing to append to the mValue since we are + // skipping whitespaces at the beginning of the new line of the + // multiline property. Set aTokenStart properly to ensure that + // nothing is appended if we find regular line-end or the end of the + // buffer. + aTokenStart = aCur + 1; + break; + } + [[fallthrough]]; + + case '\r': + // we're done! We have a key and value + mValue += Substring(aTokenStart, aCur); + FinishValueState(aOldValue); + mHaveMultiLine = false; + break; + + default: + // there is nothing to do with normal characters, + // but handle multilines correctly + if (mHaveMultiLine) { + if (aChar == ' ' || aChar == '\t') { + // don't allow another '\n' to be skipped + mMultiLineCanSkipN = false; + // Now there is nothing to append to the mValue since we are + // skipping whitespaces at the beginning of the new line of the + // multiline property. Set aTokenStart properly to ensure that + // nothing is appended if we find regular line-end or the end of + // the buffer. + aTokenStart = aCur + 1; + break; + } + mHaveMultiLine = false; + aTokenStart = aCur; + } + break; // from switch on (aChar) + } + break; // from switch on (mSpecialState) + + // saw a \ character, so parse the character after that + case eParserSpecial_Escaped: + // probably want to start parsing at the next token + // other characters, like 'u' might override this + aTokenStart = aCur + 1; + mSpecialState = eParserSpecial_None; + + switch (aChar) { + // the easy characters - \t, \n, and so forth + case 't': + mValue += char16_t('\t'); + mMinLength = mValue.Length(); + break; + case 'n': + mValue += char16_t('\n'); + mMinLength = mValue.Length(); + break; + case 'r': + mValue += char16_t('\r'); + mMinLength = mValue.Length(); + break; + case '\\': + mValue += char16_t('\\'); + break; + + // switch to unicode mode! + case 'u': + case 'U': + mSpecialState = eParserSpecial_Unicode; + mUnicodeValuesRead = 0; + mUnicodeValue = 0; + break; + + // a \ immediately followed by a newline means we're going multiline + case '\r': + case '\n': + mHaveMultiLine = true; + mMultiLineCanSkipN = (aChar == '\r'); + mSpecialState = eParserSpecial_None; + break; + + default: + // don't recognize the character, so just append it + mValue += aChar; + break; + } + break; + + // we're in the middle of parsing a 4-character unicode value + // like \u5f39 + case eParserSpecial_Unicode: + if ('0' <= aChar && aChar <= '9') { + mUnicodeValue = (mUnicodeValue << 4) | (aChar - '0'); + } else if ('a' <= aChar && aChar <= 'f') { + mUnicodeValue = (mUnicodeValue << 4) | (aChar - 'a' + 0x0a); + } else if ('A' <= aChar && aChar <= 'F') { + mUnicodeValue = (mUnicodeValue << 4) | (aChar - 'A' + 0x0a); + } else { + // non-hex character. Append what we have, and move on. + mValue += mUnicodeValue; + mMinLength = mValue.Length(); + mSpecialState = eParserSpecial_None; + + // leave aTokenStart at this unknown character, so it gets appended + aTokenStart = aCur; + + // ensure parsing this non-hex character again + return false; + } + + if (++mUnicodeValuesRead >= 4) { + aTokenStart = aCur + 1; + mSpecialState = eParserSpecial_None; + mValue += mUnicodeValue; + mMinLength = mValue.Length(); + } + + break; + } + + return true; +} + +nsresult nsPropertiesParser::SegmentWriter(nsIUnicharInputStream* aStream, + void* aClosure, + const char16_t* aFromSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + nsPropertiesParser* parser = static_cast(aClosure); + parser->ParseBuffer(aFromSegment, aCount); + + *aWriteCount = aCount; + return NS_OK; +} + +nsresult nsPropertiesParser::ParseBuffer(const char16_t* aBuffer, + uint32_t aBufferLength) { + const char16_t* cur = aBuffer; + const char16_t* end = aBuffer + aBufferLength; + + // points to the start/end of the current key or value + const char16_t* tokenStart = nullptr; + + // if we're in the middle of parsing a key or value, make sure + // the current token points to the beginning of the current buffer + if (mState == eParserState_Key || mState == eParserState_Value) { + tokenStart = aBuffer; + } + + nsAutoString oldValue; + + while (cur != end) { + char16_t c = *cur; + + switch (mState) { + case eParserState_AwaitingKey: + if (c == '#' || c == '!') { + EnterCommentState(); + } + + else if (!IsWhiteSpace(c)) { + // not a comment, not whitespace, we must have found a key! + EnterKeyState(); + tokenStart = cur; + } + break; + + case eParserState_Key: + if (c == '=' || c == ':') { + mKey += Substring(tokenStart, cur); + WaitForValue(); + } + break; + + case eParserState_AwaitingValue: + if (IsEOL(c)) { + // no value at all! mimic the normal value-ending + EnterValueState(); + FinishValueState(oldValue); + } + + // ignore white space leading up to the value + else if (!IsWhiteSpace(c)) { + tokenStart = cur; + EnterValueState(); + + // make sure to handle this first character + if (ParseValueCharacter(c, cur, tokenStart, oldValue)) { + cur++; + } + // If the character isn't consumed, don't do cur++ and parse + // the character again. This can happen f.e. for char 'X' in sequence + // "\u00X". This character can be control character and must be + // processed again. + continue; + } + break; + + case eParserState_Value: + if (ParseValueCharacter(c, cur, tokenStart, oldValue)) { + cur++; + } + // See few lines above for reason of doing this + continue; + + case eParserState_Comment: + // stay in this state till we hit EOL + if (c == '\r' || c == '\n') { + WaitForKey(); + } + break; + } + + // finally, advance to the next character + cur++; + } + + // if we're still parsing the value and are in eParserSpecial_None, then + // append whatever we have.. + if (mState == eParserState_Value && tokenStart && + mSpecialState == eParserSpecial_None) { + mValue += Substring(tokenStart, cur); + } + // if we're still parsing the key, then append whatever we have.. + else if (mState == eParserState_Key && tokenStart) { + mKey += Substring(tokenStart, cur); + } + + return NS_OK; +} + +nsPersistentProperties::nsPersistentProperties() + : mIn(nullptr), + mTable(&property_HashTableOps, sizeof(PropertyTableEntry), 16), + mArena() {} + +nsPersistentProperties::~nsPersistentProperties() = default; + +size_t nsPersistentProperties::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + // The memory used by mTable is accounted for in mArena. + size_t n = 0; + n += mArena.SizeOfExcludingThis(aMallocSizeOf); + n += mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + return aMallocSizeOf(this) + n; +} + +NS_IMPL_ISUPPORTS(nsPersistentProperties, nsIPersistentProperties, + nsIProperties) + +NS_IMETHODIMP +nsPersistentProperties::Load(nsIInputStream* aIn) { + nsresult rv = NS_NewUnicharInputStream(aIn, getter_AddRefs(mIn)); + + if (rv != NS_OK) { + NS_WARNING("Error creating UnicharInputStream"); + return NS_ERROR_FAILURE; + } + + nsPropertiesParser parser(this); + + uint32_t nProcessed; + // If this 4096 is changed to some other value, make sure to adjust + // the bug121341.properties test file accordingly. + while (NS_SUCCEEDED(rv = mIn->ReadSegments(nsPropertiesParser::SegmentWriter, + &parser, 4096, &nProcessed)) && + nProcessed != 0) + ; + mIn = nullptr; + if (NS_FAILED(rv)) { + return rv; + } + + // We may have an unprocessed value at this point + // if the last line did not have a proper line ending. + if (parser.GetState() == eParserState_Value) { + nsAutoString oldValue; + parser.FinishValueState(oldValue); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::SetStringProperty(const nsACString& aKey, + const nsAString& aNewValue, + nsAString& aOldValue) { + const nsCString& flatKey = PromiseFlatCString(aKey); + auto entry = static_cast(mTable.Add(flatKey.get())); + + if (entry->mKey) { + aOldValue = entry->mValue; + NS_WARNING( + nsPrintfCString("the property %s already exists", flatKey.get()).get()); + } else { + aOldValue.Truncate(); + } + + entry->mKey = ArenaStrdup(flatKey, mArena); + entry->mValue = ArenaStrdup(aNewValue, mArena); + + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::Save(nsIOutputStream* aOut, const nsACString& aHeader) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPersistentProperties::GetStringProperty(const nsACString& aKey, + nsAString& aValue) { + const nsCString& flatKey = PromiseFlatCString(aKey); + + auto entry = static_cast(mTable.Search(flatKey.get())); + if (!entry) { + return NS_ERROR_FAILURE; + } + + aValue = entry->mValue; + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::Enumerate(nsISimpleEnumerator** aResult) { + nsCOMArray props; + + // We know the necessary size; we can avoid growing it while adding elements + props.SetCapacity(mTable.EntryCount()); + + // Step through hash entries populating a transient array + for (auto iter = mTable.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + + RefPtr element = new nsPropertyElement( + nsDependentCString(entry->mKey), nsDependentString(entry->mValue)); + + if (!props.AppendObject(element)) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + return NS_NewArrayEnumerator(aResult, props, NS_GET_IID(nsIPropertyElement)); +} + +//////////////////////////////////////////////////////////////////////////////// +// XXX Some day we'll unify the nsIPersistentProperties interface with +// nsIProperties, but until now... + +NS_IMETHODIMP +nsPersistentProperties::Get(const char* aProp, const nsIID& aUUID, + void** aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPersistentProperties::Set(const char* aProp, nsISupports* value) { + return NS_ERROR_NOT_IMPLEMENTED; +} +NS_IMETHODIMP +nsPersistentProperties::Undefine(const char* aProp) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsPersistentProperties::Has(const char* aProp, bool* aResult) { + *aResult = !!mTable.Search(aProp); + return NS_OK; +} + +NS_IMETHODIMP +nsPersistentProperties::GetKeys(nsTArray& aKeys) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +//////////////////////////////////////////////////////////////////////////////// +// PropertyElement +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsPropertyElement::Create(REFNSIID aIID, void** aResult) { + RefPtr propElem = new nsPropertyElement(); + return propElem->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(nsPropertyElement, nsIPropertyElement) + +NS_IMETHODIMP +nsPropertyElement::GetKey(nsACString& aReturnKey) { + aReturnKey = mKey; + return NS_OK; +} + +NS_IMETHODIMP +nsPropertyElement::GetValue(nsAString& aReturnValue) { + aReturnValue = mValue; + return NS_OK; +} + +NS_IMETHODIMP +nsPropertyElement::SetKey(const nsACString& aKey) { + mKey = aKey; + return NS_OK; +} + +NS_IMETHODIMP +nsPropertyElement::SetValue(const nsAString& aValue) { + mValue = aValue; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/ds/nsPersistentProperties.h b/xpcom/ds/nsPersistentProperties.h new file mode 100644 index 0000000000..e35f938371 --- /dev/null +++ b/xpcom/ds/nsPersistentProperties.h @@ -0,0 +1,57 @@ +/* -*- 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/. */ + +#ifndef nsPersistentProperties_h___ +#define nsPersistentProperties_h___ + +#include "nsIPersistentProperties2.h" +#include "PLDHashTable.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/Attributes.h" + +class nsIUnicharInputStream; + +class nsPersistentProperties final : public nsIPersistentProperties { + public: + nsPersistentProperties(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROPERTIES + NS_DECL_NSIPERSISTENTPROPERTIES + + private: + ~nsPersistentProperties(); + + protected: + nsCOMPtr mIn; + + PLDHashTable mTable; + mozilla::ArenaAllocator<2048, 4> mArena; +}; + +class nsPropertyElement final : public nsIPropertyElement { + public: + nsPropertyElement() = default; + + nsPropertyElement(const nsACString& aKey, const nsAString& aValue) + : mKey(aKey), mValue(aValue) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTYELEMENT + + static nsresult Create(REFNSIID aIID, void** aResult); + + private: + ~nsPropertyElement() = default; + + protected: + nsCString mKey; + nsString mValue; +}; + +#endif /* nsPersistentProperties_h___ */ diff --git a/xpcom/ds/nsPointerHashKeys.h b/xpcom/ds/nsPointerHashKeys.h new file mode 100644 index 0000000000..5cf2e8c4d3 --- /dev/null +++ b/xpcom/ds/nsPointerHashKeys.h @@ -0,0 +1,50 @@ +/* -*- 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/. */ + +/* Definitions for nsPtrHashKey and nsVoidPtrHashKey. */ + +#ifndef nsPointerHashKeys_h +#define nsPointerHashKeys_h + +#include "nscore.h" + +#include "mozilla/Attributes.h" +#include "mozilla/HashFunctions.h" + +#include "PLDHashTable.h" + +/** + * hashkey wrapper using T* KeyType + * + * @see nsTHashtable::EntryType for specification + */ +template +class nsPtrHashKey : public PLDHashEntryHdr { + public: + typedef T* KeyType; + typedef const T* KeyTypePointer; + + explicit nsPtrHashKey(const T* aKey) : mKey(const_cast(aKey)) {} + nsPtrHashKey(nsPtrHashKey&& aToMove) + : PLDHashEntryHdr(std::move(aToMove)), mKey(std::move(aToMove.mKey)) {} + ~nsPtrHashKey() = default; + + KeyType GetKey() const { return mKey; } + bool KeyEquals(KeyTypePointer aKey) const { return aKey == mKey; } + + static KeyTypePointer KeyToPointer(KeyType aKey) { return aKey; } + static PLDHashNumber HashKey(KeyTypePointer aKey) { + return mozilla::HashGeneric(aKey); + } + enum { ALLOW_MEMMOVE = true }; + + protected: + T* MOZ_NON_OWNING_REF mKey; +}; + +typedef nsPtrHashKey nsVoidPtrHashKey; + +#endif // nsPointerHashKeys_h diff --git a/xpcom/ds/nsProperties.cpp b/xpcom/ds/nsProperties.cpp new file mode 100644 index 0000000000..2094981634 --- /dev/null +++ b/xpcom/ds/nsProperties.cpp @@ -0,0 +1,61 @@ +/* -*- 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 "nsProperties.h" + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsProperties, nsIProperties) + +NS_IMETHODIMP +nsProperties::Get(const char* prop, const nsIID& uuid, void** result) { + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + nsCOMPtr value; + if (!nsProperties_HashBase::Get(prop, getter_AddRefs(value))) { + return NS_ERROR_FAILURE; + } + return (value) ? value->QueryInterface(uuid, result) : NS_ERROR_NO_INTERFACE; +} + +NS_IMETHODIMP +nsProperties::Set(const char* prop, nsISupports* value) { + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + InsertOrUpdate(prop, value); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::Undefine(const char* prop) { + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + return nsProperties_HashBase::Remove(prop) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsProperties::Has(const char* prop, bool* result) { + if (NS_WARN_IF(!prop)) { + return NS_ERROR_INVALID_ARG; + } + + *result = nsProperties_HashBase::Contains(prop); + return NS_OK; +} + +NS_IMETHODIMP +nsProperties::GetKeys(nsTArray& aKeys) { + mozilla::AppendToArray(aKeys, this->Keys()); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/xpcom/ds/nsProperties.h b/xpcom/ds/nsProperties.h new file mode 100644 index 0000000000..c45c7361ea --- /dev/null +++ b/xpcom/ds/nsProperties.h @@ -0,0 +1,29 @@ +/* -*- 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/. */ + +#ifndef nsProperties_h___ +#define nsProperties_h___ + +#include "nsIProperties.h" +#include "nsInterfaceHashtable.h" +#include "nsHashKeys.h" +#include "mozilla/Attributes.h" + +typedef nsInterfaceHashtable + nsProperties_HashBase; + +class nsProperties final : public nsIProperties, public nsProperties_HashBase { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTIES + + nsProperties() = default; + + private: + ~nsProperties() = default; +}; + +#endif /* nsProperties_h___ */ diff --git a/xpcom/ds/nsQuickSort.cpp b/xpcom/ds/nsQuickSort.cpp new file mode 100644 index 0000000000..1fb3dede88 --- /dev/null +++ b/xpcom/ds/nsQuickSort.cpp @@ -0,0 +1,175 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* We need this because Solaris' version of qsort is broken and + * causes array bounds reads. + */ + +#include +#include "nsAlgorithm.h" +#include "nsQuickSort.h" + +extern "C" { + +#if !defined(DEBUG) && (defined(__cplusplus) || defined(__gcc)) +# ifndef INLINE +# define INLINE inline +# endif +#else +# define INLINE +#endif + +typedef int cmp_t(const void*, const void*, void*); +static INLINE char* med3(char*, char*, char*, cmp_t*, void*); +static INLINE void swapfunc(char*, char*, int, int); + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ +#define swapcode(TYPE, parmi, parmj, n) \ + { \ + long i = (n) / sizeof(TYPE); \ + TYPE* pi = (TYPE*)(parmi); \ + TYPE* pj = (TYPE*)(parmj); \ + do { \ + TYPE t = *pi; \ + *pi++ = *pj; \ + *pj++ = t; \ + } while (--i > 0); \ + } + +#define SWAPINIT(a, es) \ + swaptype = ((char*)a - (char*)0) % sizeof(long) || es % sizeof(long) ? 2 \ + : es == sizeof(long) ? 0 \ + : 1; + +static INLINE void swapfunc(char* a, char* b, int n, int swaptype) { + if (swaptype <= 1) swapcode(long, a, b, n) else swapcode(char, a, b, n) +} + +#define swap(a, b) \ + if (swaptype == 0) { \ + long t = *(long*)(a); \ + *(long*)(a) = *(long*)(b); \ + *(long*)(b) = t; \ + } else \ + swapfunc((char*)a, (char*)b, (int)es, swaptype) + +#define vecswap(a, b, n) \ + if ((n) > 0) swapfunc((char*)a, (char*)b, (int)n, swaptype) + +static INLINE char* med3(char* a, char* b, char* c, cmp_t* cmp, void* data) { + return cmp(a, b, data) < 0 + ? (cmp(b, c, data) < 0 ? b : (cmp(a, c, data) < 0 ? c : a)) + : (cmp(b, c, data) > 0 ? b : (cmp(a, c, data) < 0 ? a : c)); +} + +void NS_QuickSort(void* a, unsigned int n, unsigned int es, cmp_t* cmp, + void* data) { + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + int d, r, swaptype; + +loop: + SWAPINIT(a, es); + /* Use insertion sort when input is small */ + if (n < 7) { + for (pm = (char*)a + es; pm < (char*)a + n * es; pm += es) + for (pl = pm; pl > (char*)a && cmp(pl - es, pl, data) > 0; pl -= es) + swap(pl, pl - es); + return; + } + /* Choose pivot */ + pm = (char*)a + (n / 2) * es; + if (n > 7) { + pl = (char*)a; + pn = (char*)a + (n - 1) * es; + if (n > 40) { + d = (n / 8) * es; + pl = med3(pl, pl + d, pl + 2 * d, cmp, data); + pm = med3(pm - d, pm, pm + d, cmp, data); + pn = med3(pn - 2 * d, pn - d, pn, cmp, data); + } + pm = med3(pl, pm, pn, cmp, data); + } + swap(a, pm); + pa = pb = (char*)a + es; + + pc = pd = (char*)a + (n - 1) * es; + /* loop invariants: + * [a, pa) = pivot + * [pa, pb) < pivot + * [pb, pc + es) unprocessed + * [pc + es, pd + es) > pivot + * [pd + es, pn) = pivot + */ + for (;;) { + while (pb <= pc && (r = cmp(pb, a, data)) <= 0) { + if (r == 0) { + swap(pa, pb); + pa += es; + } + pb += es; + } + while (pb <= pc && (r = cmp(pc, a, data)) >= 0) { + if (r == 0) { + swap(pc, pd); + pd -= es; + } + pc -= es; + } + if (pb > pc) break; + swap(pb, pc); + pb += es; + pc -= es; + } + /* Move pivot values */ + pn = (char*)a + n * es; + r = XPCOM_MIN(pa - (char*)a, pb - pa); + vecswap(a, pb - r, r); + r = XPCOM_MIN(pd - pc, pn - pd - es); + vecswap(pb, pn - r, r); + /* Recursively process partitioned items */ + if ((r = pb - pa) > (int)es) NS_QuickSort(a, r / es, es, cmp, data); + if ((r = pd - pc) > (int)es) { + /* Iterate rather than recurse to save stack space */ + a = pn - r; + n = r / es; + goto loop; + } + /* NS_QuickSort(pn - r, r / es, es, cmp, data);*/ +} +} + +#undef INLINE +#undef swapcode +#undef SWAPINIT +#undef swap +#undef vecswap diff --git a/xpcom/ds/nsQuickSort.h b/xpcom/ds/nsQuickSort.h new file mode 100644 index 0000000000..c2b842bea3 --- /dev/null +++ b/xpcom/ds/nsQuickSort.h @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +/* We need this because Solaris' version of qsort is broken and + * causes array bounds reads. + */ + +#ifndef nsQuickSort_h___ +#define nsQuickSort_h___ + +#include "nscore.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Parameters: + * 1. the array to sort + * 2. the number of elements in the array + * 3. the size of each array element + * 4. comparison function taking two elements and parameter #5 and + * returning an integer: + * + less than zero if the first element should be before the second + * + 0 if the order of the elements does not matter + * + greater than zero if the second element should be before the first + * 5. extra data to pass to comparison function + */ +void NS_QuickSort(void*, unsigned int, unsigned int, + int (*)(const void*, const void*, void*), void*); + +#ifdef __cplusplus +} +#endif + +#endif /* nsQuickSort_h___ */ diff --git a/xpcom/ds/nsRefCountedHashtable.h b/xpcom/ds/nsRefCountedHashtable.h new file mode 100644 index 0000000000..e3a0456a09 --- /dev/null +++ b/xpcom/ds/nsRefCountedHashtable.h @@ -0,0 +1,245 @@ +/* -*- 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/. */ + +#ifndef XPCOM_DS_NSREFCOUNTEDHASHTABLE_H_ +#define XPCOM_DS_NSREFCOUNTEDHASHTABLE_H_ + +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" + +/** + * templated hashtable class maps keys to reference pointers. + * See nsBaseHashtable for complete declaration. + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + * @param PtrType the reference-type being wrapped + * @see nsClassHashtable, nsTHashMap + */ +template +class nsRefCountedHashtable + : public nsBaseHashtable< + KeyClass, PtrType, + typename mozilla::detail::SmartPtrTraits::RawPointerType> { + public: + using KeyType = typename KeyClass::KeyType; + using SmartPtrTraits = mozilla::detail::SmartPtrTraits; + using PointeeType = typename SmartPtrTraits::PointeeType; + using RawPointerType = typename SmartPtrTraits::RawPointerType; + using base_type = nsBaseHashtable; + + using base_type::base_type; + + static_assert(SmartPtrTraits::IsRefCounted); + + /** + * @copydoc nsBaseHashtable::Get + * @param aData This is an XPCOM getter, so aData is already_addrefed. + * If the key doesn't exist, *aData will be set to nullptr. + */ + bool Get(KeyType aKey, RawPointerType* aData) const; + + /** + * @copydoc nsBaseHashtable::Get + */ + [[nodiscard]] already_AddRefed Get(KeyType aKey) const; + + /** + * Gets a weak reference to the hashtable entry. + * @param aFound If not nullptr, will be set to true if the entry is found, + * to false otherwise. + * @return The entry, or nullptr if not found. Do not release this pointer! + */ + [[nodiscard]] RawPointerType GetWeak(KeyType aKey, + bool* aFound = nullptr) const; + + using base_type::InsertOrUpdate; + + template >> + void InsertOrUpdate( + KeyType aKey, + typename SmartPtrTraits::template OtherSmartPtrType&& aData); + + template >> + [[nodiscard]] bool InsertOrUpdate( + KeyType aKey, + typename SmartPtrTraits::template OtherSmartPtrType&& aData, + const mozilla::fallible_t&); + + template >> + void InsertOrUpdate(KeyType aKey, already_AddRefed&& aData); + + template >> + [[nodiscard]] bool InsertOrUpdate(KeyType aKey, already_AddRefed&& aData, + const mozilla::fallible_t&); + + /** + * Remove the entry associated with aKey (if any), optionally _moving_ its + * current value into *aData, thereby avoiding calls to AddRef and Release. + * Return true if found. + * @param aKey the key to remove from the hashtable + * @param aData where to move the value (if non-null). If an entry is not + * found it will be set to nullptr. + * @return true if an entry for aKey was found (and removed) + */ + inline bool Remove(KeyType aKey, RawPointerType* aData = nullptr); + + nsRefCountedHashtable Clone() const { + return this->template CloneAs(); + } +}; + +template +inline void ImplCycleCollectionUnlink(nsRefCountedHashtable& aField) { + aField.Clear(); +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsRefCountedHashtable& aField, const char* aName, + uint32_t aFlags = 0) { + for (auto iter = aField.ConstIter(); !iter.Done(); iter.Next()) { + CycleCollectionNoteChild(aCallback, iter.UserData(), aName, aFlags); + } +} + +// +// nsRefCountedHashtable definitions +// + +template +bool nsRefCountedHashtable::Get( + KeyType aKey, RawPointerType* aRefPtr) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRefPtr) { + *aRefPtr = ent->GetData(); + + NS_IF_ADDREF(*aRefPtr); + } + + return true; + } + + // if the key doesn't exist, set *aRefPtr to null + // so that it is a valid XPCOM getter + if (aRefPtr) { + *aRefPtr = nullptr; + } + + return false; +} + +template +already_AddRefed::PointeeType> +nsRefCountedHashtable::Get(KeyType aKey) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + if (!ent) { + return nullptr; + } + + PtrType copy = ent->GetData(); + return copy.forget(); +} + +template +typename nsRefCountedHashtable::RawPointerType +nsRefCountedHashtable::GetWeak(KeyType aKey, + bool* aFound) const { + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aFound) { + *aFound = true; + } + + return ent->GetData(); + } + + // Key does not exist, return nullptr and set aFound to false + if (aFound) { + *aFound = false; + } + + return nullptr; +} + +template +template +void nsRefCountedHashtable::InsertOrUpdate( + KeyType aKey, + typename SmartPtrTraits::template OtherSmartPtrType&& aData) { + if (!InsertOrUpdate(aKey, std::move(aData), mozilla::fallible)) { + NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount()); + } +} + +template +template +bool nsRefCountedHashtable::InsertOrUpdate( + KeyType aKey, + typename SmartPtrTraits::template OtherSmartPtrType&& aData, + const mozilla::fallible_t&) { + typename base_type::EntryType* ent = this->PutEntry(aKey, mozilla::fallible); + + if (!ent) { + return false; + } + + ent->SetData(std::move(aData)); + + return true; +} + +template +template +void nsRefCountedHashtable::InsertOrUpdate( + KeyType aKey, already_AddRefed&& aData) { + if (!InsertOrUpdate(aKey, std::move(aData), mozilla::fallible)) { + NS_ABORT_OOM(this->mTable.EntrySize() * this->mTable.EntryCount()); + } +} + +template +template +bool nsRefCountedHashtable::InsertOrUpdate( + KeyType aKey, already_AddRefed&& aData, const mozilla::fallible_t&) { + typename base_type::EntryType* ent = this->PutEntry(aKey, mozilla::fallible); + + if (!ent) { + return false; + } + + ent->SetData(std::move(aData)); + + return true; +} + +template +bool nsRefCountedHashtable::Remove(KeyType aKey, + RawPointerType* aRefPtr) { + typename base_type::EntryType* ent = this->GetEntry(aKey); + + if (ent) { + if (aRefPtr) { + ent->GetModifiableData()->forget(aRefPtr); + } + this->RemoveEntry(ent); + return true; + } + + if (aRefPtr) { + *aRefPtr = nullptr; + } + return false; +} + +#endif // XPCOM_DS_NSREFCOUNTEDHASHTABLE_H_ diff --git a/xpcom/ds/nsRefPtrHashtable.h b/xpcom/ds/nsRefPtrHashtable.h new file mode 100644 index 0000000000..55b946bf7b --- /dev/null +++ b/xpcom/ds/nsRefPtrHashtable.h @@ -0,0 +1,12 @@ +/* -*- 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/. */ + +#ifndef nsRefPtrHashtable_h__ +#define nsRefPtrHashtable_h__ + +#include "nsRefCountedHashtable.h" + +#endif // nsRefPtrHashtable_h__ diff --git a/xpcom/ds/nsSimpleEnumerator.cpp b/xpcom/ds/nsSimpleEnumerator.cpp new file mode 100644 index 0000000000..7f7ca9d674 --- /dev/null +++ b/xpcom/ds/nsSimpleEnumerator.cpp @@ -0,0 +1,78 @@ +/* -*- 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 "nsSimpleEnumerator.h" + +#include "mozilla/dom/IteratorResultBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/ResultExtensions.h" +#include "nsContentUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace { + +class JSEnumerator final : public nsIJSEnumerator { + NS_DECL_ISUPPORTS + NS_DECL_NSIJSENUMERATOR + + explicit JSEnumerator(nsISimpleEnumerator* aEnumerator, const nsID& aIID) + : mEnumerator(aEnumerator), mIID(aIID) {} + + private: + ~JSEnumerator() = default; + + nsCOMPtr mEnumerator; + const nsID mIID; +}; + +} // anonymous namespace + +nsresult JSEnumerator::Iterator(nsIJSEnumerator** aResult) { + RefPtr result(this); + result.forget(aResult); + return NS_OK; +} + +nsresult JSEnumerator::Next(JSContext* aCx, JS::MutableHandleValue aResult) { + RootedDictionary result(aCx); + + nsCOMPtr elem; + if (NS_FAILED(mEnumerator->GetNext(getter_AddRefs(elem)))) { + result.mDone = true; + } else { + result.mDone = false; + + JS::RootedValue value(aCx); + MOZ_TRY(nsContentUtils::WrapNative(aCx, elem, &mIID, &value)); + result.mValue = value; + } + + if (!ToJSValue(aCx, result, aResult)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(JSEnumerator, nsIJSEnumerator) + +nsresult nsSimpleEnumerator::Iterator(nsIJSEnumerator** aResult) { + auto result = MakeRefPtr(this, DefaultInterface()); + result.forget(aResult); + return NS_OK; +} + +nsresult nsSimpleEnumerator::Entries(const nsIID& aIface, + nsIJSEnumerator** aResult) { + auto result = MakeRefPtr(this, aIface); + result.forget(aResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsSimpleEnumerator, nsISimpleEnumerator, + nsISimpleEnumeratorBase) diff --git a/xpcom/ds/nsSimpleEnumerator.h b/xpcom/ds/nsSimpleEnumerator.h new file mode 100644 index 0000000000..60ab6419bc --- /dev/null +++ b/xpcom/ds/nsSimpleEnumerator.h @@ -0,0 +1,22 @@ +/* -*- 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/. */ + +#ifndef nsSimpleEnumerator_h +#define nsSimpleEnumerator_h + +#include "nsISimpleEnumerator.h" + +class nsSimpleEnumerator : public nsISimpleEnumerator { + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATORBASE + + virtual const nsID& DefaultInterface() { return NS_GET_IID(nsISupports); } + + protected: + virtual ~nsSimpleEnumerator() = default; +}; + +#endif diff --git a/xpcom/ds/nsStaticAtomUtils.h b/xpcom/ds/nsStaticAtomUtils.h new file mode 100644 index 0000000000..8c766f63e1 --- /dev/null +++ b/xpcom/ds/nsStaticAtomUtils.h @@ -0,0 +1,36 @@ +/* -*- 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/. */ + +#ifndef nsStaticAtomUtils_h +#define nsStaticAtomUtils_h + +#include +#include "nsAtom.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Maybe.h" + +// This class holds basic operations on arrays of static atoms. +class nsStaticAtomUtils { + public: + static mozilla::Maybe Lookup(nsAtom* aAtom, + const nsStaticAtom* aAtoms, + uint32_t aCount) { + if (aAtom->IsStatic()) { + ptrdiff_t index = aAtom->AsStatic() - aAtoms; + if (index >= 0 && index < static_cast(aCount)) { + return mozilla::Some(static_cast(index)); + } + } + return mozilla::Nothing(); + } + + static bool IsMember(nsAtom* aAtom, const nsStaticAtom* aAtoms, + uint32_t aCount) { + return Lookup(aAtom, aAtoms, aCount).isSome(); + } +}; + +#endif // nsStaticAtomUtils_h diff --git a/xpcom/ds/nsStaticNameTable.cpp b/xpcom/ds/nsStaticNameTable.cpp new file mode 100644 index 0000000000..2ee731c153 --- /dev/null +++ b/xpcom/ds/nsStaticNameTable.cpp @@ -0,0 +1,180 @@ +/* -*- 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/. */ + +/* Class to manage lookup of static names in a table. */ + +#include "mozilla/HashFunctions.h" +#include "mozilla/TextUtils.h" + +#include "nsCRT.h" + +#include "nscore.h" +#include "nsISupportsImpl.h" + +#include "nsStaticNameTable.h" + +using namespace mozilla; + +struct NameTableKey { + NameTableKey(const nsDependentCString aNameArray[], const nsCString* aKeyStr) + : mNameArray(aNameArray), mIsUnichar(false) { + mKeyStr.m1b = aKeyStr; + } + + NameTableKey(const nsDependentCString aNameArray[], const nsString* aKeyStr) + : mNameArray(aNameArray), mIsUnichar(true) { + mKeyStr.m2b = aKeyStr; + } + + const nsDependentCString* mNameArray; + union { + const nsCString* m1b; + const nsString* m2b; + } mKeyStr; + bool mIsUnichar; +}; + +struct NameTableEntry : public PLDHashEntryHdr { + int32_t mIndex; +}; + +static bool matchNameKeysCaseInsensitive(const PLDHashEntryHdr* aHdr, + const void* aVoidKey) { + auto entry = static_cast(aHdr); + auto key = static_cast(aVoidKey); + const nsDependentCString* name = &key->mNameArray[entry->mIndex]; + + return key->mIsUnichar ? key->mKeyStr.m2b->LowerCaseEqualsASCII( + name->get(), name->Length()) + : key->mKeyStr.m1b->LowerCaseEqualsASCII( + name->get(), name->Length()); +} + +/* + * caseInsensitiveHashKey is just like PLDHashTable::HashStringKey except it + * uses (*s & ~0x20) instead of simply *s. This means that "aFOO" and + * "afoo" and "aFoo" will all hash to the same thing. It also means + * that some strings that aren't case-insensensitively equal will hash + * to the same value, but it's just a hash function so it doesn't + * matter. + */ +static PLDHashNumber caseInsensitiveStringHashKey(const void* aKey) { + PLDHashNumber h = 0; + const NameTableKey* tableKey = static_cast(aKey); + if (tableKey->mIsUnichar) { + for (const char16_t* s = tableKey->mKeyStr.m2b->get(); *s != '\0'; s++) { + h = AddToHash(h, *s & ~0x20); + } + } else { + for (const unsigned char* s = reinterpret_cast( + tableKey->mKeyStr.m1b->get()); + *s != '\0'; s++) { + h = AddToHash(h, *s & ~0x20); + } + } + return h; +} + +static const struct PLDHashTableOps nametable_CaseInsensitiveHashTableOps = { + caseInsensitiveStringHashKey, + matchNameKeysCaseInsensitive, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr, +}; + +nsStaticCaseInsensitiveNameTable::nsStaticCaseInsensitiveNameTable( + const char* const aNames[], int32_t aLength) + : mNameArray(nullptr), + mNameTable(&nametable_CaseInsensitiveHashTableOps, sizeof(NameTableEntry), + aLength), + mNullStr("") { + MOZ_COUNT_CTOR(nsStaticCaseInsensitiveNameTable); + + MOZ_ASSERT(aNames, "null name table"); + MOZ_ASSERT(aLength, "0 length"); + + mNameArray = + (nsDependentCString*)moz_xmalloc(aLength * sizeof(nsDependentCString)); + + for (int32_t index = 0; index < aLength; ++index) { + const char* raw = aNames[index]; +#ifdef DEBUG + { + // verify invariants of contents + nsAutoCString temp1(raw); + nsDependentCString temp2(raw); + ToLowerCase(temp1); + MOZ_ASSERT(temp1.Equals(temp2), "upper case char in table"); + MOZ_ASSERT(IsAsciiNullTerminated(raw), + "non-ascii string in table -- " + "case-insensitive matching won't work right"); + } +#endif + // use placement-new to initialize the string object + nsDependentCString* strPtr = &mNameArray[index]; + new (strPtr) nsDependentCString(raw); + + NameTableKey key(mNameArray, strPtr); + + auto entry = static_cast(mNameTable.Add(&key, fallible)); + if (!entry) { + continue; + } + + // If the entry already exists it's unlikely but possible that its index is + // zero, in which case this assertion won't fail. But if the index is + // non-zero (highly likely) then it will fail. In other words, this + // assertion is likely but not guaranteed to detect if an entry is already + // used. + MOZ_ASSERT(entry->mIndex == 0, "Entry already exists!"); + + entry->mIndex = index; + } + mNameTable.MarkImmutable(); +} + +nsStaticCaseInsensitiveNameTable::~nsStaticCaseInsensitiveNameTable() { + // manually call the destructor on placement-new'ed objects + for (uint32_t index = 0; index < mNameTable.EntryCount(); index++) { + mNameArray[index].~nsDependentCString(); + } + free((void*)mNameArray); + MOZ_COUNT_DTOR(nsStaticCaseInsensitiveNameTable); +} + +int32_t nsStaticCaseInsensitiveNameTable::Lookup( + const nsACString& aName) const { + NS_ASSERTION(mNameArray, "not inited"); + + const nsCString& str = PromiseFlatCString(aName); + + NameTableKey key(mNameArray, &str); + auto entry = static_cast(mNameTable.Search(&key)); + + return entry ? entry->mIndex : nsStaticCaseInsensitiveNameTable::NOT_FOUND; +} + +int32_t nsStaticCaseInsensitiveNameTable::Lookup(const nsAString& aName) const { + NS_ASSERTION(mNameArray, "not inited"); + + const nsString& str = PromiseFlatString(aName); + + NameTableKey key(mNameArray, &str); + auto entry = static_cast(mNameTable.Search(&key)); + + return entry ? entry->mIndex : nsStaticCaseInsensitiveNameTable::NOT_FOUND; +} + +const nsCString& nsStaticCaseInsensitiveNameTable::GetStringValue( + int32_t aIndex) { + NS_ASSERTION(mNameArray, "not inited"); + + if ((NOT_FOUND < aIndex) && ((uint32_t)aIndex < mNameTable.EntryCount())) { + return mNameArray[aIndex]; + } + return mNullStr; +} diff --git a/xpcom/ds/nsStaticNameTable.h b/xpcom/ds/nsStaticNameTable.h new file mode 100644 index 0000000000..302d82c1e4 --- /dev/null +++ b/xpcom/ds/nsStaticNameTable.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +/* Classes to manage lookup of static names in a table. */ + +#ifndef nsStaticNameTable_h___ +#define nsStaticNameTable_h___ + +#include "PLDHashTable.h" +#include "nsString.h" + +/* This class supports case insensitive lookup. + * + * It differs from atom tables: + * - It supports case insensitive lookup. + * - It has minimal footprint by not copying the string table. + * - It does no locking. + * - It returns zero based indexes and const nsCString& as required by its + * callers in the parser. + * - It is not an xpcom interface - meant for fast lookup in static tables. + * + * ***REQUIREMENTS*** + * - It *requires* that all entries in the table be lowercase only. + * - It *requires* that the table of strings be in memory that lives at least + * as long as this table object - typically a static string array. + */ + +class nsStaticCaseInsensitiveNameTable { + public: + enum { NOT_FOUND = -1 }; + + int32_t Lookup(const nsACString& aName) const; + int32_t Lookup(const nsAString& aName) const; + const nsCString& GetStringValue(int32_t aIndex); + + nsStaticCaseInsensitiveNameTable(const char* const aNames[], int32_t aLength); + ~nsStaticCaseInsensitiveNameTable(); + + private: + nsDependentCString* mNameArray; + PLDHashTable mNameTable; + nsDependentCString mNullStr; +}; + +#endif /* nsStaticNameTable_h___ */ diff --git a/xpcom/ds/nsStringEnumerator.cpp b/xpcom/ds/nsStringEnumerator.cpp new file mode 100644 index 0000000000..7b8cf72d00 --- /dev/null +++ b/xpcom/ds/nsStringEnumerator.cpp @@ -0,0 +1,311 @@ +/* -*- 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 "nsStringEnumerator.h" +#include "nsSimpleEnumerator.h" +#include "nsSupportsPrimitives.h" +#include "mozilla/Attributes.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/dom/IteratorResultBinding.h" +#include "mozilla/dom/RootedDictionary.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsTArray.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace { + +class JSStringEnumerator final : public nsIJSEnumerator { + NS_DECL_ISUPPORTS + NS_DECL_NSIJSENUMERATOR + + explicit JSStringEnumerator(nsIStringEnumerator* aEnumerator) + : mEnumerator(aEnumerator) { + MOZ_ASSERT(mEnumerator); + } + + private: + ~JSStringEnumerator() = default; + + nsCOMPtr mEnumerator; +}; + +} // anonymous namespace + +nsresult JSStringEnumerator::Iterator(nsIJSEnumerator** aResult) { + RefPtr result(this); + result.forget(aResult); + return NS_OK; +} + +nsresult JSStringEnumerator::Next(JSContext* aCx, + JS::MutableHandleValue aResult) { + RootedDictionary result(aCx); + + nsAutoString elem; + if (NS_FAILED(mEnumerator->GetNext(elem))) { + result.mDone = true; + } else { + result.mDone = false; + + if (!ToJSValue( + aCx, elem, + JS::MutableHandleValue::fromMarkedLocation(&result.mValue))) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + if (!ToJSValue(aCx, result, aResult)) { + return NS_ERROR_OUT_OF_MEMORY; + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(JSStringEnumerator, nsIJSEnumerator) + +// +// nsStringEnumeratorBase +// + +nsresult nsStringEnumeratorBase::GetNext(nsAString& aResult) { + nsAutoCString str; + MOZ_TRY(GetNext(str)); + + CopyUTF8toUTF16(str, aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumeratorBase::StringIterator(nsIJSEnumerator** aRetVal) { + auto result = MakeRefPtr(this); + result.forget(aRetVal); + return NS_OK; +} + +// +// nsStringEnumerator +// + +class nsStringEnumerator final : public nsSimpleEnumerator, + public nsIStringEnumerator, + public nsIUTF8StringEnumerator { + public: + nsStringEnumerator(const nsTArray* aArray, bool aOwnsArray) + : mArray(aArray), mIndex(0), mOwnsArray(aOwnsArray), mIsUnicode(true) {} + + nsStringEnumerator(const nsTArray* aArray, bool aOwnsArray) + : mCArray(aArray), mIndex(0), mOwnsArray(aOwnsArray), mIsUnicode(false) {} + + nsStringEnumerator(const nsTArray* aArray, nsISupports* aOwner) + : mArray(aArray), + mIndex(0), + mOwner(aOwner), + mOwnsArray(false), + mIsUnicode(true) {} + + nsStringEnumerator(const nsTArray* aArray, nsISupports* aOwner) + : mCArray(aArray), + mIndex(0), + mOwner(aOwner), + mOwnsArray(false), + mIsUnicode(false) {} + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIUTF8STRINGENUMERATOR + NS_DECL_NSISTRINGENUMERATORBASE + + // have to declare nsIStringEnumerator manually, because of + // overlapping method names + NS_IMETHOD GetNext(nsAString& aResult) override; + NS_DECL_NSISIMPLEENUMERATOR + + const nsID& DefaultInterface() override { + if (mIsUnicode) { + return NS_GET_IID(nsISupportsString); + } + return NS_GET_IID(nsISupportsCString); + } + + private: + ~nsStringEnumerator() { + if (mOwnsArray) { + // const-casting is safe here, because the NS_New* + // constructors make sure mOwnsArray is consistent with + // the constness of the objects + if (mIsUnicode) { + delete const_cast*>(mArray); + } else { + delete const_cast*>(mCArray); + } + } + } + + union { + const nsTArray* mArray; + const nsTArray* mCArray; + }; + + inline uint32_t Count() { + return mIsUnicode ? mArray->Length() : mCArray->Length(); + } + + uint32_t mIndex; + + // the owner allows us to hold a strong reference to the object + // that owns the array. Having a non-null value in mOwner implies + // that mOwnsArray is false, because we rely on the real owner + // to release the array + nsCOMPtr mOwner; + bool mOwnsArray; + bool mIsUnicode; +}; + +NS_IMPL_ISUPPORTS_INHERITED(nsStringEnumerator, nsSimpleEnumerator, + nsIStringEnumerator, nsIUTF8StringEnumerator) + +NS_IMETHODIMP +nsStringEnumerator::HasMore(bool* aResult) { + *aResult = mIndex < Count(); + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::HasMoreElements(bool* aResult) { return HasMore(aResult); } + +NS_IMETHODIMP +nsStringEnumerator::GetNext(nsISupports** aResult) { + if (mIndex >= mArray->Length()) { + return NS_ERROR_FAILURE; + } + + if (mIsUnicode) { + nsSupportsString* stringImpl = new nsSupportsString(); + + stringImpl->SetData(mArray->ElementAt(mIndex++)); + *aResult = stringImpl; + } else { + nsSupportsCString* cstringImpl = new nsSupportsCString(); + + cstringImpl->SetData(mCArray->ElementAt(mIndex++)); + *aResult = cstringImpl; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::GetNext(nsAString& aResult) { + if (NS_WARN_IF(mIndex >= Count())) { + return NS_ERROR_UNEXPECTED; + } + + if (mIsUnicode) { + aResult = mArray->ElementAt(mIndex++); + } else { + CopyUTF8toUTF16(mCArray->ElementAt(mIndex++), aResult); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::GetNext(nsACString& aResult) { + if (NS_WARN_IF(mIndex >= Count())) { + return NS_ERROR_UNEXPECTED; + } + + if (mIsUnicode) { + CopyUTF16toUTF8(mArray->ElementAt(mIndex++), aResult); + } else { + aResult = mCArray->ElementAt(mIndex++); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsStringEnumerator::StringIterator(nsIJSEnumerator** aRetVal) { + auto result = MakeRefPtr(this); + result.forget(aRetVal); + return NS_OK; +} + +template +static inline nsresult StringEnumeratorTail(T** aResult) { + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +// +// constructors +// + +nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray* aArray, + nsISupports* aOwner) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, aOwner); + return StringEnumeratorTail(aResult); +} + +nsresult NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray* aArray, + nsISupports* aOwner) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, aOwner); + return StringEnumeratorTail(aResult); +} + +nsresult NS_NewAdoptingStringEnumerator(nsIStringEnumerator** aResult, + nsTArray* aArray) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, true); + return StringEnumeratorTail(aResult); +} + +nsresult NS_NewAdoptingUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + nsTArray* aArray) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, true); + return StringEnumeratorTail(aResult); +} + +// const ones internally just forward to the non-const equivalents +nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray* aArray) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, false); + return StringEnumeratorTail(aResult); +} + +nsresult NS_NewUTF8StringEnumerator(nsIUTF8StringEnumerator** aResult, + const nsTArray* aArray) { + if (NS_WARN_IF(!aResult) || NS_WARN_IF(!aArray)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = new nsStringEnumerator(aArray, false); + return StringEnumeratorTail(aResult); +} diff --git a/xpcom/ds/nsStringEnumerator.h b/xpcom/ds/nsStringEnumerator.h new file mode 100644 index 0000000000..0061227c34 --- /dev/null +++ b/xpcom/ds/nsStringEnumerator.h @@ -0,0 +1,102 @@ +/* -*- 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/. */ + +#ifndef nsStringEnumerator_h +#define nsStringEnumerator_h + +#include "nsIStringEnumerator.h" +#include "nsStringFwd.h" +#include "nsTArrayForwardDeclare.h" + +class nsStringEnumeratorBase : public nsIStringEnumerator, + public nsIUTF8StringEnumerator { + public: + NS_DECL_NSISTRINGENUMERATORBASE + + NS_IMETHOD GetNext(nsAString&) override; + + using nsIUTF8StringEnumerator::GetNext; + + protected: + virtual ~nsStringEnumeratorBase() = default; +}; + +// nsIStringEnumerator/nsIUTF8StringEnumerator implementations +// +// Currently all implementations support both interfaces. The +// constructors below provide the most common interface for the given +// type (i.e. nsIStringEnumerator for char16_t* strings, and so +// forth) but any resulting enumerators can be queried to the other +// type. Internally, the enumerators will hold onto the type that was +// passed in and do conversion if GetNext() for the other type of +// string is called. + +// There are a few different types of enumerators: + +// +// These enumerators hold a pointer to the array. Be careful +// because modifying the array may confuse the iterator, especially if +// you insert or remove elements in the middle of the array. +// + +// The non-adopting enumerator requires that the array sticks around +// at least as long as the enumerator does. These are for constant +// string arrays that the enumerator does not own, this could be used +// in VERY specialized cases such as when the provider KNOWS that the +// string enumerator will be consumed immediately, or will at least +// outlast the array. +// For example: +// +// nsTArray array; +// array.AppendCString("abc"); +// array.AppendCString("def"); +// NS_NewStringEnumerator(&enumerator, &array, true); +// +// // call some internal method which iterates the enumerator +// InternalMethod(enumerator); +// NS_RELEASE(enumerator); +// +[[nodiscard]] nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray* aArray, + nsISupports* aOwner); +[[nodiscard]] nsresult NS_NewUTF8StringEnumerator( + nsIUTF8StringEnumerator** aResult, const nsTArray* aArray); + +[[nodiscard]] nsresult NS_NewStringEnumerator(nsIStringEnumerator** aResult, + const nsTArray* aArray); + +// Adopting string enumerators assume ownership of the array and will +// call |operator delete| on the array when the enumerator is destroyed +// this is useful when the provider creates an array solely for the +// purpose of creating the enumerator. +// For example: +// +// nsTArray* array = new nsTArray; +// array->AppendString("abcd"); +// NS_NewAdoptingStringEnumerator(&result, array); +[[nodiscard]] nsresult NS_NewAdoptingStringEnumerator( + nsIStringEnumerator** aResult, nsTArray* aArray); + +[[nodiscard]] nsresult NS_NewAdoptingUTF8StringEnumerator( + nsIUTF8StringEnumerator** aResult, nsTArray* aArray); + +// these versions take a refcounted "owner" which will be addreffed +// when the enumerator is created, and destroyed when the enumerator +// is released. This allows providers to give non-owning pointers to +// ns*StringArray member variables without worrying about lifetime +// issues +// For example: +// +// nsresult MyClass::Enumerate(nsIUTF8StringEnumerator** aResult) { +// mCategoryList->AppendString("abcd"); +// return NS_NewStringEnumerator(aResult, mCategoryList, this); +// } +// +[[nodiscard]] nsresult NS_NewUTF8StringEnumerator( + nsIUTF8StringEnumerator** aResult, const nsTArray* aArray, + nsISupports* aOwner); + +#endif // defined nsStringEnumerator_h diff --git a/xpcom/ds/nsSupportsPrimitives.cpp b/xpcom/ds/nsSupportsPrimitives.cpp new file mode 100644 index 0000000000..1d0db73187 --- /dev/null +++ b/xpcom/ds/nsSupportsPrimitives.cpp @@ -0,0 +1,603 @@ +/* -*- 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 "nsSupportsPrimitives.h" +#include "mozilla/Assertions.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Sprintf.h" +#include + +template +static char* DataToString(const char* aFormat, T aData) { + static const int size = 32; + char buf[size]; + + SprintfLiteral(buf, aFormat, aData); + + return moz_xstrdup(buf); +} + +/***************************************************************************** + * nsSupportsCString + *****************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsCString, nsISupportsCString, nsISupportsPrimitive) + +NS_IMETHODIMP +nsSupportsCString::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsCString::GetData(nsACString& aData) { + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsCString::ToString(char** aResult) { + *aResult = ToNewCString(mData, mozilla::fallible); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsCString::SetData(const nsACString& aData) { + bool ok = mData.Assign(aData, mozilla::fallible); + if (!ok) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/***************************************************************************** + * nsSupportsString + *****************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsString, nsISupportsString, nsISupportsPrimitive) + +NS_IMETHODIMP +nsSupportsString::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_STRING; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsString::GetData(nsAString& aData) { + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsString::ToString(char16_t** aResult) { + *aResult = ToNewUnicode(mData, mozilla::fallible); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsString::SetData(const nsAString& aData) { + bool ok = mData.Assign(aData, mozilla::fallible); + if (!ok) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRBool, nsISupportsPRBool, nsISupportsPrimitive) + +nsSupportsPRBool::nsSupportsPRBool() : mData(false) {} + +NS_IMETHODIMP +nsSupportsPRBool::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRBOOL; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRBool::GetData(bool* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRBool::SetData(bool aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRBool::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = moz_xstrdup(mData ? "true" : "false"); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint8, nsISupportsPRUint8, nsISupportsPrimitive) + +nsSupportsPRUint8::nsSupportsPRUint8() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRUint8::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT8; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint8::GetData(uint8_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint8::SetData(uint8_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint8::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%u", static_cast(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint16, nsISupportsPRUint16, nsISupportsPrimitive) + +nsSupportsPRUint16::nsSupportsPRUint16() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRUint16::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT16; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint16::GetData(uint16_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint16::SetData(uint16_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint16::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%u", static_cast(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint32, nsISupportsPRUint32, nsISupportsPrimitive) + +nsSupportsPRUint32::nsSupportsPRUint32() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRUint32::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT32; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint32::GetData(uint32_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint32::SetData(uint32_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint32::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%u", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRUint64, nsISupportsPRUint64, nsISupportsPrimitive) + +nsSupportsPRUint64::nsSupportsPRUint64() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRUint64::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRUINT64; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint64::GetData(uint64_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint64::SetData(uint64_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRUint64::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%llu", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRTime, nsISupportsPRTime, nsISupportsPrimitive) + +nsSupportsPRTime::nsSupportsPRTime() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRTime::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRTIME; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRTime::GetData(PRTime* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRTime::SetData(PRTime aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRTime::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%" PRIu64, mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsChar, nsISupportsChar, nsISupportsPrimitive) + +nsSupportsChar::nsSupportsChar() : mData(0) {} + +NS_IMETHODIMP +nsSupportsChar::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_CHAR; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsChar::GetData(char* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsChar::SetData(char aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsChar::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = static_cast(moz_xmalloc(2 * sizeof(char))); + *aResult[0] = mData; + *aResult[1] = '\0'; + + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRInt16, nsISupportsPRInt16, nsISupportsPrimitive) + +nsSupportsPRInt16::nsSupportsPRInt16() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRInt16::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRINT16; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt16::GetData(int16_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt16::SetData(int16_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt16::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%d", static_cast(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRInt32, nsISupportsPRInt32, nsISupportsPrimitive) + +nsSupportsPRInt32::nsSupportsPRInt32() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRInt32::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRINT32; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt32::GetData(int32_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt32::SetData(int32_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt32::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%d", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsPRInt64, nsISupportsPRInt64, nsISupportsPrimitive) + +nsSupportsPRInt64::nsSupportsPRInt64() : mData(0) {} + +NS_IMETHODIMP +nsSupportsPRInt64::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_PRINT64; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt64::GetData(int64_t* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt64::SetData(int64_t aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsPRInt64::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%" PRId64, mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsFloat, nsISupportsFloat, nsISupportsPrimitive) + +nsSupportsFloat::nsSupportsFloat() : mData(float(0.0)) {} + +NS_IMETHODIMP +nsSupportsFloat::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_FLOAT; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsFloat::GetData(float* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsFloat::SetData(float aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsFloat::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%f", static_cast(mData)); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsDouble, nsISupportsDouble, nsISupportsPrimitive) + +nsSupportsDouble::nsSupportsDouble() : mData(double(0.0)) {} + +NS_IMETHODIMP +nsSupportsDouble::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_DOUBLE; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDouble::GetData(double* aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDouble::SetData(double aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDouble::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + *aResult = DataToString("%f", mData); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsInterfacePointer, nsISupportsInterfacePointer, + nsISupportsPrimitive) + +nsSupportsInterfacePointer::nsSupportsInterfacePointer() : mIID(nullptr) {} + +nsSupportsInterfacePointer::~nsSupportsInterfacePointer() { + if (mIID) { + free(mIID); + } +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::GetType(uint16_t* aType) { + NS_ASSERTION(aType, "Bad pointer"); + *aType = TYPE_INTERFACE_POINTER; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::GetData(nsISupports** aData) { + NS_ASSERTION(aData, "Bad pointer"); + *aData = mData; + NS_IF_ADDREF(*aData); + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::SetData(nsISupports* aData) { + mData = aData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::GetDataIID(nsID** aIID) { + NS_ASSERTION(aIID, "Bad pointer"); + + *aIID = mIID ? mIID->Clone() : nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::SetDataIID(const nsID* aIID) { + if (mIID) { + free(mIID); + } + + mIID = aIID ? aIID->Clone() : nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsInterfacePointer::ToString(char** aResult) { + NS_ASSERTION(aResult, "Bad pointer"); + + // jband sez: think about asking nsIInterfaceInfoManager whether + // the interface has a known human-readable name + *aResult = moz_xstrdup("[interface pointer]"); + return NS_OK; +} + +/***************************************************************************/ + +NS_IMPL_ISUPPORTS(nsSupportsDependentCString, nsISupportsCString, + nsISupportsPrimitive) + +nsSupportsDependentCString::nsSupportsDependentCString(const char* aStr) + : mData(aStr) {} + +NS_IMETHODIMP +nsSupportsDependentCString::GetType(uint16_t* aType) { + if (NS_WARN_IF(!aType)) { + return NS_ERROR_INVALID_ARG; + } + + *aType = TYPE_CSTRING; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDependentCString::GetData(nsACString& aData) { + aData = mData; + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDependentCString::ToString(char** aResult) { + if (NS_WARN_IF(!aResult)) { + return NS_ERROR_INVALID_ARG; + } + + *aResult = ToNewCString(mData, mozilla::fallible); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsSupportsDependentCString::SetData(const nsACString& aData) { + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/xpcom/ds/nsSupportsPrimitives.h b/xpcom/ds/nsSupportsPrimitives.h new file mode 100644 index 0000000000..28d768a135 --- /dev/null +++ b/xpcom/ds/nsSupportsPrimitives.h @@ -0,0 +1,279 @@ +/* -*- 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/. */ + +#ifndef nsSupportsPrimitives_h__ +#define nsSupportsPrimitives_h__ + +#include "mozilla/Attributes.h" + +#include "nsISupportsPrimitives.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +/***************************************************************************/ + +class nsSupportsCString final : public nsISupportsCString { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + + nsSupportsCString() = default; + + private: + ~nsSupportsCString() = default; + + nsCString mData; +}; + +/***************************************************************************/ + +class nsSupportsString final : public nsISupportsString { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSSTRING + + nsSupportsString() = default; + + private: + ~nsSupportsString() = default; + + nsString mData; +}; + +/***************************************************************************/ + +class nsSupportsPRBool final : public nsISupportsPRBool { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRBOOL + + nsSupportsPRBool(); + + private: + ~nsSupportsPRBool() = default; + + bool mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint8 final : public nsISupportsPRUint8 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT8 + + nsSupportsPRUint8(); + + private: + ~nsSupportsPRUint8() = default; + + uint8_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint16 final : public nsISupportsPRUint16 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT16 + + nsSupportsPRUint16(); + + private: + ~nsSupportsPRUint16() = default; + + uint16_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint32 final : public nsISupportsPRUint32 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT32 + + nsSupportsPRUint32(); + + private: + ~nsSupportsPRUint32() = default; + + uint32_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRUint64 final : public nsISupportsPRUint64 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRUINT64 + + nsSupportsPRUint64(); + + private: + ~nsSupportsPRUint64() = default; + + uint64_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRTime final : public nsISupportsPRTime { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRTIME + + nsSupportsPRTime(); + + private: + ~nsSupportsPRTime() = default; + + PRTime mData; +}; + +/***************************************************************************/ + +class nsSupportsChar final : public nsISupportsChar { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCHAR + + nsSupportsChar(); + + private: + ~nsSupportsChar() = default; + + char mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt16 final : public nsISupportsPRInt16 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT16 + + nsSupportsPRInt16(); + + private: + ~nsSupportsPRInt16() = default; + + int16_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt32 final : public nsISupportsPRInt32 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT32 + + nsSupportsPRInt32(); + + private: + ~nsSupportsPRInt32() = default; + + int32_t mData; +}; + +/***************************************************************************/ + +class nsSupportsPRInt64 final : public nsISupportsPRInt64 { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSPRINT64 + + nsSupportsPRInt64(); + + private: + ~nsSupportsPRInt64() = default; + + int64_t mData; +}; + +/***************************************************************************/ + +class nsSupportsFloat final : public nsISupportsFloat { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSFLOAT + + nsSupportsFloat(); + + private: + ~nsSupportsFloat() = default; + + float mData; +}; + +/***************************************************************************/ + +class nsSupportsDouble final : public nsISupportsDouble { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSDOUBLE + + nsSupportsDouble(); + + private: + ~nsSupportsDouble() = default; + + double mData; +}; + +/***************************************************************************/ + +class nsSupportsInterfacePointer final : public nsISupportsInterfacePointer { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSINTERFACEPOINTER + + nsSupportsInterfacePointer(); + + private: + ~nsSupportsInterfacePointer(); + + nsCOMPtr mData; + nsID* mIID; +}; + +/***************************************************************************/ + +/** + * Wraps a static const char* buffer for use with nsISupportsCString + * + * Only use this class with static buffers, or arena-allocated buffers of + * permanent lifetime! + */ +class nsSupportsDependentCString final : public nsISupportsCString { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSISUPPORTSPRIMITIVE + NS_DECL_NSISUPPORTSCSTRING + + explicit nsSupportsDependentCString(const char* aStr); + + private: + ~nsSupportsDependentCString() = default; + + nsDependentCString mData; +}; + +#endif /* nsSupportsPrimitives_h__ */ diff --git a/xpcom/ds/nsTArray-inl.h b/xpcom/ds/nsTArray-inl.h new file mode 100644 index 0000000000..801d3fab01 --- /dev/null +++ b/xpcom/ds/nsTArray-inl.h @@ -0,0 +1,691 @@ +/* -*- 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/. */ + +#ifndef nsTArray_h__ +# error "Don't include this file directly" +#endif + +// NOTE: We don't use MOZ_COUNT_CTOR/MOZ_COUNT_DTOR to perform leak checking of +// nsTArray_base objects intentionally for the following reasons: +// * The leak logging isn't as useful as other types of logging, as +// nsTArray_base is frequently relocated without invoking a constructor, such +// as when stored within another nsTArray. This means that +// XPCOM_MEM_LOG_CLASSES cannot be used to identify specific leaks of nsTArray +// objects. +// * The nsTArray type is layout compatible with the ThinVec crate with the +// correct flags, and ThinVec does not currently perform leak logging. +// This means that if a large number of arrays are transferred between Rust +// and C++ code using ThinVec, for example within another ThinVec, they +// will not be logged correctly and might appear as e.g. negative leaks. +// * Leaks which have been found thanks to the leak logging added by this +// type have often not been significant, and/or have needed to be +// circumvented using some other mechanism. Most leaks found with this type +// in them also include other types which will continue to be tracked. + +template +nsTArray_base::nsTArray_base() : mHdr(EmptyHdr()) {} + +template +nsTArray_base::~nsTArray_base() { + if (!HasEmptyHeader() && !UsesAutoArrayBuffer()) { + Alloc::Free(mHdr); + } +} + +template +nsTArray_base::nsTArray_base(const nsTArray_base&) + : mHdr(EmptyHdr()) { + // Actual copying happens through nsTArray_CopyEnabler, we just need to do the + // initialization of mHdr. +} + +template +nsTArray_base& +nsTArray_base::operator=(const nsTArray_base&) { + // Actual copying happens through nsTArray_CopyEnabler, so do nothing here (do + // not copy mHdr). + return *this; +} + +template +const nsTArrayHeader* +nsTArray_base::GetAutoArrayBufferUnsafe( + size_t aElemAlign) const { + // Assuming |this| points to an nsAutoArray, we want to get a pointer to + // mAutoBuf. So just cast |this| to nsAutoArray* and read &mAutoBuf! + + const void* autoBuf = + &reinterpret_cast, 1>*>(this) + ->mAutoBuf; + + // If we're on a 32-bit system and aElemAlign is 8, we need to adjust our + // pointer to take into account the extra alignment in the auto array. + + static_assert( + sizeof(void*) != 4 || (MOZ_ALIGNOF(mozilla::AlignedElem<8>) == 8 && + sizeof(AutoTArray, 1>) == + sizeof(void*) + sizeof(nsTArrayHeader) + 4 + + sizeof(mozilla::AlignedElem<8>)), + "auto array padding wasn't what we expected"); + + // We don't support alignments greater than 8 bytes. + MOZ_ASSERT(aElemAlign <= 4 || aElemAlign == 8, "unsupported alignment."); + if (sizeof(void*) == 4 && aElemAlign == 8) { + autoBuf = reinterpret_cast(autoBuf) + 4; + } + + return reinterpret_cast(autoBuf); +} + +template +bool nsTArray_base::UsesAutoArrayBuffer() const { + if (!mHdr->mIsAutoArray) { + return false; + } + + // This is nuts. If we were sane, we'd pass aElemAlign as a parameter to + // this function. Unfortunately this function is called in nsTArray_base's + // destructor, at which point we don't know value_type's alignment. + // + // We'll fall on our face and return true when we should say false if + // + // * we're not using our auto buffer, + // * aElemAlign == 4, and + // * mHdr == GetAutoArrayBuffer(8). + // + // This could happen if |*this| lives on the heap and malloc allocated our + // buffer on the heap adjacent to |*this|. + // + // However, we can show that this can't happen. If |this| is an auto array + // (as we ensured at the beginning of the method), GetAutoArrayBuffer(8) + // always points to memory owned by |*this|, because (as we assert below) + // + // * GetAutoArrayBuffer(8) is at most 4 bytes past GetAutoArrayBuffer(4), + // and + // * sizeof(nsTArrayHeader) > 4. + // + // Since AutoTArray always contains an nsTArrayHeader, + // GetAutoArrayBuffer(8) will always point inside the auto array object, + // even if it doesn't point at the beginning of the header. + // + // Note that this means that we can't store elements with alignment 16 in an + // nsTArray, because GetAutoArrayBuffer(16) could lie outside the memory + // owned by this AutoTArray. We statically assert that value_type's + // alignment is 8 bytes or less in AutoTArray. + + static_assert(sizeof(nsTArrayHeader) > 4, "see comment above"); + +#ifdef DEBUG + ptrdiff_t diff = reinterpret_cast(GetAutoArrayBuffer(8)) - + reinterpret_cast(GetAutoArrayBuffer(4)); + MOZ_ASSERT(diff >= 0 && diff <= 4, + "GetAutoArrayBuffer doesn't do what we expect."); +#endif + + return mHdr == GetAutoArrayBuffer(4) || mHdr == GetAutoArrayBuffer(8); +} + +// defined in nsTArray.cpp +bool IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity, + size_t aElemSize); + +template +template +typename ActualAlloc::ResultTypeProxy +nsTArray_base::ExtendCapacity(size_type aLength, + size_type aCount, + size_type aElemSize) { + mozilla::CheckedInt newLength = aLength; + newLength += aCount; + + if (!newLength.isValid()) { + return ActualAlloc::FailureResult(); + } + + return this->EnsureCapacity(newLength.value(), aElemSize); +} + +template +template +typename ActualAlloc::ResultTypeProxy +nsTArray_base::EnsureCapacity(size_type aCapacity, + size_type aElemSize) { + // This should be the most common case so test this first + if (aCapacity <= mHdr->mCapacity) { + return ActualAlloc::SuccessResult(); + } + + // If the requested memory allocation exceeds size_type(-1)/2, then + // our doubling algorithm may not be able to allocate it. + // Additionally, if it exceeds uint32_t(-1) then we couldn't fit in the + // Header::mCapacity member. Just bail out in cases like that. We don't want + // to be allocating 2 GB+ arrays anyway. + if (!IsTwiceTheRequiredBytesRepresentableAsUint32(aCapacity, aElemSize)) { + ActualAlloc::SizeTooBig((size_t)aCapacity * aElemSize); + return ActualAlloc::FailureResult(); + } + + size_t reqSize = sizeof(Header) + aCapacity * aElemSize; + + if (HasEmptyHeader()) { + // Malloc() new data + Header* header = static_cast(ActualAlloc::Malloc(reqSize)); + if (!header) { + return ActualAlloc::FailureResult(); + } + header->mLength = 0; + header->mCapacity = aCapacity; + header->mIsAutoArray = 0; + mHdr = header; + + return ActualAlloc::SuccessResult(); + } + + // We increase our capacity so that the allocated buffer grows exponentially, + // which gives us amortized O(1) appending. Below the threshold, we use + // powers-of-two. Above the threshold, we grow by at least 1.125, rounding up + // to the nearest MiB. + const size_t slowGrowthThreshold = 8 * 1024 * 1024; + + size_t bytesToAlloc; + if (reqSize >= slowGrowthThreshold) { + size_t currSize = sizeof(Header) + Capacity() * aElemSize; + size_t minNewSize = currSize + (currSize >> 3); // multiply by 1.125 + bytesToAlloc = reqSize > minNewSize ? reqSize : minNewSize; + + // Round up to the next multiple of MiB. + const size_t MiB = 1 << 20; + bytesToAlloc = MiB * ((bytesToAlloc + MiB - 1) / MiB); + } else { + // Round up to the next power of two. + bytesToAlloc = mozilla::RoundUpPow2(reqSize); + } + + Header* header; + if (UsesAutoArrayBuffer() || !RelocationStrategy::allowRealloc) { + // Malloc() and copy + header = static_cast(ActualAlloc::Malloc(bytesToAlloc)); + if (!header) { + return ActualAlloc::FailureResult(); + } + + RelocationStrategy::RelocateNonOverlappingRegionWithHeader( + header, mHdr, Length(), aElemSize); + + if (!UsesAutoArrayBuffer()) { + ActualAlloc::Free(mHdr); + } + } else { + // Realloc() existing data + header = static_cast(ActualAlloc::Realloc(mHdr, bytesToAlloc)); + if (!header) { + return ActualAlloc::FailureResult(); + } + } + + // How many elements can we fit in bytesToAlloc? + size_t newCapacity = (bytesToAlloc - sizeof(Header)) / aElemSize; + MOZ_ASSERT(newCapacity >= aCapacity, "Didn't enlarge the array enough!"); + header->mCapacity = newCapacity; + + mHdr = header; + + return ActualAlloc::SuccessResult(); +} + +// We don't need use Alloc template parameter specified here because failure to +// shrink the capacity will leave the array unchanged. +template +void nsTArray_base::ShrinkCapacity( + size_type aElemSize, size_t aElemAlign) { + if (HasEmptyHeader() || UsesAutoArrayBuffer()) { + return; + } + + if (mHdr->mLength >= mHdr->mCapacity) { // should never be greater than... + return; + } + + size_type length = Length(); + + if (IsAutoArray() && GetAutoArrayBuffer(aElemAlign)->mCapacity >= length) { + Header* header = GetAutoArrayBuffer(aElemAlign); + + // Move the data, but don't copy the header to avoid overwriting mCapacity. + header->mLength = length; + RelocationStrategy::RelocateNonOverlappingRegion(header + 1, mHdr + 1, + length, aElemSize); + + nsTArrayFallibleAllocator::Free(mHdr); + mHdr = header; + return; + } + + if (length == 0) { + MOZ_ASSERT(!IsAutoArray(), "autoarray should have fit 0 elements"); + nsTArrayFallibleAllocator::Free(mHdr); + mHdr = EmptyHdr(); + return; + } + + size_type newSize = sizeof(Header) + length * aElemSize; + + Header* newHeader; + if (!RelocationStrategy::allowRealloc) { + // Malloc() and copy. + newHeader = + static_cast(nsTArrayFallibleAllocator::Malloc(newSize)); + if (!newHeader) { + return; + } + + RelocationStrategy::RelocateNonOverlappingRegionWithHeader( + newHeader, mHdr, Length(), aElemSize); + + nsTArrayFallibleAllocator::Free(mHdr); + } else { + // Realloc() existing data. + newHeader = + static_cast(nsTArrayFallibleAllocator::Realloc(mHdr, newSize)); + if (!newHeader) { + return; + } + } + + mHdr = newHeader; + mHdr->mCapacity = length; +} + +template +void nsTArray_base::ShrinkCapacityToZero( + size_type aElemSize, size_t aElemAlign) { + MOZ_ASSERT(mHdr->mLength == 0); + + if (HasEmptyHeader() || UsesAutoArrayBuffer()) { + return; + } + + const bool isAutoArray = IsAutoArray(); + + nsTArrayFallibleAllocator::Free(mHdr); + + if (isAutoArray) { + mHdr = GetAutoArrayBufferUnsafe(aElemAlign); + mHdr->mLength = 0; + } else { + mHdr = EmptyHdr(); + } +} + +template +template +void nsTArray_base::ShiftData(index_type aStart, + size_type aOldLen, + size_type aNewLen, + size_type aElemSize, + size_t aElemAlign) { + if (aOldLen == aNewLen) { + return; + } + + // Determine how many elements need to be shifted + size_type num = mHdr->mLength - (aStart + aOldLen); + + // Compute the resulting length of the array + mHdr->mLength += aNewLen - aOldLen; + if (mHdr->mLength == 0) { + ShrinkCapacityToZero(aElemSize, aElemAlign); + } else { + // Maybe nothing needs to be shifted + if (num == 0) { + return; + } + // Perform shift (change units to bytes first) + aStart *= aElemSize; + aNewLen *= aElemSize; + aOldLen *= aElemSize; + char* baseAddr = reinterpret_cast(mHdr + 1) + aStart; + RelocationStrategy::RelocateOverlappingRegion( + baseAddr + aNewLen, baseAddr + aOldLen, num, aElemSize); + } +} + +template +template +void nsTArray_base::SwapFromEnd(index_type aStart, + size_type aCount, + size_type aElemSize, + size_t aElemAlign) { + // This method is part of the implementation of + // nsTArray::SwapRemoveElement{s,}At. For more information, read the + // documentation on that method. + if (aCount == 0) { + return; + } + + // We are going to be removing aCount elements. Update our length to point to + // the new end of the array. + size_type oldLength = mHdr->mLength; + mHdr->mLength -= aCount; + + if (mHdr->mLength == 0) { + // If we have no elements remaining in the array, we can free our buffer. + ShrinkCapacityToZero(aElemSize, aElemAlign); + return; + } + + // Determine how many elements we need to move from the end of the array into + // the now-removed section. This will either be the number of elements which + // were removed (if there are more elements in the tail of the array), or the + // entire tail of the array, whichever is smaller. + size_type relocCount = std::min(aCount, mHdr->mLength - aStart); + if (relocCount == 0) { + return; + } + + // Move the elements which are now stranded after the end of the array back + // into the now-vacated memory. + index_type sourceBytes = (oldLength - relocCount) * aElemSize; + index_type destBytes = aStart * aElemSize; + + // Perform the final copy. This is guaranteed to be a non-overlapping copy + // as our source contains only still-valid entries, and the destination + // contains only invalid entries which need to be overwritten. + MOZ_ASSERT(sourceBytes >= destBytes, + "The source should be after the destination."); + MOZ_ASSERT(sourceBytes - destBytes >= relocCount * aElemSize, + "The range should be nonoverlapping"); + + char* baseAddr = reinterpret_cast(mHdr + 1); + RelocationStrategy::RelocateNonOverlappingRegion( + baseAddr + destBytes, baseAddr + sourceBytes, relocCount, aElemSize); +} + +template +template +typename ActualAlloc::ResultTypeProxy +nsTArray_base::InsertSlotsAt(index_type aIndex, + size_type aCount, + size_type aElemSize, + size_t aElemAlign) { + if (MOZ_UNLIKELY(aIndex > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + + if (!ActualAlloc::Successful( + this->ExtendCapacity(Length(), aCount, aElemSize))) { + return ActualAlloc::FailureResult(); + } + + // Move the existing elements as needed. Note that this will + // change our mLength, so no need to call IncrementLength. + ShiftData(aIndex, 0, aCount, aElemSize, aElemAlign); + + return ActualAlloc::SuccessResult(); +} + +// nsTArray_base::IsAutoArrayRestorer is an RAII class which takes +// |nsTArray_base &array| in its constructor. When it's destructed, it ensures +// that +// +// * array.mIsAutoArray has the same value as it did when we started, and +// * if array has an auto buffer and mHdr would otherwise point to +// sEmptyTArrayHeader, array.mHdr points to array's auto buffer. + +template +nsTArray_base::IsAutoArrayRestorer:: + IsAutoArrayRestorer(nsTArray_base& aArray, + size_t aElemAlign) + : mArray(aArray), mElemAlign(aElemAlign), mIsAuto(aArray.IsAutoArray()) {} + +template +nsTArray_base::IsAutoArrayRestorer::~IsAutoArrayRestorer() { + // Careful: We don't want to set mIsAutoArray = 1 on sEmptyTArrayHeader. + if (mIsAuto && mArray.HasEmptyHeader()) { + // Call GetAutoArrayBufferUnsafe() because GetAutoArrayBuffer() asserts + // that mHdr->mIsAutoArray is true, which surely isn't the case here. + mArray.mHdr = mArray.GetAutoArrayBufferUnsafe(mElemAlign); + mArray.mHdr->mLength = 0; + } else if (!mArray.HasEmptyHeader()) { + mArray.mHdr->mIsAutoArray = mIsAuto; + } +} + +template +template +typename ActualAlloc::ResultTypeProxy +nsTArray_base::SwapArrayElements( + nsTArray_base& aOther, size_type aElemSize, + size_t aElemAlign) { + // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyTArrayHeader even if we + // have an auto buffer. We need to point mHdr back to our auto buffer before + // we return, otherwise we'll forget that we have an auto buffer at all! + // IsAutoArrayRestorer takes care of this for us. + + IsAutoArrayRestorer ourAutoRestorer(*this, aElemAlign); + typename nsTArray_base::IsAutoArrayRestorer + otherAutoRestorer(aOther, aElemAlign); + + // If neither array uses an auto buffer which is big enough to store the + // other array's elements, then ensure that both arrays use malloc'ed storage + // and swap their mHdr pointers. + if ((!UsesAutoArrayBuffer() || Capacity() < aOther.Length()) && + (!aOther.UsesAutoArrayBuffer() || aOther.Capacity() < Length())) { + if (!EnsureNotUsingAutoArrayBuffer(aElemSize) || + !aOther.template EnsureNotUsingAutoArrayBuffer( + aElemSize)) { + return ActualAlloc::FailureResult(); + } + + Header* temp = mHdr; + mHdr = aOther.mHdr; + aOther.mHdr = temp; + + return ActualAlloc::SuccessResult(); + } + + // Swap the two arrays by copying, since at least one is using an auto + // buffer which is large enough to hold all of the aOther's elements. We'll + // copy the shorter array into temporary storage. + // + // (We could do better than this in some circumstances. Suppose we're + // swapping arrays X and Y. X has space for 2 elements in its auto buffer, + // but currently has length 4, so it's using malloc'ed storage. Y has length + // 2. When we swap X and Y, we don't need to use a temporary buffer; we can + // write Y straight into X's auto buffer, write X's malloc'ed buffer on top + // of Y, and then switch X to using its auto buffer.) + + if (!ActualAlloc::Successful( + EnsureCapacity(aOther.Length(), aElemSize)) || + !Allocator::Successful( + aOther.template EnsureCapacity(Length(), aElemSize))) { + return ActualAlloc::FailureResult(); + } + + // The EnsureCapacity calls above shouldn't have caused *both* arrays to + // switch from their auto buffers to malloc'ed space. + MOZ_ASSERT(UsesAutoArrayBuffer() || aOther.UsesAutoArrayBuffer(), + "One of the arrays should be using its auto buffer."); + + size_type smallerLength = XPCOM_MIN(Length(), aOther.Length()); + size_type largerLength = XPCOM_MAX(Length(), aOther.Length()); + void* smallerElements; + void* largerElements; + if (Length() <= aOther.Length()) { + smallerElements = Hdr() + 1; + largerElements = aOther.Hdr() + 1; + } else { + smallerElements = aOther.Hdr() + 1; + largerElements = Hdr() + 1; + } + + // Allocate temporary storage for the smaller of the two arrays. We want to + // allocate this space on the stack, if it's not too large. Sounds like a + // job for AutoTArray! (One of the two arrays we're swapping is using an + // auto buffer, so we're likely not allocating a lot of space here. But one + // could, in theory, allocate a huge AutoTArray on the heap.) + AutoTArray temp; + if (!ActualAlloc::Successful(temp.template EnsureCapacity( + smallerLength * aElemSize, sizeof(uint8_t)))) { + return ActualAlloc::FailureResult(); + } + + RelocationStrategy::RelocateNonOverlappingRegion( + temp.Elements(), smallerElements, smallerLength, aElemSize); + RelocationStrategy::RelocateNonOverlappingRegion( + smallerElements, largerElements, largerLength, aElemSize); + RelocationStrategy::RelocateNonOverlappingRegion( + largerElements, temp.Elements(), smallerLength, aElemSize); + + // Swap the arrays' lengths. + MOZ_ASSERT((aOther.Length() == 0 || !HasEmptyHeader()) && + (Length() == 0 || !aOther.HasEmptyHeader()), + "Don't set sEmptyTArrayHeader's length."); + size_type tempLength = Length(); + + // Avoid writing to EmptyHdr, since it can trigger false + // positives with TSan. + if (!HasEmptyHeader()) { + mHdr->mLength = aOther.Length(); + } + if (!aOther.HasEmptyHeader()) { + aOther.mHdr->mLength = tempLength; + } + + return ActualAlloc::SuccessResult(); +} + +template +template +void nsTArray_base::MoveInit( + nsTArray_base& aOther, size_type aElemSize, + size_t aElemAlign) { + // This method is similar to SwapArrayElements, but specialized for the case + // where the target array is empty with no allocated heap storage. It is + // provided and used to simplify template instantiation and enable better code + // generation. + + MOZ_ASSERT(Length() == 0); + MOZ_ASSERT(Capacity() == 0 || (IsAutoArray() && UsesAutoArrayBuffer())); + + // EnsureNotUsingAutoArrayBuffer will set mHdr = sEmptyTArrayHeader even if we + // have an auto buffer. We need to point mHdr back to our auto buffer before + // we return, otherwise we'll forget that we have an auto buffer at all! + // IsAutoArrayRestorer takes care of this for us. + + IsAutoArrayRestorer ourAutoRestorer(*this, aElemAlign); + typename nsTArray_base::IsAutoArrayRestorer + otherAutoRestorer(aOther, aElemAlign); + + // If neither array uses an auto buffer which is big enough to store the + // other array's elements, then ensure that both arrays use malloc'ed storage + // and swap their mHdr pointers. + if ((!IsAutoArray() || Capacity() < aOther.Length()) && + !aOther.UsesAutoArrayBuffer()) { + mHdr = aOther.mHdr; + + aOther.mHdr = EmptyHdr(); + + return; + } + + // Move the data by copying, since at least one has an auto + // buffer which is large enough to hold all of the aOther's elements. + + EnsureCapacity(aOther.Length(), aElemSize); + + // The EnsureCapacity calls above shouldn't have caused *both* arrays to + // switch from their auto buffers to malloc'ed space. + MOZ_ASSERT(UsesAutoArrayBuffer() || aOther.UsesAutoArrayBuffer(), + "One of the arrays should be using its auto buffer."); + + RelocationStrategy::RelocateNonOverlappingRegion(Hdr() + 1, aOther.Hdr() + 1, + aOther.Length(), aElemSize); + + // Swap the arrays' lengths. + MOZ_ASSERT((aOther.Length() == 0 || !HasEmptyHeader()) && + (Length() == 0 || !aOther.HasEmptyHeader()), + "Don't set sEmptyTArrayHeader's length."); + + // Avoid writing to EmptyHdr, since it can trigger false + // positives with TSan. + if (!HasEmptyHeader()) { + mHdr->mLength = aOther.Length(); + } + if (!aOther.HasEmptyHeader()) { + aOther.mHdr->mLength = 0; + } +} + +template +template +void nsTArray_base::MoveConstructNonAutoArray( + nsTArray_base& aOther, size_type aElemSize, + size_t aElemAlign) { + // We know that we are not an (Copyable)AutoTArray and we know that we are + // empty, so don't use SwapArrayElements which doesn't know either of these + // facts and is very complex. + + if (aOther.IsEmpty()) { + return; + } + + // aOther might be an (Copyable)AutoTArray though, and it might use its inline + // buffer. + const bool otherUsesAutoArrayBuffer = aOther.UsesAutoArrayBuffer(); + if (otherUsesAutoArrayBuffer) { + // Use nsTArrayInfallibleAllocator regardless of Alloc because this is + // called from a move constructor, which cannot report an error to the + // caller. + aOther.template EnsureNotUsingAutoArrayBuffer( + aElemSize); + } + + const bool otherIsAuto = otherUsesAutoArrayBuffer || aOther.IsAutoArray(); + mHdr = aOther.mHdr; + // We might write to mHdr, so ensure it's not the static empty header. aOther + // shouldn't have been empty if we get here anyway. + MOZ_ASSERT(!HasEmptyHeader()); + + if (otherIsAuto) { + mHdr->mIsAutoArray = false; + aOther.mHdr = aOther.GetAutoArrayBufferUnsafe(aElemAlign); + aOther.mHdr->mLength = 0; + } else { + aOther.mHdr = aOther.EmptyHdr(); + } +} + +template +template +bool nsTArray_base::EnsureNotUsingAutoArrayBuffer( + size_type aElemSize) { + if (UsesAutoArrayBuffer()) { + // If you call this on a 0-length array, we'll set that array's mHdr to + // sEmptyTArrayHeader, in flagrant violation of the AutoTArray invariants. + // It's up to you to set it back! (If you don't, the AutoTArray will + // forget that it has an auto buffer.) + if (Length() == 0) { + mHdr = EmptyHdr(); + return true; + } + + size_type size = sizeof(Header) + Length() * aElemSize; + + Header* header = static_cast(ActualAlloc::Malloc(size)); + if (!header) { + return false; + } + + RelocationStrategy::RelocateNonOverlappingRegionWithHeader( + header, mHdr, Length(), aElemSize); + header->mCapacity = Length(); + mHdr = header; + } + + return true; +} diff --git a/xpcom/ds/nsTArray.cpp b/xpcom/ds/nsTArray.cpp new file mode 100644 index 0000000000..50326fb331 --- /dev/null +++ b/xpcom/ds/nsTArray.cpp @@ -0,0 +1,28 @@ +/* -*- 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 +#include "nsTArray.h" +#include "nsXPCOM.h" +#include "nsCycleCollectionNoteChild.h" +#include "nsDebug.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/IntegerPrintfMacros.h" + +// Ensure this is sufficiently aligned so that Elements() and co don't create +// unaligned pointers, or slices with unaligned pointers for empty arrays, see +// https://github.com/servo/servo/issues/22613. +alignas(8) const nsTArrayHeader sEmptyTArrayHeader = {0, 0, 0}; + +bool IsTwiceTheRequiredBytesRepresentableAsUint32(size_t aCapacity, + size_t aElemSize) { + using mozilla::CheckedUint32; + return ((CheckedUint32(aCapacity) * aElemSize) * 2).isValid(); +} + +void ::detail::SetCycleCollectionArrayFlag(uint32_t& aFlags) { + aFlags |= CycleCollectionEdgeNameArrayFlag; +} diff --git a/xpcom/ds/nsTArray.h b/xpcom/ds/nsTArray.h new file mode 100644 index 0000000000..ed8c05943c --- /dev/null +++ b/xpcom/ds/nsTArray.h @@ -0,0 +1,3345 @@ +/* -*- 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/. */ + +#ifndef nsTArray_h__ +#define nsTArray_h__ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mozilla/Alignment.h" +#include "mozilla/ArrayIterator.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/FunctionTypeTraits.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/NotNull.h" +#include "mozilla/Span.h" +#include "mozilla/fallible.h" +#include "mozilla/mozalloc.h" +#include "nsAlgorithm.h" +#include "nsDebug.h" +#include "nsISupports.h" +#include "nsQuickSort.h" +#include "nsRegionFwd.h" +#include "nsTArrayForwardDeclare.h" + +namespace JS { +template +class Heap; +} /* namespace JS */ + +class nsCycleCollectionTraversalCallback; +class nsRegion; + +namespace mozilla::a11y { +class BatchData; +} + +namespace mozilla { +namespace layers { +class Animation; +class FrameStats; +struct PropertyAnimationGroup; +struct TileClient; +} // namespace layers +} // namespace mozilla + +namespace mozilla { +struct SerializedStructuredCloneBuffer; +class SourceBufferTask; +} // namespace mozilla + +namespace mozilla::dom::binding_detail { +template +class RecordEntry; +} + +namespace mozilla::dom::ipc { +class StructuredCloneData; +} // namespace mozilla::dom::ipc + +namespace mozilla::dom { +class ClonedMessageData; +class MessageData; +class MessagePortIdentifier; +struct MozPluginParameter; +template +struct Nullable; +class OwningFileOrDirectory; +class OwningStringOrBooleanOrObject; +class OwningUTF8StringOrDouble; +class Pref; +class RefMessageData; +class ResponsiveImageCandidate; +class ServiceWorkerRegistrationData; +namespace indexedDB { +class SerializedStructuredCloneReadInfo; +class ObjectStoreCursorResponse; +class IndexCursorResponse; +} // namespace indexedDB +} // namespace mozilla::dom + +namespace mozilla::ipc { +class ContentSecurityPolicy; +template +class Endpoint; +} // namespace mozilla::ipc + +class JSStructuredCloneData; + +template +class RefPtr; + +// +// nsTArray is a resizable array class, like std::vector. +// +// Unlike std::vector, which follows C++'s construction/destruction rules, +// By default, nsTArray assumes that instances of E can be relocated safely +// using memory utils (memcpy/memmove). +// +// The public classes defined in this header are +// +// nsTArray, +// CopyableTArray, +// FallibleTArray, +// AutoTArray, +// CopyableAutoTArray +// +// nsTArray, CopyableTArray, AutoTArray and CopyableAutoTArray are infallible by +// default. To opt-in to fallible behaviour, use the `mozilla::fallible` +// parameter and check the return value. +// +// CopyableTArray and CopyableAutoTArray< are copy-constructible and +// copy-assignable. Use these only when syntactically necessary to avoid implcit +// unintentional copies. nsTArray/AutoTArray can be conveniently copied using +// the Clone() member function. Consider using std::move where possible. +// +// If you just want to declare the nsTArray types (e.g., if you're in a header +// file and don't need the full nsTArray definitions) consider including +// nsTArrayForwardDeclare.h instead of nsTArray.h. +// +// The template parameter E specifies the type of the elements and has the +// following requirements: +// +// E MUST be safely memmove()'able. +// E MUST define a copy-constructor. +// E MAY define operator< for sorting. +// E MAY define operator== for searching. +// +// (Note that the memmove requirement may be relaxed for certain types - see +// nsTArray_RelocationStrategy below.) +// +// There is a public type value_type defined as E within each array class, and +// we reference the type under this name below. +// +// For member functions taking a Comparator instance, Comparator must be either +// a functor with a tri-state comparison function with a signature compatible to +// +// /** @return negative iff a < b, 0 iff a == b, positive iff a > b */ +// int (const value_type& a, const value_type& b); +// +// or a class defining member functions with signatures compatible to: +// +// class Comparator { +// public: +// /** @return True if the elements are equals; false otherwise. */ +// bool Equals(const value_type& a, const value_type& b) const; +// +// /** @return True if (a < b); false otherwise. */ +// bool LessThan(const value_type& a, const value_type& b) const; +// }; +// +// The Equals member function is used for searching, and the LessThan member +// function is used for searching and sorting. Note that some member functions, +// e.g. Compare, are templates where a different type Item can be used for the +// element to compare to. In that case, the signatures must be compatible to +// allow those comparisons, but the details are not documented here. +// + +// +// nsTArrayFallibleResult and nsTArrayInfallibleResult types are proxy types +// which are used because you cannot use a templated type which is bound to +// void as an argument to a void function. In order to work around that, we +// encode either a void or a boolean inside these proxy objects, and pass them +// to the aforementioned function instead, and then use the type information to +// decide what to do in the function. +// +// Note that public nsTArray methods should never return a proxy type. Such +// types are only meant to be used in the internal nsTArray helper methods. +// Public methods returning non-proxy types cannot be called from other +// nsTArray members. +// +struct nsTArrayFallibleResult { + // Note: allows implicit conversions from and to bool + MOZ_IMPLICIT constexpr nsTArrayFallibleResult(bool aResult) + : mResult(aResult) {} + + MOZ_IMPLICIT constexpr operator bool() { return mResult; } + + private: + bool mResult; +}; + +struct nsTArrayInfallibleResult {}; + +// +// nsTArray*Allocators must all use the same |free()|, to allow swap()'ing +// between fallible and infallible variants. +// + +struct nsTArrayFallibleAllocatorBase { + typedef bool ResultType; + typedef nsTArrayFallibleResult ResultTypeProxy; + + static constexpr ResultType Result(ResultTypeProxy aResult) { + return aResult; + } + static constexpr bool Successful(ResultTypeProxy aResult) { return aResult; } + static constexpr ResultTypeProxy SuccessResult() { return true; } + static constexpr ResultTypeProxy FailureResult() { return false; } + static constexpr ResultType ConvertBoolToResultType(bool aValue) { + return aValue; + } +}; + +struct nsTArrayInfallibleAllocatorBase { + typedef void ResultType; + typedef nsTArrayInfallibleResult ResultTypeProxy; + + static constexpr ResultType Result(ResultTypeProxy aResult) {} + static constexpr bool Successful(ResultTypeProxy) { return true; } + static constexpr ResultTypeProxy SuccessResult() { return ResultTypeProxy(); } + + [[noreturn]] static ResultTypeProxy FailureResult() { + MOZ_CRASH("Infallible nsTArray should never fail"); + } + + template + static constexpr ResultType ConvertBoolToResultType(T aValue) { + if (!aValue) { + MOZ_CRASH("infallible nsTArray should never convert false to ResultType"); + } + } + + template + static constexpr ResultType ConvertBoolToResultType( + const mozilla::NotNull& aValue) {} +}; + +struct nsTArrayFallibleAllocator : nsTArrayFallibleAllocatorBase { + static void* Malloc(size_t aSize) { return malloc(aSize); } + static void* Realloc(void* aPtr, size_t aSize) { + return realloc(aPtr, aSize); + } + + static void Free(void* aPtr) { free(aPtr); } + static void SizeTooBig(size_t) {} +}; + +struct nsTArrayInfallibleAllocator : nsTArrayInfallibleAllocatorBase { + static void* Malloc(size_t aSize) MOZ_NONNULL_RETURN { + return moz_xmalloc(aSize); + } + static void* Realloc(void* aPtr, size_t aSize) MOZ_NONNULL_RETURN { + return moz_xrealloc(aPtr, aSize); + } + + static void Free(void* aPtr) { free(aPtr); } + static void SizeTooBig(size_t aSize) { NS_ABORT_OOM(aSize); } +}; + +// nsTArray_base stores elements into the space allocated beyond +// sizeof(*this). This is done to minimize the size of the nsTArray +// object when it is empty. +struct nsTArrayHeader { + uint32_t mLength; + uint32_t mCapacity : 31; + uint32_t mIsAutoArray : 1; +}; + +extern "C" { +extern const nsTArrayHeader sEmptyTArrayHeader; +} + +namespace detail { +// nsTArray_CopyDisabler disables copy operations. +class nsTArray_CopyDisabler { + public: + nsTArray_CopyDisabler() = default; + + nsTArray_CopyDisabler(const nsTArray_CopyDisabler&) = delete; + nsTArray_CopyDisabler& operator=(const nsTArray_CopyDisabler&) = delete; +}; + +} // namespace detail + +// This class provides a SafeElementAt method to nsTArray which does +// not take a second default value parameter. +template +struct nsTArray_SafeElementAtHelper : public ::detail::nsTArray_CopyDisabler { + typedef E* elem_type; + typedef size_t index_type; + + // No implementation is provided for these two methods, and that is on + // purpose, since we don't support these functions on non-pointer type + // instantiations. + elem_type& SafeElementAt(index_type aIndex); + const elem_type& SafeElementAt(index_type aIndex) const; +}; + +template +struct nsTArray_SafeElementAtHelper + : public ::detail::nsTArray_CopyDisabler { + typedef E* elem_type; + // typedef const E* const_elem_type; XXX: see below + typedef size_t index_type; + + elem_type SafeElementAt(index_type aIndex) { + return static_cast(this)->SafeElementAt(aIndex, nullptr); + } + + // XXX: Probably should return const_elem_type, but callsites must be fixed. + // Also, the use of const_elem_type for nsTArray in + // xpcprivate.h causes build failures on Windows because xpcGCCallback is a + // function pointer and MSVC doesn't like qualifying it with |const|. + elem_type SafeElementAt(index_type aIndex) const { + return static_cast(this)->SafeElementAt(aIndex, nullptr); + } +}; + +// E is a smart pointer type; the +// smart pointer can act as its element_type*. +template +struct nsTArray_SafeElementAtSmartPtrHelper + : public ::detail::nsTArray_CopyDisabler { + typedef typename E::element_type* elem_type; + typedef const typename E::element_type* const_elem_type; + typedef size_t index_type; + + elem_type SafeElementAt(index_type aIndex) { + auto* derived = static_cast(this); + if (aIndex < derived->Length()) { + return derived->Elements()[aIndex]; + } + return nullptr; + } + + // XXX: Probably should return const_elem_type, but callsites must be fixed. + elem_type SafeElementAt(index_type aIndex) const { + auto* derived = static_cast(this); + if (aIndex < derived->Length()) { + return derived->Elements()[aIndex]; + } + return nullptr; + } +}; + +template +class nsCOMPtr; + +template +struct nsTArray_SafeElementAtHelper, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper, Derived> {}; + +template +struct nsTArray_SafeElementAtHelper, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper, Derived> {}; + +namespace mozilla { +template +class OwningNonNull; +} // namespace mozilla + +template +struct nsTArray_SafeElementAtHelper, Derived> + : public nsTArray_SafeElementAtSmartPtrHelper, + Derived> {}; + +// Servo bindings. +extern "C" void Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity, + size_t aElementSize); +extern "C" void Gecko_ClearPODTArray(void* aArray, size_t aElementSize, + size_t aElementAlign); + +// +// This class serves as a base class for nsTArray. It shouldn't be used +// directly. It holds common implementation code that does not depend on the +// element type of the nsTArray. +// +template +class nsTArray_base { + // Allow swapping elements with |nsTArray_base|s created using a + // different allocator. This is kosher because all allocators use + // the same free(). + template + friend class nsTArray_base; + + // Needed for AppendElements from an array with a different allocator, which + // calls ShiftData. + template + friend class nsTArray_Impl; + + friend void Gecko_EnsureTArrayCapacity(void* aArray, size_t aCapacity, + size_t aElemSize); + friend void Gecko_ClearPODTArray(void* aTArray, size_t aElementSize, + size_t aElementAlign); + + protected: + typedef nsTArrayHeader Header; + + public: + typedef size_t size_type; + typedef size_t index_type; + + // @return The number of elements in the array. + size_type Length() const { return mHdr->mLength; } + + // @return True if the array is empty or false otherwise. + bool IsEmpty() const { return Length() == 0; } + + // @return The number of elements that can fit in the array without forcing + // the array to be re-allocated. The length of an array is always less + // than or equal to its capacity. + size_type Capacity() const { return mHdr->mCapacity; } + +#ifdef DEBUG + void* DebugGetHeader() const { return mHdr; } +#endif + + protected: + nsTArray_base(); + + ~nsTArray_base(); + + nsTArray_base(const nsTArray_base&); + nsTArray_base& operator=(const nsTArray_base&); + + // Resize the storage if necessary to achieve the requested capacity. + // @param aCapacity The requested number of array elements. + // @param aElemSize The size of an array element. + // @return False if insufficient memory is available; true otherwise. + template + typename ActualAlloc::ResultTypeProxy EnsureCapacity(size_type aCapacity, + size_type aElemSize); + + // Extend the storage to accommodate aCount extra elements. + // @param aLength The current size of the array. + // @param aCount The number of elements to add. + // @param aElemSize The size of an array element. + // @return False if insufficient memory is available or the new length + // would overflow; true otherwise. + template + typename ActualAlloc::ResultTypeProxy ExtendCapacity(size_type aLength, + size_type aCount, + size_type aElemSize); + + // Tries to resize the storage to the minimum required amount. If this fails, + // the array is left as-is. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + void ShrinkCapacity(size_type aElemSize, size_t aElemAlign); + + // Resizes the storage to 0. This may only be called when Length() is already + // 0. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + void ShrinkCapacityToZero(size_type aElemSize, size_t aElemAlign); + + // This method may be called to resize a "gap" in the array by shifting + // elements around. It updates mLength appropriately. If the resulting + // array has zero elements, then the array's memory is free'd. + // @param aStart The starting index of the gap. + // @param aOldLen The current length of the gap. + // @param aNewLen The desired length of the gap. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + template + void ShiftData(index_type aStart, size_type aOldLen, size_type aNewLen, + size_type aElemSize, size_t aElemAlign); + + // This method may be called to swap elements from the end of the array to + // fill a "gap" in the array. If the resulting array has zero elements, then + // the array's memory is free'd. + // @param aStart The starting index of the gap. + // @param aCount The length of the gap. + // @param aElemSize The size of an array element. + // @param aElemAlign The alignment in bytes of an array element. + template + void SwapFromEnd(index_type aStart, size_type aCount, size_type aElemSize, + size_t aElemAlign); + + // This method increments the length member of the array's header. + // Note that mHdr may actually be sEmptyTArrayHeader in the case where a + // zero-length array is inserted into our array. But then aNum should + // always be 0. + void IncrementLength(size_t aNum) { + if (HasEmptyHeader()) { + if (MOZ_UNLIKELY(aNum != 0)) { + // Writing a non-zero length to the empty header would be extremely bad. + MOZ_CRASH(); + } + } else { + mHdr->mLength += aNum; + } + } + + // This method inserts blank slots into the array. + // @param aIndex the place to insert the new elements. This must be no + // greater than the current length of the array. + // @param aCount the number of slots to insert + // @param aElementSize the size of an array element. + // @param aElemAlign the alignment in bytes of an array element. + template + typename ActualAlloc::ResultTypeProxy InsertSlotsAt(index_type aIndex, + size_type aCount, + size_type aElementSize, + size_t aElemAlign); + + template + typename ActualAlloc::ResultTypeProxy SwapArrayElements( + nsTArray_base& aOther, size_type aElemSize, + size_t aElemAlign); + + template + void MoveConstructNonAutoArray( + nsTArray_base& aOther, size_type aElemSize, + size_t aElemAlign); + + template + void MoveInit(nsTArray_base& aOther, + size_type aElemSize, size_t aElemAlign); + + // This is an RAII class used in SwapArrayElements. + class IsAutoArrayRestorer { + public: + IsAutoArrayRestorer(nsTArray_base& aArray, + size_t aElemAlign); + ~IsAutoArrayRestorer(); + + private: + nsTArray_base& mArray; + size_t mElemAlign; + bool mIsAuto; + }; + + // Helper function for SwapArrayElements. Ensures that if the array + // is an AutoTArray that it doesn't use the built-in buffer. + template + bool EnsureNotUsingAutoArrayBuffer(size_type aElemSize); + + // Returns true if this nsTArray is an AutoTArray with a built-in buffer. + bool IsAutoArray() const { return mHdr->mIsAutoArray; } + + // Returns a Header for the built-in buffer of this AutoTArray. + Header* GetAutoArrayBuffer(size_t aElemAlign) { + MOZ_ASSERT(IsAutoArray(), "Should be an auto array to call this"); + return GetAutoArrayBufferUnsafe(aElemAlign); + } + const Header* GetAutoArrayBuffer(size_t aElemAlign) const { + MOZ_ASSERT(IsAutoArray(), "Should be an auto array to call this"); + return GetAutoArrayBufferUnsafe(aElemAlign); + } + + // Returns a Header for the built-in buffer of this AutoTArray, but doesn't + // assert that we are an AutoTArray. + Header* GetAutoArrayBufferUnsafe(size_t aElemAlign) { + return const_cast( + static_cast*>(this) + ->GetAutoArrayBufferUnsafe(aElemAlign)); + } + const Header* GetAutoArrayBufferUnsafe(size_t aElemAlign) const; + + // Returns true if this is an AutoTArray and it currently uses the + // built-in buffer to store its elements. + bool UsesAutoArrayBuffer() const; + + // The array's elements (prefixed with a Header). This pointer is never + // null. If the array is empty, then this will point to sEmptyTArrayHeader. + Header* mHdr; + + Header* Hdr() const MOZ_NONNULL_RETURN { return mHdr; } + Header** PtrToHdr() MOZ_NONNULL_RETURN { return &mHdr; } + static Header* EmptyHdr() MOZ_NONNULL_RETURN { + return const_cast(&sEmptyTArrayHeader); + } + + [[nodiscard]] bool HasEmptyHeader() const { return mHdr == EmptyHdr(); } +}; + +namespace detail { + +// Used for argument checking in nsTArrayElementTraits::Emplace. +template +struct ChooseFirst; + +template <> +struct ChooseFirst<> { + // Choose a default type that is guaranteed to not match E* for any + // nsTArray. + typedef void Type; +}; + +template +struct ChooseFirst { + typedef A Type; +}; + +} // namespace detail + +// +// This class defines convenience functions for element specific operations. +// Specialize this template if necessary. +// +template +class nsTArrayElementTraits { + public: + // Invoke the default constructor in place. + static inline void Construct(E* aE) { + // Do NOT call "E()"! That triggers C++ "default initialization" + // which zeroes out POD ("plain old data") types such as regular + // ints. We don't want that because it can be a performance issue + // and people don't expect it; nsTArray should work like a regular + // C/C++ array in this respect. + new (static_cast(aE)) E; + } + // Invoke the copy-constructor in place. + template + static inline void Construct(E* aE, A&& aArg) { + using E_NoCV = std::remove_cv_t; + using A_NoCV = std::remove_cv_t; + static_assert(!std::is_same_v, + "For safety, we disallow constructing nsTArray elements " + "from E* pointers. See bug 960591."); + new (static_cast(aE)) E(std::forward(aArg)); + } + // Construct in place. + template + static inline void Emplace(E* aE, Args&&... aArgs) { + using E_NoCV = std::remove_cv_t; + using A_NoCV = + std::remove_cv_t::Type>; + static_assert(!std::is_same_v, + "For safety, we disallow constructing nsTArray elements " + "from E* pointers. See bug 960591."); + new (static_cast(aE)) E(std::forward(aArgs)...); + } + // Invoke the destructor in place. + static inline void Destruct(E* aE) { aE->~E(); } +}; + +// The default comparator used by nsTArray +template +class nsDefaultComparator { + public: + bool Equals(const A& aA, const B& aB) const { return aA == aB; } + bool LessThan(const A& aA, const B& aB) const { return aA < aB; } +}; + +template +struct AssignRangeAlgorithm { + template + static void implementation(ElemType* aElements, IndexType aStart, + SizeType aCount, const Item* aValues) { + ElemType* iter = aElements + aStart; + ElemType* end = iter + aCount; + for (; iter != end; ++iter, ++aValues) { + nsTArrayElementTraits::Construct(iter, *aValues); + } + } +}; + +template <> +struct AssignRangeAlgorithm { + template + static void implementation(ElemType* aElements, IndexType aStart, + SizeType aCount, const Item* aValues) { + if (aValues) { + memcpy(aElements + aStart, aValues, aCount * sizeof(ElemType)); + } + } +}; + +// +// Normally elements are copied with memcpy and memmove, but for some element +// types that is problematic. The nsTArray_RelocationStrategy template class +// can be specialized to ensure that copying calls constructors and destructors +// instead, as is done below for JS::Heap elements. +// + +// +// A class that defines how to copy elements using memcpy/memmove. +// +struct nsTArray_RelocateUsingMemutils { + const static bool allowRealloc = true; + + static void RelocateNonOverlappingRegionWithHeader(void* aDest, + const void* aSrc, + size_t aCount, + size_t aElemSize) { + memcpy(aDest, aSrc, sizeof(nsTArrayHeader) + aCount * aElemSize); + } + + static void RelocateOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) { + memmove(aDest, aSrc, aCount * aElemSize); + } + + static void RelocateNonOverlappingRegion(void* aDest, void* aSrc, + size_t aCount, size_t aElemSize) { + memcpy(aDest, aSrc, aCount * aElemSize); + } +}; + +// +// A template class that defines how to relocate elements using the type's move +// constructor and destructor appropriately. +// +template +struct nsTArray_RelocateUsingMoveConstructor { + typedef nsTArrayElementTraits traits; + + const static bool allowRealloc = false; + + static void RelocateNonOverlappingRegionWithHeader(void* aDest, void* aSrc, + size_t aCount, + size_t aElemSize) { + nsTArrayHeader* destHeader = static_cast(aDest); + nsTArrayHeader* srcHeader = static_cast(aSrc); + *destHeader = *srcHeader; + RelocateNonOverlappingRegion( + static_cast(aDest) + sizeof(nsTArrayHeader), + static_cast(aSrc) + sizeof(nsTArrayHeader), aCount, + aElemSize); + } + + // RelocateNonOverlappingRegion and RelocateOverlappingRegion are defined by + // analogy with memmove and memcpy that are used for relocation of + // trivially-relocatable types through nsTArray_RelocateUsingMemutils. What + // they actually do is slightly different: RelocateOverlappingRegion checks to + // see which direction the movement needs to take place, whether from + // back-to-front of the range to be moved or from front-to-back. + // RelocateNonOverlappingRegion assumes that relocating front-to-back is + // always valid. They use RelocateRegionForward and RelocateRegionBackward, + // which are analogous to std::move and std::move_backward respectively, + // except they don't move-assign the destination from the source but + // move-construct the destination from the source and destroy the source. + static void RelocateOverlappingRegion(void* aDest, void* aSrc, size_t aCount, + size_t aElemSize) { + ElemType* destBegin = static_cast(aDest); + ElemType* srcBegin = static_cast(aSrc); + + // If destination and source are the same, this is a no-op. + // In practice, we don't do this. + if (destBegin == srcBegin) { + return; + } + + ElemType* srcEnd = srcBegin + aCount; + ElemType* destEnd = destBegin + aCount; + + // Figure out whether to relocate back-to-front or front-to-back. + if (srcEnd > destBegin && srcEnd < destEnd) { + RelocateRegionBackward(srcBegin, srcEnd, destEnd); + } else { + RelocateRegionForward(srcBegin, srcEnd, destBegin); + } + } + + static void RelocateNonOverlappingRegion(void* aDest, void* aSrc, + size_t aCount, size_t aElemSize) { + ElemType* destBegin = static_cast(aDest); + ElemType* srcBegin = static_cast(aSrc); + ElemType* srcEnd = srcBegin + aCount; +#ifdef DEBUG + ElemType* destEnd = destBegin + aCount; + MOZ_ASSERT(srcEnd <= destBegin || srcBegin >= destEnd); +#endif + RelocateRegionForward(srcBegin, srcEnd, destBegin); + } + + private: + static void RelocateRegionForward(ElemType* srcBegin, ElemType* srcEnd, + ElemType* destBegin) { + ElemType* srcElem = srcBegin; + ElemType* destElem = destBegin; + + while (srcElem != srcEnd) { + RelocateElement(srcElem, destElem); + ++destElem; + ++srcElem; + } + } + + static void RelocateRegionBackward(ElemType* srcBegin, ElemType* srcEnd, + ElemType* destEnd) { + ElemType* srcElem = srcEnd; + ElemType* destElem = destEnd; + while (srcElem != srcBegin) { + --destElem; + --srcElem; + RelocateElement(srcElem, destElem); + } + } + + static void RelocateElement(ElemType* srcElem, ElemType* destElem) { + traits::Construct(destElem, std::move(*srcElem)); + traits::Destruct(srcElem); + } +}; + +// +// The default behaviour is to use memcpy/memmove for everything. +// +template +struct MOZ_NEEDS_MEMMOVABLE_TYPE nsTArray_RelocationStrategy { + using Type = nsTArray_RelocateUsingMemutils; +}; + +// +// Some classes require constructors/destructors to be called, so they are +// specialized here. +// +#define MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(E) \ + template <> \ + struct nsTArray_RelocationStrategy { \ + using Type = nsTArray_RelocateUsingMoveConstructor; \ + }; + +#define MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(T) \ + template \ + struct nsTArray_RelocationStrategy> { \ + using Type = nsTArray_RelocateUsingMoveConstructor>; \ + }; + +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(JS::Heap) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(std::function) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE(mozilla::ipc::Endpoint) + +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(nsRegion) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(nsIntRegion) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::layers::TileClient) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::SerializedStructuredCloneBuffer) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::dom::ipc::StructuredCloneData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::ClonedMessageData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::dom::indexedDB::ObjectStoreCursorResponse) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::dom::indexedDB::IndexCursorResponse) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + mozilla::dom::indexedDB::SerializedStructuredCloneReadInfo); +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(JSStructuredCloneData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::MessageData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::dom::RefMessageData) +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::SourceBufferTask) + +// +// Base class for nsTArray_Impl that is templated on element type and derived +// nsTArray_Impl class, to allow extra conversions to be added for specific +// types. +// +template +struct nsTArray_TypedBase : public nsTArray_SafeElementAtHelper {}; + +// +// Specialization of nsTArray_TypedBase for arrays containing JS::Heap +// elements. +// +// These conversions are safe because JS::Heap and E share the same +// representation, and since the result of the conversions are const references +// we won't miss any barriers. +// +// The static_cast is necessary to obtain the correct address for the derived +// class since we are a base class used in multiple inheritance. +// +template +struct nsTArray_TypedBase, Derived> + : public nsTArray_SafeElementAtHelper, Derived> { + operator const nsTArray&() { + static_assert(sizeof(E) == sizeof(JS::Heap), + "JS::Heap must be binary compatible with E."); + Derived* self = static_cast(this); + return *reinterpret_cast*>(self); + } + + operator const FallibleTArray&() { + Derived* self = static_cast(this); + return *reinterpret_cast*>(self); + } +}; + +namespace detail { + +// These helpers allow us to differentiate between tri-state comparator +// functions and classes with LessThan() and Equal() methods. If an object, when +// called as a function with two instances of our element type, returns an int, +// we treat it as a tri-state comparator. +// +// T is the type of the comparator object we want to check. U is the array +// element type that we'll be comparing. +// +// V is never passed, and is only used to allow us to specialize on the return +// value of the comparator function. +template +struct IsCompareMethod : std::false_type {}; + +template +struct IsCompareMethod< + T, U, decltype(std::declval()(std::declval(), std::declval()))> + : std::true_type {}; + +// These two wrappers allow us to use either a tri-state comparator, or an +// object with Equals() and LessThan() methods interchangeably. They provide a +// tri-state Compare() method, and Equals() method, and a LessThan() method. +// +// Depending on the type of the underlying comparator, they either pass these +// through directly, or synthesize them from the methods available on the +// comparator. +// +// Callers should always use the most-specific of these methods that match their +// purpose. + +// Comparator wrapper for a tri-state comparator function +template ::value> +struct CompareWrapper { +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable : 4180) /* Silence "qualifier applied to function \ + type has no meaning" warning */ +#endif + MOZ_IMPLICIT CompareWrapper(const T& aComparator) + : mComparator(aComparator) {} + + template + int Compare(A& aLeft, B& aRight) const { + return mComparator(aLeft, aRight); + } + + template + bool Equals(A& aLeft, B& aRight) const { + return Compare(aLeft, aRight) == 0; + } + + template + bool LessThan(A& aLeft, B& aRight) const { + return Compare(aLeft, aRight) < 0; + } + + const T& mComparator; +#ifdef _MSC_VER +# pragma warning(pop) +#endif +}; + +// Comparator wrapper for a class with Equals() and LessThan() methods. +template +struct CompareWrapper { + MOZ_IMPLICIT CompareWrapper(const T& aComparator) + : mComparator(aComparator) {} + + template + int Compare(A& aLeft, B& aRight) const { + if (Equals(aLeft, aRight)) { + return 0; + } + return LessThan(aLeft, aRight) ? -1 : 1; + } + + template + bool Equals(A& aLeft, B& aRight) const { + return mComparator.Equals(aLeft, aRight); + } + + template + bool LessThan(A& aLeft, B& aRight) const { + return mComparator.LessThan(aLeft, aRight); + } + + const T& mComparator; +}; + +} // namespace detail + +// +// nsTArray_Impl contains most of the guts supporting nsTArray, FallibleTArray, +// AutoTArray. +// +// The only situation in which you might need to use nsTArray_Impl in your code +// is if you're writing code which mutates a TArray which may or may not be +// infallible. +// +// Code which merely reads from a TArray which may or may not be infallible can +// simply cast the TArray to |const nsTArray&|; both fallible and infallible +// TArrays can be cast to |const nsTArray&|. +// +template +class nsTArray_Impl + : public nsTArray_base::Type>, + public nsTArray_TypedBase> { + private: + friend class nsTArray; + + typedef nsTArrayFallibleAllocator FallibleAlloc; + typedef nsTArrayInfallibleAllocator InfallibleAlloc; + + public: + typedef typename nsTArray_RelocationStrategy::Type relocation_type; + typedef nsTArray_base base_type; + typedef typename base_type::size_type size_type; + typedef typename base_type::index_type index_type; + typedef E value_type; + typedef nsTArray_Impl self_type; + typedef nsTArrayElementTraits elem_traits; + typedef nsTArray_SafeElementAtHelper safeelementat_helper_type; + typedef mozilla::ArrayIterator iterator; + typedef mozilla::ArrayIterator const_iterator; + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; + + using base_type::EmptyHdr; + using safeelementat_helper_type::SafeElementAt; + + // A special value that is used to indicate an invalid or unknown index + // into the array. + static const index_type NoIndex = index_type(-1); + + using base_type::Length; + + // + // Finalization method + // + + ~nsTArray_Impl() { + if (!base_type::IsEmpty()) { + ClearAndRetainStorage(); + } + // mHdr cleanup will be handled by base destructor + } + + // + // Initialization methods + // + + nsTArray_Impl() = default; + + // Initialize this array and pre-allocate some number of elements. + explicit nsTArray_Impl(size_type aCapacity) { SetCapacity(aCapacity); } + + // Initialize this array with an r-value. + // Allow different types of allocators, since the allocator doesn't matter. + template + explicit nsTArray_Impl(nsTArray_Impl&& aOther) noexcept { + // We cannot be a (Copyable)AutoTArray because that overrides this ctor. + MOZ_ASSERT(!this->IsAutoArray()); + + // This does not use SwapArrayElements because that's unnecessarily complex. + this->MoveConstructNonAutoArray(aOther, sizeof(value_type), + MOZ_ALIGNOF(value_type)); + } + + // The array's copy-constructor performs a 'deep' copy of the given array. + // @param aOther The array object to copy. + // + // It's very important that we declare this method as taking |const + // self_type&| as opposed to taking |const nsTArray_Impl| for + // an arbitrary OtherAlloc. + // + // If we don't declare a constructor taking |const self_type&|, C++ generates + // a copy-constructor for this class which merely copies the object's + // members, which is obviously wrong. + // + // You can pass an nsTArray_Impl to this method because + // nsTArray_Impl can be cast to const nsTArray_Impl&. So the + // effect on the API is the same as if we'd declared this method as taking + // |const nsTArray_Impl&|. + nsTArray_Impl(const nsTArray_Impl&) = default; + + // Allow converting to a const array with a different kind of allocator, + // Since the allocator doesn't matter for const arrays + template + [[nodiscard]] operator const nsTArray_Impl&() const& { + return *reinterpret_cast*>(this); + } + // And we have to do this for our subclasses too + [[nodiscard]] operator const nsTArray&() const& { + return *reinterpret_cast*>(this); + } + [[nodiscard]] operator const FallibleTArray&() const& { + return *reinterpret_cast*>(this); + } + + // The array's assignment operator performs a 'deep' copy of the given + // array. It is optimized to reuse existing storage if possible. + // @param aOther The array object to copy. + nsTArray_Impl& operator=(const nsTArray_Impl&) = default; + + // The array's move assignment operator steals the underlying data from + // the other array. + // @param other The array object to move from. + self_type& operator=(self_type&& aOther) { + if (this != &aOther) { + Clear(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + return *this; + } + + // Return true if this array has the same length and the same + // elements as |aOther|. + template + [[nodiscard]] bool operator==( + const nsTArray_Impl& aOther) const { + size_type len = Length(); + if (len != aOther.Length()) { + return false; + } + + // XXX std::equal would be as fast or faster here + for (index_type i = 0; i < len; ++i) { + if (!(operator[](i) == aOther[i])) { + return false; + } + } + + return true; + } + + // Return true if this array does not have the same length and the same + // elements as |aOther|. + [[nodiscard]] bool operator!=(const self_type& aOther) const { + return !operator==(aOther); + } + + // If Alloc == FallibleAlloc, ReplaceElementsAt might fail, without a way to + // signal this to the caller, so we disallow copying via operator=. Callers + // should use ReplaceElementsAt with a fallible argument instead, and check + // the result. + template , + Allocator>> + self_type& operator=(const nsTArray_Impl& aOther) { + AssignInternal(aOther.Elements(), aOther.Length()); + return *this; + } + + template + self_type& operator=(nsTArray_Impl&& aOther) { + Clear(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + return *this; + } + + // @return The amount of memory used by this nsTArray_Impl, excluding + // sizeof(*this). If you want to measure anything hanging off the array, you + // must iterate over the elements and measure them individually; hence the + // "Shallow" prefix. + [[nodiscard]] size_t ShallowSizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + if (this->UsesAutoArrayBuffer() || this->HasEmptyHeader()) { + return 0; + } + return aMallocSizeOf(this->Hdr()); + } + + // @return The amount of memory used by this nsTArray_Impl, including + // sizeof(*this). If you want to measure anything hanging off the array, you + // must iterate over the elements and measure them individually; hence the + // "Shallow" prefix. + [[nodiscard]] size_t ShallowSizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + // + // Accessor methods + // + + // This method provides direct access to the array elements. + // @return A pointer to the first element of the array. If the array is + // empty, then this pointer must not be dereferenced. + [[nodiscard]] value_type* Elements() MOZ_NONNULL_RETURN { + return reinterpret_cast(Hdr() + 1); + } + + // This method provides direct, readonly access to the array elements. + // @return A pointer to the first element of the array. If the array is + // empty, then this pointer must not be dereferenced. + [[nodiscard]] const value_type* Elements() const MOZ_NONNULL_RETURN { + return reinterpret_cast(Hdr() + 1); + } + + // This method provides direct access to an element of the array. The given + // index must be within the array bounds. + // @param aIndex The index of an element in the array. + // @return A reference to the i'th element of the array. + [[nodiscard]] value_type& ElementAt(index_type aIndex) { + if (MOZ_UNLIKELY(aIndex >= Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + return Elements()[aIndex]; + } + + // This method provides direct, readonly access to an element of the array + // The given index must be within the array bounds. + // @param aIndex The index of an element in the array. + // @return A const reference to the i'th element of the array. + [[nodiscard]] const value_type& ElementAt(index_type aIndex) const { + if (MOZ_UNLIKELY(aIndex >= Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + return Elements()[aIndex]; + } + + // This method provides direct access to an element of the array in a bounds + // safe manner. If the requested index is out of bounds the provided default + // value is returned. + // @param aIndex The index of an element in the array. + // @param aDef The value to return if the index is out of bounds. + [[nodiscard]] value_type& SafeElementAt(index_type aIndex, value_type& aDef) { + return aIndex < Length() ? Elements()[aIndex] : aDef; + } + + // This method provides direct access to an element of the array in a bounds + // safe manner. If the requested index is out of bounds the provided default + // value is returned. + // @param aIndex The index of an element in the array. + // @param aDef The value to return if the index is out of bounds. + [[nodiscard]] const value_type& SafeElementAt(index_type aIndex, + const value_type& aDef) const { + return aIndex < Length() ? Elements()[aIndex] : aDef; + } + + // Shorthand for ElementAt(aIndex) + [[nodiscard]] value_type& operator[](index_type aIndex) { + return ElementAt(aIndex); + } + + // Shorthand for ElementAt(aIndex) + [[nodiscard]] const value_type& operator[](index_type aIndex) const { + return ElementAt(aIndex); + } + + // Shorthand for ElementAt(length - 1) + [[nodiscard]] value_type& LastElement() { return ElementAt(Length() - 1); } + + // Shorthand for ElementAt(length - 1) + [[nodiscard]] const value_type& LastElement() const { + return ElementAt(Length() - 1); + } + + // Shorthand for SafeElementAt(length - 1, def) + [[nodiscard]] value_type& SafeLastElement(value_type& aDef) { + return SafeElementAt(Length() - 1, aDef); + } + + // Shorthand for SafeElementAt(length - 1, def) + [[nodiscard]] const value_type& SafeLastElement( + const value_type& aDef) const { + return SafeElementAt(Length() - 1, aDef); + } + + // Methods for range-based for loops. + [[nodiscard]] iterator begin() { return iterator(*this, 0); } + [[nodiscard]] const_iterator begin() const { + return const_iterator(*this, 0); + } + [[nodiscard]] const_iterator cbegin() const { return begin(); } + [[nodiscard]] iterator end() { return iterator(*this, Length()); } + [[nodiscard]] const_iterator end() const { + return const_iterator(*this, Length()); + } + [[nodiscard]] const_iterator cend() const { return end(); } + + // Methods for reverse iterating. + [[nodiscard]] reverse_iterator rbegin() { return reverse_iterator(end()); } + [[nodiscard]] const_reverse_iterator rbegin() const { + return const_reverse_iterator(end()); + } + [[nodiscard]] const_reverse_iterator crbegin() const { return rbegin(); } + [[nodiscard]] reverse_iterator rend() { return reverse_iterator(begin()); } + [[nodiscard]] const_reverse_iterator rend() const { + return const_reverse_iterator(begin()); + } + [[nodiscard]] const_reverse_iterator crend() const { return rend(); } + + // Span integration + + [[nodiscard]] operator mozilla::Span() { + return mozilla::Span(Elements(), Length()); + } + + [[nodiscard]] operator mozilla::Span() const { + return mozilla::Span(Elements(), Length()); + } + + // + // Search methods + // + + // This method searches for the first element in this array that is equal + // to the given element. + // @param aItem The item to search for. + // @param aComp The Comparator used to determine element equality. + // @return true if the element was found. + template + [[nodiscard]] bool Contains(const Item& aItem, + const Comparator& aComp) const { + return ApplyIf( + aItem, 0, aComp, []() { return true; }, []() { return false; }); + } + + // Like Contains(), but assumes a sorted array. + template + [[nodiscard]] bool ContainsSorted(const Item& aItem, + const Comparator& aComp) const { + return BinaryIndexOf(aItem, aComp) != NoIndex; + } + + // This method searches for the first element in this array that is equal + // to the given element. This method assumes that 'operator==' is defined + // for value_type. + // @param aItem The item to search for. + // @return true if the element was found. + template + [[nodiscard]] bool Contains(const Item& aItem) const { + return Contains(aItem, nsDefaultComparator()); + } + + // Like Contains(), but assumes a sorted array. + template + [[nodiscard]] bool ContainsSorted(const Item& aItem) const { + return BinaryIndexOf(aItem) != NoIndex; + } + + // This method searches for the offset of the first element in this + // array that is equal to the given element. + // @param aItem The item to search for. + // @param aStart The index to start from. + // @param aComp The Comparator used to determine element equality. + // @return The index of the found element or NoIndex if not found. + template + [[nodiscard]] index_type IndexOf(const Item& aItem, index_type aStart, + const Comparator& aComp) const { + ::detail::CompareWrapper comp(aComp); + + const value_type* iter = Elements() + aStart; + const value_type* iend = Elements() + Length(); + for (; iter != iend; ++iter) { + if (comp.Equals(*iter, aItem)) { + return index_type(iter - Elements()); + } + } + return NoIndex; + } + + // This method searches for the offset of the first element in this + // array that is equal to the given element. This method assumes + // that 'operator==' is defined for value_type. + // @param aItem The item to search for. + // @param aStart The index to start from. + // @return The index of the found element or NoIndex if not found. + template + [[nodiscard]] index_type IndexOf(const Item& aItem, + index_type aStart = 0) const { + return IndexOf(aItem, aStart, nsDefaultComparator()); + } + + // This method searches for the offset of the last element in this + // array that is equal to the given element. + // @param aItem The item to search for. + // @param aStart The index to start from. If greater than or equal to the + // length of the array, then the entire array is searched. + // @param aComp The Comparator used to determine element equality. + // @return The index of the found element or NoIndex if not found. + template + [[nodiscard]] index_type LastIndexOf(const Item& aItem, index_type aStart, + const Comparator& aComp) const { + ::detail::CompareWrapper comp(aComp); + + size_type endOffset = aStart >= Length() ? Length() : aStart + 1; + const value_type* iend = Elements() - 1; + const value_type* iter = iend + endOffset; + for (; iter != iend; --iter) { + if (comp.Equals(*iter, aItem)) { + return index_type(iter - Elements()); + } + } + return NoIndex; + } + + // This method searches for the offset of the last element in this + // array that is equal to the given element. This method assumes + // that 'operator==' is defined for value_type. + // @param aItem The item to search for. + // @param aStart The index to start from. If greater than or equal to the + // length of the array, then the entire array is searched. + // @return The index of the found element or NoIndex if not found. + template + [[nodiscard]] index_type LastIndexOf(const Item& aItem, + index_type aStart = NoIndex) const { + return LastIndexOf(aItem, aStart, nsDefaultComparator()); + } + + // This method searches for the offset for the element in this array + // that is equal to the given element. The array is assumed to be sorted. + // If there is more than one equivalent element, there is no guarantee + // on which one will be returned. + // @param aItem The item to search for. + // @param aComp The Comparator used. + // @return The index of the found element or NoIndex if not found. + template + [[nodiscard]] index_type BinaryIndexOf(const Item& aItem, + const Comparator& aComp) const { + using mozilla::BinarySearchIf; + ::detail::CompareWrapper comp(aComp); + + size_t index; + bool found = BinarySearchIf( + Elements(), 0, Length(), + // Note: We pass the Compare() args here in reverse order and negate the + // results for compatibility reasons. Some existing callers use Equals() + // functions with first arguments which match aElement but not aItem, or + // second arguments that match aItem but not aElement. To accommodate + // those callers, we preserve the argument order of the older version of + // this API. These callers, however, should be fixed, and this special + // case removed. + [&](const value_type& aElement) { + return -comp.Compare(aElement, aItem); + }, + &index); + return found ? index : NoIndex; + } + + // This method searches for the offset for the element in this array + // that is equal to the given element. The array is assumed to be sorted. + // This method assumes that 'operator==' and 'operator<' are defined. + // @param aItem The item to search for. + // @return The index of the found element or NoIndex if not found. + template + [[nodiscard]] index_type BinaryIndexOf(const Item& aItem) const { + return BinaryIndexOf(aItem, nsDefaultComparator()); + } + + // + // Mutation methods + // + private: + template + typename ActualAlloc::ResultType AssignInternal(const Item* aArray, + size_type aArrayLen); + + public: + template + [[nodiscard]] typename ActualAlloc::ResultType Assign( + const nsTArray_Impl& aOther) { + return AssignInternal(aOther.Elements(), aOther.Length()); + } + + template + [[nodiscard]] bool Assign(const nsTArray_Impl& aOther, + const mozilla::fallible_t&) { + return Assign(aOther); + } + + template + void Assign(nsTArray_Impl&& aOther) { + Clear(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + // This method call the destructor on each element of the array, empties it, + // but does not shrink the array's capacity. + // See also SetLengthAndRetainStorage. + // Make sure to call Compact() if needed to avoid keeping a huge array + // around. + void ClearAndRetainStorage() { + if (this->HasEmptyHeader()) { + return; + } + + DestructRange(0, Length()); + base_type::mHdr->mLength = 0; + } + + // This method modifies the length of the array, but unlike SetLength + // it doesn't deallocate/reallocate the current internal storage. + // The new length MUST be shorter than or equal to the current capacity. + // If the new length is larger than the existing length of the array, + // then new elements will be constructed using value_type's default + // constructor. If shorter, elements will be destructed and removed. + // See also ClearAndRetainStorage. + // @param aNewLen The desired length of this array. + void SetLengthAndRetainStorage(size_type aNewLen) { + MOZ_ASSERT(aNewLen <= base_type::Capacity()); + size_type oldLen = Length(); + if (aNewLen > oldLen) { + /// XXX(Bug 1631367) SetLengthAndRetainStorage should be disabled for + /// FallibleTArray. + InsertElementsAtInternal(oldLen, aNewLen - oldLen); + return; + } + if (aNewLen < oldLen) { + DestructRange(aNewLen, oldLen - aNewLen); + base_type::mHdr->mLength = aNewLen; + } + } + + // This method replaces a range of elements in this array. + // @param aStart The starting index of the elements to replace. + // @param aCount The number of elements to replace. This may be zero to + // insert elements without removing any existing elements. + // @param aArray The values to copy into this array. Must be non-null, + // and these elements must not already exist in the array + // being modified. + // @param aArrayLen The number of values to copy into this array. + // @return A pointer to the new elements in the array, or null if + // the operation failed due to insufficient memory. + private: + template + value_type* ReplaceElementsAtInternal(index_type aStart, size_type aCount, + const Item* aArray, + size_type aArrayLen); + + public: + template + [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, + size_type aCount, + const Item* aArray, + size_type aArrayLen, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal(aStart, aCount, aArray, + aArrayLen); + } + + // A variation on the ReplaceElementsAt method defined above. + template + [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, + size_type aCount, + const nsTArray& aArray, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal(aStart, aCount, aArray); + } + + template + [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, + size_type aCount, + mozilla::Span aSpan, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal(aStart, aCount, aSpan); + } + + // A variation on the ReplaceElementsAt method defined above. + template + [[nodiscard]] value_type* ReplaceElementsAt(index_type aStart, + size_type aCount, + const Item& aItem, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal(aStart, aCount, aItem); + } + + // A variation on the ReplaceElementsAt method defined above. + template + mozilla::NotNull ReplaceElementAt(index_type aIndex, + Item&& aItem) { + value_type* const elem = &ElementAt(aIndex); + elem_traits::Destruct(elem); + elem_traits::Construct(elem, std::forward(aItem)); + return mozilla::WrapNotNullUnchecked(elem); + } + + // InsertElementsAt is ReplaceElementsAt with 0 elements to replace. + // XXX Provide a proper documentation of InsertElementsAt. + template + [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, + const Item* aArray, + size_type aArrayLen, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal(aIndex, 0, aArray, + aArrayLen); + } + + template + [[nodiscard]] value_type* InsertElementsAt( + index_type aIndex, const nsTArray_Impl& aArray, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal( + aIndex, 0, aArray.Elements(), aArray.Length()); + } + + template + [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, + mozilla::Span aSpan, + const mozilla::fallible_t&) { + return ReplaceElementsAtInternal(aIndex, 0, aSpan.Elements(), + aSpan.Length()); + } + + private: + template + value_type* InsertElementAtInternal(index_type aIndex); + + // Insert a new element without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly inserted element, or null on OOM. + public: + [[nodiscard]] value_type* InsertElementAt(index_type aIndex, + const mozilla::fallible_t&) { + return InsertElementAtInternal(aIndex); + } + + private: + template + value_type* InsertElementAtInternal(index_type aIndex, Item&& aItem); + + // Insert a new element, move constructing if possible. + public: + template + [[nodiscard]] value_type* InsertElementAt(index_type aIndex, Item&& aItem, + const mozilla::fallible_t&) { + return InsertElementAtInternal(aIndex, + std::forward(aItem)); + } + + // Reconstruct the element at the given index, and return a pointer to the + // reconstructed element. This will destroy the existing element and + // default-construct a new one, giving you a state much like what single-arg + // InsertElementAt(), or no-arg AppendElement() does, but without changing the + // length of the array. + // + // array[idx] = value_type() + // + // would accomplish the same thing as long as value_type has the appropriate + // moving operator=, but some types don't for various reasons. + mozilla::NotNull ReconstructElementAt(index_type aIndex) { + value_type* elem = &ElementAt(aIndex); + elem_traits::Destruct(elem); + elem_traits::Construct(elem); + return mozilla::WrapNotNullUnchecked(elem); + } + + // This method searches for the smallest index of an element that is strictly + // greater than |aItem|. If |aItem| is inserted at this index, the array will + // remain sorted and |aItem| would come after all elements that are equal to + // it. If |aItem| is greater than or equal to all elements in the array, the + // array length is returned. + // + // Note that consumers who want to know whether there are existing items equal + // to |aItem| in the array can just check that the return value here is > 0 + // and indexing into the previous slot gives something equal to |aItem|. + // + // + // @param aItem The item to search for. + // @param aComp The Comparator used. + // @return The index of greatest element <= to |aItem| + // @precondition The array is sorted + template + [[nodiscard]] index_type IndexOfFirstElementGt( + const Item& aItem, const Comparator& aComp) const { + using mozilla::BinarySearchIf; + ::detail::CompareWrapper comp(aComp); + + size_t index; + BinarySearchIf( + Elements(), 0, Length(), + [&](const value_type& aElement) { + return comp.Compare(aElement, aItem) <= 0 ? 1 : -1; + }, + &index); + return index; + } + + // A variation on the IndexOfFirstElementGt method defined above. + template + [[nodiscard]] index_type IndexOfFirstElementGt(const Item& aItem) const { + return IndexOfFirstElementGt(aItem, + nsDefaultComparator()); + } + + private: + template + value_type* InsertElementSortedInternal(Item&& aItem, + const Comparator& aComp) { + index_type index = IndexOfFirstElementGt(aItem, aComp); + return InsertElementAtInternal(index, + std::forward(aItem)); + } + + // Inserts |aItem| at such an index to guarantee that if the array + // was previously sorted, it will remain sorted after this + // insertion. + public: + template + [[nodiscard]] value_type* InsertElementSorted(Item&& aItem, + const Comparator& aComp, + const mozilla::fallible_t&) { + return InsertElementSortedInternal(std::forward(aItem), + aComp); + } + + // A variation on the InsertElementSorted method defined above. + public: + template + [[nodiscard]] value_type* InsertElementSorted(Item&& aItem, + const mozilla::fallible_t&) { + return InsertElementSortedInternal( + std::forward(aItem), nsDefaultComparator{}); + } + + private: + template + value_type* AppendElementsInternal(const Item* aArray, size_type aArrayLen); + + // This method appends elements to the end of this array. + // @param aArray The elements to append to this array. + // @param aArrayLen The number of elements to append to this array. + // @return A pointer to the new elements in the array, or null if + // the operation failed due to insufficient memory. + public: + template + [[nodiscard]] value_type* AppendElements(const Item* aArray, + size_type aArrayLen, + const mozilla::fallible_t&) { + return AppendElementsInternal(aArray, aArrayLen); + } + + template + [[nodiscard]] value_type* AppendElements(mozilla::Span aSpan, + const mozilla::fallible_t&) { + return AppendElementsInternal(aSpan.Elements(), + aSpan.Length()); + } + + // A variation on the AppendElements method defined above. + template + [[nodiscard]] value_type* AppendElements( + const nsTArray_Impl& aArray, + const mozilla::fallible_t&) { + return AppendElementsInternal(aArray.Elements(), + aArray.Length()); + } + + private: + template + value_type* AppendElementsInternal(nsTArray_Impl&& aArray); + + // Move all elements from another array to the end of this array. + // @return A pointer to the newly appended elements, or null on OOM. + public: + template + [[nodiscard]] value_type* AppendElements( + nsTArray_Impl&& aArray, const mozilla::fallible_t&) { + return AppendElementsInternal(std::move(aArray)); + } + + // Append a new element, constructed in place from the provided arguments. + protected: + template + value_type* EmplaceBackInternal(Args&&... aItem); + + public: + template + [[nodiscard]] value_type* EmplaceBack(const mozilla::fallible_t&, + Args&&... aArgs) { + return EmplaceBackInternal( + std::forward(aArgs)...); + } + + private: + template + value_type* AppendElementInternal(Item&& aItem); + + // Append a new element, move constructing if possible. + public: + template + [[nodiscard]] value_type* AppendElement(Item&& aItem, + const mozilla::fallible_t&) { + return AppendElementInternal(std::forward(aItem)); + } + + private: + template + value_type* AppendElementsInternal(size_type aCount) { + if (!ActualAlloc::Successful(this->template ExtendCapacity( + Length(), aCount, sizeof(value_type)))) { + return nullptr; + } + value_type* elems = Elements() + Length(); + size_type i; + for (i = 0; i < aCount; ++i) { + elem_traits::Construct(elems + i); + } + this->IncrementLength(aCount); + return elems; + } + + // Append new elements without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly appended elements, or null on OOM. + public: + [[nodiscard]] value_type* AppendElements(size_type aCount, + const mozilla::fallible_t&) { + return AppendElementsInternal(aCount); + } + + private: + // Append a new element without copy-constructing. This is useful to avoid + // temporaries. + // @return A pointer to the newly appended element, or null on OOM. + public: + [[nodiscard]] value_type* AppendElement(const mozilla::fallible_t&) { + return AppendElements(1, mozilla::fallible); + } + + // This method removes a single element from this array, like + // std::vector::erase. + // @param pos to the element to remove + const_iterator RemoveElementAt(const_iterator pos) { + MOZ_ASSERT(pos.GetArray() == this); + + RemoveElementAt(pos.GetIndex()); + return pos; + } + + // This method removes a range of elements from this array, like + // std::vector::erase. + // @param first iterator to the first of elements to remove + // @param last iterator to the last of elements to remove + const_iterator RemoveElementsRange(const_iterator first, + const_iterator last) { + MOZ_ASSERT(first.GetArray() == this); + MOZ_ASSERT(last.GetArray() == this); + MOZ_ASSERT(last.GetIndex() >= first.GetIndex()); + + RemoveElementsAt(first.GetIndex(), last.GetIndex() - first.GetIndex()); + return first; + } + + // This method removes a range of elements from this array. + // @param aStart The starting index of the elements to remove. + // @param aCount The number of elements to remove. + void RemoveElementsAt(index_type aStart, size_type aCount); + + private: + // Remove a range of elements from this array, but do not check that + // the range is in bounds. + // @param aStart The starting index of the elements to remove. + // @param aCount The number of elements to remove. + void RemoveElementsAtUnsafe(index_type aStart, size_type aCount); + + public: + // A variation on the RemoveElementsAt method defined above. + void RemoveElementAt(index_type aIndex) { RemoveElementsAt(aIndex, 1); } + + // A variation on RemoveElementAt that removes the last element. + void RemoveLastElement() { RemoveLastElements(1); } + + // A variation on RemoveElementsAt that removes the last 'aCount' elements. + void RemoveLastElements(const size_type aCount) { + // This assertion is redundant, but produces a better error message than the + // release assertion within TruncateLength. + MOZ_ASSERT(aCount <= Length()); + TruncateLength(Length() - aCount); + } + + // Removes the last element of the array and returns a copy of it. + [[nodiscard]] value_type PopLastElement() { + // This function intentionally does not call ElementsAt and calls + // TruncateLengthUnsafe directly to avoid multiple release checks for + // non-emptiness. + // This debug assertion is redundant, but produces a better error message + // than the release assertion below. + MOZ_ASSERT(!base_type::IsEmpty()); + const size_type oldLen = Length(); + if (MOZ_UNLIKELY(0 == oldLen)) { + mozilla::detail::InvalidArrayIndex_CRASH(1, 0); + } + value_type elem = std::move(Elements()[oldLen - 1]); + TruncateLengthUnsafe(oldLen - 1); + return elem; + } + + // This method performs index-based removals from an array without preserving + // the order of the array. This is useful if you are using the array as a + // set-like data structure. + // + // These removals are efficient, as they move as few elements as possible. At + // most N elements, where N is the number of removed elements, will have to + // be relocated. + // + // ## Examples + // + // When removing an element from the end of the array, it can be removed in + // place, by destroying it and decrementing the length. + // + // [ 1, 2, 3 ] => [ 1, 2 ] + // ^ + // + // When removing any other single element, it is removed by swapping it with + // the last element, and then decrementing the length as before. + // + // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 6, 3, 4, 5 ] + // ^ + // + // This method also supports efficiently removing a range of elements. If they + // are at the end, then they can all be removed like in the one element case. + // + // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ] + // ^--------^ + // + // If more elements are removed than exist after the removed section, the + // remaining elements will be shifted down like in a normal removal. + // + // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ] + // ^--------^ + // + // And if fewer elements are removed than exist after the removed section, + // elements will be moved from the end of the array to fill the vacated space. + // + // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ] + // ^--^ + // + // @param aStart The starting index of the elements to remove. @param aCount + // The number of elements to remove. + void UnorderedRemoveElementsAt(index_type aStart, size_type aCount); + + // A variation on the UnorderedRemoveElementsAt method defined above to remove + // a single element. This operation is sometimes called `SwapRemove`. + // + // This method is O(1), but does not preserve the order of the elements. + void UnorderedRemoveElementAt(index_type aIndex) { + UnorderedRemoveElementsAt(aIndex, 1); + } + + void Clear() { + ClearAndRetainStorage(); + base_type::ShrinkCapacityToZero(sizeof(value_type), + MOZ_ALIGNOF(value_type)); + } + + // This method removes elements based on the return value of the + // callback function aPredicate. If the function returns true for + // an element, the element is removed. aPredicate will be called + // for each element in order. It is not safe to access the array + // inside aPredicate. + // + // Returns the number of elements removed. + template + size_type RemoveElementsBy(Predicate aPredicate); + + // This helper function combines IndexOf with RemoveElementAt to "search + // and destroy" the first element that is equal to the given element. + // @param aItem The item to search for. + // @param aComp The Comparator used to determine element equality. + // @return true if the element was found + template + bool RemoveElement(const Item& aItem, const Comparator& aComp) { + index_type i = IndexOf(aItem, 0, aComp); + if (i == NoIndex) { + return false; + } + + RemoveElementsAtUnsafe(i, 1); + return true; + } + + // A variation on the RemoveElement method defined above that assumes + // that 'operator==' is defined for value_type. + template + bool RemoveElement(const Item& aItem) { + return RemoveElement(aItem, nsDefaultComparator()); + } + + // This helper function combines IndexOfFirstElementGt with + // RemoveElementAt to "search and destroy" the last element that + // is equal to the given element. + // @param aItem The item to search for. + // @param aComp The Comparator used to determine element equality. + // @return true if the element was found + template + bool RemoveElementSorted(const Item& aItem, const Comparator& aComp) { + index_type index = IndexOfFirstElementGt(aItem, aComp); + if (index > 0 && aComp.Equals(ElementAt(index - 1), aItem)) { + RemoveElementsAtUnsafe(index - 1, 1); + return true; + } + return false; + } + + // A variation on the RemoveElementSorted method defined above. + template + bool RemoveElementSorted(const Item& aItem) { + return RemoveElementSorted(aItem, nsDefaultComparator()); + } + + // This method causes the elements contained in this array and the given + // array to be swapped. + template + void SwapElements(nsTArray_Impl& aOther) { + // The only case this might fail were if someone called this with a + // AutoTArray upcast to nsTArray_Impl, under the conditions mentioned in the + // overload for AutoTArray below. + this->template SwapArrayElements( + aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + template + void SwapElements(AutoTArray& aOther) { + // Allocation might fail if Alloc==FallibleAlloc and + // Allocator==InfallibleAlloc and aOther uses auto storage. Allow this for + // small inline sizes, and crash in the rare case of a small OOM error. + static_assert(!std::is_same_v || + sizeof(E) * N <= 1024); + this->template SwapArrayElements( + aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + template + [[nodiscard]] auto SwapElements(nsTArray_Impl& aOther, + const mozilla::fallible_t&) { + // Allocation might fail if Alloc==FallibleAlloc and + // Allocator==InfallibleAlloc and aOther uses auto storage. + return FallibleAlloc::Result( + this->template SwapArrayElements( + aOther, sizeof(value_type), MOZ_ALIGNOF(value_type))); + } + + private: + // Used by ApplyIf functions to invoke a callable that takes either: + // - Nothing: F(void) + // - Index only: F(size_t) + // - Reference to element only: F(maybe-const value_type&) + // - Both index and reference: F(size_t, maybe-const value_type&) + // `value_type` must be const when called from const method. + template + struct InvokeWithIndexAndOrReferenceHelper { + static constexpr bool valid = false; + }; + template + struct InvokeWithIndexAndOrReferenceHelper { + static constexpr bool valid = true; + template + static auto Invoke(F&& f, size_t, T&) { + return f(); + } + }; + template + struct InvokeWithIndexAndOrReferenceHelper { + static constexpr bool valid = true; + template + static auto Invoke(F&& f, size_t i, T&) { + return f(i); + } + }; + template + struct InvokeWithIndexAndOrReferenceHelper { + static constexpr bool valid = true; + template + static auto Invoke(F&& f, size_t, T& e) { + return f(e); + } + }; + template + struct InvokeWithIndexAndOrReferenceHelper { + static constexpr bool valid = true; + template + static auto Invoke(F&& f, size_t, T& e) { + return f(e); + } + }; + template + struct InvokeWithIndexAndOrReferenceHelper { + static constexpr bool valid = true; + template + static auto Invoke(F&& f, size_t i, T& e) { + return f(i, e); + } + }; + template + struct InvokeWithIndexAndOrReferenceHelper { + static constexpr bool valid = true; + template + static auto Invoke(F&& f, size_t i, T& e) { + return f(i, e); + } + }; + template + static auto InvokeWithIndexAndOrReference(F&& f, size_t i, T& e) { + using Invoker = InvokeWithIndexAndOrReferenceHelper< + T, typename mozilla::FunctionTypeTraits::template ParameterType<0>, + typename mozilla::FunctionTypeTraits::template ParameterType<1>>; + static_assert(Invoker::valid, + "ApplyIf's Function parameters must match either: (void), " + "(size_t), (maybe-const value_type&), or " + "(size_t, maybe-const value_type&)"); + return Invoker::Invoke(std::forward(f), i, e); + } + + public: + // 'Apply' family of methods. + // + // The advantages of using Apply methods with lambdas include: + // - Safety of accessing elements from within the call, when the array cannot + // have been modified between the iteration and the subsequent access. + // - Avoiding moot conversions: pointer->index during a search, followed by + // index->pointer after the search when accessing the element. + // - Embedding your code into the algorithm, giving the compiler more chances + // to optimize. + + // Search for the first element comparing equal to aItem with the given + // comparator (`==` by default). + // If such an element exists, return the result of evaluating either: + // - `aFunction()` + // - `aFunction(index_type)` + // - `aFunction(maybe-const? value_type&)` + // - `aFunction(index_type, maybe-const? value_type&)` + // (`aFunction` must have one of the above signatures with these exact types, + // including references; implicit conversions or generic types not allowed. + // If `this` array is const, the referenced `value_type` must be const too; + // otherwise it may be either const or non-const.) + // But if the element is not found, return the result of evaluating + // `aFunctionElse()`. + template + auto ApplyIf(const Item& aItem, index_type aStart, const Comparator& aComp, + Function&& aFunction, FunctionElse&& aFunctionElse) const { + static_assert( + std::is_same_v< + typename mozilla::FunctionTypeTraits::ReturnType, + typename mozilla::FunctionTypeTraits::ReturnType>, + "ApplyIf's `Function` and `FunctionElse` must return the same type."); + + ::detail::CompareWrapper comp(aComp); + + const value_type* const elements = Elements(); + const value_type* const iend = elements + Length(); + for (const value_type* iter = elements + aStart; iter != iend; ++iter) { + if (comp.Equals(*iter, aItem)) { + return InvokeWithIndexAndOrReference( + std::forward(aFunction), iter - elements, *iter); + } + } + return aFunctionElse(); + } + template + auto ApplyIf(const Item& aItem, index_type aStart, const Comparator& aComp, + Function&& aFunction, FunctionElse&& aFunctionElse) { + static_assert( + std::is_same_v< + typename mozilla::FunctionTypeTraits::ReturnType, + typename mozilla::FunctionTypeTraits::ReturnType>, + "ApplyIf's `Function` and `FunctionElse` must return the same type."); + + ::detail::CompareWrapper comp(aComp); + + value_type* const elements = Elements(); + value_type* const iend = elements + Length(); + for (value_type* iter = elements + aStart; iter != iend; ++iter) { + if (comp.Equals(*iter, aItem)) { + return InvokeWithIndexAndOrReference( + std::forward(aFunction), iter - elements, *iter); + } + } + return aFunctionElse(); + } + template + auto ApplyIf(const Item& aItem, index_type aStart, Function&& aFunction, + FunctionElse&& aFunctionElse) const { + return ApplyIf(aItem, aStart, nsDefaultComparator(), + std::forward(aFunction), + std::forward(aFunctionElse)); + } + template + auto ApplyIf(const Item& aItem, index_type aStart, Function&& aFunction, + FunctionElse&& aFunctionElse) { + return ApplyIf(aItem, aStart, nsDefaultComparator(), + std::forward(aFunction), + std::forward(aFunctionElse)); + } + template + auto ApplyIf(const Item& aItem, Function&& aFunction, + FunctionElse&& aFunctionElse) const { + return ApplyIf(aItem, 0, std::forward(aFunction), + std::forward(aFunctionElse)); + } + template + auto ApplyIf(const Item& aItem, Function&& aFunction, + FunctionElse&& aFunctionElse) { + return ApplyIf(aItem, 0, std::forward(aFunction), + std::forward(aFunctionElse)); + } + + // + // Allocation + // + + // This method may increase the capacity of this array object to the + // specified amount. This method may be called in advance of several + // AppendElement operations to minimize heap re-allocations. This method + // will not reduce the number of elements in this array. + // @param aCapacity The desired capacity of this array. + // @return True if the operation succeeded; false if we ran out of memory + protected: + template + typename ActualAlloc::ResultType SetCapacity(size_type aCapacity) { + return ActualAlloc::Result(this->template EnsureCapacity( + aCapacity, sizeof(value_type))); + } + + public: + [[nodiscard]] bool SetCapacity(size_type aCapacity, + const mozilla::fallible_t&) { + return SetCapacity(aCapacity); + } + + // This method modifies the length of the array. If the new length is + // larger than the existing length of the array, then new elements will be + // constructed using value_type's default constructor. Otherwise, this call + // removes elements from the array (see also RemoveElementsAt). + // @param aNewLen The desired length of this array. + // @return True if the operation succeeded; false otherwise. + // See also TruncateLength for a more efficient variant if the new length is + // guaranteed to be smaller than the old. + protected: + template + typename ActualAlloc::ResultType SetLength(size_type aNewLen) { + const size_type oldLen = Length(); + if (aNewLen > oldLen) { + return ActualAlloc::ConvertBoolToResultType( + InsertElementsAtInternal(oldLen, aNewLen - oldLen) != + nullptr); + } + + TruncateLengthUnsafe(aNewLen); + return ActualAlloc::ConvertBoolToResultType(true); + } + + public: + [[nodiscard]] bool SetLength(size_type aNewLen, const mozilla::fallible_t&) { + return SetLength(aNewLen); + } + + // This method modifies the length of the array, but may only be + // called when the new length is shorter than the old. It can + // therefore be called when value_type has no default constructor, + // unlike SetLength. It removes elements from the array (see also + // RemoveElementsAt). + // @param aNewLen The desired length of this array. + void TruncateLength(size_type aNewLen) { + // This assertion is redundant, but produces a better error message than the + // release assertion below. + MOZ_ASSERT(aNewLen <= Length(), "caller should use SetLength instead"); + + if (MOZ_UNLIKELY(aNewLen > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aNewLen, Length()); + } + + TruncateLengthUnsafe(aNewLen); + } + + private: + void TruncateLengthUnsafe(size_type aNewLen) { + const size_type oldLen = Length(); + if (oldLen) { + DestructRange(aNewLen, oldLen - aNewLen); + base_type::mHdr->mLength = aNewLen; + } + } + + // This method ensures that the array has length at least the given + // length. If the current length is shorter than the given length, + // then new elements will be constructed using value_type's default + // constructor. + // @param aMinLen The desired minimum length of this array. + // @return True if the operation succeeded; false otherwise. + protected: + template + typename ActualAlloc::ResultType EnsureLengthAtLeast(size_type aMinLen) { + size_type oldLen = Length(); + if (aMinLen > oldLen) { + return ActualAlloc::ConvertBoolToResultType( + !!InsertElementsAtInternal(oldLen, aMinLen - oldLen)); + } + return ActualAlloc::ConvertBoolToResultType(true); + } + + public: + [[nodiscard]] bool EnsureLengthAtLeast(size_type aMinLen, + const mozilla::fallible_t&) { + return EnsureLengthAtLeast(aMinLen); + } + + // This method inserts elements into the array, constructing + // them using value_type's default constructor. + // @param aIndex the place to insert the new elements. This must be no + // greater than the current length of the array. + // @param aCount the number of elements to insert + private: + template + value_type* InsertElementsAtInternal(index_type aIndex, size_type aCount) { + if (!ActualAlloc::Successful(this->template InsertSlotsAt( + aIndex, aCount, sizeof(value_type), MOZ_ALIGNOF(value_type)))) { + return nullptr; + } + + // Initialize the extra array elements + value_type* iter = Elements() + aIndex; + value_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Construct(iter); + } + + return Elements() + aIndex; + } + + public: + [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, + size_type aCount, + const mozilla::fallible_t&) { + return InsertElementsAtInternal(aIndex, aCount); + } + + // This method inserts elements into the array, constructing them + // value_type's copy constructor (or whatever one-arg constructor + // happens to match the Item type). + // @param aIndex the place to insert the new elements. This must be no + // greater than the current length of the array. + // @param aCount the number of elements to insert. + // @param aItem the value to use when constructing the new elements. + private: + template + value_type* InsertElementsAtInternal(index_type aIndex, size_type aCount, + const Item& aItem); + + public: + template + [[nodiscard]] value_type* InsertElementsAt(index_type aIndex, + size_type aCount, + const Item& aItem, + const mozilla::fallible_t&) { + return InsertElementsAt(aIndex, aCount, aItem); + } + + // This method may be called to minimize the memory used by this array. + void Compact() { + ShrinkCapacity(sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + // + // Sorting + // + + // This function is meant to be used with the NS_QuickSort function. It + // maps the callback API expected by NS_QuickSort to the Comparator API + // used by nsTArray_Impl. See nsTArray_Impl::Sort. + template + static int Compare(const void* aE1, const void* aE2, void* aData) { + const Comparator* c = reinterpret_cast(aData); + const value_type* a = static_cast(aE1); + const value_type* b = static_cast(aE2); + return c->Compare(*a, *b); + } + + // This method sorts the elements of the array. It uses the LessThan + // method defined on the given Comparator object to collate elements. + // @param aComp The Comparator used to collate elements. + template + void Sort(const Comparator& aComp) { + ::detail::CompareWrapper comp(aComp); + + NS_QuickSort(Elements(), Length(), sizeof(value_type), + Compare, &comp); + } + + // A variation on the Sort method defined above that assumes that + // 'operator<' is defined for value_type. + void Sort() { Sort(nsDefaultComparator()); } + + // This method sorts the elements of the array in a stable way (i.e. not + // changing the relative order of elements considered equal by the + // Comparator). It uses the LessThan + // method defined on the given Comparator object to collate elements. + // @param aComp The Comparator used to collate elements. + template + void StableSort(const Comparator& aComp) { + const ::detail::CompareWrapper comp(aComp); + + std::stable_sort(Elements(), Elements() + Length(), + [&comp](const auto& lhs, const auto& rhs) { + return comp.LessThan(lhs, rhs); + }); + } + + // This method reverses the array in place. + void Reverse() { + value_type* elements = Elements(); + const size_type len = Length(); + for (index_type i = 0, iend = len / 2; i < iend; ++i) { + std::swap(elements[i], elements[len - i - 1]); + } + } + + protected: + using base_type::Hdr; + using base_type::ShrinkCapacity; + + // This method invokes value_type's destructor on a range of elements. + // @param aStart The index of the first element to destroy. + // @param aCount The number of elements to destroy. + void DestructRange(index_type aStart, size_type aCount) { + value_type* iter = Elements() + aStart; + value_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Destruct(iter); + } + } + + // This method invokes value_type's copy-constructor on a range of elements. + // @param aStart The index of the first element to construct. + // @param aCount The number of elements to construct. + // @param aValues The array of elements to copy. + template + void AssignRange(index_type aStart, size_type aCount, const Item* aValues) { + AssignRangeAlgorithm< + std::is_trivially_copy_constructible_v, + std::is_same_v>::implementation(Elements(), aStart, + aCount, aValues); + } +}; + +template +template +auto nsTArray_Impl::AssignInternal(const Item* aArray, + size_type aArrayLen) -> + typename ActualAlloc::ResultType { + static_assert(std::is_same_v || + std::is_same_v); + + if constexpr (std::is_same_v) { + ClearAndRetainStorage(); + } + // Adjust memory allocation up-front to catch errors in the fallible case. + // We might relocate the elements to be destroyed unnecessarily. This could be + // optimized, but would make things more complicated. + if (!ActualAlloc::Successful(this->template EnsureCapacity( + aArrayLen, sizeof(value_type)))) { + return ActualAlloc::ConvertBoolToResultType(false); + } + + MOZ_ASSERT_IF(this->HasEmptyHeader(), aArrayLen == 0); + if (!this->HasEmptyHeader()) { + if constexpr (std::is_same_v) { + ClearAndRetainStorage(); + } + AssignRange(0, aArrayLen, aArray); + base_type::mHdr->mLength = aArrayLen; + } + + return ActualAlloc::ConvertBoolToResultType(true); +} + +template +template +auto nsTArray_Impl::ReplaceElementsAtInternal(index_type aStart, + size_type aCount, + const Item* aArray, + size_type aArrayLen) + -> value_type* { + if (MOZ_UNLIKELY(aStart > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length()); + } + if (MOZ_UNLIKELY(aCount > Length() - aStart)) { + mozilla::detail::InvalidArrayIndex_CRASH(aStart + aCount, Length()); + } + + // Adjust memory allocation up-front to catch errors. + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + aArrayLen - aCount, sizeof(value_type)))) { + return nullptr; + } + DestructRange(aStart, aCount); + this->template ShiftData( + aStart, aCount, aArrayLen, sizeof(value_type), MOZ_ALIGNOF(value_type)); + AssignRange(aStart, aArrayLen, aArray); + return Elements() + aStart; +} + +template +void nsTArray_Impl::RemoveElementsAt(index_type aStart, + size_type aCount) { + MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index"); + + mozilla::CheckedInt rangeEnd = aStart; + rangeEnd += aCount; + + if (MOZ_UNLIKELY(!rangeEnd.isValid() || rangeEnd.value() > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length()); + } + + RemoveElementsAtUnsafe(aStart, aCount); +} + +template +void nsTArray_Impl::RemoveElementsAtUnsafe(index_type aStart, + size_type aCount) { + DestructRange(aStart, aCount); + this->template ShiftData( + aStart, aCount, 0, sizeof(value_type), MOZ_ALIGNOF(value_type)); +} + +template +void nsTArray_Impl::UnorderedRemoveElementsAt(index_type aStart, + size_type aCount) { + MOZ_ASSERT(aCount == 0 || aStart < Length(), "Invalid aStart index"); + + mozilla::CheckedInt rangeEnd = aStart; + rangeEnd += aCount; + + if (MOZ_UNLIKELY(!rangeEnd.isValid() || rangeEnd.value() > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aStart, Length()); + } + + // Destroy the elements which are being removed, and then swap elements in to + // replace them from the end. See the docs on the declaration of this + // function. + DestructRange(aStart, aCount); + this->template SwapFromEnd( + aStart, aCount, sizeof(value_type), MOZ_ALIGNOF(value_type)); +} + +template +template +auto nsTArray_Impl::RemoveElementsBy(Predicate aPredicate) + -> size_type { + if (this->HasEmptyHeader()) { + return 0; + } + + index_type j = 0; + const index_type len = Length(); + value_type* const elements = Elements(); + for (index_type i = 0; i < len; ++i) { + const bool result = aPredicate(elements[i]); + + // Check that the array has not been modified by the predicate. + MOZ_DIAGNOSTIC_ASSERT(len == base_type::mHdr->mLength && + elements == Elements()); + + if (result) { + elem_traits::Destruct(elements + i); + } else { + if (j < i) { + relocation_type::RelocateNonOverlappingRegion( + elements + j, elements + i, 1, sizeof(value_type)); + } + ++j; + } + } + + base_type::mHdr->mLength = j; + return len - j; +} + +template +template +auto nsTArray_Impl::InsertElementsAtInternal(index_type aIndex, + size_type aCount, + const Item& aItem) + -> value_type* { + if (!ActualAlloc::Successful(this->template InsertSlotsAt( + aIndex, aCount, sizeof(value_type), MOZ_ALIGNOF(value_type)))) { + return nullptr; + } + + // Initialize the extra array elements + value_type* iter = Elements() + aIndex; + value_type* iend = iter + aCount; + for (; iter != iend; ++iter) { + elem_traits::Construct(iter, aItem); + } + + return Elements() + aIndex; +} + +template +template +auto nsTArray_Impl::InsertElementAtInternal(index_type aIndex) + -> value_type* { + if (MOZ_UNLIKELY(aIndex > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + + // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + 1, sizeof(value_type)))) { + return nullptr; + } + this->template ShiftData(aIndex, 0, 1, sizeof(value_type), + MOZ_ALIGNOF(value_type)); + value_type* elem = Elements() + aIndex; + elem_traits::Construct(elem); + return elem; +} + +template +template +auto nsTArray_Impl::InsertElementAtInternal(index_type aIndex, + Item&& aItem) + -> value_type* { + if (MOZ_UNLIKELY(aIndex > Length())) { + mozilla::detail::InvalidArrayIndex_CRASH(aIndex, Length()); + } + + // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + 1, sizeof(value_type)))) { + return nullptr; + } + this->template ShiftData(aIndex, 0, 1, sizeof(value_type), + MOZ_ALIGNOF(value_type)); + value_type* elem = Elements() + aIndex; + elem_traits::Construct(elem, std::forward(aItem)); + return elem; +} + +template +template +auto nsTArray_Impl::AppendElementsInternal(const Item* aArray, + size_type aArrayLen) + -> value_type* { + if (!ActualAlloc::Successful(this->template ExtendCapacity( + Length(), aArrayLen, sizeof(value_type)))) { + return nullptr; + } + index_type len = Length(); + AssignRange(len, aArrayLen, aArray); + this->IncrementLength(aArrayLen); + return Elements() + len; +} + +template +template +auto nsTArray_Impl::AppendElementsInternal( + nsTArray_Impl&& aArray) -> value_type* { + if constexpr (std::is_same_v) { + MOZ_ASSERT(&aArray != this, "argument must be different aArray"); + } + if (Length() == 0) { + // XXX This might still be optimized. If aArray uses auto-storage but we + // won't, we might better retain our storage if it's sufficiently large. + this->ShrinkCapacityToZero(sizeof(value_type), MOZ_ALIGNOF(value_type)); + this->MoveInit(aArray, sizeof(value_type), MOZ_ALIGNOF(value_type)); + return Elements(); + } + + index_type len = Length(); + index_type otherLen = aArray.Length(); + if (!ActualAlloc::Successful(this->template ExtendCapacity( + len, otherLen, sizeof(value_type)))) { + return nullptr; + } + relocation_type::RelocateNonOverlappingRegion( + Elements() + len, aArray.Elements(), otherLen, sizeof(value_type)); + this->IncrementLength(otherLen); + aArray.template ShiftData(0, otherLen, 0, sizeof(value_type), + MOZ_ALIGNOF(value_type)); + return Elements() + len; +} + +template +template +auto nsTArray_Impl::AppendElementInternal(Item&& aItem) + -> value_type* { + // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + 1, sizeof(value_type)))) { + return nullptr; + } + value_type* elem = Elements() + Length(); + elem_traits::Construct(elem, std::forward(aItem)); + this->mHdr->mLength += 1; + return elem; +} + +template +template +auto nsTArray_Impl::EmplaceBackInternal(Args&&... aArgs) + -> value_type* { + // Length() + 1 is guaranteed to not overflow, so EnsureCapacity is OK. + if (!ActualAlloc::Successful(this->template EnsureCapacity( + Length() + 1, sizeof(value_type)))) { + return nullptr; + } + value_type* elem = Elements() + Length(); + elem_traits::Emplace(elem, std::forward(aArgs)...); + this->mHdr->mLength += 1; + return elem; +} + +template +inline void ImplCycleCollectionUnlink(nsTArray_Impl& aField) { + aField.Clear(); +} + +namespace detail { +// This is defined in the cpp file to avoid including +// nsCycleCollectionNoteChild.h in this header file. +void SetCycleCollectionArrayFlag(uint32_t& aFlags); +} // namespace detail + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsTArray_Impl& aField, const char* aName, uint32_t aFlags = 0) { + ::detail::SetCycleCollectionArrayFlag(aFlags); + size_t length = aField.Length(); + for (size_t i = 0; i < length; ++i) { + ImplCycleCollectionTraverse(aCallback, aField[i], aName, aFlags); + } +} + +// +// nsTArray is an infallible vector class. See the comment at the top of this +// file for more details. +// +template +class nsTArray : public nsTArray_Impl { + public: + using InfallibleAlloc = nsTArrayInfallibleAllocator; + using base_type = nsTArray_Impl; + using self_type = nsTArray; + using typename base_type::index_type; + using typename base_type::size_type; + using typename base_type::value_type; + + nsTArray() {} + explicit nsTArray(size_type aCapacity) : base_type(aCapacity) {} + MOZ_IMPLICIT nsTArray(std::initializer_list aIL) { + AppendElements(aIL.begin(), aIL.size()); + } + + template + nsTArray(const Item* aArray, size_type aArrayLen) { + AppendElements(aArray, aArrayLen); + } + + template + explicit nsTArray(mozilla::Span aSpan) { + AppendElements(aSpan); + } + + template + explicit nsTArray(const nsTArray_Impl& aOther) + : base_type(aOther) {} + template + MOZ_IMPLICIT nsTArray(nsTArray_Impl&& aOther) + : base_type(std::move(aOther)) {} + + template + self_type& operator=(const nsTArray_Impl& aOther) { + base_type::operator=(aOther); + return *this; + } + template + self_type& operator=(nsTArray_Impl&& aOther) { + // This is quite complex, since we don't know if we are an AutoTArray. While + // AutoTArray overrides this operator=, this might be called on a nsTArray& + // bound to an AutoTArray. + base_type::operator=(std::move(aOther)); + return *this; + } + + using base_type::AppendElement; + using base_type::AppendElements; + using base_type::EmplaceBack; + using base_type::EnsureLengthAtLeast; + using base_type::InsertElementAt; + using base_type::InsertElementsAt; + using base_type::InsertElementSorted; + using base_type::ReplaceElementsAt; + using base_type::SetCapacity; + using base_type::SetLength; + + template + mozilla::NotNull AppendElements(const Item* aArray, + size_type aArrayLen) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal(aArray, + aArrayLen)); + } + + template + mozilla::NotNull AppendElements(mozilla::Span aSpan) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal(aSpan.Elements(), + aSpan.Length())); + } + + template + mozilla::NotNull AppendElements( + const nsTArray_Impl& aArray) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal( + aArray.Elements(), aArray.Length())); + } + + template + mozilla::NotNull AppendElements( + nsTArray_Impl&& aArray) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal( + std::move(aArray))); + } + + template + mozilla::NotNull AppendElement(Item&& aItem) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementInternal( + std::forward(aItem))); + } + + mozilla::NotNull AppendElements(size_type aCount) { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal(aCount)); + } + + mozilla::NotNull AppendElement() { + return mozilla::WrapNotNullUnchecked( + this->template AppendElementsInternal(1)); + } + + self_type Clone() const { + self_type result; + result.Assign(*this); + return result; + } + + mozilla::NotNull InsertElementsAt(index_type aIndex, + size_type aCount) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementsAtInternal(aIndex, + aCount)); + } + + template + mozilla::NotNull InsertElementsAt(index_type aIndex, + size_type aCount, + const Item& aItem) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementsAtInternal(aIndex, aCount, + aItem)); + } + + template + mozilla::NotNull InsertElementsAt(index_type aIndex, + const Item* aArray, + size_type aArrayLen) { + return mozilla::WrapNotNullUnchecked( + this->template ReplaceElementsAtInternal( + aIndex, 0, aArray, aArrayLen)); + } + + template + mozilla::NotNull InsertElementsAt( + index_type aIndex, const nsTArray_Impl& aArray) { + return mozilla::WrapNotNullUnchecked( + this->template ReplaceElementsAtInternal( + aIndex, 0, aArray.Elements(), aArray.Length())); + } + + template + mozilla::NotNull InsertElementsAt(index_type aIndex, + mozilla::Span aSpan) { + return mozilla::WrapNotNullUnchecked( + this->template ReplaceElementsAtInternal( + aIndex, 0, aSpan.Elements(), aSpan.Length())); + } + + mozilla::NotNull InsertElementAt(index_type aIndex) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementAtInternal(aIndex)); + } + + template + mozilla::NotNull InsertElementAt(index_type aIndex, + Item&& aItem) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementAtInternal( + aIndex, std::forward(aItem))); + } + + template + mozilla::NotNull ReplaceElementsAt(index_type aStart, + size_type aCount, + const Item* aArray, + size_type aArrayLen) { + return mozilla::WrapNotNullUnchecked( + this->template ReplaceElementsAtInternal( + aStart, aCount, aArray, aArrayLen)); + } + + template + mozilla::NotNull ReplaceElementsAt( + index_type aStart, size_type aCount, const nsTArray& aArray) { + return ReplaceElementsAt(aStart, aCount, aArray.Elements(), + aArray.Length()); + } + + template + mozilla::NotNull ReplaceElementsAt(index_type aStart, + size_type aCount, + mozilla::Span aSpan) { + return ReplaceElementsAt(aStart, aCount, aSpan.Elements(), aSpan.Length()); + } + + template + mozilla::NotNull ReplaceElementsAt(index_type aStart, + size_type aCount, + const Item& aItem) { + return ReplaceElementsAt(aStart, aCount, &aItem, 1); + } + + template + mozilla::NotNull InsertElementSorted(Item&& aItem, + const Comparator& aComp) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementSortedInternal( + std::forward(aItem), aComp)); + } + + template + mozilla::NotNull InsertElementSorted(Item&& aItem) { + return mozilla::WrapNotNullUnchecked( + this->template InsertElementSortedInternal( + std::forward(aItem), + nsDefaultComparator{})); + } + + template + mozilla::NotNull EmplaceBack(Args&&... aArgs) { + return mozilla::WrapNotNullUnchecked( + this->template EmplaceBackInternal( + std::forward(aArgs)...)); + } +}; + +template +class CopyableTArray : public nsTArray { + public: + using nsTArray::nsTArray; + + CopyableTArray(const CopyableTArray& aOther) : nsTArray() { + this->Assign(aOther); + } + CopyableTArray& operator=(const CopyableTArray& aOther) { + if (this != &aOther) { + this->Assign(aOther); + } + return *this; + } + template + MOZ_IMPLICIT CopyableTArray(const nsTArray_Impl& aOther) { + this->Assign(aOther); + } + template + CopyableTArray& operator=(const nsTArray_Impl& aOther) { + if constexpr (std::is_same_v) { + if (this == &aOther) { + return *this; + } + } + this->Assign(aOther); + return *this; + } + template + MOZ_IMPLICIT CopyableTArray(nsTArray_Impl&& aOther) + : nsTArray{std::move(aOther)} {} + template + CopyableTArray& operator=(nsTArray_Impl&& aOther) { + static_cast&>(*this) = std::move(aOther); + return *this; + } + + CopyableTArray(CopyableTArray&&) = default; + CopyableTArray& operator=(CopyableTArray&&) = default; +}; + +// +// FallibleTArray is a fallible vector class. +// +template +class FallibleTArray : public nsTArray_Impl { + public: + typedef nsTArray_Impl base_type; + typedef FallibleTArray self_type; + typedef typename base_type::size_type size_type; + + FallibleTArray() = default; + explicit FallibleTArray(size_type aCapacity) : base_type(aCapacity) {} + + template + explicit FallibleTArray(const nsTArray_Impl& aOther) + : base_type(aOther) {} + template + explicit FallibleTArray(nsTArray_Impl&& aOther) + : base_type(std::move(aOther)) {} + + template + self_type& operator=(const nsTArray_Impl& aOther) { + base_type::operator=(aOther); + return *this; + } + template + self_type& operator=(nsTArray_Impl&& aOther) { + base_type::operator=(std::move(aOther)); + return *this; + } +}; + +// +// AutoTArray is like nsTArray, but with N elements of inline storage. +// Storing more than N elements is fine, but it will cause a heap allocation. +// +template +class MOZ_NON_MEMMOVABLE AutoTArray : public nsTArray { + static_assert(N != 0, "AutoTArray should be specialized"); + + public: + typedef AutoTArray self_type; + typedef nsTArray base_type; + typedef typename base_type::Header Header; + typedef typename base_type::value_type value_type; + + AutoTArray() : mAlign() { Init(); } + + AutoTArray(self_type&& aOther) : nsTArray() { + Init(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + explicit AutoTArray(base_type&& aOther) : mAlign() { + Init(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + template + explicit AutoTArray(nsTArray_Impl&& aOther) { + Init(); + this->MoveInit(aOther, sizeof(value_type), MOZ_ALIGNOF(value_type)); + } + + MOZ_IMPLICIT AutoTArray(std::initializer_list aIL) : mAlign() { + Init(); + this->AppendElements(aIL.begin(), aIL.size()); + } + + self_type& operator=(self_type&& aOther) { + base_type::operator=(std::move(aOther)); + return *this; + } + + template + self_type& operator=(nsTArray_Impl&& aOther) { + base_type::operator=(std::move(aOther)); + return *this; + } + + // Intentionally hides nsTArray_Impl::Clone to make clones usually be + // AutoTArray as well. + self_type Clone() const { + self_type result; + result.Assign(*this); + return result; + } + + private: + // nsTArray_base casts itself as an nsAutoArrayBase in order to get a pointer + // to mAutoBuf. + template + friend class nsTArray_base; + + void Init() { + static_assert(MOZ_ALIGNOF(value_type) <= 8, + "can't handle alignments greater than 8, " + "see nsTArray_base::UsesAutoArrayBuffer()"); + // Temporary work around for VS2012 RC compiler crash + Header** phdr = base_type::PtrToHdr(); + *phdr = reinterpret_cast(&mAutoBuf); + (*phdr)->mLength = 0; + (*phdr)->mCapacity = N; + (*phdr)->mIsAutoArray = 1; + + MOZ_ASSERT(base_type::GetAutoArrayBuffer(MOZ_ALIGNOF(value_type)) == + reinterpret_cast(&mAutoBuf), + "GetAutoArrayBuffer needs to be fixed"); + } + + // Declare mAutoBuf aligned to the maximum of the header's alignment and + // value_type's alignment. We need to use a union rather than + // MOZ_ALIGNED_DECL because GCC is picky about what goes into + // __attribute__((aligned(foo))). + union { + char mAutoBuf[sizeof(nsTArrayHeader) + N * sizeof(value_type)]; + // Do the max operation inline to ensure that it is a compile-time constant. + mozilla::AlignedElem<(MOZ_ALIGNOF(Header) > MOZ_ALIGNOF(value_type)) + ? MOZ_ALIGNOF(Header) + : MOZ_ALIGNOF(value_type)> + mAlign; + }; +}; + +// +// Specialization of AutoTArray for the case where N == 0. +// AutoTArray behaves exactly like nsTArray, but without this +// specialization, it stores a useless inline header. +// +// We do have many AutoTArray objects in memory: about 2,000 per tab as +// of May 2014. These are typically not explicitly AutoTArray but rather +// AutoTArray for some value N depending on template parameters, in +// generic code. +// +// For that reason, we optimize this case with the below partial specialization, +// which ensures that AutoTArray is just like nsTArray, without any +// inline header overhead. +// +template +class AutoTArray : public nsTArray { + using nsTArray::nsTArray; +}; + +template +struct nsTArray_RelocationStrategy> { + using Type = nsTArray_RelocateUsingMoveConstructor>; +}; + +template +class CopyableAutoTArray : public AutoTArray { + public: + typedef CopyableAutoTArray self_type; + using AutoTArray::AutoTArray; + + CopyableAutoTArray(const CopyableAutoTArray& aOther) : AutoTArray() { + this->Assign(aOther); + } + CopyableAutoTArray& operator=(const CopyableAutoTArray& aOther) { + if (this != &aOther) { + this->Assign(aOther); + } + return *this; + } + template + MOZ_IMPLICIT CopyableAutoTArray(const nsTArray_Impl& aOther) { + this->Assign(aOther); + } + template + CopyableAutoTArray& operator=(const nsTArray_Impl& aOther) { + if constexpr (std::is_same_v) { + if (this == &aOther) { + return *this; + } + } + this->Assign(aOther); + return *this; + } + template + MOZ_IMPLICIT CopyableAutoTArray(nsTArray_Impl&& aOther) + : AutoTArray{std::move(aOther)} {} + template + CopyableAutoTArray& operator=(nsTArray_Impl&& aOther) { + static_cast&>(*this) = std::move(aOther); + return *this; + } + + // CopyableTArray exists for cases where an explicit Clone is not possible. + // These uses should not be mixed, so we delete Clone() here. + self_type Clone() const = delete; + + CopyableAutoTArray(CopyableAutoTArray&&) = default; + CopyableAutoTArray& operator=(CopyableAutoTArray&&) = default; +}; + +namespace mozilla { +template +class nsTArrayBackInserter { + ArrayT* mArray; + + class Proxy { + ArrayT& mArray; + + public: + explicit Proxy(ArrayT& aArray) : mArray{aArray} {} + + template + void operator=(E2&& aValue) { + mArray.AppendElement(std::forward(aValue)); + } + }; + + public: + using iterator_category = std::output_iterator_tag; + using value_type = void; + using difference_type = void; + using pointer = void; + using reference = void; + explicit nsTArrayBackInserter(ArrayT& aArray) : mArray{&aArray} {} + + // Return a proxy so that nsTArrayBackInserter has the default special member + // functions, and the operator= template is defined in Proxy rather than this + // class (which otherwise breaks with recent MS STL versions). + // See also Bug 1331137, comment 11. + Proxy operator*() { return Proxy(*mArray); } + + nsTArrayBackInserter& operator++() { return *this; } + nsTArrayBackInserter& operator++(int) { return *this; } +}; +} // namespace mozilla + +template +auto MakeBackInserter(nsTArray& aArray) { + return mozilla::nsTArrayBackInserter>{aArray}; +} + +// Span integration +namespace mozilla { +template +Span(nsTArray_Impl&) -> Span; + +template +Span(const nsTArray_Impl&) -> Span; + +// Provides a view on a nsTArray through which the existing array elements can +// be accessed in a non-const way, but the array itself cannot be modified, so +// that references to elements are guaranteed to be stable. +template +class nsTArrayView { + public: + using element_type = E; + using pointer = element_type*; + using reference = element_type&; + using index_type = typename Span::index_type; + using size_type = typename Span::index_type; + + explicit nsTArrayView(nsTArray aArray) + : mArray(std::move(aArray)), mSpan(mArray) {} + + element_type& operator[](index_type aIndex) { return mSpan[aIndex]; } + + const element_type& operator[](index_type aIndex) const { + return mSpan[aIndex]; + } + + size_type Length() const { return mSpan.Length(); } + + auto begin() { return mSpan.begin(); } + auto end() { return mSpan.end(); } + auto begin() const { return mSpan.begin(); } + auto end() const { return mSpan.end(); } + auto cbegin() const { return mSpan.cbegin(); } + auto cend() const { return mSpan.cend(); } + + Span AsSpan() { return mSpan; } + Span AsSpan() const { return mSpan; } + + private: + nsTArray mArray; + const Span mSpan; +}; + +template ::iterator_category, + std::random_access_iterator_tag>>> +auto RangeSize(const Range& aRange) { + // See https://en.cppreference.com/w/cpp/iterator/begin, section 'User-defined + // overloads'. + using std::begin; + using std::end; + + return std::distance(begin(aRange), end(aRange)); +} + +/** + * Materialize a range as a nsTArray (or a compatible variant, like AutoTArray) + * of an explicitly specified type. The array value type must be implicitly + * convertible from the range's value type. + */ +template +auto ToTArray(const Range& aRange) { + using std::begin; + using std::end; + + Array res; + res.SetCapacity(RangeSize(aRange)); + std::copy(begin(aRange), end(aRange), MakeBackInserter(res)); + return res; +} + +/** + * Materialize a range as a nsTArray of its (decayed) value type. + */ +template +auto ToArray(const Range& aRange) { + return ToTArray::value_type>>>( + aRange); +} + +/** + * Appends all elements from a range to an array. + */ +template +void AppendToArray(Array& aArray, const Range& aRange) { + using std::begin; + using std::end; + + aArray.SetCapacity(aArray.Length() + RangeSize(aRange)); + std::copy(begin(aRange), end(aRange), MakeBackInserter(aArray)); +} + +} // namespace mozilla + +// MOZ_DBG support + +template +std::ostream& operator<<(std::ostream& aOut, + const nsTArray_Impl& aTArray) { + return aOut << mozilla::Span(aTArray); +} + +// Assert that AutoTArray doesn't have any extra padding inside. +// +// It's important that the data stored in this auto array takes up a multiple of +// 8 bytes; e.g. AutoTArray wouldn't work. Since AutoTArray +// contains a pointer, its size must be a multiple of alignof(void*). (This is +// because any type may be placed into an array, and there's no padding between +// elements of an array.) The compiler pads the end of the structure to +// enforce this rule. +// +// If we used AutoTArray below, this assertion would fail on a +// 64-bit system, where the compiler inserts 4 bytes of padding at the end of +// the auto array to make its size a multiple of alignof(void*) == 8 bytes. + +static_assert(sizeof(AutoTArray) == + sizeof(void*) + sizeof(nsTArrayHeader) + sizeof(uint32_t) * 2, + "AutoTArray shouldn't contain any extra padding, " + "see the comment"); + +// Definitions of nsTArray_Impl methods +#include "nsTArray-inl.h" + +#endif // nsTArray_h__ diff --git a/xpcom/ds/nsTArrayForwardDeclare.h b/xpcom/ds/nsTArrayForwardDeclare.h new file mode 100644 index 0000000000..f888550803 --- /dev/null +++ b/xpcom/ds/nsTArrayForwardDeclare.h @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +#ifndef nsTArrayForwardDeclare_h__ +#define nsTArrayForwardDeclare_h__ + +// +// This simple header file contains forward declarations for the TArray family +// of classes. +// +// Including this header is preferable to writing +// +// template class nsTArray; +// +// yourself, since this header gives us flexibility to e.g. change the default +// template parameters. +// + +#include + +template +class nsTArray; + +template +class FallibleTArray; + +template +class CopyableTArray; + +template +class AutoTArray; + +template +class CopyableAutoTArray; + +#endif diff --git a/xpcom/ds/nsTHashMap.h b/xpcom/ds/nsTHashMap.h new file mode 100644 index 0000000000..8357d2ae7b --- /dev/null +++ b/xpcom/ds/nsTHashMap.h @@ -0,0 +1,70 @@ +/* -*- 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/. */ + +#ifndef XPCOM_DS_NSTHASHMAP_H_ +#define XPCOM_DS_NSTHASHMAP_H_ + +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" +#include "nsBaseHashtable.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsHashtablesFwd.h" +#include + +namespace mozilla::detail { +template +struct nsKeyClass< + KeyType, std::enable_if_t>> { + using type = KeyType; +}; + +template +struct nsKeyClass { + using type = nsPtrHashKey; +}; + +template <> +struct nsKeyClass { + using type = nsCStringHashKey; +}; + +// This uses the case-sensitive hash key class, if you want the +// case-insensitive hash key, use nsStringCaseInsensitiveHashKey explicitly. +template <> +struct nsKeyClass { + using type = nsStringHashKey; +}; + +template +struct nsKeyClass || + std::is_enum_v>> { + using type = nsIntegralHashKey; +}; + +template <> +struct nsKeyClass> { + using type = nsISupportsHashKey; +}; + +template +struct nsKeyClass> { + using type = nsRefPtrHashKey; +}; + +template <> +struct nsKeyClass { + using type = nsIDHashKey; +}; + +} // namespace mozilla::detail + +// The actual definition of nsTHashMap is in nsHashtablesFwd.h, since it is a +// type alias. + +#endif // XPCOM_DS_NSTHASHMAP_H_ diff --git a/xpcom/ds/nsTHashSet.h b/xpcom/ds/nsTHashSet.h new file mode 100644 index 0000000000..4d905299db --- /dev/null +++ b/xpcom/ds/nsTHashSet.h @@ -0,0 +1,193 @@ +/* -*- 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/. */ + +#ifndef XPCOM_DS_NSTHASHSET_H_ +#define XPCOM_DS_NSTHASHSET_H_ + +#include "nsHashtablesFwd.h" +#include "nsTHashMap.h" // for nsKeyClass + +/** + * Templated hash set. Don't use this directly, but use nsTHashSet instead + * (defined as a type alias in nsHashtablesFwd.h). + * + * @param KeyClass a wrapper-class for the hashtable key, see nsHashKeys.h + * for a complete specification. + */ +template +class nsTBaseHashSet : protected nsTHashtable { + using Base = nsTHashtable; + typedef mozilla::fallible_t fallible_t; + + public: + // XXX We have a similar situation here like with DataType/UserDataType in + // nsBaseHashtable. It's less problematic here due to the more constrained + // interface, but it may still be confusing. KeyType is not the stored key + // type, but the one exposed to the user, i.e. as a parameter type, and as the + // value type when iterating. It is currently impossible to move-insert a + // RefPtr, e.g., since the KeyType is T* in that case. + using ValueType = typename KeyClass::KeyType; + + // KeyType is defined just for compatibility with nsTHashMap. For a set, the + // key type is conceptually equivalent to the value type. + using KeyType = typename KeyClass::KeyType; + + using Base::Base; + + // Query operations. + + using Base::Contains; + using Base::GetGeneration; + using Base::ShallowSizeOfExcludingThis; + using Base::ShallowSizeOfIncludingThis; + using Base::SizeOfExcludingThis; + using Base::SizeOfIncludingThis; + + /** + * Return the number of entries in the table. + * @return number of entries + */ + [[nodiscard]] uint32_t Count() const { return Base::Count(); } + + /** + * Return whether the table is empty. + * @return whether empty + */ + [[nodiscard]] bool IsEmpty() const { return Base::IsEmpty(); } + + using iterator = ::detail::nsTHashtableKeyIterator; + using const_iterator = iterator; + + [[nodiscard]] auto begin() const { return Base::Keys().begin(); } + + [[nodiscard]] auto end() const { return Base::Keys().end(); } + + [[nodiscard]] auto cbegin() const { return Base::Keys().cbegin(); } + + [[nodiscard]] auto cend() const { return Base::Keys().cend(); } + + // Mutating operations. + + using Base::Clear; + using Base::MarkImmutable; + + /** + * Inserts a value into the set. Has no effect if the value is already in the + * set. This overload is infallible and crashes if memory is exhausted. + * + * \note For strict consistency with nsTHashtable::EntryHandle method naming, + * this should rather be called OrInsert, as it is legal to call it when the + * value is already in the set. For simplicity, as we don't have two methods, + * we still use "Insert" instead. + */ + void Insert(ValueType aValue) { Base::PutEntry(aValue); } + + /** + * Inserts a value into the set. Has no effect if the value is already in the + * set. This overload is fallible and returns false if memory is exhausted. + * + * \note See note on infallible overload. + */ + [[nodiscard]] bool Insert(ValueType aValue, const mozilla::fallible_t&) { + return Base::PutEntry(aValue, mozilla::fallible); + } + + /** + * Inserts a value into the set. Has no effect if the value is already in the + * set. This member function is infallible and crashes if memory is exhausted. + * + * \return true if the value was actually inserted, false if it was already in + * the set. + */ + bool EnsureInserted(ValueType aValue) { return Base::EnsureInserted(aValue); } + + using Base::Remove; + + /** + * Removes a value from the set. Has no effect if the value is not in the set. + * + * \note For strict consistency with nsTHashtable::EntryHandle method naming, + * this should rather be called OrRemove, as it is legal to call it when the + * value is not in the set. For simplicity, as we don't have two methods, + * we still use "Remove" instead. + */ + void Remove(ValueType aValue) { Base::RemoveEntry(aValue); } + + using Base::EnsureRemoved; + + /** + * Removes all elements matching a predicate. + * + * The predicate must be compatible with signature bool (ValueType). + */ + template + void RemoveIf(Pred&& aPred) { + for (auto it = cbegin(), end = cend(); it != end; ++it) { + if (aPred(static_cast(*it))) { + Remove(it); + } + } + } +}; + +template +auto RangeSize(const nsTBaseHashSet& aRange) { + return aRange.Count(); +} + +class nsCycleCollectionTraversalCallback; + +template +inline void ImplCycleCollectionUnlink(nsTBaseHashSet& aField) { + aField.Clear(); +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + const nsTBaseHashSet& aField, const char* aName, + uint32_t aFlags = 0) { + for (const auto& entry : aField) { + CycleCollectionNoteChild(aCallback, mozilla::detail::PtrGetWeak(entry), + aName, aFlags); + } +} + +namespace mozilla { +template +class nsTSetInserter { + SetT* mSet; + + class Proxy { + SetT& mSet; + + public: + explicit Proxy(SetT& aSet) : mSet{aSet} {} + + template + void operator=(E2&& aValue) { + mSet.Insert(std::forward(aValue)); + } + }; + + public: + using iterator_category = std::output_iterator_tag; + + explicit nsTSetInserter(SetT& aSet) : mSet{&aSet} {} + + Proxy operator*() { return Proxy(*mSet); } + + nsTSetInserter& operator++() { return *this; } + nsTSetInserter& operator++(int) { return *this; } +}; +} // namespace mozilla + +template +auto MakeInserter(nsTBaseHashSet& aSet) { + return mozilla::nsTSetInserter>{aSet}; +} + +#endif // XPCOM_DS_NSTHASHSET_H_ diff --git a/xpcom/ds/nsTHashtable.h b/xpcom/ds/nsTHashtable.h new file mode 100644 index 0000000000..d4a385551f --- /dev/null +++ b/xpcom/ds/nsTHashtable.h @@ -0,0 +1,966 @@ +/* -*- 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/. */ + +// See the comment at the top of mfbt/HashTable.h for a comparison between +// PLDHashTable and mozilla::HashTable. + +#ifndef nsTHashtable_h__ +#define nsTHashtable_h__ + +#include +#include +#include +#include + +#include "PLDHashTable.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/PodOperations.h" +#include "mozilla/fallible.h" +#include "nsPointerHashKeys.h" +#include "nsTArrayForwardDeclare.h" + +template +class nsTHashtable; + +namespace detail { +class nsTHashtableIteratorBase { + public: + using EndIteratorTag = PLDHashTable::Iterator::EndIteratorTag; + + nsTHashtableIteratorBase(nsTHashtableIteratorBase&& aOther) = default; + + nsTHashtableIteratorBase& operator=(nsTHashtableIteratorBase&& aOther) { + // User-defined because the move assignment operator is deleted in + // PLDHashtable::Iterator. + return operator=(static_cast(aOther)); + } + + nsTHashtableIteratorBase(const nsTHashtableIteratorBase& aOther) + : mIterator{aOther.mIterator.Clone()} {} + nsTHashtableIteratorBase& operator=(const nsTHashtableIteratorBase& aOther) { + // Since PLDHashTable::Iterator has no assignment operator, we destroy and + // recreate mIterator. + mIterator.~Iterator(); + new (&mIterator) PLDHashTable::Iterator(aOther.mIterator.Clone()); + return *this; + } + + explicit nsTHashtableIteratorBase(PLDHashTable::Iterator aFrom) + : mIterator{std::move(aFrom)} {} + + explicit nsTHashtableIteratorBase(const PLDHashTable& aTable) + : mIterator{&const_cast(aTable)} {} + + nsTHashtableIteratorBase(const PLDHashTable& aTable, EndIteratorTag aTag) + : mIterator{&const_cast(aTable), aTag} {} + + bool operator==(const nsTHashtableIteratorBase& aRhs) const { + return mIterator == aRhs.mIterator; + } + bool operator!=(const nsTHashtableIteratorBase& aRhs) const { + return !(*this == aRhs); + } + + protected: + PLDHashTable::Iterator mIterator; +}; + +// STL-style iterators to allow the use in range-based for loops, e.g. +template +class nsTHashtableEntryIterator : public nsTHashtableIteratorBase { + friend class nsTHashtable>; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = int32_t; + using pointer = value_type*; + using reference = value_type&; + + using iterator_type = nsTHashtableEntryIterator; + using const_iterator_type = nsTHashtableEntryIterator; + + using nsTHashtableIteratorBase::nsTHashtableIteratorBase; + + value_type* operator->() const { + return static_cast(mIterator.Get()); + } + value_type& operator*() const { + return *static_cast(mIterator.Get()); + } + + iterator_type& operator++() { + mIterator.Next(); + return *this; + } + iterator_type operator++(int) { + iterator_type it = *this; + ++*this; + return it; + } + + operator const_iterator_type() const { + return const_iterator_type{mIterator.Clone()}; + } +}; + +template +class nsTHashtableKeyIterator : public nsTHashtableIteratorBase { + friend class nsTHashtable; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const std::decay_t; + using difference_type = int32_t; + using pointer = value_type*; + using reference = value_type&; + + using iterator_type = nsTHashtableKeyIterator; + using const_iterator_type = nsTHashtableKeyIterator; + + using nsTHashtableIteratorBase::nsTHashtableIteratorBase; + + value_type* operator->() const { + return &static_cast(mIterator.Get())->GetKey(); + } + decltype(auto) operator*() const { + return static_cast(mIterator.Get())->GetKey(); + } + + iterator_type& operator++() { + mIterator.Next(); + return *this; + } + iterator_type operator++(int) { + iterator_type it = *this; + ++*this; + return it; + } +}; + +template +class nsTHashtableKeyRange { + public: + using IteratorType = nsTHashtableKeyIterator; + using iterator = IteratorType; + + explicit nsTHashtableKeyRange(const PLDHashTable& aHashtable) + : mHashtable{aHashtable} {} + + auto begin() const { return IteratorType{mHashtable}; } + auto end() const { + return IteratorType{mHashtable, typename IteratorType::EndIteratorTag{}}; + } + auto cbegin() const { return begin(); } + auto cend() const { return end(); } + + uint32_t Count() const { return mHashtable.EntryCount(); } + + private: + const PLDHashTable& mHashtable; +}; + +template +auto RangeSize(const ::detail::nsTHashtableKeyRange& aRange) { + return aRange.Count(); +} + +} // namespace detail + +/** + * a base class for templated hashtables. + * + * Clients will rarely need to use this class directly. Check the derived + * classes first, to see if they will meet your needs. + * + * @param EntryType the templated entry-type class that is managed by the + * hashtable. EntryType must extend the following declaration, + * and must not declare any virtual functions or derive from classes + * with virtual functions. Any vtable pointer would break the + * PLDHashTable code. + *
   class EntryType : public PLDHashEntryHdr
+ *   {
+ *   public: or friend nsTHashtable;
+ *     // KeyType is what we use when Get()ing or Put()ing this entry
+ *     // this should either be a simple datatype (uint32_t, nsISupports*) or
+ *     // a const reference (const nsAString&)
+ *     typedef something KeyType;
+ *     // KeyTypePointer is the pointer-version of KeyType, because
+ *     // PLDHashTable.h requires keys to cast to const void*
+ *     typedef const something* KeyTypePointer;
+ *
+ *     EntryType(KeyTypePointer aKey);
+ *
+ *     // A copy or C++11 Move constructor must be defined, even if
+ *     // AllowMemMove() == true, otherwise you will cause link errors.
+ *     EntryType(const EntryType& aEnt);  // Either this...
+ *     EntryType(EntryType&& aEnt);       // ...or this
+ *
+ *     // the destructor must be defined... or you will cause link errors!
+ *     ~EntryType();
+ *
+ *     // KeyEquals(): does this entry match this key?
+ *     bool KeyEquals(KeyTypePointer aKey) const;
+ *
+ *     // KeyToPointer(): Convert KeyType to KeyTypePointer
+ *     static KeyTypePointer KeyToPointer(KeyType aKey);
+ *
+ *     // HashKey(): calculate the hash number
+ *     static PLDHashNumber HashKey(KeyTypePointer aKey);
+ *
+ *     // ALLOW_MEMMOVE can we move this class with memmove(), or do we have
+ *     // to use the copy constructor?
+ *     enum { ALLOW_MEMMOVE = true/false };
+ *   }
+ * + * @see nsInterfaceHashtable + * @see nsClassHashtable + * @see nsTHashMap + * @author "Benjamin Smedberg " + */ + +template +class MOZ_NEEDS_NO_VTABLE_TYPE nsTHashtable { + typedef mozilla::fallible_t fallible_t; + static_assert(std::is_pointer_v, + "KeyTypePointer should be a pointer"); + + public: + // Separate constructors instead of default aInitLength parameter since + // otherwise the default no-arg constructor isn't found. + nsTHashtable() + : mTable(Ops(), sizeof(EntryType), PLDHashTable::kDefaultInitialLength) {} + explicit nsTHashtable(uint32_t aInitLength) + : mTable(Ops(), sizeof(EntryType), aInitLength) {} + + /** + * destructor, cleans up and deallocates + */ + ~nsTHashtable() = default; + + nsTHashtable(nsTHashtable&& aOther); + nsTHashtable& operator=(nsTHashtable&& aOther); + + nsTHashtable(const nsTHashtable&) = delete; + nsTHashtable& operator=(const nsTHashtable&) = delete; + + /** + * Return the generation number for the table. This increments whenever + * the table data items are moved. + */ + uint32_t GetGeneration() const { return mTable.Generation(); } + + /** + * KeyType is typedef'ed for ease of use. + */ + typedef typename EntryType::KeyType KeyType; + + /** + * KeyTypePointer is typedef'ed for ease of use. + */ + typedef typename EntryType::KeyTypePointer KeyTypePointer; + + /** + * Return the number of entries in the table. + * @return number of entries + */ + uint32_t Count() const { return mTable.EntryCount(); } + + /** + * Return true if the hashtable is empty. + */ + bool IsEmpty() const { return Count() == 0; } + + /** + * Get the entry associated with a key. + * @param aKey the key to retrieve + * @return pointer to the entry class, if the key exists; nullptr if the + * key doesn't exist + */ + EntryType* GetEntry(KeyType aKey) const { + return static_cast( + mTable.Search(EntryType::KeyToPointer(aKey))); + } + + /** + * Return true if an entry for the given key exists, false otherwise. + * @param aKey the key to retrieve + * @return true if the key exists, false if the key doesn't exist + */ + bool Contains(KeyType aKey) const { return !!GetEntry(aKey); } + + /** + * Infallibly get the entry associated with a key, or create a new entry, + * @param aKey the key to retrieve + * @return pointer to the entry retrieved; never nullptr + */ + EntryType* PutEntry(KeyType aKey) { + // Infallible WithEntryHandle. + return WithEntryHandle( + aKey, [](auto entryHandle) { return entryHandle.OrInsert(); }); + } + + /** + * Fallibly get the entry associated with a key, or create a new entry, + * @param aKey the key to retrieve + * @return pointer to the entry retrieved; nullptr only if memory can't + * be allocated + */ + [[nodiscard]] EntryType* PutEntry(KeyType aKey, const fallible_t& aFallible) { + return WithEntryHandle(aKey, aFallible, [](auto maybeEntryHandle) { + return maybeEntryHandle ? maybeEntryHandle->OrInsert() : nullptr; + }); + } + + /** + * Get the entry associated with a key, or create a new entry using infallible + * allocation and insert that. + * @param aKey the key to retrieve + * @param aEntry will be assigned (if non-null) to the entry that was + * found or created + * @return true if a new entry was created, or false if an existing entry + * was found + */ + [[nodiscard]] bool EnsureInserted(KeyType aKey, + EntryType** aEntry = nullptr) { + auto oldCount = Count(); + EntryType* entry = PutEntry(aKey); + if (aEntry) { + *aEntry = entry; + } + return oldCount != Count(); + } + + /** + * Remove the entry associated with a key. + * @param aKey of the entry to remove + */ + void RemoveEntry(KeyType aKey) { + mTable.Remove(EntryType::KeyToPointer(aKey)); + } + + /** + * Lookup the entry associated with aKey and remove it if found, otherwise + * do nothing. + * @param aKey of the entry to remove + * @return true if an entry was found and removed, or false if no entry + * was found for aKey + */ + bool EnsureRemoved(KeyType aKey) { + auto* entry = GetEntry(aKey); + if (entry) { + RemoveEntry(entry); + return true; + } + return false; + } + + /** + * Remove the entry associated with a key. + * @param aEntry the entry-pointer to remove (obtained from GetEntry) + */ + void RemoveEntry(EntryType* aEntry) { mTable.RemoveEntry(aEntry); } + + /** + * Remove the entry associated with a key, but don't resize the hashtable. + * This is a low-level method, and is not recommended unless you know what + * you're doing. If you use it, please add a comment explaining why you + * didn't use RemoveEntry(). + * @param aEntry the entry-pointer to remove (obtained from GetEntry) + */ + void RawRemoveEntry(EntryType* aEntry) { mTable.RawRemove(aEntry); } + + protected: + class EntryHandle { + public: + EntryHandle(EntryHandle&& aOther) = default; + ~EntryHandle() = default; + + EntryHandle(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&&) = delete; + + KeyType Key() const { return mKey; } + + bool HasEntry() const { return mEntryHandle.HasEntry(); } + + explicit operator bool() const { return mEntryHandle.operator bool(); } + + EntryType* Entry() { return static_cast(mEntryHandle.Entry()); } + + void Insert() { InsertInternal(); } + + EntryType* OrInsert() { + if (!HasEntry()) { + Insert(); + } + return Entry(); + } + + void Remove() { mEntryHandle.Remove(); } + + void OrRemove() { mEntryHandle.OrRemove(); } + + protected: + template + void InsertInternal(Args&&... aArgs) { + MOZ_RELEASE_ASSERT(!HasEntry()); + mEntryHandle.Insert([&](PLDHashEntryHdr* entry) { + new (mozilla::KnownNotNull, entry) EntryType( + EntryType::KeyToPointer(mKey), std::forward(aArgs)...); + }); + } + + private: + friend class nsTHashtable; + + EntryHandle(KeyType aKey, PLDHashTable::EntryHandle&& aEntryHandle) + : mKey(aKey), mEntryHandle(std::move(aEntryHandle)) {} + + KeyType mKey; + PLDHashTable::EntryHandle mEntryHandle; + }; + + template + auto WithEntryHandle(KeyType aKey, F&& aFunc) + -> std::invoke_result_t { + return this->mTable.WithEntryHandle( + EntryType::KeyToPointer(aKey), + [&aKey, &aFunc](auto entryHandle) -> decltype(auto) { + return std::forward(aFunc)( + EntryHandle{aKey, std::move(entryHandle)}); + }); + } + + template + auto WithEntryHandle(KeyType aKey, const mozilla::fallible_t& aFallible, + F&& aFunc) + -> std::invoke_result_t&&> { + return this->mTable.WithEntryHandle( + EntryType::KeyToPointer(aKey), aFallible, + [&aKey, &aFunc](auto maybeEntryHandle) { + return std::forward(aFunc)( + maybeEntryHandle + ? mozilla::Some(EntryHandle{aKey, maybeEntryHandle.extract()}) + : mozilla::Nothing()); + }); + } + + public: + class ConstIterator { + public: + explicit ConstIterator(nsTHashtable* aTable) + : mBaseIterator(&aTable->mTable) {} + ~ConstIterator() = default; + + KeyType Key() const { return Get()->GetKey(); } + + const EntryType* Get() const { + return static_cast(mBaseIterator.Get()); + } + + bool Done() const { return mBaseIterator.Done(); } + void Next() { mBaseIterator.Next(); } + + ConstIterator() = delete; + ConstIterator(const ConstIterator&) = delete; + ConstIterator(ConstIterator&& aOther) = delete; + ConstIterator& operator=(const ConstIterator&) = delete; + ConstIterator& operator=(ConstIterator&&) = delete; + + protected: + PLDHashTable::Iterator mBaseIterator; + }; + + // This is an iterator that also allows entry removal. Example usage: + // + // for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + // Entry* entry = iter.Get(); + // // ... do stuff with |entry| ... + // // ... possibly call iter.Remove() once ... + // } + // + class Iterator final : public ConstIterator { + public: + using ConstIterator::ConstIterator; + + using ConstIterator::Get; + + EntryType* Get() const { + return static_cast(this->mBaseIterator.Get()); + } + + void Remove() { this->mBaseIterator.Remove(); } + }; + + Iterator Iter() { return Iterator(this); } + + ConstIterator ConstIter() const { + return ConstIterator(const_cast(this)); + } + + using const_iterator = ::detail::nsTHashtableEntryIterator; + using iterator = ::detail::nsTHashtableEntryIterator; + + iterator begin() { return iterator{mTable}; } + const_iterator begin() const { return const_iterator{mTable}; } + const_iterator cbegin() const { return begin(); } + iterator end() { + return iterator{mTable, typename iterator::EndIteratorTag{}}; + } + const_iterator end() const { + return const_iterator{mTable, typename const_iterator::EndIteratorTag{}}; + } + const_iterator cend() const { return end(); } + + void Remove(const_iterator& aIter) { aIter.mIterator.Remove(); } + + /** + * Return a range of the keys (of KeyType). Note this range iterates over the + * keys in place, so modifications to the nsTHashtable invalidate the range + * while it's iterated, except when calling Remove() with a key iterator + * derived from that range. + */ + auto Keys() const { + return ::detail::nsTHashtableKeyRange{mTable}; + } + + /** + * Remove an entry from a key range, specified via a key iterator, e.g. + * + * for (auto it = hash.Keys().begin(), end = hash.Keys().end(); + * it != end; * ++it) { + * if (*it > 42) { hash.Remove(it); } + * } + */ + void Remove(::detail::nsTHashtableKeyIterator& aIter) { + aIter.mIterator.Remove(); + } + + /** + * Remove all entries, return hashtable to "pristine" state. It's + * conceptually the same as calling the destructor and then re-calling the + * constructor. + */ + void Clear() { mTable.Clear(); } + + /** + * Measure the size of the table's entry storage. Does *not* measure anything + * hanging off table entries; hence the "Shallow" prefix. To measure that, + * either use SizeOfExcludingThis() or iterate manually over the entries, + * calling SizeOfExcludingThis() on each one. + * + * @param aMallocSizeOf the function used to measure heap-allocated blocks + * @return the measured shallow size of the table + */ + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return mTable.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Like ShallowSizeOfExcludingThis, but includes sizeof(*this). + */ + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + /** + * This is a "deep" measurement of the table. To use it, |EntryType| must + * define SizeOfExcludingThis, and that method will be called on all live + * entries. + */ + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto iter = ConstIter(); !iter.Done(); iter.Next()) { + n += (*iter.Get()).SizeOfExcludingThis(aMallocSizeOf); + } + return n; + } + + /** + * Like SizeOfExcludingThis, but includes sizeof(*this). + */ + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + /** + * Swap the elements in this hashtable with the elements in aOther. + */ + void SwapElements(nsTHashtable& aOther) { + MOZ_ASSERT_IF(this->mTable.Ops() && aOther.mTable.Ops(), + this->mTable.Ops() == aOther.mTable.Ops()); + std::swap(this->mTable, aOther.mTable); + } + + /** + * Mark the table as constant after initialization. + * + * This will prevent assertions when a read-only hash is accessed on multiple + * threads without synchronization. + */ + void MarkImmutable() { mTable.MarkImmutable(); } + + protected: + PLDHashTable mTable; + + static PLDHashNumber s_HashKey(const void* aKey); + + static bool s_MatchEntry(const PLDHashEntryHdr* aEntry, const void* aKey); + + static void s_CopyEntry(PLDHashTable* aTable, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo); + + static void s_ClearEntry(PLDHashTable* aTable, PLDHashEntryHdr* aEntry); + + private: + // copy constructor, not implemented + nsTHashtable(nsTHashtable& aToCopy) = delete; + + /** + * Gets the table's ops. + */ + static const PLDHashTableOps* Ops(); + + // assignment operator, not implemented + nsTHashtable& operator=(nsTHashtable& aToEqual) = + delete; +}; + +namespace mozilla { +namespace detail { + +// Like PLDHashTable::MoveEntryStub, but specialized for fixed N (i.e. the size +// of the entries in the hashtable). Saves a memory read to figure out the size +// from the table and gives the compiler the opportunity to inline the memcpy. +// +// We define this outside of nsTHashtable so only one copy exists for every N, +// rather than separate copies for every EntryType used with nsTHashtable. +template +static void FixedSizeEntryMover(PLDHashTable*, const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) { + memcpy(aTo, aFrom, N); +} + +} // namespace detail +} // namespace mozilla + +// +// template definitions +// + +template +nsTHashtable::nsTHashtable(nsTHashtable&& aOther) + : mTable(std::move(aOther.mTable)) {} + +template +nsTHashtable& nsTHashtable::operator=( + nsTHashtable&& aOther) { + mTable = std::move(aOther.mTable); + return *this; +} + +template +/* static */ const PLDHashTableOps* nsTHashtable::Ops() { + // If this variable is a global variable, we get strange start-up failures on + // WindowsCrtPatch.h (see bug 1166598 comment 20). But putting it inside a + // function avoids that problem. + static const PLDHashTableOps sOps = { + s_HashKey, s_MatchEntry, + EntryType::ALLOW_MEMMOVE + ? mozilla::detail::FixedSizeEntryMover + : s_CopyEntry, + // Simplify hashtable clearing in case our entries are trivially + // destructible. + std::is_trivially_destructible_v ? nullptr : s_ClearEntry, + // We don't use a generic initEntry hook because we want to allow + // initialization of data members defined in derived classes directly + // in the entry constructor (for example when a member can't be default + // constructed). + nullptr}; + return &sOps; +} + +// static definitions + +template +PLDHashNumber nsTHashtable::s_HashKey(const void* aKey) { + return EntryType::HashKey(static_cast(aKey)); +} + +template +bool nsTHashtable::s_MatchEntry(const PLDHashEntryHdr* aEntry, + const void* aKey) { + return (static_cast(aEntry)) + ->KeyEquals(static_cast(aKey)); +} + +template +void nsTHashtable::s_CopyEntry(PLDHashTable* aTable, + const PLDHashEntryHdr* aFrom, + PLDHashEntryHdr* aTo) { + auto* fromEntry = const_cast*>( + static_cast(aFrom)); + + new (mozilla::KnownNotNull, aTo) EntryType(std::move(*fromEntry)); + + fromEntry->~EntryType(); +} + +template +void nsTHashtable::s_ClearEntry(PLDHashTable* aTable, + PLDHashEntryHdr* aEntry) { + static_cast(aEntry)->~EntryType(); +} + +class nsCycleCollectionTraversalCallback; + +template +inline void ImplCycleCollectionUnlink(nsTHashtable& aField) { + aField.Clear(); +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsTHashtable& aField, const char* aName, uint32_t aFlags = 0) { + for (auto iter = aField.Iter(); !iter.Done(); iter.Next()) { + EntryType* entry = iter.Get(); + ImplCycleCollectionTraverse(aCallback, *entry, aName, aFlags); + } +} + +/** + * For nsTHashtable with pointer entries, we can have a template specialization + * that layers a typed T* interface on top of a common implementation that + * works internally with void pointers. This arrangement saves code size and + * might slightly improve performance as well. + */ + +/** + * We need a separate entry type class for the inheritance structure of the + * nsTHashtable specialization below; nsVoidPtrHashKey is simply typedefed to a + * specialization of nsPtrHashKey, and the formulation: + * + * class nsTHashtable> : + * protected nsTHashtable + * + * is not going to turn out very well, since we'd wind up with an nsTHashtable + * instantiation that is its own base class. + */ +namespace detail { + +class VoidPtrHashKey : public nsPtrHashKey { + typedef nsPtrHashKey Base; + + public: + explicit VoidPtrHashKey(const void* aKey) : Base(aKey) {} +}; + +} // namespace detail + +/** + * See the main nsTHashtable documentation for descriptions of this class's + * methods. + */ +template +class nsTHashtable> + : protected nsTHashtable<::detail::VoidPtrHashKey> { + typedef nsTHashtable<::detail::VoidPtrHashKey> Base; + typedef nsPtrHashKey EntryType; + + // We play games with reinterpret_cast'ing between these two classes, so + // try to ensure that playing said games is reasonable. + static_assert(sizeof(nsPtrHashKey) == sizeof(::detail::VoidPtrHashKey), + "hash keys must be the same size"); + + nsTHashtable(const nsTHashtable& aOther) = delete; + nsTHashtable& operator=(const nsTHashtable& aOther) = delete; + + public: + nsTHashtable() = default; + explicit nsTHashtable(uint32_t aInitLength) : Base(aInitLength) {} + + ~nsTHashtable() = default; + + nsTHashtable(nsTHashtable&&) = default; + + using Base::Clear; + using Base::Count; + using Base::GetGeneration; + using Base::IsEmpty; + + using Base::MarkImmutable; + using Base::ShallowSizeOfExcludingThis; + using Base::ShallowSizeOfIncludingThis; + + /* Wrapper functions */ + EntryType* GetEntry(T* aKey) const { + return reinterpret_cast(Base::GetEntry(aKey)); + } + + bool Contains(T* aKey) const { return Base::Contains(aKey); } + + EntryType* PutEntry(T* aKey) { + return reinterpret_cast(Base::PutEntry(aKey)); + } + + [[nodiscard]] EntryType* PutEntry(T* aKey, + const mozilla::fallible_t& aFallible) { + return reinterpret_cast(Base::PutEntry(aKey, aFallible)); + } + + [[nodiscard]] bool EnsureInserted(T* aKey, EntryType** aEntry = nullptr) { + return Base::EnsureInserted( + aKey, reinterpret_cast<::detail::VoidPtrHashKey**>(aEntry)); + } + + void RemoveEntry(T* aKey) { Base::RemoveEntry(aKey); } + + bool EnsureRemoved(T* aKey) { return Base::EnsureRemoved(aKey); } + + void RemoveEntry(EntryType* aEntry) { + Base::RemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry)); + } + + void RawRemoveEntry(EntryType* aEntry) { + Base::RawRemoveEntry(reinterpret_cast<::detail::VoidPtrHashKey*>(aEntry)); + } + + protected: + class EntryHandle : protected Base::EntryHandle { + public: + using Base = nsTHashtable::Base::EntryHandle; + + EntryHandle(EntryHandle&& aOther) = default; + ~EntryHandle() = default; + + EntryHandle(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&) = delete; + EntryHandle& operator=(const EntryHandle&&) = delete; + + using Base::Key; + + using Base::HasEntry; + + using Base::operator bool; + + EntryType* Entry() { return reinterpret_cast(Base::Entry()); } + + using Base::Insert; + + EntryType* OrInsert() { + if (!HasEntry()) { + Insert(); + } + return Entry(); + } + + using Base::Remove; + + using Base::OrRemove; + + private: + friend class nsTHashtable; + + explicit EntryHandle(Base&& aBase) : Base(std::move(aBase)) {} + }; + + template + auto WithEntryHandle(KeyType aKey, F aFunc) + -> std::invoke_result_t { + return Base::WithEntryHandle(aKey, [&aFunc](auto entryHandle) { + return aFunc(EntryHandle{std::move(entryHandle)}); + }); + } + + template + auto WithEntryHandle(KeyType aKey, const mozilla::fallible_t& aFallible, + F aFunc) + -> std::invoke_result_t&&> { + return Base::WithEntryHandle( + aKey, aFallible, [&aFunc](auto maybeEntryHandle) { + return aFunc(maybeEntryHandle ? mozilla::Some(EntryHandle{ + maybeEntryHandle.extract()}) + : mozilla::Nothing()); + }); + } + + public: + class ConstIterator { + public: + explicit ConstIterator(nsTHashtable* aTable) + : mBaseIterator(&aTable->mTable) {} + ~ConstIterator() = default; + + KeyType Key() const { return Get()->GetKey(); } + + const EntryType* Get() const { + return static_cast(mBaseIterator.Get()); + } + + bool Done() const { return mBaseIterator.Done(); } + void Next() { mBaseIterator.Next(); } + + ConstIterator() = delete; + ConstIterator(const ConstIterator&) = delete; + ConstIterator(ConstIterator&& aOther) = delete; + ConstIterator& operator=(const ConstIterator&) = delete; + ConstIterator& operator=(ConstIterator&&) = delete; + + protected: + PLDHashTable::Iterator mBaseIterator; + }; + + class Iterator final : public ConstIterator { + public: + using ConstIterator::ConstIterator; + + using ConstIterator::Get; + + EntryType* Get() const { + return static_cast(this->mBaseIterator.Get()); + } + + void Remove() { this->mBaseIterator.Remove(); } + }; + + Iterator Iter() { return Iterator(this); } + + ConstIterator ConstIter() const { + return ConstIterator(const_cast(this)); + } + + using const_iterator = ::detail::nsTHashtableEntryIterator; + using iterator = ::detail::nsTHashtableEntryIterator; + + iterator begin() { return iterator{mTable}; } + const_iterator begin() const { return const_iterator{mTable}; } + const_iterator cbegin() const { return begin(); } + iterator end() { + return iterator{mTable, typename iterator::EndIteratorTag{}}; + } + const_iterator end() const { + return const_iterator{mTable, typename const_iterator::EndIteratorTag{}}; + } + const_iterator cend() const { return end(); } + + auto Keys() const { + return ::detail::nsTHashtableKeyRange>{mTable}; + } + + void Remove(::detail::nsTHashtableKeyIterator& aIter) { + aIter.mIterator.Remove(); + } + + void SwapElements(nsTHashtable& aOther) { Base::SwapElements(aOther); } +}; + +#endif // nsTHashtable_h__ diff --git a/xpcom/ds/nsTObserverArray.cpp b/xpcom/ds/nsTObserverArray.cpp new file mode 100644 index 0000000000..68211873dd --- /dev/null +++ b/xpcom/ds/nsTObserverArray.cpp @@ -0,0 +1,27 @@ +/* -*- 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 "nsTObserverArray.h" + +void nsTObserverArray_base::AdjustIterators(index_type aModPos, + diff_type aAdjustment) { + MOZ_ASSERT(aAdjustment == -1 || aAdjustment == 1, "invalid adjustment"); + Iterator_base* iter = mIterators; + while (iter) { + if (iter->mPosition > aModPos) { + iter->mPosition += aAdjustment; + } + iter = iter->mNext; + } +} + +void nsTObserverArray_base::ClearIterators() { + Iterator_base* iter = mIterators; + while (iter) { + iter->mPosition = 0; + iter = iter->mNext; + } +} diff --git a/xpcom/ds/nsTObserverArray.h b/xpcom/ds/nsTObserverArray.h new file mode 100644 index 0000000000..4d3e4087e0 --- /dev/null +++ b/xpcom/ds/nsTObserverArray.h @@ -0,0 +1,583 @@ +/* -*- 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/. */ + +#ifndef nsTObserverArray_h___ +#define nsTObserverArray_h___ + +#include "mozilla/MemoryReporting.h" +#include "mozilla/ReverseIterator.h" +#include "nsTArray.h" +#include "nsCycleCollectionNoteChild.h" + +/** + * An array of observers. Like a normal array, but supports iterators that are + * stable even if the array is modified during iteration. + * The template parameter T is the observer type the array will hold; + * N is the number of built-in storage slots that come with the array. + * NOTE: You probably want to use nsTObserverArray, unless you specifically + * want built-in storage. See below. + * @see nsTObserverArray, nsTArray + */ + +class nsTObserverArray_base { + public: + typedef size_t index_type; + typedef size_t size_type; + typedef ptrdiff_t diff_type; + + protected: + class Iterator_base { + public: + Iterator_base(const Iterator_base&) = delete; + + protected: + friend class nsTObserverArray_base; + + Iterator_base(index_type aPosition, Iterator_base* aNext) + : mPosition(aPosition), mNext(aNext) {} + + // The current position of the iterator. Its exact meaning differs + // depending on iterator. See nsTObserverArray::ForwardIterator. + index_type mPosition; + + // The next iterator currently iterating the same array + Iterator_base* mNext; + }; + + nsTObserverArray_base() : mIterators(nullptr) {} + + ~nsTObserverArray_base() { + NS_ASSERTION(mIterators == nullptr, "iterators outlasting array"); + } + + /** + * Adjusts iterators after an element has been inserted or removed + * from the array. + * @param aModPos Position where elements were added or removed. + * @param aAdjustment -1 if an element was removed, 1 if an element was + * added. + */ + void AdjustIterators(index_type aModPos, diff_type aAdjustment); + + /** + * Clears iterators when the array is destroyed. + */ + void ClearIterators(); + + mutable Iterator_base* mIterators; +}; + +template +class nsAutoTObserverArray : protected nsTObserverArray_base { + public: + typedef T value_type; + typedef nsTArray array_type; + + nsAutoTObserverArray() = default; + + // + // Accessor methods + // + + // @return The number of elements in the array. + size_type Length() const { return mArray.Length(); } + + // @return True if the array is empty or false otherwise. + bool IsEmpty() const { return mArray.IsEmpty(); } + + // This method provides direct, readonly access to the array elements. + // @return A pointer to the first element of the array. If the array is + // empty, then this pointer must not be dereferenced. + const value_type* Elements() const { return mArray.Elements(); } + value_type* Elements() { return mArray.Elements(); } + + // This method provides direct access to an element of the array. The given + // index must be within the array bounds. If the underlying array may change + // during iteration, use an iterator instead of this function. + // @param aIndex The index of an element in the array. + // @return A reference to the i'th element of the array. + value_type& ElementAt(index_type aIndex) { return mArray.ElementAt(aIndex); } + + // Same as above, but readonly. + const value_type& ElementAt(index_type aIndex) const { + return mArray.ElementAt(aIndex); + } + + // This method provides direct access to an element of the array in a bounds + // safe manner. If the requested index is out of bounds the provided default + // value is returned. + // @param aIndex The index of an element in the array. + // @param aDef The value to return if the index is out of bounds. + value_type& SafeElementAt(index_type aIndex, value_type& aDef) { + return mArray.SafeElementAt(aIndex, aDef); + } + + // Same as above, but readonly. + const value_type& SafeElementAt(index_type aIndex, + const value_type& aDef) const { + return mArray.SafeElementAt(aIndex, aDef); + } + + // No operator[] is provided because the point of this class is to support + // allow modifying the array during iteration, and ElementAt() is not safe + // in those conditions. + + // + // Search methods + // + + // This method searches, starting from the beginning of the array, + // for the first element in this array that is equal to the given element. + // 'operator==' must be defined for value_type. + // @param aItem The item to search for. + // @return true if the element was found. + template + bool Contains(const Item& aItem) const { + return IndexOf(aItem) != array_type::NoIndex; + } + + // This method searches for the offset of the first element in this + // array that is equal to the given element. + // 'operator==' must be defined for value_type. + // @param aItem The item to search for. + // @param aStart The index to start from. + // @return The index of the found element or NoIndex if not found. + template + index_type IndexOf(const Item& aItem, index_type aStart = 0) const { + return mArray.IndexOf(aItem, aStart); + } + + // + // Mutation methods + // + + // Insert a given element at the given index. + // @param aIndex The index at which to insert item. + // @param aItem The item to insert, + template + void InsertElementAt(index_type aIndex, const Item& aItem) { + mArray.InsertElementAt(aIndex, aItem); + AdjustIterators(aIndex, 1); + } + + // Same as above but without copy constructing. + // This is useful to avoid temporaries. + value_type* InsertElementAt(index_type aIndex) { + value_type* item = mArray.InsertElementAt(aIndex); + AdjustIterators(aIndex, 1); + return item; + } + + // Prepend an element to the array unless it already exists in the array. + // 'operator==' must be defined for value_type. + // @param aItem The item to prepend. + template + void PrependElementUnlessExists(const Item& aItem) { + if (!Contains(aItem)) { + mArray.InsertElementAt(0, aItem); + AdjustIterators(0, 1); + } + } + + // Append an element to the array. + // @param aItem The item to append. + template + void AppendElement(Item&& aItem) { + mArray.AppendElement(std::forward(aItem)); + } + + // Same as above, but without copy-constructing. This is useful to avoid + // temporaries. + value_type* AppendElement() { return mArray.AppendElement(); } + + // Append an element to the array unless it already exists in the array. + // 'operator==' must be defined for value_type. + // @param aItem The item to append. + template + void AppendElementUnlessExists(const Item& aItem) { + if (!Contains(aItem)) { + mArray.AppendElement(aItem); + } + } + + // Remove an element from the array. + // @param aIndex The index of the item to remove. + void RemoveElementAt(index_type aIndex) { + NS_ASSERTION(aIndex < mArray.Length(), "invalid index"); + mArray.RemoveElementAt(aIndex); + AdjustIterators(aIndex, -1); + } + + // This helper function combines IndexOf with RemoveElementAt to "search + // and destroy" the first element that is equal to the given element. + // 'operator==' must be defined for value_type. + // @param aItem The item to search for. + // @return true if the element was found and removed. + template + bool RemoveElement(const Item& aItem) { + index_type index = mArray.IndexOf(aItem, 0); + if (index == array_type::NoIndex) { + return false; + } + + mArray.RemoveElementAt(index); + AdjustIterators(index, -1); + return true; + } + + // See nsTArray::RemoveElementsBy. Neither the predicate nor the removal of + // elements from the array must have any side effects that modify the array. + template + void NonObservingRemoveElementsBy(Predicate aPredicate) { + index_type i = 0; + mArray.RemoveElementsBy([&](const value_type& aItem) { + if (aPredicate(aItem)) { + // This element is going to be removed. + AdjustIterators(i, -1); + return true; + } + ++i; + return false; + }); + } + + // Removes all observers and collapses all iterators to the beginning of + // the array. The result is that forward iterators will see all elements + // in the array. + void Clear() { + mArray.Clear(); + ClearIterators(); + } + + // Compact the array to minimize the memory it uses + void Compact() { mArray.Compact(); } + + // Returns the number of bytes on the heap taken up by this object, not + // including sizeof(*this). If you want to measure anything hanging off the + // array, you must iterate over the elements and measure them individually; + // hence the "Shallow" prefix. + size_t ShallowSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return mArray.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + // + // Iterators + // + + // Base class for iterators. Do not use this directly. + class Iterator : public Iterator_base { + protected: + friend class nsAutoTObserverArray; + typedef nsAutoTObserverArray array_type; + + Iterator(const Iterator& aOther) + : Iterator(aOther.mPosition, aOther.mArray) {} + + Iterator(index_type aPosition, const array_type& aArray) + : Iterator_base(aPosition, aArray.mIterators), + mArray(const_cast(aArray)) { + aArray.mIterators = this; + } + + ~Iterator() { + NS_ASSERTION(mArray.mIterators == this, + "Iterators must currently be destroyed in opposite order " + "from the construction order. It is suggested that you " + "simply put them on the stack"); + mArray.mIterators = mNext; + } + + // The array we're iterating + array_type& mArray; + }; + + // Iterates the array forward from beginning to end. mPosition points + // to the element that will be returned on next call to GetNext. + // Elements: + // - prepended to the array during iteration *will not* be traversed + // - appended during iteration *will* be traversed + // - removed during iteration *will not* be traversed. + // @see EndLimitedIterator + class ForwardIterator : protected Iterator { + public: + typedef nsAutoTObserverArray array_type; + typedef Iterator base_type; + + explicit ForwardIterator(const array_type& aArray) : Iterator(0, aArray) {} + + ForwardIterator(const array_type& aArray, index_type aPos) + : Iterator(aPos, aArray) {} + + bool operator<(const ForwardIterator& aOther) const { + NS_ASSERTION(&this->mArray == &aOther.mArray, + "not iterating the same array"); + return base_type::mPosition < aOther.mPosition; + } + + // Returns true if there are more elements to iterate. + // This must precede a call to GetNext(). If false is + // returned, GetNext() must not be called. + bool HasMore() const { + return base_type::mPosition < base_type::mArray.Length(); + } + + // Returns the next element and steps one step. This must + // be preceded by a call to HasMore(). + // @return The next observer. + value_type& GetNext() { + NS_ASSERTION(HasMore(), "iterating beyond end of array"); + return base_type::mArray.Elements()[base_type::mPosition++]; + } + + // Removes the element at the current iterator position. + // (the last element returned from |GetNext()|) + // This will not affect the next call to |GetNext()| + void Remove() { + return base_type::mArray.RemoveElementAt(base_type::mPosition - 1); + } + }; + + // EndLimitedIterator works like ForwardIterator, but will not iterate new + // observers appended to the array after the iterator was created. + class EndLimitedIterator : protected ForwardIterator { + public: + typedef nsAutoTObserverArray array_type; + typedef Iterator base_type; + + explicit EndLimitedIterator(const array_type& aArray) + : ForwardIterator(aArray), mEnd(aArray, aArray.Length()) {} + + // Returns true if there are more elements to iterate. + // This must precede a call to GetNext(). If false is + // returned, GetNext() must not be called. + bool HasMore() const { return *this < mEnd; } + + // Returns the next element and steps one step. This must + // be preceded by a call to HasMore(). + // @return The next observer. + value_type& GetNext() { + NS_ASSERTION(HasMore(), "iterating beyond end of array"); + return base_type::mArray.Elements()[base_type::mPosition++]; + } + + // Removes the element at the current iterator position. + // (the last element returned from |GetNext()|) + // This will not affect the next call to |GetNext()| + void Remove() { + return base_type::mArray.RemoveElementAt(base_type::mPosition - 1); + } + + private: + ForwardIterator mEnd; + }; + + // Iterates the array backward from end to start. mPosition points + // to the element that was returned last. + // Elements: + // - prepended to the array during iteration *will* be traversed, + // unless the iteration already arrived at the first element + // - appended during iteration *will not* be traversed + // - removed during iteration *will not* be traversed. + class BackwardIterator : protected Iterator { + public: + typedef nsAutoTObserverArray array_type; + typedef Iterator base_type; + + explicit BackwardIterator(const array_type& aArray) + : Iterator(aArray.Length(), aArray) {} + + // Returns true if there are more elements to iterate. + // This must precede a call to GetNext(). If false is + // returned, GetNext() must not be called. + bool HasMore() const { return base_type::mPosition > 0; } + + // Returns the next element and steps one step. This must + // be preceded by a call to HasMore(). + // @return The next observer. + value_type& GetNext() { + NS_ASSERTION(HasMore(), "iterating beyond start of array"); + return base_type::mArray.Elements()[--base_type::mPosition]; + } + + // Removes the element at the current iterator position. + // (the last element returned from |GetNext()|) + // This will not affect the next call to |GetNext()| + void Remove() { + return base_type::mArray.RemoveElementAt(base_type::mPosition); + } + }; + + struct EndSentinel {}; + + // Internal type, do not use directly, see + // ForwardRange()/EndLimitedRange()/BackwardRange(). + template + struct STLIterator { + using value_type = std::remove_reference_t; + + explicit STLIterator(const nsAutoTObserverArray& aArray) + : mIterator{aArray} { + operator++(); + } + + bool operator!=(const EndSentinel&) const { + // We are a non-end-sentinel and the other is an end-sentinel, so we are + // still valid if mCurrent is valid. + return mCurrent; + } + + STLIterator& operator++() { + mCurrent = mIterator.HasMore() ? &mIterator.GetNext() : nullptr; + return *this; + } + + value_type* operator->() { return mCurrent; } + U& operator*() { return *mCurrent; } + + private: + Iterator mIterator; + value_type* mCurrent; + }; + + // Internal type, do not use directly, see + // ForwardRange()/EndLimitedRange()/BackwardRange(). + template + class STLIteratorRange { + public: + using iterator = STLIterator; + + explicit STLIteratorRange(const nsAutoTObserverArray& aArray) + : mArray{aArray} {} + + STLIteratorRange(const STLIteratorRange& aOther) = delete; + + iterator begin() const { return iterator{mArray}; } + EndSentinel end() const { return {}; } + + private: + const nsAutoTObserverArray& mArray; + }; + + template + using STLForwardIteratorRange = STLIteratorRange; + + template + using STLEndLimitedIteratorRange = STLIteratorRange; + + template + using STLBackwardIteratorRange = STLIteratorRange; + + // Constructs a range (usable with range-based for) based on the + // ForwardIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto ForwardRange() { return STLForwardIteratorRange{*this}; } + + // Constructs a const range (usable with range-based for) based on the + // ForwardIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto ForwardRange() const { return STLForwardIteratorRange{*this}; } + + // Constructs a range (usable with range-based for) based on the + // EndLimitedIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto EndLimitedRange() { return STLEndLimitedIteratorRange{*this}; } + + // Constructs a const range (usable with range-based for) based on the + // EndLimitedIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto EndLimitedRange() const { + return STLEndLimitedIteratorRange{*this}; + } + + // Constructs a range (usable with range-based for) based on the + // BackwardIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto BackwardRange() { return STLBackwardIteratorRange{*this}; } + + // Constructs a const range (usable with range-based for) based on the + // BackwardIterator semantics. Note that this range does not provide + // full-feature STL-style iterators usable with STL-style algorithms. + auto BackwardRange() const { + return STLBackwardIteratorRange{*this}; + } + + // Constructs a const range (usable with range-based for and STL-style + // algorithms) based on a non-observing iterator. The array must not be + // modified during iteration. + auto NonObservingRange() const { + return mozilla::detail::IteratorRange< + typename AutoTArray::const_iterator, + typename AutoTArray::const_reverse_iterator>{mArray.cbegin(), + mArray.cend()}; + } + + protected: + AutoTArray mArray; +}; + +template +class nsTObserverArray : public nsAutoTObserverArray { + public: + typedef nsAutoTObserverArray base_type; + typedef nsTObserverArray_base::size_type size_type; + + // + // Initialization methods + // + + nsTObserverArray() = default; + + // Initialize this array and pre-allocate some number of elements. + explicit nsTObserverArray(size_type aCapacity) { + base_type::mArray.SetCapacity(aCapacity); + } + + nsTObserverArray Clone() const { + auto result = nsTObserverArray{}; + result.mArray.Assign(this->mArray); + return result; + } +}; + +template +auto MakeBackInserter(nsAutoTObserverArray& aArray) { + return mozilla::nsTArrayBackInserter>{aArray}; +} + +template +inline void ImplCycleCollectionUnlink(nsAutoTObserverArray& aField) { + aField.Clear(); +} + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsAutoTObserverArray& aField, const char* aName, + uint32_t aFlags = 0) { + aFlags |= CycleCollectionEdgeNameArrayFlag; + size_t length = aField.Length(); + for (size_t i = 0; i < length; ++i) { + ImplCycleCollectionTraverse(aCallback, aField.ElementAt(i), aName, aFlags); + } +} + +// Note that this macro only works if the array holds pointers to XPCOM objects. +#define NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(array_, func_, params_) \ + do { \ + for (RefPtr obs_ : (array_).ForwardRange()) { \ + obs_->func_ params_; \ + } \ + } while (0) + +// Note that this macro only works if the array holds pointers to XPCOM objects. +#define NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(array_, func_, params_) \ + do { \ + for (auto* obs_ : (array_).ForwardRange()) { \ + obs_->func_ params_; \ + } \ + } while (0) + +#endif // nsTObserverArray_h___ diff --git a/xpcom/ds/nsTPriorityQueue.h b/xpcom/ds/nsTPriorityQueue.h new file mode 100644 index 0000000000..1fa1cc1e50 --- /dev/null +++ b/xpcom/ds/nsTPriorityQueue.h @@ -0,0 +1,147 @@ +/* -*- 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/. */ + +#ifndef NS_TPRIORITY_QUEUE_H_ +#define NS_TPRIORITY_QUEUE_H_ + +#include "mozilla/Assertions.h" +#include "nsTArray.h" + +/** + * A templatized priority queue data structure that uses an nsTArray to serve as + * a binary heap. The default comparator causes this to act like a min-heap. + * Only the LessThan method of the comparator is used. + */ +template > +class nsTPriorityQueue { + public: + typedef typename nsTArray::size_type size_type; + + /** + * Default constructor also creates a comparator object using the default + * constructor for type Compare. + */ + nsTPriorityQueue() : mCompare(Compare()) {} + + /** + * Constructor to allow a specific instance of a comparator object to be + * used. + */ + explicit nsTPriorityQueue(const Compare& aComp) : mCompare(aComp) {} + + nsTPriorityQueue(nsTPriorityQueue&&) = default; + nsTPriorityQueue& operator=(nsTPriorityQueue&&) = default; + + /** + * @return True if the queue is empty or false otherwise. + */ + bool IsEmpty() const { return mElements.IsEmpty(); } + + /** + * @return The number of elements in the queue. + */ + size_type Length() const { return mElements.Length(); } + + /** + * @return The topmost element in the queue without changing the queue. This + * is the element 'a' such that there is no other element 'b' in the queue for + * which Compare(b, a) returns true. (Since this container does not check + * for duplicate entries there may exist 'b' for which Compare(a, b) returns + * false.) + */ + const T& Top() const { + MOZ_ASSERT(!mElements.IsEmpty(), "Empty queue"); + return mElements[0]; + } + + /** + * Adds an element to the queue + * @param aElement The element to add + * @return true on success, false on out of memory. + */ + void Push(T&& aElement) { + mElements.AppendElement(std::move(aElement)); + + // Sift up + size_type i = mElements.Length() - 1; + while (i) { + size_type parent = (size_type)((i - 1) / 2); + if (mCompare.LessThan(mElements[parent], mElements[i])) { + break; + } + std::swap(mElements[i], mElements[parent]); + i = parent; + } + } + + /** + * Removes and returns the top-most element from the queue. + * @return The topmost element, that is, the element 'a' such that there is no + * other element 'b' in the queue for which Compare(b, a) returns true. + * @see Top() + */ + T Pop() { + MOZ_ASSERT(!mElements.IsEmpty(), "Empty queue"); + T pop = std::move(mElements[0]); + + const size_type newLength = mElements.Length() - 1; + if (newLength == 0) { + mElements.Clear(); + return pop; + } + + // Move last to front + mElements[0] = mElements.PopLastElement(); + + // Sift down + size_type i = 0; + while (2 * i + 1 < newLength) { + size_type swap = i; + size_type l_child = 2 * i + 1; + if (mCompare.LessThan(mElements[l_child], mElements[i])) { + swap = l_child; + } + size_type r_child = l_child + 1; + if (r_child < newLength && + mCompare.LessThan(mElements[r_child], mElements[swap])) { + swap = r_child; + } + if (swap == i) { + break; + } + std::swap(mElements[i], mElements[swap]); + i = swap; + } + + return pop; + } + + /** + * Removes all elements from the queue. + */ + void Clear() { mElements.Clear(); } + + /** + * Provides readonly access to the queue elements as an array. Generally this + * should be avoided but may be needed in some situations such as when the + * elements contained in the queue need to be enumerated for cycle-collection. + * @return A pointer to the first element of the array. If the array is + * empty, then this pointer must not be dereferenced. + */ + const T* Elements() const { return mElements.Elements(); } + + nsTPriorityQueue Clone() const { + auto res = nsTPriorityQueue{mCompare}; + res.mElements = mElements.Clone(); + return res; + } + + protected: + nsTArray mElements; + Compare mCompare; // Comparator object +}; + +#endif // NS_TPRIORITY_QUEUE_H_ diff --git a/xpcom/ds/nsVariant.cpp b/xpcom/ds/nsVariant.cpp new file mode 100644 index 0000000000..f8a40431c7 --- /dev/null +++ b/xpcom/ds/nsVariant.cpp @@ -0,0 +1,1876 @@ +/* -*- 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 "nsVariant.h" +#include "prprf.h" +#include "prdtoa.h" +#include +#include "nsCycleCollectionParticipant.h" +#include "xptinfo.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsCRTGlue.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Printf.h" + +/***************************************************************************/ +// Helpers for static convert functions... + +static nsresult String2Double(const char* aString, double* aResult) { + char* next; + double value = PR_strtod(aString, &next); + if (next == aString) { + return NS_ERROR_CANNOT_CONVERT_DATA; + } + *aResult = value; + return NS_OK; +} + +static nsresult AString2Double(const nsAString& aString, double* aResult) { + char* pChars = ToNewCString(aString, mozilla::fallible); + if (!pChars) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsresult rv = String2Double(pChars, aResult); + free(pChars); + return rv; +} + +static nsresult AUTF8String2Double(const nsAUTF8String& aString, + double* aResult) { + return String2Double(PromiseFlatUTF8String(aString).get(), aResult); +} + +static nsresult ACString2Double(const nsACString& aString, double* aResult) { + return String2Double(PromiseFlatCString(aString).get(), aResult); +} + +// Fills aOutData with double, uint32_t, or int32_t. +// Returns NS_OK, an error code, or a non-NS_OK success code +nsresult nsDiscriminatedUnion::ToManageableNumber( + nsDiscriminatedUnion* aOutData) const { + nsresult rv; + + switch (mType) { + // This group results in a int32_t... + +#define CASE__NUMBER_INT32(type_, member_) \ + case nsIDataType::type_: \ + aOutData->u.mInt32Value = u.member_; \ + aOutData->mType = nsIDataType::VTYPE_INT32; \ + return NS_OK; + + CASE__NUMBER_INT32(VTYPE_INT8, mInt8Value) + CASE__NUMBER_INT32(VTYPE_INT16, mInt16Value) + CASE__NUMBER_INT32(VTYPE_INT32, mInt32Value) + CASE__NUMBER_INT32(VTYPE_UINT8, mUint8Value) + CASE__NUMBER_INT32(VTYPE_UINT16, mUint16Value) + CASE__NUMBER_INT32(VTYPE_BOOL, mBoolValue) + CASE__NUMBER_INT32(VTYPE_CHAR, mCharValue) + CASE__NUMBER_INT32(VTYPE_WCHAR, mWCharValue) + +#undef CASE__NUMBER_INT32 + + // This group results in a uint32_t... + + case nsIDataType::VTYPE_UINT32: + aOutData->u.mUint32Value = u.mUint32Value; + aOutData->mType = nsIDataType::VTYPE_UINT32; + return NS_OK; + + // This group results in a double... + + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT64: + // XXX Need boundary checking here. + // We may need to return NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA + aOutData->u.mDoubleValue = double(u.mInt64Value); + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_FLOAT: + aOutData->u.mDoubleValue = u.mFloatValue; + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_DOUBLE: + aOutData->u.mDoubleValue = u.mDoubleValue; + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + rv = String2Double(u.str.mStringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_ASTRING: + rv = AString2Double(*u.mAStringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + rv = AUTF8String2Double(*u.mUTF8StringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + rv = ACString2Double(*u.mCStringValue, &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + rv = AString2Double(nsDependentString(u.wstr.mWStringValue), + &aOutData->u.mDoubleValue); + if (NS_FAILED(rv)) { + return rv; + } + aOutData->mType = nsIDataType::VTYPE_DOUBLE; + return NS_OK; + + // This group fails... + + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +/***************************************************************************/ +// Array helpers... + +void nsDiscriminatedUnion::FreeArray() { + NS_ASSERTION(mType == nsIDataType::VTYPE_ARRAY, "bad FreeArray call"); + NS_ASSERTION(u.array.mArrayValue, "bad array"); + NS_ASSERTION(u.array.mArrayCount, "bad array count"); + +#define CASE__FREE_ARRAY_PTR(type_, ctype_) \ + case nsIDataType::type_: { \ + ctype_** p = (ctype_**)u.array.mArrayValue; \ + for (uint32_t i = u.array.mArrayCount; i > 0; p++, i--) \ + if (*p) free((char*)*p); \ + break; \ + } + +#define CASE__FREE_ARRAY_IFACE(type_, ctype_) \ + case nsIDataType::type_: { \ + ctype_** p = (ctype_**)u.array.mArrayValue; \ + for (uint32_t i = u.array.mArrayCount; i > 0; p++, i--) \ + if (*p) (*p)->Release(); \ + break; \ + } + + switch (u.array.mArrayType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + break; + + // XXX We ASSUME that "array of nsID" means "array of pointers to nsID". + CASE__FREE_ARRAY_PTR(VTYPE_ID, nsID) + CASE__FREE_ARRAY_PTR(VTYPE_CHAR_STR, char) + CASE__FREE_ARRAY_PTR(VTYPE_WCHAR_STR, char16_t) + CASE__FREE_ARRAY_IFACE(VTYPE_INTERFACE, nsISupports) + CASE__FREE_ARRAY_IFACE(VTYPE_INTERFACE_IS, nsISupports) + + // The rest are illegal. + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + NS_ERROR("bad type in array!"); + break; + } + + // Free the array memory. + free((char*)u.array.mArrayValue); + +#undef CASE__FREE_ARRAY_PTR +#undef CASE__FREE_ARRAY_IFACE +} + +static nsresult CloneArray(uint16_t aInType, const nsIID* aInIID, + uint32_t aInCount, void* aInValue, + uint16_t* aOutType, nsIID* aOutIID, + uint32_t* aOutCount, void** aOutValue) { + NS_ASSERTION(aInCount, "bad param"); + NS_ASSERTION(aInValue, "bad param"); + NS_ASSERTION(aOutType, "bad param"); + NS_ASSERTION(aOutCount, "bad param"); + NS_ASSERTION(aOutValue, "bad param"); + + uint32_t i; + + // First we figure out the size of the elements for the new u.array. + + size_t elementSize; + size_t allocSize; + + switch (aInType) { + case nsIDataType::VTYPE_INT8: + elementSize = sizeof(int8_t); + break; + case nsIDataType::VTYPE_INT16: + elementSize = sizeof(int16_t); + break; + case nsIDataType::VTYPE_INT32: + elementSize = sizeof(int32_t); + break; + case nsIDataType::VTYPE_INT64: + elementSize = sizeof(int64_t); + break; + case nsIDataType::VTYPE_UINT8: + elementSize = sizeof(uint8_t); + break; + case nsIDataType::VTYPE_UINT16: + elementSize = sizeof(uint16_t); + break; + case nsIDataType::VTYPE_UINT32: + elementSize = sizeof(uint32_t); + break; + case nsIDataType::VTYPE_UINT64: + elementSize = sizeof(uint64_t); + break; + case nsIDataType::VTYPE_FLOAT: + elementSize = sizeof(float); + break; + case nsIDataType::VTYPE_DOUBLE: + elementSize = sizeof(double); + break; + case nsIDataType::VTYPE_BOOL: + elementSize = sizeof(bool); + break; + case nsIDataType::VTYPE_CHAR: + elementSize = sizeof(char); + break; + case nsIDataType::VTYPE_WCHAR: + elementSize = sizeof(char16_t); + break; + + // XXX We ASSUME that "array of nsID" means "array of pointers to nsID". + case nsIDataType::VTYPE_ID: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + elementSize = sizeof(void*); + break; + + // The rest are illegal. + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + default: + NS_ERROR("bad type in array!"); + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + // Alloc the u.array. + + allocSize = aInCount * elementSize; + *aOutValue = moz_xmalloc(allocSize); + + // Clone the elements. + + switch (aInType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + memcpy(*aOutValue, aInValue, allocSize); + break; + + case nsIDataType::VTYPE_INTERFACE_IS: + if (aOutIID) { + *aOutIID = *aInIID; + } + [[fallthrough]]; + + case nsIDataType::VTYPE_INTERFACE: { + memcpy(*aOutValue, aInValue, allocSize); + + nsISupports** p = (nsISupports**)*aOutValue; + for (i = aInCount; i > 0; ++p, --i) + if (*p) { + (*p)->AddRef(); + } + break; + } + + // XXX We ASSUME that "array of nsID" means "array of pointers to nsID". + case nsIDataType::VTYPE_ID: { + nsID** inp = (nsID**)aInValue; + nsID** outp = (nsID**)*aOutValue; + for (i = aInCount; i > 0; --i) { + nsID* idp = *(inp++); + if (idp) { + *(outp++) = idp->Clone(); + } else { + *(outp++) = nullptr; + } + } + break; + } + + case nsIDataType::VTYPE_CHAR_STR: { + char** inp = (char**)aInValue; + char** outp = (char**)*aOutValue; + for (i = aInCount; i > 0; i--) { + char* str = *(inp++); + if (str) { + *(outp++) = moz_xstrdup(str); + } else { + *(outp++) = nullptr; + } + } + break; + } + + case nsIDataType::VTYPE_WCHAR_STR: { + char16_t** inp = (char16_t**)aInValue; + char16_t** outp = (char16_t**)*aOutValue; + for (i = aInCount; i > 0; i--) { + char16_t* str = *(inp++); + if (str) { + *(outp++) = NS_xstrdup(str); + } else { + *(outp++) = nullptr; + } + } + break; + } + + // The rest are illegal. + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + default: + NS_ERROR("bad type in array!"); + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + *aOutType = aInType; + *aOutCount = aInCount; + return NS_OK; +} + +/***************************************************************************/ + +#define TRIVIAL_DATA_CONVERTER(type_, member_, retval_) \ + if (mType == nsIDataType::type_) { \ + *retval_ = u.member_; \ + return NS_OK; \ + } + +#define NUMERIC_CONVERSION_METHOD_BEGIN(type_, Ctype_, name_) \ + nsresult nsDiscriminatedUnion::ConvertTo##name_(Ctype_* aResult) const { \ + TRIVIAL_DATA_CONVERTER(type_, m##name_##Value, aResult) \ + nsDiscriminatedUnion tempData; \ + nsresult rv = ToManageableNumber(&tempData); \ + /* */ \ + /* NOTE: rv may indicate a success code that we want to preserve */ \ + /* For the final return. So all the return cases below should return */ \ + /* this rv when indicating success. */ \ + /* */ \ + if (NS_FAILED(rv)) return rv; \ + switch (tempData.mType) { +#define CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(Ctype_) \ + case nsIDataType::VTYPE_INT32: \ + *aResult = (Ctype_)tempData.u.mInt32Value; \ + return rv; + +#define CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(Ctype_, min_, max_) \ + case nsIDataType::VTYPE_INT32: { \ + int32_t value = tempData.u.mInt32Value; \ + if (value < (min_) || value > (max_)) { \ + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + } \ + *aResult = (Ctype_)value; \ + return rv; \ + } + +#define CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(Ctype_) \ + case nsIDataType::VTYPE_UINT32: \ + *aResult = (Ctype_)tempData.u.mUint32Value; \ + return rv; + +#define CASE__NUMERIC_CONVERSION_UINT32_MAX(Ctype_, max_) \ + case nsIDataType::VTYPE_UINT32: { \ + uint32_t value = tempData.u.mUint32Value; \ + if (value > (max_)) return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + *aResult = (Ctype_)value; \ + return rv; \ + } + +#define CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(Ctype_) \ + case nsIDataType::VTYPE_DOUBLE: \ + *aResult = (Ctype_)tempData.u.mDoubleValue; \ + return rv; + +#define CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(Ctype_, min_, max_) \ + case nsIDataType::VTYPE_DOUBLE: { \ + double value = tempData.u.mDoubleValue; \ + if (std::isnan(value) || value < (min_) || value > (max_)) { \ + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; \ + } \ + *aResult = (Ctype_)value; \ + return (0.0 == fmod(value, 1.0)) ? rv \ + : NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA; \ + } + +#define CASES__NUMERIC_CONVERSION_NORMAL(Ctype_, min_, max_) \ + CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(Ctype_, min_, max_) \ + CASE__NUMERIC_CONVERSION_UINT32_MAX(Ctype_, max_) \ + CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(Ctype_, min_, max_) + +#define NUMERIC_CONVERSION_METHOD_END \ + default: \ + NS_ERROR("bad type returned from ToManageableNumber"); \ + return NS_ERROR_CANNOT_CONVERT_DATA; \ + } \ + } + +#define NUMERIC_CONVERSION_METHOD_NORMAL(type_, Ctype_, name_, min_, max_) \ + NUMERIC_CONVERSION_METHOD_BEGIN(type_, Ctype_, name_) \ + CASES__NUMERIC_CONVERSION_NORMAL(Ctype_, min_, max_) \ + NUMERIC_CONVERSION_METHOD_END + +/***************************************************************************/ +// These expand into full public methods... + +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_INT8, uint8_t, Int8, (-127 - 1), 127) +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_INT16, int16_t, Int16, (-32767 - 1), + 32767) + +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_INT32, int32_t, Int32) +CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(int32_t) +CASE__NUMERIC_CONVERSION_UINT32_MAX(int32_t, 2147483647) +CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(int32_t, (-2147483647 - 1), + 2147483647) +NUMERIC_CONVERSION_METHOD_END + +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_UINT8, uint8_t, Uint8, 0, 255) +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_UINT16, uint16_t, Uint16, 0, 65535) + +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_UINT32, uint32_t, Uint32) +CASE__NUMERIC_CONVERSION_INT32_MIN_MAX(uint32_t, 0, 2147483647) +CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(uint32_t) +CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT(uint32_t, 0, 4294967295U) +NUMERIC_CONVERSION_METHOD_END + +// XXX toFloat conversions need to be fixed! +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_FLOAT, float, Float) +CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(float) +CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(float) +CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(float) +NUMERIC_CONVERSION_METHOD_END + +NUMERIC_CONVERSION_METHOD_BEGIN(VTYPE_DOUBLE, double, Double) +CASE__NUMERIC_CONVERSION_INT32_JUST_CAST(double) +CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST(double) +CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST(double) +NUMERIC_CONVERSION_METHOD_END + +// XXX toChar conversions need to be fixed! +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_CHAR, char, Char, CHAR_MIN, CHAR_MAX) + +// XXX toWChar conversions need to be fixed! +NUMERIC_CONVERSION_METHOD_NORMAL(VTYPE_WCHAR, char16_t, WChar, 0, + std::numeric_limits::max()) + +#undef NUMERIC_CONVERSION_METHOD_BEGIN +#undef CASE__NUMERIC_CONVERSION_INT32_JUST_CAST +#undef CASE__NUMERIC_CONVERSION_INT32_MIN_MAX +#undef CASE__NUMERIC_CONVERSION_UINT32_JUST_CAST +#undef CASE__NUMERIC_CONVERSION_UINT32_MIN_MAX +#undef CASE__NUMERIC_CONVERSION_DOUBLE_JUST_CAST +#undef CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX +#undef CASE__NUMERIC_CONVERSION_DOUBLE_MIN_MAX_INT +#undef CASES__NUMERIC_CONVERSION_NORMAL +#undef NUMERIC_CONVERSION_METHOD_END +#undef NUMERIC_CONVERSION_METHOD_NORMAL + +/***************************************************************************/ + +// Just leverage a numeric converter for bool (but restrict the values). +// XXX Is this really what we want to do? + +nsresult nsDiscriminatedUnion::ConvertToBool(bool* aResult) const { + TRIVIAL_DATA_CONVERTER(VTYPE_BOOL, mBoolValue, aResult) + + double val; + nsresult rv = ConvertToDouble(&val); + if (NS_FAILED(rv)) { + return rv; + } + // NaN is falsy in JS, so we might as well make it false here. + if (std::isnan(val)) { + *aResult = false; + } else { + *aResult = 0.0 != val; + } + return rv; +} + +/***************************************************************************/ + +nsresult nsDiscriminatedUnion::ConvertToInt64(int64_t* aResult) const { + TRIVIAL_DATA_CONVERTER(VTYPE_INT64, mInt64Value, aResult) + TRIVIAL_DATA_CONVERTER(VTYPE_UINT64, mUint64Value, aResult) + + nsDiscriminatedUnion tempData; + nsresult rv = ToManageableNumber(&tempData); + if (NS_FAILED(rv)) { + return rv; + } + switch (tempData.mType) { + case nsIDataType::VTYPE_INT32: + *aResult = tempData.u.mInt32Value; + return rv; + case nsIDataType::VTYPE_UINT32: + *aResult = tempData.u.mUint32Value; + return rv; + case nsIDataType::VTYPE_DOUBLE: { + double value = tempData.u.mDoubleValue; + if (std::isnan(value)) { + return NS_ERROR_LOSS_OF_SIGNIFICANT_DATA; + } + // XXX should check for data loss here! + *aResult = value; + return rv; + } + default: + NS_ERROR("bad type returned from ToManageableNumber"); + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +nsresult nsDiscriminatedUnion::ConvertToUint64(uint64_t* aResult) const { + return ConvertToInt64((int64_t*)aResult); +} + +/***************************************************************************/ + +bool nsDiscriminatedUnion::String2ID(nsID* aPid) const { + nsAutoString tempString; + nsAString* pString; + + switch (mType) { + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + return aPid->Parse(u.str.mStringValue); + case nsIDataType::VTYPE_CSTRING: + return aPid->Parse(PromiseFlatCString(*u.mCStringValue).get()); + case nsIDataType::VTYPE_UTF8STRING: + return aPid->Parse(PromiseFlatUTF8String(*u.mUTF8StringValue).get()); + case nsIDataType::VTYPE_ASTRING: + pString = u.mAStringValue; + break; + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + tempString.Assign(u.wstr.mWStringValue); + pString = &tempString; + break; + default: + NS_ERROR("bad type in call to String2ID"); + return false; + } + + char* pChars = ToNewCString(*pString, mozilla::fallible); + if (!pChars) { + return false; + } + bool result = aPid->Parse(pChars); + free(pChars); + return result; +} + +nsresult nsDiscriminatedUnion::ConvertToID(nsID* aResult) const { + nsID id; + + switch (mType) { + case nsIDataType::VTYPE_ID: + *aResult = u.mIDValue; + return NS_OK; + case nsIDataType::VTYPE_INTERFACE: + *aResult = NS_GET_IID(nsISupports); + return NS_OK; + case nsIDataType::VTYPE_INTERFACE_IS: + *aResult = u.iface.mInterfaceID; + return NS_OK; + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + if (!String2ID(&id)) { + return NS_ERROR_CANNOT_CONVERT_DATA; + } + *aResult = id; + return NS_OK; + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +/***************************************************************************/ + +nsresult nsDiscriminatedUnion::ToString(nsACString& aOutString) const { + mozilla::SmprintfPointer pptr; + + switch (mType) { + // all the stuff we don't handle... + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_UTF8STRING: + case nsIDataType::VTYPE_CSTRING: + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + case nsIDataType::VTYPE_WCHAR: + NS_ERROR("ToString being called for a string type - screwy logic!"); + [[fallthrough]]; + + // XXX We might want stringified versions of these... ??? + + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_EMPTY: + aOutString.SetIsVoid(true); + return NS_OK; + + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_ARRAY: + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + + // nsID has its own text formatter. + + case nsIDataType::VTYPE_ID: { + aOutString.Assign(u.mIDValue.ToString().get()); + return NS_OK; + } + + // Can't use Smprintf for floats, since it's locale-dependent +#define CASE__APPENDFLOAT_NUMBER(type_, member_) \ + case nsIDataType::type_: { \ + nsAutoCString str; \ + str.AppendFloat(u.member_); \ + aOutString.Assign(str); \ + return NS_OK; \ + } + + CASE__APPENDFLOAT_NUMBER(VTYPE_FLOAT, mFloatValue) + CASE__APPENDFLOAT_NUMBER(VTYPE_DOUBLE, mDoubleValue) + +#undef CASE__APPENDFLOAT_NUMBER + + // the rest can be Smprintf'd and use common code. + +#define CASE__SMPRINTF_NUMBER(type_, format_, cast_, member_) \ + case nsIDataType::type_: \ + static_assert(sizeof(cast_) >= sizeof(u.member_), \ + "size of type should be at least as big as member"); \ + pptr = mozilla::Smprintf(format_, (cast_)u.member_); \ + break; + + CASE__SMPRINTF_NUMBER(VTYPE_INT8, "%d", int, mInt8Value) + CASE__SMPRINTF_NUMBER(VTYPE_INT16, "%d", int, mInt16Value) + CASE__SMPRINTF_NUMBER(VTYPE_INT32, "%d", int, mInt32Value) + CASE__SMPRINTF_NUMBER(VTYPE_INT64, "%" PRId64, int64_t, mInt64Value) + + CASE__SMPRINTF_NUMBER(VTYPE_UINT8, "%u", unsigned, mUint8Value) + CASE__SMPRINTF_NUMBER(VTYPE_UINT16, "%u", unsigned, mUint16Value) + CASE__SMPRINTF_NUMBER(VTYPE_UINT32, "%u", unsigned, mUint32Value) + CASE__SMPRINTF_NUMBER(VTYPE_UINT64, "%" PRIu64, int64_t, mUint64Value) + + // XXX Would we rather print "true" / "false" ? + CASE__SMPRINTF_NUMBER(VTYPE_BOOL, "%d", int, mBoolValue) + + CASE__SMPRINTF_NUMBER(VTYPE_CHAR, "%c", char, mCharValue) + +#undef CASE__SMPRINTF_NUMBER + } + + if (!pptr) { + return NS_ERROR_OUT_OF_MEMORY; + } + aOutString.Assign(pptr.get()); + return NS_OK; +} + +nsresult nsDiscriminatedUnion::ConvertToAString(nsAString& aResult) const { + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + aResult.Assign(*u.mAStringValue); + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + CopyASCIItoUTF16(*u.mCStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + CopyUTF8toUTF16(*u.mUTF8StringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + CopyASCIItoUTF16(mozilla::MakeStringSpan(u.str.mStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + aResult.Assign(u.wstr.mWStringValue); + return NS_OK; + case nsIDataType::VTYPE_STRING_SIZE_IS: + CopyASCIItoUTF16( + nsDependentCString(u.str.mStringValue, u.str.mStringLength), aResult); + return NS_OK; + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + aResult.Assign(u.wstr.mWStringValue, u.wstr.mWStringLength); + return NS_OK; + case nsIDataType::VTYPE_WCHAR: + aResult.Assign(u.mWCharValue); + return NS_OK; + default: { + nsAutoCString tempCString; + nsresult rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + CopyASCIItoUTF16(tempCString, aResult); + return NS_OK; + } + } +} + +nsresult nsDiscriminatedUnion::ConvertToACString(nsACString& aResult) const { + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + LossyCopyUTF16toASCII(*u.mAStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + aResult.Assign(*u.mCStringValue); + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + // XXX This is an extra copy that should be avoided + // once Jag lands support for UTF8String and associated + // conversion methods. + LossyCopyUTF16toASCII(NS_ConvertUTF8toUTF16(*u.mUTF8StringValue), + aResult); + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + aResult.Assign(*u.str.mStringValue); + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + LossyCopyUTF16toASCII(nsDependentString(u.wstr.mWStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_STRING_SIZE_IS: + aResult.Assign(u.str.mStringValue, u.str.mStringLength); + return NS_OK; + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + LossyCopyUTF16toASCII( + nsDependentString(u.wstr.mWStringValue, u.wstr.mWStringLength), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR: { + const char16_t* str = &u.mWCharValue; + LossyCopyUTF16toASCII(Substring(str, 1), aResult); + return NS_OK; + } + default: + return ToString(aResult); + } +} + +nsresult nsDiscriminatedUnion::ConvertToAUTF8String( + nsAUTF8String& aResult) const { + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + CopyUTF16toUTF8(*u.mAStringValue, aResult); + return NS_OK; + case nsIDataType::VTYPE_CSTRING: + // XXX Extra copy, can be removed if we're sure CSTRING can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(*u.mCStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_UTF8STRING: + aResult.Assign(*u.mUTF8StringValue); + return NS_OK; + case nsIDataType::VTYPE_CHAR_STR: + // XXX Extra copy, can be removed if we're sure CHAR_STR can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(u.str.mStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR_STR: + CopyUTF16toUTF8(mozilla::MakeStringSpan(u.wstr.mWStringValue), aResult); + return NS_OK; + case nsIDataType::VTYPE_STRING_SIZE_IS: + // XXX Extra copy, can be removed if we're sure CHAR_STR can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(nsDependentCString( + u.str.mStringValue, u.str.mStringLength)), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + CopyUTF16toUTF8( + nsDependentString(u.wstr.mWStringValue, u.wstr.mWStringLength), + aResult); + return NS_OK; + case nsIDataType::VTYPE_WCHAR: { + const char16_t* str = &u.mWCharValue; + CopyUTF16toUTF8(Substring(str, 1), aResult); + return NS_OK; + } + default: { + nsAutoCString tempCString; + nsresult rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + // XXX Extra copy, can be removed if we're sure tempCString can + // only contain ASCII. + CopyUTF16toUTF8(NS_ConvertASCIItoUTF16(tempCString), aResult); + return NS_OK; + } + } +} + +nsresult nsDiscriminatedUnion::ConvertToString(char** aResult) const { + uint32_t ignored; + return ConvertToStringWithSize(&ignored, aResult); +} + +nsresult nsDiscriminatedUnion::ConvertToWString(char16_t** aResult) const { + uint32_t ignored; + return ConvertToWStringWithSize(&ignored, aResult); +} + +nsresult nsDiscriminatedUnion::ConvertToStringWithSize(uint32_t* aSize, + char** aStr) const { + nsAutoString tempString; + nsAutoCString tempCString; + nsresult rv; + + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + *aSize = u.mAStringValue->Length(); + *aStr = ToNewCString(*u.mAStringValue); + break; + case nsIDataType::VTYPE_CSTRING: + *aSize = u.mCStringValue->Length(); + *aStr = ToNewCString(*u.mCStringValue); + break; + case nsIDataType::VTYPE_UTF8STRING: { + // XXX This is doing 1 extra copy. Need to fix this + // when Jag lands UTF8String + // we want: + // *aSize = *mUTF8StringValue->Length(); + // *aStr = ToNewCString(*mUTF8StringValue); + // But this will have to do for now. + const NS_ConvertUTF8toUTF16 tempString16(*u.mUTF8StringValue); + *aSize = tempString16.Length(); + *aStr = ToNewCString(tempString16); + break; + } + case nsIDataType::VTYPE_CHAR_STR: { + nsDependentCString cString(u.str.mStringValue); + *aSize = cString.Length(); + *aStr = ToNewCString(cString); + break; + } + case nsIDataType::VTYPE_WCHAR_STR: { + nsDependentString string(u.wstr.mWStringValue); + *aSize = string.Length(); + *aStr = ToNewCString(string); + break; + } + case nsIDataType::VTYPE_STRING_SIZE_IS: { + nsDependentCString cString(u.str.mStringValue, u.str.mStringLength); + *aSize = cString.Length(); + *aStr = ToNewCString(cString); + break; + } + case nsIDataType::VTYPE_WSTRING_SIZE_IS: { + nsDependentString string(u.wstr.mWStringValue, u.wstr.mWStringLength); + *aSize = string.Length(); + *aStr = ToNewCString(string); + break; + } + case nsIDataType::VTYPE_WCHAR: + tempString.Assign(u.mWCharValue); + *aSize = tempString.Length(); + *aStr = ToNewCString(tempString); + break; + default: + rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + *aSize = tempCString.Length(); + *aStr = ToNewCString(tempCString); + break; + } + + return *aStr ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} +nsresult nsDiscriminatedUnion::ConvertToWStringWithSize(uint32_t* aSize, + char16_t** aStr) const { + nsAutoString tempString; + nsAutoCString tempCString; + nsresult rv; + + switch (mType) { + case nsIDataType::VTYPE_ASTRING: + *aSize = u.mAStringValue->Length(); + *aStr = ToNewUnicode(*u.mAStringValue); + break; + case nsIDataType::VTYPE_CSTRING: + *aSize = u.mCStringValue->Length(); + *aStr = ToNewUnicode(*u.mCStringValue); + break; + case nsIDataType::VTYPE_UTF8STRING: { + *aStr = UTF8ToNewUnicode(*u.mUTF8StringValue, aSize); + break; + } + case nsIDataType::VTYPE_CHAR_STR: { + nsDependentCString cString(u.str.mStringValue); + *aSize = cString.Length(); + *aStr = ToNewUnicode(cString); + break; + } + case nsIDataType::VTYPE_WCHAR_STR: { + nsDependentString string(u.wstr.mWStringValue); + *aSize = string.Length(); + *aStr = ToNewUnicode(string); + break; + } + case nsIDataType::VTYPE_STRING_SIZE_IS: { + nsDependentCString cString(u.str.mStringValue, u.str.mStringLength); + *aSize = cString.Length(); + *aStr = ToNewUnicode(cString); + break; + } + case nsIDataType::VTYPE_WSTRING_SIZE_IS: { + nsDependentString string(u.wstr.mWStringValue, u.wstr.mWStringLength); + *aSize = string.Length(); + *aStr = ToNewUnicode(string); + break; + } + case nsIDataType::VTYPE_WCHAR: + tempString.Assign(u.mWCharValue); + *aSize = tempString.Length(); + *aStr = ToNewUnicode(tempString); + break; + default: + rv = ToString(tempCString); + if (NS_FAILED(rv)) { + return rv; + } + *aSize = tempCString.Length(); + *aStr = ToNewUnicode(tempCString); + break; + } + + return *aStr ? NS_OK : NS_ERROR_OUT_OF_MEMORY; +} + +nsresult nsDiscriminatedUnion::ConvertToISupports(nsISupports** aResult) const { + switch (mType) { + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + if (u.iface.mInterfaceValue) { + return u.iface.mInterfaceValue->QueryInterface(NS_GET_IID(nsISupports), + (void**)aResult); + } else { + *aResult = nullptr; + return NS_OK; + } + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } +} + +nsresult nsDiscriminatedUnion::ConvertToInterface(nsIID** aIID, + void** aInterface) const { + const nsIID* piid; + + switch (mType) { + case nsIDataType::VTYPE_INTERFACE: + piid = &NS_GET_IID(nsISupports); + break; + case nsIDataType::VTYPE_INTERFACE_IS: + piid = &u.iface.mInterfaceID; + break; + default: + return NS_ERROR_CANNOT_CONVERT_DATA; + } + + *aIID = piid->Clone(); + + if (u.iface.mInterfaceValue) { + return u.iface.mInterfaceValue->QueryInterface(*piid, aInterface); + } + + *aInterface = nullptr; + return NS_OK; +} + +nsresult nsDiscriminatedUnion::ConvertToArray(uint16_t* aType, nsIID* aIID, + uint32_t* aCount, + void** aPtr) const { + // XXX perhaps we'd like to add support for converting each of the various + // types into an array containing one element of that type. We can leverage + // CloneArray to do this if we want to support this. + + if (mType == nsIDataType::VTYPE_ARRAY) { + return CloneArray(u.array.mArrayType, &u.array.mArrayInterfaceID, + u.array.mArrayCount, u.array.mArrayValue, aType, aIID, + aCount, aPtr); + } + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +/***************************************************************************/ +// static setter functions... + +#define DATA_SETTER_PROLOGUE Cleanup() + +#define DATA_SETTER_EPILOGUE(type_) mType = nsIDataType::type_; + +#define DATA_SETTER(type_, member_, value_) \ + DATA_SETTER_PROLOGUE; \ + u.member_ = value_; \ + DATA_SETTER_EPILOGUE(type_) + +#define DATA_SETTER_WITH_CAST(type_, member_, cast_, value_) \ + DATA_SETTER_PROLOGUE; \ + u.member_ = cast_ value_; \ + DATA_SETTER_EPILOGUE(type_) + +/********************************************/ + +#define CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) { +#define CASE__SET_FROM_VARIANT_VTYPE__GETTER(member_, name_) \ + rv = aValue->GetAs##name_(&(u.member_)); + +#define CASE__SET_FROM_VARIANT_VTYPE__GETTER_CAST(cast_, member_, name_) \ + rv = aValue->GetAs##name_(cast_ & (u.member_)); + +#define CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) \ + if (NS_SUCCEEDED(rv)) { \ + mType = nsIDataType::type_; \ + } \ + break; \ + } + +#define CASE__SET_FROM_VARIANT_TYPE(type_, member_, name_) \ + case nsIDataType::type_: \ + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) \ + CASE__SET_FROM_VARIANT_VTYPE__GETTER(member_, name_) \ + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) + +#define CASE__SET_FROM_VARIANT_VTYPE_CAST(type_, cast_, member_, name_) \ + case nsIDataType::type_: \ + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(type_) \ + CASE__SET_FROM_VARIANT_VTYPE__GETTER_CAST(cast_, member_, name_) \ + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(type_) + +nsresult nsDiscriminatedUnion::SetFromVariant(nsIVariant* aValue) { + nsresult rv = NS_OK; + + Cleanup(); + + uint16_t type = aValue->GetDataType(); + + switch (type) { + CASE__SET_FROM_VARIANT_VTYPE_CAST(VTYPE_INT8, (uint8_t*), mInt8Value, Int8) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_INT16, mInt16Value, Int16) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_INT32, mInt32Value, Int32) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT8, mUint8Value, Uint8) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT16, mUint16Value, Uint16) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_UINT32, mUint32Value, Uint32) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_FLOAT, mFloatValue, Float) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_DOUBLE, mDoubleValue, Double) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_BOOL, mBoolValue, Bool) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_CHAR, mCharValue, Char) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_WCHAR, mWCharValue, WChar) + CASE__SET_FROM_VARIANT_TYPE(VTYPE_ID, mIDValue, ID) + + case nsIDataType::VTYPE_ASTRING: + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_ASTRING); + u.mAStringValue = new nsString(); + + rv = aValue->GetAsAString(*u.mAStringValue); + if (NS_FAILED(rv)) { + delete u.mAStringValue; + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_ASTRING) + + case nsIDataType::VTYPE_CSTRING: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_CSTRING); + u.mCStringValue = new nsCString(); + + rv = aValue->GetAsACString(*u.mCStringValue); + if (NS_FAILED(rv)) { + delete u.mCStringValue; + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_CSTRING) + + case nsIDataType::VTYPE_UTF8STRING: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_UTF8STRING); + u.mUTF8StringValue = new nsUTF8String(); + + rv = aValue->GetAsAUTF8String(*u.mUTF8StringValue); + if (NS_FAILED(rv)) { + delete u.mUTF8StringValue; + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_UTF8STRING) + + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_STRING_SIZE_IS); + rv = aValue->GetAsStringWithSize(&u.str.mStringLength, + &u.str.mStringValue); + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_STRING_SIZE_IS) + + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_INTERFACE_IS); + // XXX This iid handling is ugly! + nsIID* iid; + rv = aValue->GetAsInterface(&iid, (void**)&u.iface.mInterfaceValue); + if (NS_SUCCEEDED(rv)) { + u.iface.mInterfaceID = *iid; + free((char*)iid); + } + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_INTERFACE_IS) + + case nsIDataType::VTYPE_ARRAY: + CASE__SET_FROM_VARIANT_VTYPE_PROLOGUE(VTYPE_ARRAY); + rv = aValue->GetAsArray(&u.array.mArrayType, &u.array.mArrayInterfaceID, + &u.array.mArrayCount, &u.array.mArrayValue); + CASE__SET_FROM_VARIANT_VTYPE_EPILOGUE(VTYPE_ARRAY) + + case nsIDataType::VTYPE_VOID: + SetToVoid(); + rv = NS_OK; + break; + case nsIDataType::VTYPE_EMPTY_ARRAY: + SetToEmptyArray(); + rv = NS_OK; + break; + case nsIDataType::VTYPE_EMPTY: + SetToEmpty(); + rv = NS_OK; + break; + default: + NS_ERROR("bad type in variant!"); + rv = NS_ERROR_FAILURE; + break; + } + return rv; +} + +void nsDiscriminatedUnion::SetFromInt8(uint8_t aValue) { + DATA_SETTER_WITH_CAST(VTYPE_INT8, mInt8Value, (uint8_t), aValue); +} +void nsDiscriminatedUnion::SetFromInt16(int16_t aValue) { + DATA_SETTER(VTYPE_INT16, mInt16Value, aValue); +} +void nsDiscriminatedUnion::SetFromInt32(int32_t aValue) { + DATA_SETTER(VTYPE_INT32, mInt32Value, aValue); +} +void nsDiscriminatedUnion::SetFromInt64(int64_t aValue) { + DATA_SETTER(VTYPE_INT64, mInt64Value, aValue); +} +void nsDiscriminatedUnion::SetFromUint8(uint8_t aValue) { + DATA_SETTER(VTYPE_UINT8, mUint8Value, aValue); +} +void nsDiscriminatedUnion::SetFromUint16(uint16_t aValue) { + DATA_SETTER(VTYPE_UINT16, mUint16Value, aValue); +} +void nsDiscriminatedUnion::SetFromUint32(uint32_t aValue) { + DATA_SETTER(VTYPE_UINT32, mUint32Value, aValue); +} +void nsDiscriminatedUnion::SetFromUint64(uint64_t aValue) { + DATA_SETTER(VTYPE_UINT64, mUint64Value, aValue); +} +void nsDiscriminatedUnion::SetFromFloat(float aValue) { + DATA_SETTER(VTYPE_FLOAT, mFloatValue, aValue); +} +void nsDiscriminatedUnion::SetFromDouble(double aValue) { + DATA_SETTER(VTYPE_DOUBLE, mDoubleValue, aValue); +} +void nsDiscriminatedUnion::SetFromBool(bool aValue) { + DATA_SETTER(VTYPE_BOOL, mBoolValue, aValue); +} +void nsDiscriminatedUnion::SetFromChar(char aValue) { + DATA_SETTER(VTYPE_CHAR, mCharValue, aValue); +} +void nsDiscriminatedUnion::SetFromWChar(char16_t aValue) { + DATA_SETTER(VTYPE_WCHAR, mWCharValue, aValue); +} +void nsDiscriminatedUnion::SetFromID(const nsID& aValue) { + DATA_SETTER(VTYPE_ID, mIDValue, aValue); +} +void nsDiscriminatedUnion::SetFromAString(const nsAString& aValue) { + DATA_SETTER_PROLOGUE; + u.mAStringValue = new nsString(aValue); + DATA_SETTER_EPILOGUE(VTYPE_ASTRING); +} + +void nsDiscriminatedUnion::SetFromACString(const nsACString& aValue) { + DATA_SETTER_PROLOGUE; + u.mCStringValue = new nsCString(aValue); + DATA_SETTER_EPILOGUE(VTYPE_CSTRING); +} + +void nsDiscriminatedUnion::SetFromAUTF8String(const nsAUTF8String& aValue) { + DATA_SETTER_PROLOGUE; + u.mUTF8StringValue = new nsUTF8String(aValue); + DATA_SETTER_EPILOGUE(VTYPE_UTF8STRING); +} + +nsresult nsDiscriminatedUnion::SetFromString(const char* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + return SetFromStringWithSize(strlen(aValue), aValue); +} +nsresult nsDiscriminatedUnion::SetFromWString(const char16_t* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + return SetFromWStringWithSize(NS_strlen(aValue), aValue); +} +void nsDiscriminatedUnion::SetFromISupports(nsISupports* aValue) { + return SetFromInterface(NS_GET_IID(nsISupports), aValue); +} +void nsDiscriminatedUnion::SetFromInterface(const nsIID& aIID, + nsISupports* aValue) { + DATA_SETTER_PROLOGUE; + NS_IF_ADDREF(aValue); + u.iface.mInterfaceValue = aValue; + u.iface.mInterfaceID = aIID; + DATA_SETTER_EPILOGUE(VTYPE_INTERFACE_IS); +} +nsresult nsDiscriminatedUnion::SetFromArray(uint16_t aType, const nsIID* aIID, + uint32_t aCount, void* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue || !aCount) { + return NS_ERROR_NULL_POINTER; + } + + nsresult rv = CloneArray(aType, aIID, aCount, aValue, &u.array.mArrayType, + &u.array.mArrayInterfaceID, &u.array.mArrayCount, + &u.array.mArrayValue); + if (NS_FAILED(rv)) { + return rv; + } + DATA_SETTER_EPILOGUE(VTYPE_ARRAY); + return NS_OK; +} +nsresult nsDiscriminatedUnion::SetFromStringWithSize(uint32_t aSize, + const char* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + u.str.mStringValue = (char*)moz_xmemdup(aValue, (aSize + 1) * sizeof(char)); + u.str.mStringLength = aSize; + DATA_SETTER_EPILOGUE(VTYPE_STRING_SIZE_IS); + return NS_OK; +} +nsresult nsDiscriminatedUnion::SetFromWStringWithSize(uint32_t aSize, + const char16_t* aValue) { + DATA_SETTER_PROLOGUE; + if (!aValue) { + return NS_ERROR_NULL_POINTER; + } + u.wstr.mWStringValue = + (char16_t*)moz_xmemdup(aValue, (aSize + 1) * sizeof(char16_t)); + u.wstr.mWStringLength = aSize; + DATA_SETTER_EPILOGUE(VTYPE_WSTRING_SIZE_IS); + return NS_OK; +} +void nsDiscriminatedUnion::AllocateWStringWithSize(uint32_t aSize) { + DATA_SETTER_PROLOGUE; + u.wstr.mWStringValue = (char16_t*)moz_xmalloc((aSize + 1) * sizeof(char16_t)); + u.wstr.mWStringValue[aSize] = '\0'; + u.wstr.mWStringLength = aSize; + DATA_SETTER_EPILOGUE(VTYPE_WSTRING_SIZE_IS); +} +void nsDiscriminatedUnion::SetToVoid() { + DATA_SETTER_PROLOGUE; + DATA_SETTER_EPILOGUE(VTYPE_VOID); +} +void nsDiscriminatedUnion::SetToEmpty() { + DATA_SETTER_PROLOGUE; + DATA_SETTER_EPILOGUE(VTYPE_EMPTY); +} +void nsDiscriminatedUnion::SetToEmptyArray() { + DATA_SETTER_PROLOGUE; + DATA_SETTER_EPILOGUE(VTYPE_EMPTY_ARRAY); +} + +/***************************************************************************/ + +void nsDiscriminatedUnion::Cleanup() { + switch (mType) { + case nsIDataType::VTYPE_INT8: + case nsIDataType::VTYPE_INT16: + case nsIDataType::VTYPE_INT32: + case nsIDataType::VTYPE_INT64: + case nsIDataType::VTYPE_UINT8: + case nsIDataType::VTYPE_UINT16: + case nsIDataType::VTYPE_UINT32: + case nsIDataType::VTYPE_UINT64: + case nsIDataType::VTYPE_FLOAT: + case nsIDataType::VTYPE_DOUBLE: + case nsIDataType::VTYPE_BOOL: + case nsIDataType::VTYPE_CHAR: + case nsIDataType::VTYPE_WCHAR: + case nsIDataType::VTYPE_VOID: + case nsIDataType::VTYPE_ID: + break; + case nsIDataType::VTYPE_ASTRING: + delete u.mAStringValue; + break; + case nsIDataType::VTYPE_CSTRING: + delete u.mCStringValue; + break; + case nsIDataType::VTYPE_UTF8STRING: + delete u.mUTF8StringValue; + break; + case nsIDataType::VTYPE_CHAR_STR: + case nsIDataType::VTYPE_STRING_SIZE_IS: + free((char*)u.str.mStringValue); + break; + case nsIDataType::VTYPE_WCHAR_STR: + case nsIDataType::VTYPE_WSTRING_SIZE_IS: + free((char*)u.wstr.mWStringValue); + break; + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + NS_IF_RELEASE(u.iface.mInterfaceValue); + break; + case nsIDataType::VTYPE_ARRAY: + FreeArray(); + break; + case nsIDataType::VTYPE_EMPTY_ARRAY: + case nsIDataType::VTYPE_EMPTY: + break; + default: + NS_ERROR("bad type in variant!"); + break; + } + + mType = nsIDataType::VTYPE_EMPTY; +} + +void nsDiscriminatedUnion::Traverse( + nsCycleCollectionTraversalCallback& aCb) const { + switch (mType) { + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mData"); + aCb.NoteXPCOMChild(u.iface.mInterfaceValue); + break; + case nsIDataType::VTYPE_ARRAY: + switch (u.array.mArrayType) { + case nsIDataType::VTYPE_INTERFACE: + case nsIDataType::VTYPE_INTERFACE_IS: { + nsISupports** p = (nsISupports**)u.array.mArrayValue; + for (uint32_t i = u.array.mArrayCount; i > 0; ++p, --i) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mData[i]"); + aCb.NoteXPCOMChild(*p); + } + break; + } + default: + break; + } + break; + default: + break; + } +} + +/***************************************************************************/ +/***************************************************************************/ +// members... + +nsVariantBase::nsVariantBase() : mWritable(true) {} + +// For all the data getters we just forward to the static (and sharable) +// 'ConvertTo' functions. + +uint16_t nsVariantBase::GetDataType() { return mData.GetType(); } + +NS_IMETHODIMP +nsVariantBase::GetAsInt8(uint8_t* aResult) { + return mData.ConvertToInt8(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt16(int16_t* aResult) { + return mData.ConvertToInt16(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt32(int32_t* aResult) { + return mData.ConvertToInt32(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsInt64(int64_t* aResult) { + return mData.ConvertToInt64(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint8(uint8_t* aResult) { + return mData.ConvertToUint8(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint16(uint16_t* aResult) { + return mData.ConvertToUint16(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint32(uint32_t* aResult) { + return mData.ConvertToUint32(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsUint64(uint64_t* aResult) { + return mData.ConvertToUint64(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsFloat(float* aResult) { + return mData.ConvertToFloat(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsDouble(double* aResult) { + return mData.ConvertToDouble(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsBool(bool* aResult) { return mData.ConvertToBool(aResult); } + +NS_IMETHODIMP +nsVariantBase::GetAsChar(char* aResult) { return mData.ConvertToChar(aResult); } + +NS_IMETHODIMP +nsVariantBase::GetAsWChar(char16_t* aResult) { + return mData.ConvertToWChar(aResult); +} + +NS_IMETHODIMP_(nsresult) +nsVariantBase::GetAsID(nsID* aResult) { return mData.ConvertToID(aResult); } + +NS_IMETHODIMP +nsVariantBase::GetAsAString(nsAString& aResult) { + return mData.ConvertToAString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsACString(nsACString& aResult) { + return mData.ConvertToACString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsAUTF8String(nsAUTF8String& aResult) { + return mData.ConvertToAUTF8String(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsString(char** aResult) { + return mData.ConvertToString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsWString(char16_t** aResult) { + return mData.ConvertToWString(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsISupports(nsISupports** aResult) { + return mData.ConvertToISupports(aResult); +} + +NS_IMETHODIMP +nsVariantBase::GetAsJSVal(JS::MutableHandleValue) { + // Can only get the jsval from an XPCVariant. + return NS_ERROR_CANNOT_CONVERT_DATA; +} + +NS_IMETHODIMP +nsVariantBase::GetAsInterface(nsIID** aIID, void** aInterface) { + return mData.ConvertToInterface(aIID, aInterface); +} + +NS_IMETHODIMP_(nsresult) +nsVariantBase::GetAsArray(uint16_t* aType, nsIID* aIID, uint32_t* aCount, + void** aPtr) { + return mData.ConvertToArray(aType, aIID, aCount, aPtr); +} + +NS_IMETHODIMP +nsVariantBase::GetAsStringWithSize(uint32_t* aSize, char** aStr) { + return mData.ConvertToStringWithSize(aSize, aStr); +} + +NS_IMETHODIMP +nsVariantBase::GetAsWStringWithSize(uint32_t* aSize, char16_t** aStr) { + return mData.ConvertToWStringWithSize(aSize, aStr); +} + +/***************************************************************************/ + +NS_IMETHODIMP +nsVariantBase::GetWritable(bool* aWritable) { + *aWritable = mWritable; + return NS_OK; +} +NS_IMETHODIMP +nsVariantBase::SetWritable(bool aWritable) { + if (!mWritable && aWritable) { + return NS_ERROR_FAILURE; + } + mWritable = aWritable; + return NS_OK; +} + +/***************************************************************************/ + +// For all the data setters we just forward to the static (and sharable) +// 'SetFrom' functions. + +NS_IMETHODIMP +nsVariantBase::SetAsInt8(uint8_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt8(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInt16(int16_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt16(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInt32(int32_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt32(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInt64(int64_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInt64(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint8(uint8_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint8(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint16(uint16_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint16(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint32(uint32_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint32(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsUint64(uint64_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromUint64(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsFloat(float aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromFloat(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsDouble(double aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromDouble(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsBool(bool aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromBool(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsChar(char aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromChar(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsWChar(char16_t aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromWChar(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsID(const nsID& aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromID(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsAString(const nsAString& aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromAString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsACString(const nsACString& aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromACString(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsAUTF8String(const nsAUTF8String& aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromAUTF8String(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsString(const char* aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromString(aValue); +} + +NS_IMETHODIMP +nsVariantBase::SetAsWString(const char16_t* aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromWString(aValue); +} + +NS_IMETHODIMP +nsVariantBase::SetAsISupports(nsISupports* aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromISupports(aValue); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsInterface(const nsIID& aIID, void* aInterface) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetFromInterface(aIID, (nsISupports*)aInterface); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsArray(uint16_t aType, const nsIID* aIID, uint32_t aCount, + void* aPtr) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromArray(aType, aIID, aCount, aPtr); +} + +NS_IMETHODIMP +nsVariantBase::SetAsStringWithSize(uint32_t aSize, const char* aStr) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromStringWithSize(aSize, aStr); +} + +NS_IMETHODIMP +nsVariantBase::SetAsWStringWithSize(uint32_t aSize, const char16_t* aStr) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromWStringWithSize(aSize, aStr); +} + +NS_IMETHODIMP +nsVariantBase::SetAsVoid() { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetToVoid(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsEmpty() { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetToEmpty(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetAsEmptyArray() { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + mData.SetToEmptyArray(); + return NS_OK; +} + +NS_IMETHODIMP +nsVariantBase::SetFromVariant(nsIVariant* aValue) { + if (!mWritable) { + return NS_ERROR_OBJECT_IS_IMMUTABLE; + } + return mData.SetFromVariant(aValue); +} + +/* nsVariant implementation */ + +NS_IMPL_ISUPPORTS(nsVariant, nsIVariant, nsIWritableVariant) + +/* nsVariantCC implementation */ +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsVariantCC) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsIVariant) + NS_INTERFACE_MAP_ENTRY(nsIWritableVariant) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsVariantCC) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsVariantCC) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsVariantCC) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsVariantCC) + tmp->mData.Traverse(cb); +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsVariantCC) + tmp->mData.Cleanup(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END diff --git a/xpcom/ds/nsVariant.h b/xpcom/ds/nsVariant.h new file mode 100644 index 0000000000..8de1fe4618 --- /dev/null +++ b/xpcom/ds/nsVariant.h @@ -0,0 +1,223 @@ +/* -*- 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/. */ + +#ifndef nsVariant_h +#define nsVariant_h + +#include "nsIVariant.h" +#include "nsStringFwd.h" +#include "mozilla/Attributes.h" +#include "nsCycleCollectionParticipant.h" + +/** + * Map the nsAUTF8String, nsUTF8String classes to the nsACString and + * nsCString classes respectively for now. These defines need to be removed + * once Jag lands his nsUTF8String implementation. + */ +#define nsAUTF8String nsACString +#define nsUTF8String nsCString +#define PromiseFlatUTF8String PromiseFlatCString + +/** + * nsDiscriminatedUnion is a class that nsIVariant implementors can use + * to hold the underlying data. + */ + +class nsDiscriminatedUnion { + public: + nsDiscriminatedUnion() : mType(nsIDataType::VTYPE_EMPTY) { + u.mInt8Value = '\0'; + } + nsDiscriminatedUnion(const nsDiscriminatedUnion&) = delete; + nsDiscriminatedUnion(nsDiscriminatedUnion&&) = delete; + + ~nsDiscriminatedUnion() { Cleanup(); } + + nsDiscriminatedUnion& operator=(const nsDiscriminatedUnion&) = delete; + nsDiscriminatedUnion& operator=(nsDiscriminatedUnion&&) = delete; + + void Cleanup(); + + uint16_t GetType() const { return mType; } + + [[nodiscard]] nsresult ConvertToInt8(uint8_t* aResult) const; + [[nodiscard]] nsresult ConvertToInt16(int16_t* aResult) const; + [[nodiscard]] nsresult ConvertToInt32(int32_t* aResult) const; + [[nodiscard]] nsresult ConvertToInt64(int64_t* aResult) const; + [[nodiscard]] nsresult ConvertToUint8(uint8_t* aResult) const; + [[nodiscard]] nsresult ConvertToUint16(uint16_t* aResult) const; + [[nodiscard]] nsresult ConvertToUint32(uint32_t* aResult) const; + [[nodiscard]] nsresult ConvertToUint64(uint64_t* aResult) const; + [[nodiscard]] nsresult ConvertToFloat(float* aResult) const; + [[nodiscard]] nsresult ConvertToDouble(double* aResult) const; + [[nodiscard]] nsresult ConvertToBool(bool* aResult) const; + [[nodiscard]] nsresult ConvertToChar(char* aResult) const; + [[nodiscard]] nsresult ConvertToWChar(char16_t* aResult) const; + + [[nodiscard]] nsresult ConvertToID(nsID* aResult) const; + + [[nodiscard]] nsresult ConvertToAString(nsAString& aResult) const; + [[nodiscard]] nsresult ConvertToAUTF8String(nsAUTF8String& aResult) const; + [[nodiscard]] nsresult ConvertToACString(nsACString& aResult) const; + [[nodiscard]] nsresult ConvertToString(char** aResult) const; + [[nodiscard]] nsresult ConvertToWString(char16_t** aResult) const; + [[nodiscard]] nsresult ConvertToStringWithSize(uint32_t* aSize, + char** aStr) const; + [[nodiscard]] nsresult ConvertToWStringWithSize(uint32_t* aSize, + char16_t** aStr) const; + + [[nodiscard]] nsresult ConvertToISupports(nsISupports** aResult) const; + [[nodiscard]] nsresult ConvertToInterface(nsIID** aIID, + void** aInterface) const; + [[nodiscard]] nsresult ConvertToArray(uint16_t* aType, nsIID* aIID, + uint32_t* aCount, void** aPtr) const; + + [[nodiscard]] nsresult SetFromVariant(nsIVariant* aValue); + + void SetFromInt8(uint8_t aValue); + void SetFromInt16(int16_t aValue); + void SetFromInt32(int32_t aValue); + void SetFromInt64(int64_t aValue); + void SetFromUint8(uint8_t aValue); + void SetFromUint16(uint16_t aValue); + void SetFromUint32(uint32_t aValue); + void SetFromUint64(uint64_t aValue); + void SetFromFloat(float aValue); + void SetFromDouble(double aValue); + void SetFromBool(bool aValue); + void SetFromChar(char aValue); + void SetFromWChar(char16_t aValue); + void SetFromID(const nsID& aValue); + void SetFromAString(const nsAString& aValue); + void SetFromAUTF8String(const nsAUTF8String& aValue); + void SetFromACString(const nsACString& aValue); + [[nodiscard]] nsresult SetFromString(const char* aValue); + [[nodiscard]] nsresult SetFromWString(const char16_t* aValue); + void SetFromISupports(nsISupports* aValue); + void SetFromInterface(const nsIID& aIID, nsISupports* aValue); + [[nodiscard]] nsresult SetFromArray(uint16_t aType, const nsIID* aIID, + uint32_t aCount, void* aValue); + [[nodiscard]] nsresult SetFromStringWithSize(uint32_t aSize, + const char* aValue); + [[nodiscard]] nsresult SetFromWStringWithSize(uint32_t aSize, + const char16_t* aValue); + + // Like SetFromWStringWithSize, but leaves the string uninitialized. It does + // does write the null-terminator. + void AllocateWStringWithSize(uint32_t aSize); + + void SetToVoid(); + void SetToEmpty(); + void SetToEmptyArray(); + + void Traverse(nsCycleCollectionTraversalCallback& aCb) const; + + private: + [[nodiscard]] nsresult ToManageableNumber( + nsDiscriminatedUnion* aOutData) const; + void FreeArray(); + [[nodiscard]] bool String2ID(nsID* aPid) const; + [[nodiscard]] nsresult ToString(nsACString& aOutString) const; + + public: + union { + int8_t mInt8Value; + int16_t mInt16Value; + int32_t mInt32Value; + int64_t mInt64Value; + uint8_t mUint8Value; + uint16_t mUint16Value; + uint32_t mUint32Value; + uint64_t mUint64Value; + float mFloatValue; + double mDoubleValue; + bool mBoolValue; + char mCharValue; + char16_t mWCharValue; + nsIID mIDValue; + nsAString* mAStringValue; + nsAUTF8String* mUTF8StringValue; + nsACString* mCStringValue; + struct { + // This is an owning reference that cannot be an nsCOMPtr because + // nsDiscriminatedUnion needs to be POD. AddRef/Release are manually + // called on this. + nsISupports* MOZ_OWNING_REF mInterfaceValue; + nsIID mInterfaceID; + } iface; + struct { + nsIID mArrayInterfaceID; + void* mArrayValue; + uint32_t mArrayCount; + uint16_t mArrayType; + } array; + struct { + char* mStringValue; + uint32_t mStringLength; + } str; + struct { + char16_t* mWStringValue; + uint32_t mWStringLength; + } wstr; + } u; + uint16_t mType; +}; + +/** + * nsVariant implements the generic variant support. The xpcom module registers + * a factory (see NS_VARIANT_CONTRACTID in nsIVariant.idl) that will create + * these objects. They are created 'empty' and 'writable'. + * + * nsIVariant users won't usually need to see this class. + */ +class nsVariantBase : public nsIWritableVariant { + public: + NS_DECL_NSIVARIANT + NS_DECL_NSIWRITABLEVARIANT + + nsVariantBase(); + + protected: + ~nsVariantBase() = default; + + nsDiscriminatedUnion mData; + bool mWritable; +}; + +class nsVariant final : public nsVariantBase { + public: + NS_DECL_ISUPPORTS + + nsVariant() = default; + + private: + ~nsVariant() = default; +}; + +class nsVariantCC final : public nsVariantBase { + public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsVariantCC) + + nsVariantCC() = default; + + private: + ~nsVariantCC() = default; +}; + +/** + * Users of nsIVariant should be using the contractID and not this CID. + * - see NS_VARIANT_CONTRACTID in nsIVariant.idl. + */ + +#define NS_VARIANT_CID \ + { /* 0D6EA1D0-879C-11d5-90EF-0010A4E73D9A */ \ + 0xd6ea1d0, 0x879c, 0x11d5, { \ + 0x90, 0xef, 0x0, 0x10, 0xa4, 0xe7, 0x3d, 0x9a \ + } \ + } + +#endif // nsVariant_h diff --git a/xpcom/ds/nsWhitespaceTokenizer.h b/xpcom/ds/nsWhitespaceTokenizer.h new file mode 100644 index 0000000000..77fea70850 --- /dev/null +++ b/xpcom/ds/nsWhitespaceTokenizer.h @@ -0,0 +1,96 @@ +/* -*- 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/. */ + +#ifndef __nsWhitespaceTokenizer_h +#define __nsWhitespaceTokenizer_h + +#include "mozilla/RangedPtr.h" +#include "nsDependentSubstring.h" +#include "nsCRTGlue.h" + +template +class nsTWhitespaceTokenizer { + typedef typename DependentSubstringType::char_type CharType; + typedef typename DependentSubstringType::substring_type SubstringType; + + public: + explicit nsTWhitespaceTokenizer(const SubstringType& aSource) + : mIter(aSource.Data(), aSource.Length()), + mEnd(aSource.Data() + aSource.Length(), aSource.Data(), + aSource.Length()), + mWhitespaceBeforeFirstToken(false), + mWhitespaceAfterCurrentToken(false) { + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceBeforeFirstToken = true; + ++mIter; + } + } + + /** + * Checks if any more tokens are available. + */ + bool hasMoreTokens() const { return mIter < mEnd; } + + /* + * Returns true if there is whitespace prior to the first token. + */ + bool whitespaceBeforeFirstToken() const { + return mWhitespaceBeforeFirstToken; + } + + /* + * Returns true if there is any whitespace after the current token. + * This is always true unless we're reading the last token. + */ + bool whitespaceAfterCurrentToken() const { + return mWhitespaceAfterCurrentToken; + } + + /** + * Returns the next token. + */ + const DependentSubstringType nextToken() { + const mozilla::RangedPtr tokenStart = mIter; + while (mIter < mEnd && !IsWhitespace(*mIter)) { + ++mIter; + } + const mozilla::RangedPtr tokenEnd = mIter; + mWhitespaceAfterCurrentToken = false; + while (mIter < mEnd && IsWhitespace(*mIter)) { + mWhitespaceAfterCurrentToken = true; + ++mIter; + } + return Substring(tokenStart.get(), tokenEnd.get()); + } + + private: + mozilla::RangedPtr mIter; + const mozilla::RangedPtr mEnd; + bool mWhitespaceBeforeFirstToken; + bool mWhitespaceAfterCurrentToken; +}; + +template +class nsWhitespaceTokenizerTemplate + : public nsTWhitespaceTokenizer { + public: + explicit nsWhitespaceTokenizerTemplate(const nsAString& aSource) + : nsTWhitespaceTokenizer(aSource) {} +}; + +typedef nsWhitespaceTokenizerTemplate<> nsWhitespaceTokenizer; + +template +class nsCWhitespaceTokenizerTemplate + : public nsTWhitespaceTokenizer { + public: + explicit nsCWhitespaceTokenizerTemplate(const nsACString& aSource) + : nsTWhitespaceTokenizer(aSource) {} +}; + +typedef nsCWhitespaceTokenizerTemplate<> nsCWhitespaceTokenizer; + +#endif /* __nsWhitespaceTokenizer_h */ diff --git a/xpcom/ds/nsWindowsRegKey.cpp b/xpcom/ds/nsWindowsRegKey.cpp new file mode 100644 index 0000000000..1f9bb58ac6 --- /dev/null +++ b/xpcom/ds/nsWindowsRegKey.cpp @@ -0,0 +1,523 @@ +/* -*- 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 +#include +#include +#include "nsWindowsRegKey.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" + +//----------------------------------------------------------------------------- + +// According to MSDN, the following limits apply (in characters excluding room +// for terminating null character): +#define MAX_KEY_NAME_LEN 255 +#define MAX_VALUE_NAME_LEN 16383 + +class nsWindowsRegKey final : public nsIWindowsRegKey { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWINDOWSREGKEY + + nsWindowsRegKey() + : mKey(nullptr), mWatchEvent(nullptr), mWatchRecursive(FALSE) {} + + private: + ~nsWindowsRegKey() { Close(); } + + HKEY mKey; + HANDLE mWatchEvent; + BOOL mWatchRecursive; +}; + +NS_IMPL_ISUPPORTS(nsWindowsRegKey, nsIWindowsRegKey) + +NS_IMETHODIMP +nsWindowsRegKey::GetKey(HKEY* aKey) { + *aKey = mKey; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::SetKey(HKEY aKey) { + // We do not close the older aKey! + StopWatching(); + + mKey = aKey; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::Close() { + StopWatching(); + + if (mKey) { + RegCloseKey(mKey); + mKey = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::Open(uint32_t aRootKey, const nsAString& aPath, + uint32_t aMode) { + Close(); + + LONG rv = + RegOpenKeyExW((HKEY)(intptr_t)aRootKey, PromiseFlatString(aPath).get(), 0, + (REGSAM)aMode, &mKey); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::Create(uint32_t aRootKey, const nsAString& aPath, + uint32_t aMode) { + Close(); + + DWORD disposition; + LONG rv = RegCreateKeyExW( + (HKEY)(intptr_t)aRootKey, PromiseFlatString(aPath).get(), 0, nullptr, + REG_OPTION_NON_VOLATILE, (REGSAM)aMode, nullptr, &mKey, &disposition); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::OpenChild(const nsAString& aPath, uint32_t aMode, + nsIWindowsRegKey** aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr child = new nsWindowsRegKey(); + + nsresult rv = child->Open((uintptr_t)mKey, aPath, aMode); + if (NS_FAILED(rv)) { + return rv; + } + + child.swap(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::CreateChild(const nsAString& aPath, uint32_t aMode, + nsIWindowsRegKey** aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr child = new nsWindowsRegKey(); + + nsresult rv = child->Create((uintptr_t)mKey, aPath, aMode); + if (NS_FAILED(rv)) { + return rv; + } + + child.swap(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetChildCount(uint32_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD numSubKeys; + LONG rv = + RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, &numSubKeys, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + *aResult = numSubKeys; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetChildName(uint32_t aIndex, nsAString& aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + FILETIME lastWritten; + + wchar_t nameBuf[MAX_KEY_NAME_LEN + 1]; + DWORD nameLen = sizeof(nameBuf) / sizeof(nameBuf[0]); + + LONG rv = RegEnumKeyExW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr, + nullptr, &lastWritten); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_NOT_AVAILABLE; // XXX what's the best error code here? + } + + aResult.Assign(nameBuf, nameLen); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::HasChild(const nsAString& aName, bool* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // Check for the existence of a child key by opening the key with minimal + // rights. Perhaps there is a more efficient way to do this? + + HKEY key; + LONG rv = RegOpenKeyExW(mKey, PromiseFlatString(aName).get(), 0, + STANDARD_RIGHTS_READ, &key); + + if ((*aResult = (rv == ERROR_SUCCESS && key))) { + RegCloseKey(key); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetValueCount(uint32_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD numValues; + LONG rv = + RegQueryInfoKeyW(mKey, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, &numValues, nullptr, nullptr, nullptr, nullptr); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + *aResult = numValues; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetValueName(uint32_t aIndex, nsAString& aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + wchar_t nameBuf[MAX_VALUE_NAME_LEN]; + DWORD nameLen = sizeof(nameBuf) / sizeof(nameBuf[0]); + + LONG rv = RegEnumValueW(mKey, aIndex, nameBuf, &nameLen, nullptr, nullptr, + nullptr, nullptr); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_NOT_AVAILABLE; // XXX what's the best error code here? + } + + aResult.Assign(nameBuf, nameLen); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::HasValue(const nsAString& aName, bool* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + nullptr, nullptr); + + *aResult = (rv == ERROR_SUCCESS); + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::RemoveChild(const nsAString& aName) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegDeleteKeyW(mKey, PromiseFlatString(aName).get()); + + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::RemoveValue(const nsAString& aName) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegDeleteValueW(mKey, PromiseFlatString(aName).get()); + + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::GetValueType(const nsAString& aName, uint32_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, + (LPDWORD)aResult, nullptr, nullptr); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadStringValue(const nsAString& aName, nsAString& aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD type, size; + + const nsString& flatName = PromiseFlatString(aName); + + LONG rv = RegQueryValueExW(mKey, flatName.get(), 0, &type, nullptr, &size); + if (rv != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + // This must be a string type in order to fetch the value as a string. + // We're being a bit forgiving here by allowing types other than REG_SZ. + if (type != REG_SZ && type != REG_EXPAND_SZ && type != REG_MULTI_SZ) { + return NS_ERROR_FAILURE; + } + + // The buffer size must be a multiple of 2. + if (size % 2 != 0) { + return NS_ERROR_UNEXPECTED; + } + + if (size == 0) { + aResult.Truncate(); + return NS_OK; + } + + // |size| may or may not include the terminating null character. + DWORD resultLen = size / 2; + + if (!aResult.SetLength(resultLen, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + auto begin = aResult.BeginWriting(); + + rv = RegQueryValueExW(mKey, flatName.get(), 0, &type, (LPBYTE)begin, &size); + + if (!aResult.CharAt(resultLen - 1)) { + // The string passed to us had a null terminator in the final position. + aResult.Truncate(resultLen - 1); + } + + // Expand the environment variables if needed + if (type == REG_EXPAND_SZ) { + const nsString& flatSource = PromiseFlatString(aResult); + resultLen = ExpandEnvironmentStringsW(flatSource.get(), nullptr, 0); + if (resultLen > 1) { + nsAutoString expandedResult; + // |resultLen| includes the terminating null character + --resultLen; + if (!expandedResult.SetLength(resultLen, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + resultLen = ExpandEnvironmentStringsW( + flatSource.get(), expandedResult.get(), resultLen + 1); + if (resultLen <= 0) { + rv = ERROR_UNKNOWN_FEATURE; + aResult.Truncate(); + } else { + rv = ERROR_SUCCESS; + aResult = expandedResult; + } + } else if (resultLen == 1) { + // It apparently expands to nothing (just a null terminator). + aResult.Truncate(); + } + } + + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadIntValue(const nsAString& aName, uint32_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD size = sizeof(*aResult); + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + (LPBYTE)aResult, &size); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadInt64Value(const nsAString& aName, uint64_t* aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD size = sizeof(*aResult); + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + (LPBYTE)aResult, &size); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::ReadBinaryValue(const nsAString& aName, nsACString& aResult) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + DWORD size; + LONG rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + nullptr, &size); + + if (rv != ERROR_SUCCESS) { + return NS_ERROR_FAILURE; + } + + if (!size) { + aResult.Truncate(); + return NS_OK; + } + + if (!aResult.SetLength(size, mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + auto begin = aResult.BeginWriting(); + + rv = RegQueryValueExW(mKey, PromiseFlatString(aName).get(), 0, nullptr, + (LPBYTE)begin, &size); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteStringValue(const nsAString& aName, + const nsAString& aValue) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // Need to indicate complete size of buffer including null terminator. + const nsString& flatValue = PromiseFlatString(aValue); + + LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_SZ, + (const BYTE*)flatValue.get(), + (flatValue.Length() + 1) * sizeof(char16_t)); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteIntValue(const nsAString& aName, uint32_t aValue) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_DWORD, + (const BYTE*)&aValue, sizeof(aValue)); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteInt64Value(const nsAString& aName, uint64_t aValue) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_QWORD, + (const BYTE*)&aValue, sizeof(aValue)); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::WriteBinaryValue(const nsAString& aName, + const nsACString& aValue) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + const nsCString& flatValue = PromiseFlatCString(aValue); + LONG rv = RegSetValueExW(mKey, PromiseFlatString(aName).get(), 0, REG_BINARY, + (const BYTE*)flatValue.get(), flatValue.Length()); + return (rv == ERROR_SUCCESS) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsWindowsRegKey::StartWatching(bool aRecurse) { + if (NS_WARN_IF(!mKey)) { + return NS_ERROR_NOT_INITIALIZED; + } + + if (mWatchEvent) { + return NS_OK; + } + + mWatchEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); + if (!mWatchEvent) { + return NS_ERROR_OUT_OF_MEMORY; + } + + DWORD filter = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_ATTRIBUTES | + REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY; + + LONG rv = RegNotifyChangeKeyValue(mKey, aRecurse, filter, mWatchEvent, TRUE); + if (rv != ERROR_SUCCESS) { + StopWatching(); + // On older versions of Windows, this call is not implemented, so simply + // return NS_OK in those cases and pretend that the watching is happening. + return (rv == ERROR_CALL_NOT_IMPLEMENTED) ? NS_OK : NS_ERROR_FAILURE; + } + + mWatchRecursive = aRecurse; + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::StopWatching() { + if (mWatchEvent) { + CloseHandle(mWatchEvent); + mWatchEvent = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::HasChanged(bool* aResult) { + if (mWatchEvent && WaitForSingleObject(mWatchEvent, 0) == WAIT_OBJECT_0) { + // An event only gets signaled once, then it's done, so we have to set up + // another event to watch. + StopWatching(); + StartWatching(mWatchRecursive); + *aResult = true; + } else { + *aResult = false; + } + return NS_OK; +} + +NS_IMETHODIMP +nsWindowsRegKey::IsWatching(bool* aResult) { + *aResult = (mWatchEvent != nullptr); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +void NS_NewWindowsRegKey(nsIWindowsRegKey** aResult) { + RefPtr key = new nsWindowsRegKey(); + key.forget(aResult); +} + +//----------------------------------------------------------------------------- + +nsresult nsWindowsRegKeyConstructor(const nsIID& aIID, void** aResult) { + nsCOMPtr key; + NS_NewWindowsRegKey(getter_AddRefs(key)); + return key->QueryInterface(aIID, aResult); +} diff --git a/xpcom/ds/nsWindowsRegKey.h b/xpcom/ds/nsWindowsRegKey.h new file mode 100644 index 0000000000..c8032540ab --- /dev/null +++ b/xpcom/ds/nsWindowsRegKey.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +#ifndef nsWindowsRegKey_h__ +#define nsWindowsRegKey_h__ + +//----------------------------------------------------------------------------- + +#include "nsIWindowsRegKey.h" + +/** + * This ContractID may be used to instantiate a windows registry key object + * via the XPCOM component manager. + */ +#define NS_WINDOWSREGKEY_CONTRACTID "@mozilla.org/windows-registry-key;1" + +/** + * This function may be used to instantiate a windows registry key object prior + * to XPCOM being initialized. + */ +extern "C" void NS_NewWindowsRegKey(nsIWindowsRegKey** aResult); + +//----------------------------------------------------------------------------- + +#ifdef IMPL_LIBXUL + +// a53bc624-d577-4839-b8ec-bb5040a52ff4 +# define NS_WINDOWSREGKEY_CID \ + { \ + 0xa53bc624, 0xd577, 0x4839, { \ + 0xb8, 0xec, 0xbb, 0x50, 0x40, 0xa5, 0x2f, 0xf4 \ + } \ + } + +[[nodiscard]] extern nsresult nsWindowsRegKeyConstructor(const nsIID& aIID, + void** aResult); + +#endif // IMPL_LIBXUL + +//----------------------------------------------------------------------------- + +#endif // nsWindowsRegKey_h__ diff --git a/xpcom/ds/test/python.ini b/xpcom/ds/test/python.ini new file mode 100644 index 0000000000..680185c496 --- /dev/null +++ b/xpcom/ds/test/python.ini @@ -0,0 +1,4 @@ +[DEFAULT] +subsuite = xpcom + +[test_dafsa.py] diff --git a/xpcom/ds/test/test_dafsa.py b/xpcom/ds/test/test_dafsa.py new file mode 100644 index 0000000000..9becdd6c06 --- /dev/null +++ b/xpcom/ds/test/test_dafsa.py @@ -0,0 +1,546 @@ +# 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/. + +import unittest +from io import StringIO + +import mozunit +from incremental_dafsa import Dafsa, Node + + +def _node_to_string(node: Node, prefix, buffer, cache): + if not node.is_end_node: + prefix += ( + str(ord(node.character)) if ord(node.character) < 10 else node.character + ) + else: + prefix += "$" + cached = cache.get(id(node)) + buffer.write("{}{}".format(prefix, "=" if cached else "").strip() + "\n") + + if not cached: + cache[id(node)] = node + if node: + for node in sorted(node.children.values(), key=lambda n: n.character): + _node_to_string(node, prefix, buffer, cache) + + +def _dafsa_to_string(dafsa: Dafsa): + """Encodes the dafsa into a string notation. + + Each node is printed on its own line with all the nodes that precede it. + The end node is designated with the "$" character. + If it joins into an existing node, the end of the line is adorned with a "=". + Though this doesn't carry information about which other prefix it has joined with, + it has seemed to be precise enough for testing. + + For example, with the input data of: + * a1 + * ac1 + * bc1 + + [root] --- a ---- 1 --- [end] + | | / + -- b -- c--- + + The output will be: + a + a1 + a1$ <- end node was found + ac + ac1= <- joins with the "a1" prefix + b + bc= <- joins with the "ac" prefix + """ + buffer = StringIO() + cache = {} + + for node in sorted(dafsa.root_node.children.values(), key=lambda n: n.character): + _node_to_string(node, "", buffer, cache) + return buffer.getvalue().strip() + + +def _to_words(data): + return [line.strip() for line in data.strip().split("\n")] + + +def _assert_dafsa(data, expected): + words = _to_words(data) + dafsa = Dafsa.from_tld_data(words) + + expected = expected.strip() + expected = "\n".join([line.strip() for line in expected.split("\n")]) + as_string = _dafsa_to_string(dafsa) + assert as_string == expected + + +class TestDafsa(unittest.TestCase): + def test_1(self): + _assert_dafsa( + """ + a1 + ac1 + acc1 + bd1 + bc1 + bcc1 + """, + """ + a + a1 + a1$ + ac + ac1= + acc + acc1= + b + bc= + bd + bd1= + """, + ) + + def test_2(self): + _assert_dafsa( + """ + ab1 + b1 + bb1 + bbb1 + """, + """ + a + ab + ab1 + ab1$ + b + b1= + bb + bb1= + bbb= + """, + ) + + def test_3(self): + _assert_dafsa( + """ + a.ca1 + a.com1 + c.corg1 + b.ca1 + b.com1 + b.corg1 + """, + """ + a + a. + a.c + a.ca + a.ca1 + a.ca1$ + a.co + a.com + a.com1= + b + b. + b.c + b.ca= + b.co + b.com= + b.cor + b.corg + b.corg1= + c + c. + c.c + c.co + c.cor= + """, + ) + + def test_4(self): + _assert_dafsa( + """ + acom1 + bcomcom1 + acomcom1 + """, + """ + a + ac + aco + acom + acom1 + acom1$ + acomc + acomco + acomcom + acomcom1= + b + bc + bco + bcom + bcomc= + """, + ) + + def test_5(self): + _assert_dafsa( + """ + a.d1 + a.c.d1 + b.d1 + b.c.d1 + """, + """ + a + a. + a.c + a.c. + a.c.d + a.c.d1 + a.c.d1$ + a.d= + b + b.= + """, + ) + + def test_6(self): + _assert_dafsa( + """ + a61 + a661 + b61 + b661 + """, + """ + a + a6 + a61 + a61$ + a66 + a661= + b + b6= + """, + ) + + def test_7(self): + _assert_dafsa( + """ + a61 + a6661 + b61 + b6661 + """, + """ + a + a6 + a61 + a61$ + a66 + a666 + a6661= + b + b6= + """, + ) + + def test_8(self): + _assert_dafsa( + """ + acc1 + bc1 + bccc1 + """, + """ + a + ac + acc + acc1 + acc1$ + b + bc + bc1= + bcc= + """, + ) + + def test_9(self): + _assert_dafsa( + """ + acc1 + bc1 + bcc1 + """, + """ + a + ac + acc + acc1 + acc1$ + b + bc + bc1= + bcc= + """, + ) + + def test_10(self): + _assert_dafsa( + """ + acc1 + cc1 + cccc1 + """, + """ + a + ac + acc + acc1 + acc1$ + c + cc + cc1= + ccc= + """, + ) + + def test_11(self): + _assert_dafsa( + """ + ac1 + acc1 + bc1 + bcc1 + """, + """ + a + ac + ac1 + ac1$ + acc + acc1= + b + bc= + """, + ) + + def test_12(self): + _assert_dafsa( + """ + acd1 + bcd1 + bcdd1 + """, + """ + a + ac + acd + acd1 + acd1$ + b + bc + bcd + bcd1= + bcdd= + """, + ) + + def test_13(self): + _assert_dafsa( + """ + ac1 + acc1 + bc1 + bcc1 + bccc1 + """, + """ + a + ac + ac1 + ac1$ + acc + acc1= + b + bc + bc1= + bcc= + """, + ) + + def test_14(self): + _assert_dafsa( + """ + acc1 + acccc1 + bcc1 + bcccc1 + bcccccc1 + """, + """ + a + ac + acc + acc1 + acc1$ + accc + acccc + acccc1= + b + bc + bcc + bcc1= + bccc= + """, + ) + + def test_15(self): + _assert_dafsa( + """ + ac1 + bc1 + acac1 + """, + """ + a + ac + ac1 + ac1$ + aca + acac + acac1= + b + bc= + """, + ) + + def test_16(self): + _assert_dafsa( + """ + bat1 + t1 + tbat1 + """, + """ + b + ba + bat + bat1 + bat1$ + t + t1= + tb= + """, + ) + + def test_17(self): + _assert_dafsa( + """ + acow1 + acat1 + t1 + tcat1 + acatcat1 + """, + """ + a + ac + aca + acat + acat1 + acat1$ + acatc + acatca + acatcat + acatcat1= + aco + acow + acow1= + t= + """, + ) + + def test_18(self): + _assert_dafsa( + """ + bc1 + abc1 + abcxyzc1 + """, + """ + a + ab + abc + abc1 + abc1$ + abcx + abcxy + abcxyz + abcxyzc + abcxyzc1= + b + bc= + """, + ) + + def test_19(self): + _assert_dafsa( + """ + a.z1 + a.y1 + c.z1 + d.z1 + d.y1 + """, + """ + a + a. + a.y + a.y1 + a.y1$ + a.z + a.z1= + c + c. + c.z= + d + d.= + """, + ) + + def test_20(self): + _assert_dafsa( + """ + acz1 + acy1 + accz1 + acccz1 + bcz1 + bcy1 + bccz1 + bcccz1 + """, + """ + a + ac + acc + accc + acccz + acccz1 + acccz1$ + accz= + acy + acy1= + acz= + b + bc= + """, + ) + + +if __name__ == "__main__": + mozunit.main() diff --git a/xpcom/ds/tools/incremental_dafsa.py b/xpcom/ds/tools/incremental_dafsa.py new file mode 100644 index 0000000000..1157c26850 --- /dev/null +++ b/xpcom/ds/tools/incremental_dafsa.py @@ -0,0 +1,509 @@ +# 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/. + +""" +Incremental algorithm for creating a "deterministic acyclic finite state +automaton" (DAFSA). At the time of writing this algorithm, there was existing logic +that depended on a different format for the DAFSA, so this contains convenience +functions for converting to a compatible structure. This legacy format is defined +in make_dafsa.py. +""" + +from typing import Callable, Dict, List, Optional + + +class Node: + children: Dict[str, "Node"] + parents: Dict[str, List["Node"]] + character: str + is_root_node: bool + is_end_node: bool + + def __init__(self, character, is_root_node=False, is_end_node=False): + self.children = {} + self.parents = {} + self.character = character + self.is_root_node = is_root_node + self.is_end_node = is_end_node + + def __str__(self): + """Produce a helpful string representation of this node. + + This is expected to only be used for debugging. + The produced output is: + + "c[def.] <123>" + ^ ^ ^ + | | Internal python ID of the node (used for de-duping) + | | + | One possible path through the tree to the end + | + Current node character + """ + + if self.is_root_node: + return "" + elif self.is_end_node: + return "" + + first_potential_match = "" + node = self + while node.children: + first_character = next(iter(node.children)) + if first_character: + first_potential_match += first_character + node = node.children[first_character] + + return "%s[%s] <%d>" % (self.character, first_potential_match, id(self)) + + def add_child(self, child): + self.children[child.character] = child + child.parents.setdefault(self.character, []) + child.parents[self.character].append(self) + + def remove(self): + # remove() must only be called when this node has only a single parent, and that + # parent doesn't need this child anymore. + # The caller is expected to have performed this validation. + # (placing asserts here add a non-trivial performance hit) + + # There's only a single parent, so only one list should be in the "parents" map + parent_list = next(iter(self.parents.values())) + self.remove_parent(parent_list[0]) + for child in list(self.children.values()): + child.remove_parent(self) + + def remove_parent(self, parent_node: "Node"): + parent_node.children.pop(self.character) + parents_for_character = self.parents[parent_node.character] + parents_for_character.remove(parent_node) + if not parents_for_character: + self.parents.pop(parent_node.character) + + def copy_fork_node(self, fork_node: "Node", child_to_avoid: Optional["Node"]): + """Shallow-copy a node's children. + + When adding a new word, sometimes previously-joined suffixes aren't perfect + matches any more. When this happens, some nodes need to be "copied" out. + For all non-end nodes, there's a child to avoid in the shallow-copy. + """ + + for child in fork_node.children.values(): + if child is not child_to_avoid: + self.add_child(child) + + def is_fork(self): + """Check if this node has multiple parents""" + + if len(self.parents) == 0: + return False + + if len(self.parents) > 1: + return True + + return len(next(iter(self.parents.values()))) > 1 + + def is_replacement_for_prefix_end_node(self, old: "Node"): + """Check if this node is a valid replacement for an old end node. + + A node is a valid replacement if it maintains all existing child paths while + adding the new child path needed for the new word. + + Args: + old: node being replaced + + Returns: True if this node is a valid replacement node. + """ + + if len(self.children) != len(old.children) + 1: + return False + + for character, other_node in old.children.items(): + this_node = self.children.get(character) + if other_node is not this_node: + return False + + return True + + def is_replacement_for_prefix_node(self, old: "Node"): + """Check if this node is a valid replacement for a non-end node. + + A node is a valid replacement if it: + * Has one new child that the old node doesn't + * Is missing a child that the old node has + * Shares all other children + + Returns: True if this node is a valid replacement node. + """ + + if len(self.children) != len(old.children): + return False + + found_extra_child = False + + for character, other_node in old.children.items(): + this_node = self.children.get(character) + if other_node is not this_node: + if found_extra_child: + # Found two children in the old node that aren't in the new one, + # this isn't a valid replacement + return False + else: + found_extra_child = True + + return found_extra_child + + +class SuffixCursor: + index: int # Current position of the cursor within the DAFSA. + node: Node + + def __init__(self, index, node): + self.index = index + self.node = node + + def _query(self, character: str, check: Callable[[Node], bool]): + for node in self.node.parents.get(character, []): + if check(node): + self.index -= 1 + self.node = node + return True + return False + + def find_single_child(self, character): + """Find the next matching suffix node that has a single child. + + Return True if such a node is found.""" + return self._query(character, lambda node: len(node.children) == 1) + + def find_end_of_prefix_replacement(self, end_of_prefix: Node): + """Find the next matching suffix node that replaces the old prefix-end node. + + Return True if such a node is found.""" + return self._query( + end_of_prefix.character, + lambda node: node.is_replacement_for_prefix_end_node(end_of_prefix), + ) + + def find_inside_of_prefix_replacement(self, prefix_node: Node): + """Find the next matching suffix node that replaces a node within the prefix. + + Return True if such a node is found.""" + return self._query( + prefix_node.character, + lambda node: node.is_replacement_for_prefix_node(prefix_node), + ) + + +class DafsaAppendStateMachine: + """State machine for adding a word to a Dafsa. + + Each state returns a function reference to the "next state". States should be + invoked until "None" is returned, in which case the new word has been appended. + + The prefix and suffix indexes are placed according to the currently-known valid + value (not the next value being investigated). Additionally, they are 0-indexed + against the root node (which sits behind the beginning of the string). + + Let's imagine we're at the following state when adding, for example, the + word "mozilla.org": + + mozilla.org + ^ ^ ^ ^ + | | | | + / | | \ + [root] | | [end] node + node | \ + | suffix + \ + prefix + + In this state, the furthest prefix match we could find was: + [root] - m - o - z - i - l + The index of the prefix match is "5". + + Additionally, we've been looking for suffix nodes, and we've already found: + r - g - [end] + The current suffix index is "10". + The next suffix node we'll attempt to find is at index "9". + """ + + root_node: Node + prefix_index: int + suffix_cursor: SuffixCursor + stack: List[Node] + word: str + suffix_overlaps_prefix: bool + first_fork_index: Optional[int] + _state: Callable + + def __init__(self, word, root_node, end_node): + self.root_node = root_node + self.prefix_index = 0 + self.suffix_cursor = SuffixCursor(len(word) + 1, end_node) + self.stack = [root_node] + self.word = word + self.suffix_overlaps_prefix = False + self.first_fork_index = None + self._state = self._find_prefix + + def run(self): + """Run this state machine to completion, adding the new word.""" + while self._state is not None: + self._state = self._state() + + def _find_prefix(self): + """Find the longest existing prefix that matches the current word.""" + prefix_node = self.root_node + while self.prefix_index < len(self.word): + next_character = self.word[self.prefix_index] + next_node = prefix_node.children.get(next_character) + if not next_node: + # We're finished finding the prefix, let's find the longest suffix + # match now. + return self._find_suffix_nodes_after_prefix + + self.prefix_index += 1 + prefix_node = next_node + self.stack.append(next_node) + + if not self.first_fork_index and next_node.is_fork(): + self.first_fork_index = self.prefix_index + + # Deja vu, we've appended this string before. Since this string has + # already been appended, we don't have to do anything. + return None + + def _find_suffix_nodes_after_prefix(self): + """Find the chain of suffix nodes for characters after the prefix.""" + while self.suffix_cursor.index - 1 > self.prefix_index: + # To fetch the next character, we need to subtract two from the current + # suffix index. This is because: + # * The next suffix node is 1 node before our current node (subtract 1) + # * The suffix index includes the root node before the beginning of the + # string - it's like the string is 1-indexed (subtract 1 again). + next_character = self.word[self.suffix_cursor.index - 2] + if not self.suffix_cursor.find_single_child(next_character): + return self._add_new_nodes + + if self.suffix_cursor.node is self.stack[-1]: + # The suffix match is overlapping with the prefix! This can happen in + # cases like: + # * "ab" + # * "abb" + # The suffix cursor is at the same node as the prefix match, but they're + # at different positions in the word. + # + # [root] - a - b - [end] + # ^ + # / \ + # / \ + # prefix suffix + # \ / + # \ / + # VV + # "abb" + if not self.first_fork_index: + # There hasn't been a fork, so our prefix isn't shared. So, we + # can mark this node as a fork, since the repetition means + # that there's two paths that are now using this node + self.first_fork_index = self.prefix_index + return self._add_new_nodes + + # Removes the link between the unique part of the prefix and the + # shared part of the prefix. + self.stack[self.first_fork_index].remove_parent( + self.stack[self.first_fork_index - 1] + ) + self.suffix_overlaps_prefix = True + + if self.first_fork_index is None: + return self._find_next_suffix_nodes + elif self.suffix_cursor.index - 1 == self.first_fork_index: + return self._find_next_suffix_node_at_prefix_end_at_fork + else: + return self._find_next_suffix_node_at_prefix_end_after_fork + + def _find_next_suffix_node_at_prefix_end_at_fork(self): + """Find the next suffix node that replaces the end of the prefix. + + In this state, the prefix_end node is the same as the first fork node. + Therefore, if a match can be found, the old prefix node can't be entirely + deleted since it's used elsewhere. Instead, just the link between our + unique prefix and the end of the fork is removed. + """ + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_end_of_prefix_replacement(existing_node): + return self._add_new_nodes + + self.prefix_index -= 1 + self.first_fork_index = None + + if not self.suffix_overlaps_prefix: + existing_node.remove_parent(self.stack[self.prefix_index]) + else: + # When the suffix overlaps the prefix, the old "parent link" was removed + # earlier in the "find_suffix_nodes_after_prefix" step. + self.suffix_overlaps_prefix = False + + return self._find_next_suffix_nodes + + def _find_next_suffix_node_at_prefix_end_after_fork(self): + """Find the next suffix node that replaces the end of the prefix. + + In this state, the prefix_end node is after the first fork node. + Therefore, even if a match is found, we don't want to modify the replaced + prefix node since an unrelated word chain uses it. + """ + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_end_of_prefix_replacement(existing_node): + return self._add_new_nodes + + self.prefix_index -= 1 + if self.prefix_index == self.first_fork_index: + return self._find_next_suffix_node_within_prefix_at_fork + else: + return self._find_next_suffix_nodes_within_prefix_after_fork + + def _find_next_suffix_node_within_prefix_at_fork(self): + """Find the next suffix node within a prefix. + + In this state, we've already worked our way back and found nodes in the suffix + to replace prefix nodes after the fork node. We have now reached the fork node, + and if we find a replacement for it, then we can remove the link between it + and our then-unique prefix and clear the fork status. + """ + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_inside_of_prefix_replacement(existing_node): + return self._add_new_nodes + + self.prefix_index -= 1 + self.first_fork_index = None + + if not self.suffix_overlaps_prefix: + existing_node.remove_parent(self.stack[self.prefix_index]) + else: + # When the suffix overlaps the prefix, the old "parent link" was removed + # earlier in the "find_suffix_nodes_after_prefix" step. + self.suffix_overlaps_prefix = False + + return self._find_next_suffix_nodes + + def _find_next_suffix_nodes_within_prefix_after_fork(self): + """Find the next suffix nodes within a prefix. + + Finds suffix nodes to replace prefix nodes, but doesn't modify the prefix + nodes since they're after a fork (so, we're sharing prefix nodes with + other words and can't modify them). + """ + while True: + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_inside_of_prefix_replacement(existing_node): + return self._add_new_nodes + + self.prefix_index -= 1 + if self.prefix_index == self.first_fork_index: + return self._find_next_suffix_node_within_prefix_at_fork + + def _find_next_suffix_nodes(self): + """Find all remaining suffix nodes in the chain. + + In this state, there's no (longer) any fork, so there's no other words + using our current prefix. Therefore, as we find replacement nodes as we + work our way backwards, we can remove the now-unused prefix nodes. + """ + while True: + existing_node = self.stack[self.prefix_index] + if not self.suffix_cursor.find_end_of_prefix_replacement(existing_node): + return self._add_new_nodes + + # This prefix node is wholly replaced by the new suffix node, so it can + # be deleted. + existing_node.remove() + self.prefix_index -= 1 + + def _add_new_nodes(self): + """Adds new nodes to support the new word. + + Duplicates forked nodes to make room for new links, adds new nodes for new + characters, and splices the prefix to the suffix to finish embedding the new + word into the DAFSA. + """ + if self.first_fork_index is not None: + front_node = _duplicate_fork_nodes( + self.stack, + self.first_fork_index, + self.prefix_index, + # if suffix_overlaps_parent, the parent link was removed + # earlier in the word-adding process. + remove_parent_link=not self.suffix_overlaps_prefix, + ) + else: + front_node = self.stack[self.prefix_index] + + new_text = self.word[self.prefix_index : self.suffix_cursor.index - 1] + for character in new_text: + new_node = Node(character) + front_node.add_child(new_node) + front_node = new_node + + front_node.add_child(self.suffix_cursor.node) + return None # Done! + + +def _duplicate_fork_nodes(stack, fork_index, prefix_index, remove_parent_link=True): + parent_node = stack[fork_index - 1] + if remove_parent_link: + # remove link to old chain that we're going to be copying + stack[fork_index].remove_parent(parent_node) + + for index in range(fork_index, prefix_index + 1): + fork_node = stack[index] + replacement_node = Node(fork_node.character) + child_to_avoid = None + if index < len(stack) - 1: + # We're going to be manually replacing the next node in the stack, + # so don't connect it as a child. + child_to_avoid = stack[index + 1] + + replacement_node.copy_fork_node(fork_node, child_to_avoid) + parent_node.add_child(replacement_node) + parent_node = replacement_node + + return parent_node + + +class Dafsa: + root_node: Node + end_node: Node + + def __init__(self): + self.root_node = Node(None, is_root_node=True) + self.end_node = Node(None, is_end_node=True) + + @classmethod + def from_tld_data(cls, lines): + """Create a dafsa for TLD data. + + TLD data has a domain and a "type" enum. The source data encodes the type as a + text number, but the dafsa-consuming code assumes that the type is a raw byte + number (e.g.: "1" => 0x01). + + This function acts as a helper, processing this TLD detail before creating a + standard dafsa. + """ + + dafsa = cls() + for i, word in enumerate(lines): + domain_number = word[-1] + # Convert type from string to byte representation + raw_domain_number = chr(ord(domain_number) & 0x0F) + + word = "%s%s" % (word[:-1], raw_domain_number) + dafsa.append(word) + return dafsa + + def append(self, word): + state_machine = DafsaAppendStateMachine(word, self.root_node, self.end_node) + state_machine.run() diff --git a/xpcom/ds/tools/make_dafsa.py b/xpcom/ds/tools/make_dafsa.py new file mode 100644 index 0000000000..a9cedf78a8 --- /dev/null +++ b/xpcom/ds/tools/make_dafsa.py @@ -0,0 +1,372 @@ +#!/usr/bin/env python +# Copyright 2014 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +""" +A Deterministic acyclic finite state automaton (DAFSA) is a compact +representation of an unordered word list (dictionary). + +http://en.wikipedia.org/wiki/Deterministic_acyclic_finite_state_automaton + +This python program converts a list of strings to a byte array in C++. +This python program fetches strings and return values from a gperf file +and generates a C++ file with a byte array representing graph that can be +used as a memory efficient replacement for the perfect hash table. + +The input strings are assumed to consist of printable 7-bit ASCII characters +and the return values are assumed to be one digit integers. + +In this program a DAFSA is a diamond shaped graph starting at a common +root node and ending at a common end node. All internal nodes contain +a character and each word is represented by the characters in one path from +the root node to the end node. + +The order of the operations is crucial since lookups will be performed +starting from the source with no backtracking. Thus a node must have at +most one child with a label starting by the same character. The output +is also arranged so that all jumps are to increasing addresses, thus forward +in memory. + +The generated output has suffix free decoding so that the sign of leading +bits in a link (a reference to a child node) indicate if it has a size of one, +two or three bytes and if it is the last outgoing link from the actual node. +A node label is terminated by a byte with the leading bit set. + +The generated byte array can described by the following BNF: + + ::= < 8-bit value in range [0x00-0xFF] > + + ::= < printable 7-bit ASCII character, byte in range [0x20-0x7F] > + ::= < char + 0x80, byte in range [0xA0-0xFF] > + ::= < value + 0x80, byte in range [0x80-0x8F] > + + ::= < byte in range [0x00-0x3F] > + ::= < byte in range [0x40-0x5F] > + ::= < byte in range [0x60-0x7F] > + + ::= < byte in range [0x80-0xBF] > + ::= < byte in range [0xC0-0xDF] > + ::= < byte in range [0xE0-0xFF] > + + ::= + + + +

The tree

+ +
+
+mozilla/xpcom/reflect/xptcall
+  +--public  // exported headers
+  +--src  // core source
+  |  \--md  // platform specific parts
+  |     +--mac  // mac ppc
+  |     +--unix  // all unix
+  |     \--win32  // win32
+  |     +--test  // simple tests to get started
+  \--tests  // full tests via api
+
+ + Porters are free to create subdirectories under the base md + directory for their given platforms and to integrate into the build system + as appropriate for their platform. +
+ +

Theory of operation

+ +
+ There are really two pieces of functionality: invoke and + stubs... + +

+ The invoke functionality requires the implementation of + the following on each platform (from + xptcall/xptcall.h): +

+ +
+XPTC_PUBLIC_API(nsresult)
+NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex,
+                   uint32_t paramCount, nsXPTCVariant* params);
+
+ + Calling code is expected to supply an array of + nsXPTCVariant structs. These are discriminated unions + describing the type and value of each parameter of the target function. + The platform specific code then builds a call frame and invokes the method + indicated by the index methodIndex on the xpcom interface + that. + +

+ Here are examples of this implementation for + Win32 + and + Linux x86, NetBSD x86, and FreeBSD. Both of these implementations use the basic strategy of: figure out + how much stack space is needed for the params, make the space in a new + frame, copy the params to that space, invoke the method, cleanup and + return. C++ is used where appropriate, Assembly language is used where + necessary. Inline assembly language is used here, but it is equally + valid to use separate assembly language source files. Porters can decide + how best to do this for their platforms. +

+ +

+ The stubs functionality is more complex. The goal here is + a class whose vtbl can look like the vtbl of any arbitrary xpcom + interface. Objects of this class can then be built to impersonate any + xpcom object. The base interface for this is (from + xptcall/xptcall.h): +

+ +
+class nsXPTCStubBase : public nsISupports
+{
+public:
+    // Include generated vtbl stub declarations.
+    // These are virtual and *also* implemented by this class..
+#include "xptcstubsdecl.inc"
+
+    // The following methods must be provided by inheritor of this class.
+
+    // return a refcounted pointer to the InterfaceInfo for this object
+    // NOTE: on some platforms this MUST not fail or we crash!
+    NS_IMETHOD GetInterfaceInfo(const nsXPTInterfaceInfo** info) = 0;
+
+    // call this method and return result
+    NS_IMETHOD CallMethod(uint16_t methodIndex,
+                          const nsXPTMethodInfo* info,
+                          nsXPTCMiniVariant* params) = 0;
+};
+
+ + Code that wishes to make use of this stubs functionality (such as + XPConnect) implement a + class which inherits from nsXPTCStubBase and implements the + GetInterfaceInfo and CallMethod to let the + platform specific code know how to get interface information and how to + dispatch methods once their parameters have been pulled out of the + platform specific calling frame. + +

+ Porters of this functionality implement the platform specific code for + the + stub methods that fill the vtbl for this class. The idea here is + that the class has a vtbl full of a large number of generic stubs. All + instances of this class share that vtbl and the same stubs. The stubs + forward calls to a platform specific method that uses the interface + information supplied by the overridden GetInterfaceInfo to + extract the parameters and build an array of platform independent + nsXPTCMiniVariant structs which are in turn passed on to + the overridden CallMethod. The platform dependent code is + responsible for doing any cleanup and returning. +

+ +

+ The stub methods are declared in + xptcall/xptcstubsdecl.inc. These are '#included' into the declaration of + nsXPTCStubBase. A similar include file (xptcall/xptcstubsdef.inc) is expanded using platform specific macros to define the stub + functions. These '.inc' files are checked into cvs. However, they can be + regenerated as necessary (i.e. to change the number of stubs or to + change their specific declaration) using the Perl script + xptcall/genstubs.pl. +

+ +

+ Here are examples of this implementation for + Win32 + and + Linux x86, NetBSD x86, and FreeBSD. Both of these examples use inline assembly language. That is just how + I decided to do it. You can do it as you choose. +

+ +

+ The Win32 version is somewhat tighter because the __declspec(naked) + feature allows for very small stubs. However, the __stdcall requires the + callee to clean up the stack, so it is imperative that the interface + information scheme allow the code to determine the correct stack pointer + fixup for return without fail, else the process will crash. +

+ +

+ I opted to use inline assembler for the gcc Linux x86 port. I ended up + with larger stubs than I would have preferred rather than battle the + compiler over what would happen to the stack before my asm code began + running. +

+ +

+ I believe that the non-assembly parts of these files can be copied and + reused with minimal (but not zero) platform specific tweaks. Feel free + to copy and paste as necessary. Please remember that safety and + reliability are more important than speed optimizations. This code is + primarily used to connect XPCOM components with JavaScript; function + call overhead is a tiny part of the time involved. +

+ +

+ I put together + xptcall/md/test + + as a place to evolve the basic functionality as a port is coming + together. Not all of the functionality is exercised, but it is a place + to get started. + xptcall/tests + + has an api level test for NS_InvokeByIndex, but no tests + for the stubs functionality. Such a test ought to be written, but + this has not yet been done. +

+ +

+ A full 'test' at this point requires building the client and running the + XPConnect test called TestXPC in + mozilla/js/xpconnect/tests . +

+
+ +
+ Author: + John Bandhauer <jband@netscape.com>
+ Last modified: 31 May 1999 + + diff --git a/xpcom/reflect/xptcall/xptcall.cpp b/xpcom/reflect/xptcall/xptcall.cpp new file mode 100644 index 0000000000..0b28791b54 --- /dev/null +++ b/xpcom/reflect/xptcall/xptcall.cpp @@ -0,0 +1,55 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* entry point wrappers. */ + +#include "xptcprivate.h" + +using namespace mozilla; + +NS_IMETHODIMP +nsXPTCStubBase::QueryInterface(REFNSIID aIID, void** aInstancePtr) { + if (aIID.Equals(mEntry->IID())) { + NS_ADDREF_THIS(); + *aInstancePtr = static_cast(this); + return NS_OK; + } + + return mOuter->QueryInterface(aIID, aInstancePtr); +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXPTCStubBase::AddRef() { return mOuter->AddRef(); } + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXPTCStubBase::Release() { return mOuter->Release(); } + +EXPORT_XPCOM_API(nsresult) +NS_GetXPTCallStub(REFNSIID aIID, nsIXPTCProxy* aOuter, + nsISomeInterface** aResult) { + if (NS_WARN_IF(!aOuter) || NS_WARN_IF(!aResult)) return NS_ERROR_INVALID_ARG; + + const nsXPTInterfaceInfo* iie = nsXPTInterfaceInfo::ByIID(aIID); + if (!iie || iie->IsBuiltinClass()) { + return NS_ERROR_FAILURE; + } + + *aResult = new nsXPTCStubBase(aOuter, iie); + return NS_OK; +} + +EXPORT_XPCOM_API(void) +NS_DestroyXPTCallStub(nsISomeInterface* aStub) { + nsXPTCStubBase* stub = static_cast(aStub); + delete (stub); +} + +EXPORT_XPCOM_API(size_t) +NS_SizeOfIncludingThisXPTCallStub(const nsISomeInterface* aStub, + mozilla::MallocSizeOf aMallocSizeOf) { + // We could cast aStub to nsXPTCStubBase, but that class doesn't seem to own + // anything, so just measure the size of the object itself. + return aMallocSizeOf(aStub); +} diff --git a/xpcom/reflect/xptcall/xptcall.h b/xpcom/reflect/xptcall/xptcall.h new file mode 100644 index 0000000000..a36ca53d23 --- /dev/null +++ b/xpcom/reflect/xptcall/xptcall.h @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Public declarations for xptcall. */ + +#ifndef xptcall_h___ +#define xptcall_h___ + +#include "nscore.h" +#include "nsISupports.h" +#include "xptinfo.h" +#include "js/Value.h" +#include "mozilla/MemoryReporting.h" + +struct nsXPTCMiniVariant { + // No ctors or dtors so that we can use arrays of these on the stack + // with no penalty. + union Union { + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + float f; + double d; + bool b; + char c; + char16_t wc; + void* p; + }; + + Union val; +}; + +static_assert(offsetof(nsXPTCMiniVariant, val) == 0, + "nsXPTCMiniVariant must be a thin wrapper"); + +struct nsXPTCVariant { + union ExtendedVal { + // ExtendedVal is an extension on nsXPTCMiniVariant. It contains types + // unknown to the assembly implementations which must be passed by indirect + // semantics. + // + // nsXPTCVariant contains enough space to store ExtendedVal inline, which + // can be used to store these types when IsIndirect() is true. + nsXPTCMiniVariant mini; + + nsID nsid; + nsCString nscstr; + nsString nsstr; + JS::Value jsval; + xpt::detail::UntypedTArray array; + + // This type contains non-standard-layout types, so needs an explicit + // Ctor/Dtor - we'll just delete them. + ExtendedVal() = delete; + ~ExtendedVal() = delete; + }; + + union { + // The `val` field from nsXPTCMiniVariant. + nsXPTCMiniVariant::Union val; + + // Storage for any extended variants. + ExtendedVal ext; + }; + + nsXPTType type; + uint8_t flags; + + // Clear to a valid, null state. + nsXPTCVariant() { + memset(this, 0, sizeof(nsXPTCVariant)); + type = nsXPTType::T_VOID; + } + + enum { + // + // Bitflag definitions + // + + // Indicates that we &val.p should be passed n the stack, i.e. that + // val should be passed by reference. + IS_INDIRECT = 0x1, + }; + + void ClearFlags() { flags = 0; } + void SetIndirect() { flags |= IS_INDIRECT; } + + bool IsIndirect() const { return 0 != (flags & IS_INDIRECT); } + + // Implicitly convert to nsXPTCMiniVariant. + operator nsXPTCMiniVariant&() { return *(nsXPTCMiniVariant*)&val; } + operator const nsXPTCMiniVariant&() const { + return *(const nsXPTCMiniVariant*)&val; + } + + // As this type contains an anonymous union, we need to provide an explicit + // destructor. + ~nsXPTCVariant() {} +}; + +static_assert(offsetof(nsXPTCVariant, val) == offsetof(nsXPTCVariant, ext), + "nsXPTCVariant::{ext,val} must have matching offsets"); + +// static_assert that nsXPTCVariant::ExtendedVal is large enough and +// well-aligned enough for every XPT-supported type. +#define XPT_CHECK_SIZEOF(xpt, type) \ + static_assert(sizeof(nsXPTCVariant::ExtendedVal) >= sizeof(type), \ + "nsXPTCVariant::ext not big enough for " #xpt " (" #type ")"); \ + static_assert(MOZ_ALIGNOF(nsXPTCVariant::ExtendedVal) >= MOZ_ALIGNOF(type), \ + "nsXPTCVariant::ext not aligned enough for " #xpt " (" #type \ + ")"); +XPT_FOR_EACH_TYPE(XPT_CHECK_SIZEOF) +#undef XPT_CHECK_SIZEOF + +class nsIXPTCProxy : public nsISupports { + public: + NS_IMETHOD CallMethod(uint16_t aMethodIndex, const nsXPTMethodInfo* aInfo, + nsXPTCMiniVariant* aParams) = 0; +}; + +/** + * This is a typedef to avoid confusion between the canonical + * nsISupports* that provides object identity and an interface pointer + * for inheriting interfaces that aren't known at compile-time. + */ +typedef nsISupports nsISomeInterface; + +/** + * Get a proxy object to implement the specified interface. + * + * @param aIID The IID of the interface to implement. + * @param aOuter An object to receive method calls from the proxy object. + * The stub forwards QueryInterface/AddRef/Release to the + * outer object. The proxy object does not hold a reference to + * the outer object; it is the caller's responsibility to + * ensure that this pointer remains valid until the stub has + * been destroyed. + * @param aStub Out parameter for the new proxy object. The object is + * not addrefed. The object never destroys itself. It must be + * explicitly destroyed by calling + * NS_DestroyXPTCallStub when it is no longer needed. + */ +XPCOM_API(nsresult) +NS_GetXPTCallStub(REFNSIID aIID, nsIXPTCProxy* aOuter, + nsISomeInterface** aStub); + +/** + * Destroys an XPTCall stub previously created with NS_GetXPTCallStub. + */ +XPCOM_API(void) +NS_DestroyXPTCallStub(nsISomeInterface* aStub); + +/** + * Measures the size of an XPTCall stub previously created with + * NS_GetXPTCallStub. + */ +XPCOM_API(size_t) +NS_SizeOfIncludingThisXPTCallStub(const nsISomeInterface* aStub, + mozilla::MallocSizeOf aMallocSizeOf); + +// this is extern "C" because on some platforms it is implemented in assembly +extern "C" nsresult NS_InvokeByIndex(nsISupports* that, uint32_t methodIndex, + uint32_t paramCount, + nsXPTCVariant* params); + +#endif /* xptcall_h___ */ diff --git a/xpcom/reflect/xptcall/xptcprivate.h b/xpcom/reflect/xptcall/xptcprivate.h new file mode 100644 index 0000000000..02f408dc38 --- /dev/null +++ b/xpcom/reflect/xptcall/xptcprivate.h @@ -0,0 +1,68 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* All the xptcall private declarations - only include locally. */ + +#ifndef xptcprivate_h___ +#define xptcprivate_h___ + +#include "xptcall.h" +#include "mozilla/Attributes.h" + +#if !defined(__ia64) || \ + (!defined(__hpux) && !defined(__linux__) && !defined(__FreeBSD__)) +# define STUB_ENTRY(n) NS_IMETHOD Stub##n() = 0; +#else +# define STUB_ENTRY(n) \ + NS_IMETHOD Stub##n(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, \ + uint64_t, uint64_t, uint64_t) = 0; +#endif + +#define SENTINEL_ENTRY(n) NS_IMETHOD Sentinel##n() = 0; + +class nsIXPTCStubBase : public nsISupports { + public: +#include "xptcstubsdef.inc" +}; + +#undef STUB_ENTRY +#undef SENTINEL_ENTRY + +#if !defined(__ia64) || \ + (!defined(__hpux) && !defined(__linux__) && !defined(__FreeBSD__)) +# define STUB_ENTRY(n) NS_IMETHOD Stub##n() override; +#else +# define STUB_ENTRY(n) \ + NS_IMETHOD Stub##n(uint64_t, uint64_t, uint64_t, uint64_t, uint64_t, \ + uint64_t, uint64_t, uint64_t) override; +#endif + +#define SENTINEL_ENTRY(n) NS_IMETHOD Sentinel##n() override; + +class nsXPTCStubBase final : public nsIXPTCStubBase { + public: + NS_DECL_ISUPPORTS_INHERITED + +#include "xptcstubsdef.inc" + + nsXPTCStubBase(nsIXPTCProxy* aOuter, const nsXPTInterfaceInfo* aEntry) + : mOuter(aOuter), mEntry(aEntry) {} + + nsIXPTCProxy* mOuter; + const nsXPTInterfaceInfo* mEntry; + + ~nsXPTCStubBase() = default; +}; + +#undef STUB_ENTRY +#undef SENTINEL_ENTRY + +#if defined(__clang__) || defined(__GNUC__) +# define ATTRIBUTE_USED __attribute__((__used__)) +#else +# define ATTRIBUTE_USED +#endif + +#endif /* xptcprivate_h___ */ diff --git a/xpcom/reflect/xptcall/xptcstubsdecl.inc b/xpcom/reflect/xptcall/xptcstubsdecl.inc new file mode 100644 index 0000000000..91d35fe322 --- /dev/null +++ b/xpcom/reflect/xptcall/xptcstubsdecl.inc @@ -0,0 +1,761 @@ +/* generated file - DO NOT EDIT */ + +/* includes 247 stub entries, and 5 sentinel entries */ + +/* +* declarations of normal stubs... +* 0 is QueryInterface +* 1 is AddRef +* 2 is Release +*/ +#if !defined(__ia64) || (!defined(__hpux) && !defined(__linux__) && !defined(__FreeBSD__)) +NS_IMETHOD Stub3(); +NS_IMETHOD Stub4(); +NS_IMETHOD Stub5(); +NS_IMETHOD Stub6(); +NS_IMETHOD Stub7(); +NS_IMETHOD Stub8(); +NS_IMETHOD Stub9(); +NS_IMETHOD Stub10(); +NS_IMETHOD Stub11(); +NS_IMETHOD Stub12(); +NS_IMETHOD Stub13(); +NS_IMETHOD Stub14(); +NS_IMETHOD Stub15(); +NS_IMETHOD Stub16(); +NS_IMETHOD Stub17(); +NS_IMETHOD Stub18(); +NS_IMETHOD Stub19(); +NS_IMETHOD Stub20(); +NS_IMETHOD Stub21(); +NS_IMETHOD Stub22(); +NS_IMETHOD Stub23(); +NS_IMETHOD Stub24(); +NS_IMETHOD Stub25(); +NS_IMETHOD Stub26(); +NS_IMETHOD Stub27(); +NS_IMETHOD Stub28(); +NS_IMETHOD Stub29(); +NS_IMETHOD Stub30(); +NS_IMETHOD Stub31(); +NS_IMETHOD Stub32(); +NS_IMETHOD Stub33(); +NS_IMETHOD Stub34(); +NS_IMETHOD Stub35(); +NS_IMETHOD Stub36(); +NS_IMETHOD Stub37(); +NS_IMETHOD Stub38(); +NS_IMETHOD Stub39(); +NS_IMETHOD Stub40(); +NS_IMETHOD Stub41(); +NS_IMETHOD Stub42(); +NS_IMETHOD Stub43(); +NS_IMETHOD Stub44(); +NS_IMETHOD Stub45(); +NS_IMETHOD Stub46(); +NS_IMETHOD Stub47(); +NS_IMETHOD Stub48(); +NS_IMETHOD Stub49(); +NS_IMETHOD Stub50(); +NS_IMETHOD Stub51(); +NS_IMETHOD Stub52(); +NS_IMETHOD Stub53(); +NS_IMETHOD Stub54(); +NS_IMETHOD Stub55(); +NS_IMETHOD Stub56(); +NS_IMETHOD Stub57(); +NS_IMETHOD Stub58(); +NS_IMETHOD Stub59(); +NS_IMETHOD Stub60(); +NS_IMETHOD Stub61(); +NS_IMETHOD Stub62(); +NS_IMETHOD Stub63(); +NS_IMETHOD Stub64(); +NS_IMETHOD Stub65(); +NS_IMETHOD Stub66(); +NS_IMETHOD Stub67(); +NS_IMETHOD Stub68(); +NS_IMETHOD Stub69(); +NS_IMETHOD Stub70(); +NS_IMETHOD Stub71(); +NS_IMETHOD Stub72(); +NS_IMETHOD Stub73(); +NS_IMETHOD Stub74(); +NS_IMETHOD Stub75(); +NS_IMETHOD Stub76(); +NS_IMETHOD Stub77(); +NS_IMETHOD Stub78(); +NS_IMETHOD Stub79(); +NS_IMETHOD Stub80(); +NS_IMETHOD Stub81(); +NS_IMETHOD Stub82(); +NS_IMETHOD Stub83(); +NS_IMETHOD Stub84(); +NS_IMETHOD Stub85(); +NS_IMETHOD Stub86(); +NS_IMETHOD Stub87(); +NS_IMETHOD Stub88(); +NS_IMETHOD Stub89(); +NS_IMETHOD Stub90(); +NS_IMETHOD Stub91(); +NS_IMETHOD Stub92(); +NS_IMETHOD Stub93(); +NS_IMETHOD Stub94(); +NS_IMETHOD Stub95(); +NS_IMETHOD Stub96(); +NS_IMETHOD Stub97(); +NS_IMETHOD Stub98(); +NS_IMETHOD Stub99(); +NS_IMETHOD Stub100(); +NS_IMETHOD Stub101(); +NS_IMETHOD Stub102(); +NS_IMETHOD Stub103(); +NS_IMETHOD Stub104(); +NS_IMETHOD Stub105(); +NS_IMETHOD Stub106(); +NS_IMETHOD Stub107(); +NS_IMETHOD Stub108(); +NS_IMETHOD Stub109(); +NS_IMETHOD Stub110(); +NS_IMETHOD Stub111(); +NS_IMETHOD Stub112(); +NS_IMETHOD Stub113(); +NS_IMETHOD Stub114(); +NS_IMETHOD Stub115(); +NS_IMETHOD Stub116(); +NS_IMETHOD Stub117(); +NS_IMETHOD Stub118(); +NS_IMETHOD Stub119(); +NS_IMETHOD Stub120(); +NS_IMETHOD Stub121(); +NS_IMETHOD Stub122(); +NS_IMETHOD Stub123(); +NS_IMETHOD Stub124(); +NS_IMETHOD Stub125(); +NS_IMETHOD Stub126(); +NS_IMETHOD Stub127(); +NS_IMETHOD Stub128(); +NS_IMETHOD Stub129(); +NS_IMETHOD Stub130(); +NS_IMETHOD Stub131(); +NS_IMETHOD Stub132(); +NS_IMETHOD Stub133(); +NS_IMETHOD Stub134(); +NS_IMETHOD Stub135(); +NS_IMETHOD Stub136(); +NS_IMETHOD Stub137(); +NS_IMETHOD Stub138(); +NS_IMETHOD Stub139(); +NS_IMETHOD Stub140(); +NS_IMETHOD Stub141(); +NS_IMETHOD Stub142(); +NS_IMETHOD Stub143(); +NS_IMETHOD Stub144(); +NS_IMETHOD Stub145(); +NS_IMETHOD Stub146(); +NS_IMETHOD Stub147(); +NS_IMETHOD Stub148(); +NS_IMETHOD Stub149(); +NS_IMETHOD Stub150(); +NS_IMETHOD Stub151(); +NS_IMETHOD Stub152(); +NS_IMETHOD Stub153(); +NS_IMETHOD Stub154(); +NS_IMETHOD Stub155(); +NS_IMETHOD Stub156(); +NS_IMETHOD Stub157(); +NS_IMETHOD Stub158(); +NS_IMETHOD Stub159(); +NS_IMETHOD Stub160(); +NS_IMETHOD Stub161(); +NS_IMETHOD Stub162(); +NS_IMETHOD Stub163(); +NS_IMETHOD Stub164(); +NS_IMETHOD Stub165(); +NS_IMETHOD Stub166(); +NS_IMETHOD Stub167(); +NS_IMETHOD Stub168(); +NS_IMETHOD Stub169(); +NS_IMETHOD Stub170(); +NS_IMETHOD Stub171(); +NS_IMETHOD Stub172(); +NS_IMETHOD Stub173(); +NS_IMETHOD Stub174(); +NS_IMETHOD Stub175(); +NS_IMETHOD Stub176(); +NS_IMETHOD Stub177(); +NS_IMETHOD Stub178(); +NS_IMETHOD Stub179(); +NS_IMETHOD Stub180(); +NS_IMETHOD Stub181(); +NS_IMETHOD Stub182(); +NS_IMETHOD Stub183(); +NS_IMETHOD Stub184(); +NS_IMETHOD Stub185(); +NS_IMETHOD Stub186(); +NS_IMETHOD Stub187(); +NS_IMETHOD Stub188(); +NS_IMETHOD Stub189(); +NS_IMETHOD Stub190(); +NS_IMETHOD Stub191(); +NS_IMETHOD Stub192(); +NS_IMETHOD Stub193(); +NS_IMETHOD Stub194(); +NS_IMETHOD Stub195(); +NS_IMETHOD Stub196(); +NS_IMETHOD Stub197(); +NS_IMETHOD Stub198(); +NS_IMETHOD Stub199(); +NS_IMETHOD Stub200(); +NS_IMETHOD Stub201(); +NS_IMETHOD Stub202(); +NS_IMETHOD Stub203(); +NS_IMETHOD Stub204(); +NS_IMETHOD Stub205(); +NS_IMETHOD Stub206(); +NS_IMETHOD Stub207(); +NS_IMETHOD Stub208(); +NS_IMETHOD Stub209(); +NS_IMETHOD Stub210(); +NS_IMETHOD Stub211(); +NS_IMETHOD Stub212(); +NS_IMETHOD Stub213(); +NS_IMETHOD Stub214(); +NS_IMETHOD Stub215(); +NS_IMETHOD Stub216(); +NS_IMETHOD Stub217(); +NS_IMETHOD Stub218(); +NS_IMETHOD Stub219(); +NS_IMETHOD Stub220(); +NS_IMETHOD Stub221(); +NS_IMETHOD Stub222(); +NS_IMETHOD Stub223(); +NS_IMETHOD Stub224(); +NS_IMETHOD Stub225(); +NS_IMETHOD Stub226(); +NS_IMETHOD Stub227(); +NS_IMETHOD Stub228(); +NS_IMETHOD Stub229(); +NS_IMETHOD Stub230(); +NS_IMETHOD Stub231(); +NS_IMETHOD Stub232(); +NS_IMETHOD Stub233(); +NS_IMETHOD Stub234(); +NS_IMETHOD Stub235(); +NS_IMETHOD Stub236(); +NS_IMETHOD Stub237(); +NS_IMETHOD Stub238(); +NS_IMETHOD Stub239(); +NS_IMETHOD Stub240(); +NS_IMETHOD Stub241(); +NS_IMETHOD Stub242(); +NS_IMETHOD Stub243(); +NS_IMETHOD Stub244(); +NS_IMETHOD Stub245(); +NS_IMETHOD Stub246(); +NS_IMETHOD Stub247(); +NS_IMETHOD Stub248(); +NS_IMETHOD Stub249(); +#else +NS_IMETHOD Stub3(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub4(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub5(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub6(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub7(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub8(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub9(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub10(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub11(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub12(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub13(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub14(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub15(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub16(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub17(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub18(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub19(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub20(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub21(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub22(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub23(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub24(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub25(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub26(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub27(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub28(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub29(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub30(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub31(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub32(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub33(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub34(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub35(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub36(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub37(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub38(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub39(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub40(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub41(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub42(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub43(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub44(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub45(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub46(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub47(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub48(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub49(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub50(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub51(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub52(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub53(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub54(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub55(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub56(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub57(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub58(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub59(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub60(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub61(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub62(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub63(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub64(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub65(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub66(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub67(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub68(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub69(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub70(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub71(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub72(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub73(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub74(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub75(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub76(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub77(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub78(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub79(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub80(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub81(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub82(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub83(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub84(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub85(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub86(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub87(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub88(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub89(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub90(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub91(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub92(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub93(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub94(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub95(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub96(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub97(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub98(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub99(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub100(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub101(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub102(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub103(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub104(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub105(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub106(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub107(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub108(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub109(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub110(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub111(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub112(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub113(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub114(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub115(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub116(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub117(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub118(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub119(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub120(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub121(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub122(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub123(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub124(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub125(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub126(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub127(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub128(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub129(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub130(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub131(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub132(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub133(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub134(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub135(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub136(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub137(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub138(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub139(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub140(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub141(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub142(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub143(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub144(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub145(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub146(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub147(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub148(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub149(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub150(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub151(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub152(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub153(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub154(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub155(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub156(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub157(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub158(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub159(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub160(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub161(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub162(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub163(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub164(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub165(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub166(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub167(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub168(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub169(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub170(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub171(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub172(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub173(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub174(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub175(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub176(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub177(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub178(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub179(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub180(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub181(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub182(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub183(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub184(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub185(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub186(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub187(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub188(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub189(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub190(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub191(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub192(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub193(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub194(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub195(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub196(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub197(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub198(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub199(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub200(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub201(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub202(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub203(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub204(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub205(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub206(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub207(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub208(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub209(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub210(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub211(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub212(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub213(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub214(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub215(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub216(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub217(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub218(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub219(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub220(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub221(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub222(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub223(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub224(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub225(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub226(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub227(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub228(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub229(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub230(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub231(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub232(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub233(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub234(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub235(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub236(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub237(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub238(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub239(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub240(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub241(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub242(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub243(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub244(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub245(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub246(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub247(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub248(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +NS_IMETHOD Stub249(uint64_t,uint64_t, + uint64_t,uint64_t,uint64_t,uint64_t,uint64_t,uint64_t); +#endif + +/* declarations of sentinel stubs */ +NS_IMETHOD Sentinel0(); +NS_IMETHOD Sentinel1(); +NS_IMETHOD Sentinel2(); +NS_IMETHOD Sentinel3(); +NS_IMETHOD Sentinel4(); diff --git a/xpcom/reflect/xptcall/xptcstubsdef.inc b/xpcom/reflect/xptcall/xptcstubsdef.inc new file mode 100644 index 0000000000..2df455406a --- /dev/null +++ b/xpcom/reflect/xptcall/xptcstubsdef.inc @@ -0,0 +1,252 @@ +STUB_ENTRY(3) +STUB_ENTRY(4) +STUB_ENTRY(5) +STUB_ENTRY(6) +STUB_ENTRY(7) +STUB_ENTRY(8) +STUB_ENTRY(9) +STUB_ENTRY(10) +STUB_ENTRY(11) +STUB_ENTRY(12) +STUB_ENTRY(13) +STUB_ENTRY(14) +STUB_ENTRY(15) +STUB_ENTRY(16) +STUB_ENTRY(17) +STUB_ENTRY(18) +STUB_ENTRY(19) +STUB_ENTRY(20) +STUB_ENTRY(21) +STUB_ENTRY(22) +STUB_ENTRY(23) +STUB_ENTRY(24) +STUB_ENTRY(25) +STUB_ENTRY(26) +STUB_ENTRY(27) +STUB_ENTRY(28) +STUB_ENTRY(29) +STUB_ENTRY(30) +STUB_ENTRY(31) +STUB_ENTRY(32) +STUB_ENTRY(33) +STUB_ENTRY(34) +STUB_ENTRY(35) +STUB_ENTRY(36) +STUB_ENTRY(37) +STUB_ENTRY(38) +STUB_ENTRY(39) +STUB_ENTRY(40) +STUB_ENTRY(41) +STUB_ENTRY(42) +STUB_ENTRY(43) +STUB_ENTRY(44) +STUB_ENTRY(45) +STUB_ENTRY(46) +STUB_ENTRY(47) +STUB_ENTRY(48) +STUB_ENTRY(49) +STUB_ENTRY(50) +STUB_ENTRY(51) +STUB_ENTRY(52) +STUB_ENTRY(53) +STUB_ENTRY(54) +STUB_ENTRY(55) +STUB_ENTRY(56) +STUB_ENTRY(57) +STUB_ENTRY(58) +STUB_ENTRY(59) +STUB_ENTRY(60) +STUB_ENTRY(61) +STUB_ENTRY(62) +STUB_ENTRY(63) +STUB_ENTRY(64) +STUB_ENTRY(65) +STUB_ENTRY(66) +STUB_ENTRY(67) +STUB_ENTRY(68) +STUB_ENTRY(69) +STUB_ENTRY(70) +STUB_ENTRY(71) +STUB_ENTRY(72) +STUB_ENTRY(73) +STUB_ENTRY(74) +STUB_ENTRY(75) +STUB_ENTRY(76) +STUB_ENTRY(77) +STUB_ENTRY(78) +STUB_ENTRY(79) +STUB_ENTRY(80) +STUB_ENTRY(81) +STUB_ENTRY(82) +STUB_ENTRY(83) +STUB_ENTRY(84) +STUB_ENTRY(85) +STUB_ENTRY(86) +STUB_ENTRY(87) +STUB_ENTRY(88) +STUB_ENTRY(89) +STUB_ENTRY(90) +STUB_ENTRY(91) +STUB_ENTRY(92) +STUB_ENTRY(93) +STUB_ENTRY(94) +STUB_ENTRY(95) +STUB_ENTRY(96) +STUB_ENTRY(97) +STUB_ENTRY(98) +STUB_ENTRY(99) +STUB_ENTRY(100) +STUB_ENTRY(101) +STUB_ENTRY(102) +STUB_ENTRY(103) +STUB_ENTRY(104) +STUB_ENTRY(105) +STUB_ENTRY(106) +STUB_ENTRY(107) +STUB_ENTRY(108) +STUB_ENTRY(109) +STUB_ENTRY(110) +STUB_ENTRY(111) +STUB_ENTRY(112) +STUB_ENTRY(113) +STUB_ENTRY(114) +STUB_ENTRY(115) +STUB_ENTRY(116) +STUB_ENTRY(117) +STUB_ENTRY(118) +STUB_ENTRY(119) +STUB_ENTRY(120) +STUB_ENTRY(121) +STUB_ENTRY(122) +STUB_ENTRY(123) +STUB_ENTRY(124) +STUB_ENTRY(125) +STUB_ENTRY(126) +STUB_ENTRY(127) +STUB_ENTRY(128) +STUB_ENTRY(129) +STUB_ENTRY(130) +STUB_ENTRY(131) +STUB_ENTRY(132) +STUB_ENTRY(133) +STUB_ENTRY(134) +STUB_ENTRY(135) +STUB_ENTRY(136) +STUB_ENTRY(137) +STUB_ENTRY(138) +STUB_ENTRY(139) +STUB_ENTRY(140) +STUB_ENTRY(141) +STUB_ENTRY(142) +STUB_ENTRY(143) +STUB_ENTRY(144) +STUB_ENTRY(145) +STUB_ENTRY(146) +STUB_ENTRY(147) +STUB_ENTRY(148) +STUB_ENTRY(149) +STUB_ENTRY(150) +STUB_ENTRY(151) +STUB_ENTRY(152) +STUB_ENTRY(153) +STUB_ENTRY(154) +STUB_ENTRY(155) +STUB_ENTRY(156) +STUB_ENTRY(157) +STUB_ENTRY(158) +STUB_ENTRY(159) +STUB_ENTRY(160) +STUB_ENTRY(161) +STUB_ENTRY(162) +STUB_ENTRY(163) +STUB_ENTRY(164) +STUB_ENTRY(165) +STUB_ENTRY(166) +STUB_ENTRY(167) +STUB_ENTRY(168) +STUB_ENTRY(169) +STUB_ENTRY(170) +STUB_ENTRY(171) +STUB_ENTRY(172) +STUB_ENTRY(173) +STUB_ENTRY(174) +STUB_ENTRY(175) +STUB_ENTRY(176) +STUB_ENTRY(177) +STUB_ENTRY(178) +STUB_ENTRY(179) +STUB_ENTRY(180) +STUB_ENTRY(181) +STUB_ENTRY(182) +STUB_ENTRY(183) +STUB_ENTRY(184) +STUB_ENTRY(185) +STUB_ENTRY(186) +STUB_ENTRY(187) +STUB_ENTRY(188) +STUB_ENTRY(189) +STUB_ENTRY(190) +STUB_ENTRY(191) +STUB_ENTRY(192) +STUB_ENTRY(193) +STUB_ENTRY(194) +STUB_ENTRY(195) +STUB_ENTRY(196) +STUB_ENTRY(197) +STUB_ENTRY(198) +STUB_ENTRY(199) +STUB_ENTRY(200) +STUB_ENTRY(201) +STUB_ENTRY(202) +STUB_ENTRY(203) +STUB_ENTRY(204) +STUB_ENTRY(205) +STUB_ENTRY(206) +STUB_ENTRY(207) +STUB_ENTRY(208) +STUB_ENTRY(209) +STUB_ENTRY(210) +STUB_ENTRY(211) +STUB_ENTRY(212) +STUB_ENTRY(213) +STUB_ENTRY(214) +STUB_ENTRY(215) +STUB_ENTRY(216) +STUB_ENTRY(217) +STUB_ENTRY(218) +STUB_ENTRY(219) +STUB_ENTRY(220) +STUB_ENTRY(221) +STUB_ENTRY(222) +STUB_ENTRY(223) +STUB_ENTRY(224) +STUB_ENTRY(225) +STUB_ENTRY(226) +STUB_ENTRY(227) +STUB_ENTRY(228) +STUB_ENTRY(229) +STUB_ENTRY(230) +STUB_ENTRY(231) +STUB_ENTRY(232) +STUB_ENTRY(233) +STUB_ENTRY(234) +STUB_ENTRY(235) +STUB_ENTRY(236) +STUB_ENTRY(237) +STUB_ENTRY(238) +STUB_ENTRY(239) +STUB_ENTRY(240) +STUB_ENTRY(241) +STUB_ENTRY(242) +STUB_ENTRY(243) +STUB_ENTRY(244) +STUB_ENTRY(245) +STUB_ENTRY(246) +STUB_ENTRY(247) +STUB_ENTRY(248) +STUB_ENTRY(249) +SENTINEL_ENTRY(0) +SENTINEL_ENTRY(1) +SENTINEL_ENTRY(2) +SENTINEL_ENTRY(3) +SENTINEL_ENTRY(4) diff --git a/xpcom/reflect/xptinfo/moz.build b/xpcom/reflect/xptinfo/moz.build new file mode 100644 index 0000000000..9e4ce23258 --- /dev/null +++ b/xpcom/reflect/xptinfo/moz.build @@ -0,0 +1,21 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + "xptinfo.cpp", +] + +SOURCES += [ + "!xptdata.cpp", +] + +EXPORTS += [ + "xptinfo.h", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/xpcom/reflect/xptinfo/xptcodegen.py b/xpcom/reflect/xptinfo/xptcodegen.py new file mode 100644 index 0000000000..34131f6678 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptcodegen.py @@ -0,0 +1,646 @@ +#!/usr/bin/env python +# jsonlink.py - Merge JSON typelib files into a .cpp file +# +# 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/. + +import json +from collections import OrderedDict + +import buildconfig +from perfecthash import PerfectHash + +# Pick a nice power-of-two size for our intermediate PHF tables. +PHFSIZE = 512 + + +def indented(s): + return s.replace("\n", "\n ") + + +def cpp(v): + if type(v) == bool: + return "true" if v else "false" + return str(v) + + +def mkstruct(*fields): + def mk(comment, **vals): + assert len(fields) == len(vals) + r = "{ // " + comment + r += indented(",".join("\n/* %s */ %s" % (k, cpp(vals[k])) for k in fields)) + r += "\n}" + return r + + return mk + + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTInterfaceInfo = mkstruct( + "mIID", + "mName", + "mParent", + "mBuiltinClass", + "mMainProcessScriptableOnly", + "mMethods", + "mConsts", + "mFunction", + "mNumMethods", + "mNumConsts", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTType = mkstruct( + "mTag", + "mInParam", + "mOutParam", + "mOptionalParam", + "mData1", + "mData2", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTParamInfo = mkstruct( + "mType", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTMethodInfo = mkstruct( + "mName", + "mParams", + "mNumParams", + "mGetter", + "mSetter", + "mReflectable", + "mOptArgc", + "mContext", + "mHasRetval", + "mIsSymbol", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTDOMObjectInfo = mkstruct( + "mUnwrap", + "mWrap", + "mCleanup", +) + +########################################################## +# Ensure these fields are in the same order as xptinfo.h # +########################################################## +nsXPTConstantInfo = mkstruct( + "mName", + "mSigned", + "mValue", +) + + +# Helper functions for dealing with IIDs. +# +# Unfortunately, the way we represent IIDs in memory depends on the endianness +# of the target architecture. We store an nsIID as a 16-byte, 4-tuple of: +# +# (uint32_t, uint16_t, uint16_t, [uint8_t; 8]) +# +# Unfortunately, this means that when we hash the bytes of the nsIID on a +# little-endian target system, we need to hash them in little-endian order. +# These functions let us split the input hexadecimal string into components, +# encoding each as a little-endian value, and producing an accurate bytearray. +# +# It would be nice to have a consistent representation of IIDs in memory such +# that we don't have to do these gymnastics to get an accurate hash. + + +def split_at_idxs(s, lengths): + idx = 0 + for length in lengths: + yield s[idx : idx + length] + idx += length + assert idx == len(s) + + +def split_iid(iid): # Get the individual components out of an IID string. + iid = iid.replace("-", "") # Strip any '-' delimiters + return tuple(split_at_idxs(iid, (8, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2))) + + +def iid_bytes(iid): # Get the byte representation of the IID for hashing. + bs = bytearray() + for num in split_iid(iid): + b = bytearray.fromhex(num) + # Match endianness of the target platform for each component + if buildconfig.substs["TARGET_ENDIANNESS"] == "little": + b.reverse() + bs += b + return bs + + +# Split a 16-bit integer into its high and low 8 bits +def splitint(i): + assert i < 2 ** 16 + return (i >> 8, i & 0xFF) + + +# Occasionally in xpconnect, we need to fabricate types to pass into the +# conversion methods. In some cases, these types need to be arrays, which hold +# indicies into the extra types array. +# +# These are some types which should have known indexes into the extra types +# array. +utility_types = [ + {"tag": "TD_INT8"}, + {"tag": "TD_UINT8"}, + {"tag": "TD_INT16"}, + {"tag": "TD_UINT16"}, + {"tag": "TD_INT32"}, + {"tag": "TD_UINT32"}, + {"tag": "TD_INT64"}, + {"tag": "TD_UINT64"}, + {"tag": "TD_FLOAT"}, + {"tag": "TD_DOUBLE"}, + {"tag": "TD_BOOL"}, + {"tag": "TD_CHAR"}, + {"tag": "TD_WCHAR"}, + {"tag": "TD_NSIDPTR"}, + {"tag": "TD_PSTRING"}, + {"tag": "TD_PWSTRING"}, + {"tag": "TD_INTERFACE_IS_TYPE", "iid_is": 0}, +] + + +# Core of the code generator. Takes a list of raw JSON XPT interfaces, and +# writes out a file containing the necessary static declarations into fd. +def link_to_cpp(interfaces, fd, header_fd): + # Perfect Hash from IID to interface. + iid_phf = PerfectHash(interfaces, PHFSIZE, key=lambda i: iid_bytes(i["uuid"])) + for idx, iface in enumerate(iid_phf.entries): + iface["idx"] = idx # Store the index in iid_phf of the entry. + + # Perfect Hash from name to iid_phf index. + name_phf = PerfectHash(interfaces, PHFSIZE, key=lambda i: i["name"].encode("ascii")) + + def interface_idx(name): + entry = name and name_phf.get_entry(name.encode("ascii")) + if entry: + return entry["idx"] + 1 # 1-based, use 0 as a sentinel. + return 0 + + # NOTE: State used while linking. This is done with closures rather than a + # class due to how this file's code evolved. + includes = set() + types = [] + type_cache = {} + params = [] + param_cache = {} + methods = [] + max_params = 0 + method_with_max_params = None + consts = [] + domobjects = [] + domobject_cache = {} + strings = OrderedDict() + + def lower_uuid(uuid): + return ( + "{0x%s, 0x%s, 0x%s, {0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s, 0x%s}}" + % split_iid(uuid) + ) + + def lower_domobject(do): + assert do["tag"] == "TD_DOMOBJECT" + + idx = domobject_cache.get(do["name"]) + if idx is None: + idx = domobject_cache[do["name"]] = len(domobjects) + + includes.add(do["headerFile"]) + domobjects.append( + nsXPTDOMObjectInfo( + "%d = %s" % (idx, do["name"]), + # These methods are defined at the top of the generated file. + mUnwrap="UnwrapDOMObject" + % (do["name"], do["native"]), + mWrap="WrapDOMObject<%s>" % do["native"], + mCleanup="CleanupDOMObject<%s>" % do["native"], + ) + ) + + return idx + + def lower_string(s): + if s in strings: + # We've already seen this string. + return strings[s] + elif len(strings): + # Get the last string we inserted (should be O(1) on OrderedDict). + last_s = next(reversed(strings)) + strings[s] = strings[last_s] + len(last_s) + 1 + else: + strings[s] = 0 + return strings[s] + + def lower_symbol(s): + return "uint32_t(JS::SymbolCode::%s)" % s + + def lower_extra_type(type): + key = describe_type(type) + idx = type_cache.get(key) + if idx is None: + idx = type_cache[key] = len(types) + # Make sure `types` is the proper length for any recursive calls + # to `lower_extra_type` that might happen from within `lower_type`. + types.append(None) + realtype = lower_type(type) + types[idx] = realtype + return idx + + def describe_type(type): # Create the type's documentation comment. + tag = type["tag"][3:].lower() + if tag == "legacy_array": + return "%s[size_is=%d]" % (describe_type(type["element"]), type["size_is"]) + elif tag == "array": + return "Array<%s>" % describe_type(type["element"]) + elif tag == "interface_type" or tag == "domobject": + return type["name"] + elif tag == "interface_is_type": + return "iid_is(%d)" % type["iid_is"] + elif tag.endswith("_size_is"): + return "%s(size_is=%d)" % (tag, type["size_is"]) + return tag + + def lower_type(type, in_=False, out=False, optional=False): + tag = type["tag"] + d1 = d2 = 0 + + # TD_VOID is used for types that can't be represented in JS, so they + # should not be represented in the XPT info. + assert tag != "TD_VOID" + + if tag == "TD_LEGACY_ARRAY": + d1 = type["size_is"] + d2 = lower_extra_type(type["element"]) + + elif tag == "TD_ARRAY": + # NOTE: TD_ARRAY can hold 16 bits of type index, while + # TD_LEGACY_ARRAY can only hold 8. + d1, d2 = splitint(lower_extra_type(type["element"])) + + elif tag == "TD_INTERFACE_TYPE": + d1, d2 = splitint(interface_idx(type["name"])) + + elif tag == "TD_INTERFACE_IS_TYPE": + d1 = type["iid_is"] + + elif tag == "TD_DOMOBJECT": + d1, d2 = splitint(lower_domobject(type)) + + elif tag.endswith("_SIZE_IS"): + d1 = type["size_is"] + + assert d1 < 256 and d2 < 256, "Data values too large" + return nsXPTType( + describe_type(type), + mTag=tag, + mData1=d1, + mData2=d2, + mInParam=in_, + mOutParam=out, + mOptionalParam=optional, + ) + + def lower_param(param, paramname): + params.append( + nsXPTParamInfo( + "%d = %s" % (len(params), paramname), + mType=lower_type( + param["type"], + in_="in" in param["flags"], + out="out" in param["flags"], + optional="optional" in param["flags"], + ), + ) + ) + + def is_type_reflectable(type): + # All native types end up getting tagged as void*, or as wrapper types around void* + if type["tag"] == "TD_VOID": + return False + if type["tag"] in ("TD_ARRAY", "TD_LEGACY_ARRAY"): + return is_type_reflectable(type["element"]) + return True + + def is_method_reflectable(method): + if "hidden" in method["flags"]: + return False + + for param in method["params"]: + # Reflected methods can't use non-reflectable types. + if not is_type_reflectable(param["type"]): + return False + + return True + + def lower_method(method, ifacename): + methodname = "%s::%s" % (ifacename, method["name"]) + + isSymbol = "symbol" in method["flags"] + reflectable = is_method_reflectable(method) + + if not reflectable: + # Hide the parameters of methods that can't be called from JS to + # reduce the size of the file. + paramidx = name = numparams = 0 + else: + if isSymbol: + name = lower_symbol(method["name"]) + else: + name = lower_string(method["name"]) + + numparams = len(method["params"]) + + # Check cache for parameters + cachekey = json.dumps(method["params"], sort_keys=True) + paramidx = param_cache.get(cachekey) + if paramidx is None: + paramidx = param_cache[cachekey] = len(params) + for idx, param in enumerate(method["params"]): + lower_param(param, "%s[%d]" % (methodname, idx)) + + nonlocal max_params, method_with_max_params + if numparams > max_params: + max_params = numparams + method_with_max_params = methodname + methods.append( + nsXPTMethodInfo( + "%d = %s" % (len(methods), methodname), + mName=name, + mParams=paramidx, + mNumParams=numparams, + # Flags + mGetter="getter" in method["flags"], + mSetter="setter" in method["flags"], + mReflectable=reflectable, + mOptArgc="optargc" in method["flags"], + mContext="jscontext" in method["flags"], + mHasRetval="hasretval" in method["flags"], + mIsSymbol=isSymbol, + ) + ) + + def lower_const(const, ifacename): + assert const["type"]["tag"] in [ + "TD_INT16", + "TD_INT32", + "TD_UINT8", + "TD_UINT16", + "TD_UINT32", + ] + is_signed = const["type"]["tag"] in ["TD_INT16", "TD_INT32"] + + # Constants are always either signed or unsigned 16 or 32 bit integers, + # which we will only need to convert to JS values. To save on space, + # don't bother storing the type, and instead just store a 32-bit + # unsigned integer, and stash whether to interpret it as signed. + consts.append( + nsXPTConstantInfo( + "%d = %s::%s" % (len(consts), ifacename, const["name"]), + mName=lower_string(const["name"]), + mSigned=is_signed, + mValue="(uint32_t)%d" % const["value"], + ) + ) + + def ancestors(iface): + yield iface + while iface["parent"]: + iface = name_phf.get_entry(iface["parent"].encode("ascii")) + yield iface + + def lower_iface(iface): + method_cnt = sum(len(i["methods"]) for i in ancestors(iface)) + const_cnt = sum(len(i["consts"]) for i in ancestors(iface)) + + # The number of maximum methods is not arbitrary. It is the same value + # as in xpcom/reflect/xptcall/genstubs.pl; do not change this value + # without changing that one or you WILL see problems. + # + # In addition, mNumMethods and mNumConsts are stored as a 8-bit ints, + # meaning we cannot exceed 255 methods/consts on any interface. + assert method_cnt < 250, "%s has too many methods" % iface["name"] + assert const_cnt < 256, "%s has too many constants" % iface["name"] + + # Store the lowered interface as 'cxx' on the iface object. + iface["cxx"] = nsXPTInterfaceInfo( + "%d = %s" % (iface["idx"], iface["name"]), + mIID=lower_uuid(iface["uuid"]), + mName=lower_string(iface["name"]), + mParent=interface_idx(iface["parent"]), + mMethods=len(methods), + mNumMethods=method_cnt, + mConsts=len(consts), + mNumConsts=const_cnt, + # Flags + mBuiltinClass="builtinclass" in iface["flags"], + mMainProcessScriptableOnly="main_process_only" in iface["flags"], + mFunction="function" in iface["flags"], + ) + + # Lower methods and constants used by this interface + for method in iface["methods"]: + lower_method(method, iface["name"]) + for const in iface["consts"]: + lower_const(const, iface["name"]) + + # Lower the types which have fixed indexes first, and check that the indexes + # seem correct. + for expected, ty in enumerate(utility_types): + got = lower_extra_type(ty) + assert got == expected, "Wrong index when lowering" + + # Lower interfaces in the order of the IID phf's entries lookup. + for iface in iid_phf.entries: + lower_iface(iface) + + # Write out the final output files + fd.write("/* THIS FILE WAS GENERATED BY xptcodegen.py - DO NOT EDIT */\n\n") + header_fd.write("/* THIS FILE WAS GENERATED BY xptcodegen.py - DO NOT EDIT */\n\n") + + header_fd.write( + """ +#ifndef xptdata_h +#define xptdata_h + +enum class nsXPTInterface : uint16_t { +""" + ) + + for entry in iid_phf.entries: + header_fd.write(" %s,\n" % entry["name"]) + + header_fd.write( + """ +}; + +#endif +""" + ) + + # Include any bindings files which we need to include for webidl types + for include in sorted(includes): + fd.write('#include "%s"\n' % include) + + # Write out our header + fd.write( + """ +#include "xptinfo.h" +#include "mozilla/PerfectHash.h" +#include "mozilla/dom/BindingUtils.h" + +// These template methods are specialized to be used in the sDOMObjects table. +template +static nsresult UnwrapDOMObject(JS::Handle aHandle, void** aObj, JSContext* aCx) +{ + RefPtr p; + nsresult rv = mozilla::dom::UnwrapObject(aHandle, p, aCx); + p.forget(aObj); + return rv; +} + +template +static bool WrapDOMObject(JSContext* aCx, void* aObj, JS::MutableHandle aHandle) +{ + return mozilla::dom::GetOrCreateDOMReflector(aCx, reinterpret_cast(aObj), aHandle); +} + +template +static void CleanupDOMObject(void* aObj) +{ + RefPtr p = already_AddRefed(reinterpret_cast(aObj)); +} + +namespace xpt { +namespace detail { + +""" + ) + + # Static data arrays + def array(ty, name, els): + fd.write( + "const %s %s[] = {%s\n};\n\n" + % (ty, name, ",".join(indented("\n" + str(e)) for e in els)) + ) + + array("nsXPTType", "sTypes", types) + array("nsXPTParamInfo", "sParams", params) + array("nsXPTMethodInfo", "sMethods", methods) + # Verify that stack-allocated buffers will do for xptcall implementations. + msg = ( + "Too many method arguments in %s. " + "Either reduce the number of arguments " + "or increase PARAM_BUFFER_COUNT." % method_with_max_params + ) + fd.write('static_assert(%s <= PARAM_BUFFER_COUNT, "%s");\n\n' % (max_params, msg)) + array("nsXPTDOMObjectInfo", "sDOMObjects", domobjects) + array("nsXPTConstantInfo", "sConsts", consts) + + # The strings array. We write out individual characters to avoid MSVC restrictions. + fd.write("const char sStrings[] = {\n") + for s, off in strings.items(): + fd.write(" // %d = %s\n '%s','\\0',\n" % (off, s, "','".join(s))) + fd.write("};\n\n") + + # Build the perfect hash table for InterfaceByIID + fd.write( + iid_phf.cxx_codegen( + name="InterfaceByIID", + entry_type="nsXPTInterfaceInfo", + entries_name="sInterfaces", + lower_entry=lambda iface: iface["cxx"], + # Check that the IIDs match to support IID keys not in the map. + return_type="const nsXPTInterfaceInfo*", + return_entry="return entry.IID().Equals(aKey) ? &entry : nullptr;", + key_type="const nsIID&", + key_bytes="reinterpret_cast(&aKey)", + key_length="sizeof(nsIID)", + ) + ) + fd.write("\n") + + # Build the perfect hash table for InterfaceByName + fd.write( + name_phf.cxx_codegen( + name="InterfaceByName", + entry_type="uint16_t", + lower_entry=lambda iface: "%-4d /* %s */" % (iface["idx"], iface["name"]), + # Get the actual nsXPTInterfaceInfo from sInterfaces, and + # double-check that names match. + return_type="const nsXPTInterfaceInfo*", + return_entry="return strcmp(sInterfaces[entry].Name(), aKey) == 0" + " ? &sInterfaces[entry] : nullptr;", + ) + ) + fd.write("\n") + + # Generate some checks that the indexes for the utility types match the + # declared ones in xptinfo.h + for idx, ty in enumerate(utility_types): + fd.write( + 'static_assert(%d == (uint8_t)nsXPTType::Idx::%s, "Bad idx");\n' + % (idx, ty["tag"][3:]) + ) + + fd.write( + """ +const uint16_t sInterfacesSize = mozilla::ArrayLength(sInterfaces); + +} // namespace detail +} // namespace xpt +""" + ) + + +def link_and_write(files, outfile, outheader): + interfaces = [] + for file in files: + with open(file, "r") as fd: + interfaces += json.load(fd) + + iids = set() + names = set() + for interface in interfaces: + assert interface["uuid"] not in iids, "duplicated UUID %s" % interface["uuid"] + assert interface["name"] not in names, "duplicated name %s" % interface["name"] + iids.add(interface["uuid"]) + names.add(interface["name"]) + + link_to_cpp(interfaces, outfile, outheader) + + +def main(): + import sys + from argparse import ArgumentParser + + parser = ArgumentParser() + parser.add_argument("outfile", help="Output C++ file to generate") + parser.add_argument("outheader", help="Output C++ header file to generate") + parser.add_argument("xpts", nargs="*", help="source xpt files") + + args = parser.parse_args(sys.argv[1:]) + with open(args.outfile, "w") as fd, open(args.outheader, "w") as header_fd: + link_and_write(args.xpts, fd, header_fd) + + +if __name__ == "__main__": + main() diff --git a/xpcom/reflect/xptinfo/xptinfo.cpp b/xpcom/reflect/xptinfo/xptinfo.cpp new file mode 100644 index 0000000000..c44e97cad4 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptinfo.cpp @@ -0,0 +1,105 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "xptinfo.h" +#include "nsISupports.h" +#include "mozilla/dom/DOMJSClass.h" +#include "mozilla/dom/BindingUtils.h" +#include "mozilla/ArrayUtils.h" + +#include "jsfriendapi.h" +#include "js/Symbol.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace xpt::detail; + +//////////////////////////////////// +// Constant Lookup Helper Methods // +//////////////////////////////////// + +bool nsXPTInterfaceInfo::HasAncestor(const nsIID& aIID) const { + for (const auto* info = this; info; info = info->GetParent()) { + if (info->IID() == aIID) { + return true; + } + } + return false; +} + +const nsXPTConstantInfo& nsXPTInterfaceInfo::Constant(uint16_t aIndex) const { + MOZ_ASSERT(aIndex < ConstantCount()); + + if (const nsXPTInterfaceInfo* pi = GetParent()) { + if (aIndex < pi->ConstantCount()) { + return pi->Constant(aIndex); + } + aIndex -= pi->ConstantCount(); + } + + return xpt::detail::GetConstant(mConsts + aIndex); +} + +const nsXPTMethodInfo& nsXPTInterfaceInfo::Method(uint16_t aIndex) const { + MOZ_ASSERT(aIndex < MethodCount()); + + if (const nsXPTInterfaceInfo* pi = GetParent()) { + if (aIndex < pi->MethodCount()) { + return pi->Method(aIndex); + } + aIndex -= pi->MethodCount(); + } + + return xpt::detail::GetMethod(mMethods + aIndex); +} + +nsresult nsXPTInterfaceInfo::GetMethodInfo( + uint16_t aIndex, const nsXPTMethodInfo** aInfo) const { + *aInfo = aIndex < MethodCount() ? &Method(aIndex) : nullptr; + return *aInfo ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsXPTInterfaceInfo::GetConstant(uint16_t aIndex, + JS::MutableHandle aConstant, + char** aName) const { + if (aIndex < ConstantCount()) { + aConstant.set(Constant(aIndex).JSValue()); + *aName = moz_xstrdup(Constant(aIndex).Name()); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +//////////////////////////////////// +// nsXPTMethodInfo symbol helpers // +//////////////////////////////////// + +const char* nsXPTMethodInfo::SymbolDescription() const { + switch (GetSymbolCode()) { +#define XPC_WELL_KNOWN_SYMBOL_DESCR_CASE(name) \ + case JS::SymbolCode::name: \ + return #name; + JS_FOR_EACH_WELL_KNOWN_SYMBOL(XPC_WELL_KNOWN_SYMBOL_DESCR_CASE) +#undef XPC_WELL_KNOWN_SYMBOL_DESCR_CASE + + default: + return ""; + } +} + +bool nsXPTMethodInfo::GetId(JSContext* aCx, jsid& aId) const { + if (IsSymbol()) { + aId = JS::PropertyKey::Symbol(GetSymbol(aCx)); + return true; + } + + JSString* str = JS_AtomizeString(aCx, Name()); + if (!str) { + return false; + } + aId = JS::PropertyKey::NonIntAtom(str); + return true; +} diff --git a/xpcom/reflect/xptinfo/xptinfo.h b/xpcom/reflect/xptinfo/xptinfo.h new file mode 100644 index 0000000000..2456c2c2b5 --- /dev/null +++ b/xpcom/reflect/xptinfo/xptinfo.h @@ -0,0 +1,711 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +/** + * Structures and methods with information about XPCOM interfaces for use by + * XPConnect. The static backing data structures used by this file are generated + * from xpidl interfaces by the jsonxpt.py and xptcodegen.py scripts. + */ + +#ifndef xptinfo_h +#define xptinfo_h + +#include +#include "nsID.h" +#include "mozilla/Assertions.h" +#include "jsapi.h" +#include "js/Symbol.h" +#include "js/Value.h" +#include "nsString.h" +#include "nsTArray.h" + +// Forward Declarations +namespace mozilla { +namespace dom { +struct NativePropertyHooks; +} // namespace dom +} // namespace mozilla + +struct nsXPTInterfaceInfo; +struct nsXPTType; +struct nsXPTParamInfo; +struct nsXPTMethodInfo; +struct nsXPTConstantInfo; +struct nsXPTDOMObjectInfo; + +enum class nsXPTInterface : uint16_t; + +// Internal helper methods. +namespace xpt { +namespace detail { + +inline const nsXPTInterfaceInfo* GetInterface(uint16_t aIndex); +inline const nsXPTType& GetType(uint16_t aIndex); +inline const nsXPTParamInfo& GetParam(uint16_t aIndex); +inline const nsXPTMethodInfo& GetMethod(uint16_t aIndex); +inline const nsXPTConstantInfo& GetConstant(uint16_t aIndex); +inline const nsXPTDOMObjectInfo& GetDOMObjectInfo(uint16_t aIndex); +inline const char* GetString(uint32_t aIndex); + +const nsXPTInterfaceInfo* InterfaceByIID(const nsIID& aIID); +const nsXPTInterfaceInfo* InterfaceByName(const char* aName); + +extern const uint16_t sInterfacesSize; + +} // namespace detail +} // namespace xpt + +/* + * An Interface describes a single XPCOM interface, including all of its + * methods. We don't record non-scriptable interfaces. + */ +struct nsXPTInterfaceInfo { + // High efficiency getters for Interfaces based on perfect hashes. + static const nsXPTInterfaceInfo* ByIID(const nsIID& aIID) { + return xpt::detail::InterfaceByIID(aIID); + } + static const nsXPTInterfaceInfo* ByName(const char* aName) { + return xpt::detail::InterfaceByName(aName); + } + + static const nsXPTInterfaceInfo* Get(nsXPTInterface aID) { + return ByIndex(uint16_t(aID)); + } + + // These are only needed for Components_interfaces's enumerator. + static const nsXPTInterfaceInfo* ByIndex(uint16_t aIndex) { + // NOTE: We add 1 here, as the internal index 0 is reserved for null. + return xpt::detail::GetInterface(aIndex + 1); + } + static uint16_t InterfaceCount() { return xpt::detail::sInterfacesSize; } + + // Interface flag getters + bool IsFunction() const { return mFunction; } + bool IsBuiltinClass() const { return mBuiltinClass; } + bool IsMainProcessScriptableOnly() const { + return mMainProcessScriptableOnly; + } + + const char* Name() const { return xpt::detail::GetString(mName); } + const nsIID& IID() const { return mIID; } + + // Get the parent interface, or null if this interface doesn't have a parent. + const nsXPTInterfaceInfo* GetParent() const { + return xpt::detail::GetInterface(mParent); + } + + // Do we have an ancestor interface with the given IID? + bool HasAncestor(const nsIID& aIID) const; + + // Get methods & constants + uint16_t ConstantCount() const { return mNumConsts; } + const nsXPTConstantInfo& Constant(uint16_t aIndex) const; + uint16_t MethodCount() const { return mNumMethods; } + const nsXPTMethodInfo& Method(uint16_t aIndex) const; + + nsresult GetMethodInfo(uint16_t aIndex, const nsXPTMethodInfo** aInfo) const; + nsresult GetConstant(uint16_t aIndex, JS::MutableHandle constant, + char** aName) const; + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + nsID mIID; + uint32_t mName; // Index into xpt::detail::sStrings + + uint16_t mParent : 14; + uint16_t mBuiltinClass : 1; + // XXX(nika): Do we need this if we don't have addons anymore? + uint16_t mMainProcessScriptableOnly : 1; + + uint16_t mMethods; // Index into xpt::detail::sMethods + + uint16_t mConsts : 14; // Index into xpt::detail::sConsts + uint16_t mFunction : 1; + // uint16_t unused : 1; + + uint8_t mNumMethods; // NOTE(24/04/18): largest=nsIDocShell (193) + uint8_t mNumConsts; // NOTE(24/04/18): largest=nsIAccessibleRole (175) +}; + +// The fields in nsXPTInterfaceInfo were carefully ordered to minimize size. +static_assert(sizeof(nsXPTInterfaceInfo) == 28, "wrong size?"); + +/* + * The following enum represents contains the different tag types which + * can be found in nsXPTTypeInfo::mTag. + * + * WARNING: mTag is 5 bits wide, supporting at most 32 tags. + */ +enum nsXPTTypeTag : uint8_t { + // Arithmetic (POD) Types + // - Do not require cleanup, + // - All bit patterns are valid, + // - Outparams may be uninitialized by caller, + // - Directly supported in xptcall. + // + // NOTE: The name 'Arithmetic' comes from Harbison/Steele. Despite being a tad + // unclear, it is used frequently in xptcall, so is unlikely to be changed. + TD_INT8 = 0, + TD_INT16 = 1, + TD_INT32 = 2, + TD_INT64 = 3, + TD_UINT8 = 4, + TD_UINT16 = 5, + TD_UINT32 = 6, + TD_UINT64 = 7, + TD_FLOAT = 8, + TD_DOUBLE = 9, + TD_BOOL = 10, + TD_CHAR = 11, + TD_WCHAR = 12, + _TD_LAST_ARITHMETIC = TD_WCHAR, + + // Pointer Types + // - Require cleanup unless NULL, + // - All-zeros (NULL) bit pattern is valid, + // - Outparams may be uninitialized by caller, + // - Supported in xptcall as raw pointer. + TD_VOID = 13, + TD_NSIDPTR = 14, + TD_PSTRING = 15, + TD_PWSTRING = 16, + TD_INTERFACE_TYPE = 17, + TD_INTERFACE_IS_TYPE = 18, + TD_LEGACY_ARRAY = 19, + TD_PSTRING_SIZE_IS = 20, + TD_PWSTRING_SIZE_IS = 21, + TD_DOMOBJECT = 22, + TD_PROMISE = 23, + _TD_LAST_POINTER = TD_PROMISE, + + // Complex Types + // - Require cleanup, + // - Always passed indirectly, + // - Outparams must be initialized by caller, + // - Supported in xptcall due to indirection. + TD_UTF8STRING = 24, + TD_CSTRING = 25, + TD_ASTRING = 26, + TD_NSID = 27, + TD_JSVAL = 28, + TD_ARRAY = 29, + _TD_LAST_COMPLEX = TD_ARRAY +}; + +static_assert(_TD_LAST_COMPLEX < 32, "nsXPTTypeTag must fit in 5 bits"); + +/* + * A nsXPTType is a union used to identify the type of a method argument or + * return value. The internal data is stored as an 5-bit tag, and two 8-bit + * integers, to keep alignment requirements low. + * + * nsXPTType contains 3 extra bits, reserved for use by nsXPTParamInfo. + */ +struct nsXPTType { + nsXPTTypeTag Tag() const { return static_cast(mTag); } + + // The index in the function argument list which should be used when + // determining the iid_is or size_is properties of this dependent type. + uint8_t ArgNum() const { + MOZ_ASSERT(Tag() == TD_INTERFACE_IS_TYPE || Tag() == TD_PSTRING_SIZE_IS || + Tag() == TD_PWSTRING_SIZE_IS || Tag() == TD_LEGACY_ARRAY); + return mData1; + } + + private: + // Helper for reading 16-bit data values split between mData1 and mData2. + uint16_t Data16() const { + return static_cast(mData1 << 8) | mData2; + } + + public: + // Get the type of the element in the current array or sequence. Arrays only + // fit 8 bits of type data, while sequences support up to 16 bits of type data + // due to not needing to store an ArgNum. + const nsXPTType& ArrayElementType() const { + if (Tag() == TD_LEGACY_ARRAY) { + return xpt::detail::GetType(mData2); + } + MOZ_ASSERT(Tag() == TD_ARRAY); + return xpt::detail::GetType(Data16()); + } + + // We store the 16-bit iface value as two 8-bit values in order to + // avoid 16-bit alignment requirements for XPTTypeDescriptor, which + // reduces its size and also the size of XPTParamDescriptor. + const nsXPTInterfaceInfo* GetInterface() const { + MOZ_ASSERT(Tag() == TD_INTERFACE_TYPE); + return xpt::detail::GetInterface(Data16()); + } + + const nsXPTDOMObjectInfo& GetDOMObjectInfo() const { + MOZ_ASSERT(Tag() == TD_DOMOBJECT); + return xpt::detail::GetDOMObjectInfo(Data16()); + } + + // See the comments in nsXPTTypeTag for an explanation as to what each of + // these categories mean. + bool IsArithmetic() const { return Tag() <= _TD_LAST_ARITHMETIC; } + bool IsPointer() const { + return !IsArithmetic() && Tag() <= _TD_LAST_POINTER; + } + bool IsComplex() const { return Tag() > _TD_LAST_POINTER; } + + bool IsInterfacePointer() const { + return Tag() == TD_INTERFACE_TYPE || Tag() == TD_INTERFACE_IS_TYPE; + } + + bool IsDependent() const { + return (Tag() == TD_ARRAY && InnermostType().IsDependent()) || + Tag() == TD_INTERFACE_IS_TYPE || Tag() == TD_LEGACY_ARRAY || + Tag() == TD_PSTRING_SIZE_IS || Tag() == TD_PWSTRING_SIZE_IS; + } + + // Unwrap a nested type to its innermost value (e.g. through arrays). + const nsXPTType& InnermostType() const { + if (Tag() == TD_LEGACY_ARRAY || Tag() == TD_ARRAY) { + return ArrayElementType().InnermostType(); + } + return *this; + } + + // In-memory size of native type in bytes. + inline size_t Stride() const; + + // Offset the given base pointer to reference the element at the given index. + void* ElementPtr(const void* aBase, uint32_t aIndex) const { + return (char*)aBase + (aIndex * Stride()); + } + + // Zero out a native value of the given type. The type must not be 'complex'. + void ZeroValue(void* aValue) const { + MOZ_RELEASE_ASSERT(!IsComplex(), "Cannot zero a complex value"); + memset(aValue, 0, Stride()); + } + + // Indexes into the extra types array of a small set of known types. + enum class Idx : uint8_t { + INT8 = 0, + UINT8, + INT16, + UINT16, + INT32, + UINT32, + INT64, + UINT64, + FLOAT, + DOUBLE, + BOOL, + CHAR, + WCHAR, + NSIDPTR, + PSTRING, + PWSTRING, + INTERFACE_IS_TYPE + }; + + // Helper methods for fabricating nsXPTType values used by xpconnect. + static nsXPTType MkArrayType(Idx aInner) { + MOZ_ASSERT(aInner <= Idx::INTERFACE_IS_TYPE); + return {TD_LEGACY_ARRAY, false, false, false, 0, (uint8_t)aInner}; + } + static const nsXPTType& Get(Idx aInner) { + MOZ_ASSERT(aInner <= Idx::INTERFACE_IS_TYPE); + return xpt::detail::GetType((uint8_t)aInner); + } + + /////////////////////////////////////// + // nsXPTType backwards compatibility // + /////////////////////////////////////// + + nsXPTType& operator=(nsXPTTypeTag aPrefix) { + mTag = aPrefix; + return *this; + } + operator nsXPTTypeTag() const { return Tag(); } + +#define TD_ALIAS_(name_, value_) static constexpr nsXPTTypeTag name_ = value_ + TD_ALIAS_(T_I8, TD_INT8); + TD_ALIAS_(T_I16, TD_INT16); + TD_ALIAS_(T_I32, TD_INT32); + TD_ALIAS_(T_I64, TD_INT64); + TD_ALIAS_(T_U8, TD_UINT8); + TD_ALIAS_(T_U16, TD_UINT16); + TD_ALIAS_(T_U32, TD_UINT32); + TD_ALIAS_(T_U64, TD_UINT64); + TD_ALIAS_(T_FLOAT, TD_FLOAT); + TD_ALIAS_(T_DOUBLE, TD_DOUBLE); + TD_ALIAS_(T_BOOL, TD_BOOL); + TD_ALIAS_(T_CHAR, TD_CHAR); + TD_ALIAS_(T_WCHAR, TD_WCHAR); + TD_ALIAS_(T_VOID, TD_VOID); + TD_ALIAS_(T_NSIDPTR, TD_NSIDPTR); + TD_ALIAS_(T_CHAR_STR, TD_PSTRING); + TD_ALIAS_(T_WCHAR_STR, TD_PWSTRING); + TD_ALIAS_(T_INTERFACE, TD_INTERFACE_TYPE); + TD_ALIAS_(T_INTERFACE_IS, TD_INTERFACE_IS_TYPE); + TD_ALIAS_(T_LEGACY_ARRAY, TD_LEGACY_ARRAY); + TD_ALIAS_(T_PSTRING_SIZE_IS, TD_PSTRING_SIZE_IS); + TD_ALIAS_(T_PWSTRING_SIZE_IS, TD_PWSTRING_SIZE_IS); + TD_ALIAS_(T_UTF8STRING, TD_UTF8STRING); + TD_ALIAS_(T_CSTRING, TD_CSTRING); + TD_ALIAS_(T_ASTRING, TD_ASTRING); + TD_ALIAS_(T_NSID, TD_NSID); + TD_ALIAS_(T_JSVAL, TD_JSVAL); + TD_ALIAS_(T_DOMOBJECT, TD_DOMOBJECT); + TD_ALIAS_(T_PROMISE, TD_PROMISE); + TD_ALIAS_(T_ARRAY, TD_ARRAY); +#undef TD_ALIAS_ + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + uint8_t mTag : 5; + + // Parameter bitflags are packed into the XPTTypeDescriptor to save space. + // When the TypeDescriptor is not in a parameter, these flags are ignored. + uint8_t mInParam : 1; + uint8_t mOutParam : 1; + uint8_t mOptionalParam : 1; + + // The data for the different variants is stored in these two data fields. + // These should only be accessed via the getter methods above, which will + // assert if the tag is invalid. + uint8_t mData1; + uint8_t mData2; +}; + +// The fields in nsXPTType were carefully ordered to minimize size. +static_assert(sizeof(nsXPTType) == 3, "wrong size"); + +/* + * A nsXPTParamInfo is used to describe either a single argument to a method or + * a method's result. It stores its flags in the type descriptor to save space. + */ +struct nsXPTParamInfo { + bool IsIn() const { return mType.mInParam; } + bool IsOut() const { return mType.mOutParam; } + bool IsOptional() const { return mType.mOptionalParam; } + bool IsShared() const { return false; } // XXX remove (backcompat) + + // Get the type of this parameter. + const nsXPTType& Type() const { return mType; } + const nsXPTType& GetType() const { + return Type(); + } // XXX remove (backcompat) + + // Whether this parameter is passed indirectly on the stack. All out/inout + // params are passed indirectly, and complex types are always passed + // indirectly. + bool IsIndirect() const { return IsOut() || Type().IsComplex(); } + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + nsXPTType mType; +}; + +// The fields in nsXPTParamInfo were carefully ordered to minimize size. +static_assert(sizeof(nsXPTParamInfo) == 3, "wrong size"); + +/* + * A nsXPTMethodInfo is used to describe a single interface method. + */ +struct nsXPTMethodInfo { + bool IsGetter() const { return mGetter; } + bool IsSetter() const { return mSetter; } + bool IsReflectable() const { return mReflectable; } + bool IsSymbol() const { return mIsSymbol; } + bool WantsOptArgc() const { return mOptArgc; } + bool WantsContext() const { return mContext; } + uint8_t ParamCount() const { return mNumParams; } + + const char* Name() const { + MOZ_ASSERT(!IsSymbol()); + return xpt::detail::GetString(mName); + } + const nsXPTParamInfo& Param(uint8_t aIndex) const { + MOZ_ASSERT(aIndex < mNumParams); + return xpt::detail::GetParam(mParams + aIndex); + } + + bool HasRetval() const { return mHasRetval; } + const nsXPTParamInfo* GetRetval() const { + return mHasRetval ? &Param(mNumParams - 1) : nullptr; + } + + // If this is an [implicit_jscontext] method, returns the index of the + // implicit JSContext* argument in the C++ method's argument list. + // Otherwise returns UINT8_MAX. + uint8_t IndexOfJSContext() const { + if (!WantsContext()) { + return UINT8_MAX; + } + if (IsGetter() || IsSetter()) { + // Getters/setters always have the context as first argument. + return 0; + } + // The context comes before the return value, if there is one. + MOZ_ASSERT_IF(HasRetval(), ParamCount() > 0); + return ParamCount() - uint8_t(HasRetval()); + } + + JS::SymbolCode GetSymbolCode() const { + MOZ_ASSERT(IsSymbol()); + return JS::SymbolCode(mName); + } + + JS::Symbol* GetSymbol(JSContext* aCx) const { + return JS::GetWellKnownSymbol(aCx, GetSymbolCode()); + } + + const char* SymbolDescription() const; + + const char* NameOrDescription() const { + if (IsSymbol()) { + return SymbolDescription(); + } + return Name(); + } + + bool GetId(JSContext* aCx, jsid& aId) const; + + ///////////////////////////////////////////// + // nsXPTMethodInfo backwards compatibility // + ///////////////////////////////////////////// + + const char* GetName() const { return Name(); } + + uint8_t GetParamCount() const { return ParamCount(); } + const nsXPTParamInfo& GetParam(uint8_t aIndex) const { return Param(aIndex); } + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + uint32_t mName; // Index into xpt::detail::sStrings. + uint16_t mParams; // Index into xpt::detail::sParams. + uint8_t mNumParams; + + uint8_t mGetter : 1; + uint8_t mSetter : 1; + uint8_t mReflectable : 1; + uint8_t mOptArgc : 1; + uint8_t mContext : 1; + uint8_t mHasRetval : 1; + uint8_t mIsSymbol : 1; +}; + +// The fields in nsXPTMethodInfo were carefully ordered to minimize size. +static_assert(sizeof(nsXPTMethodInfo) == 8, "wrong size"); + +// This number is chosen to be no larger than the maximum number of parameters +// any XPIDL-defined function needs; there is a static assert in the generated +// code from xptcodegen.py to verify that decision. It is therefore also the +// maximum number of stack allocated nsXPTCMiniVariant structures for argument +// passing purposes in PrepareAndDispatch implementations. +#if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) +# define PARAM_BUFFER_COUNT 18 +#else +# define PARAM_BUFFER_COUNT 14 +#endif + +/** + * A nsXPTConstantInfo is used to describe a single interface constant. + */ +struct nsXPTConstantInfo { + const char* Name() const { return xpt::detail::GetString(mName); } + + JS::Value JSValue() const { + if (mSigned || mValue <= uint32_t(INT32_MAX)) { + return JS::Int32Value(int32_t(mValue)); + } + return JS::DoubleValue(mValue); + } + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + uint32_t mName : 31; // Index into xpt::detail::mStrings. + + // Whether the value should be interpreted as a int32_t or uint32_t. + uint32_t mSigned : 1; + uint32_t mValue; // The value stored as a u32 +}; + +// The fields in nsXPTConstantInfo were carefully ordered to minimize size. +static_assert(sizeof(nsXPTConstantInfo) == 8, "wrong size"); + +/** + * Object representing the information required to wrap and unwrap DOMObjects. + * + * This object will not live in rodata as it contains relocations. + */ +struct nsXPTDOMObjectInfo { + nsresult Unwrap(JS::Handle aHandle, void** aObj, + JSContext* aCx) const { + return mUnwrap(aHandle, aObj, aCx); + } + + bool Wrap(JSContext* aCx, void* aObj, + JS::MutableHandle aHandle) const { + return mWrap(aCx, aObj, aHandle); + } + + void Cleanup(void* aObj) const { return mCleanup(aObj); } + + //////////////////////////////////////////////////////////////// + // Ensure these fields are in the same order as xptcodegen.py // + //////////////////////////////////////////////////////////////// + + nsresult (*mUnwrap)(JS::Handle aHandle, void** aObj, + JSContext* aCx); + bool (*mWrap)(JSContext* aCx, void* aObj, + JS::MutableHandle aHandle); + void (*mCleanup)(void* aObj); +}; + +namespace xpt { +namespace detail { + +// The UntypedTArray type allows low-level access from XPConnect to nsTArray +// internals without static knowledge of the array element type in question. +class UntypedTArray : public nsTArray_base { + public: + void* Elements() const { return static_cast(Hdr() + 1); } + + // Changes the length and capacity to be at least large enough for aTo + // elements. + bool SetLength(const nsXPTType& aEltTy, uint32_t aTo) { + if (!EnsureCapacity(aTo, aEltTy.Stride())) { + return false; + } + + if (mHdr != EmptyHdr()) { + mHdr->mLength = aTo; + } + + return true; + } + + // Free backing memory for the nsTArray object. + void Clear() { + if (mHdr != EmptyHdr() && !UsesAutoArrayBuffer()) { + nsTArrayFallibleAllocator::Free(mHdr); + } + mHdr = EmptyHdr(); + } +}; + +////////////////////////////////////////////// +// Raw typelib data stored in const statics // +////////////////////////////////////////////// + +// XPIDL information +extern const nsXPTInterfaceInfo sInterfaces[]; +extern const nsXPTType sTypes[]; +extern const nsXPTParamInfo sParams[]; +extern const nsXPTMethodInfo sMethods[]; +extern const nsXPTConstantInfo sConsts[]; +extern const nsXPTDOMObjectInfo sDOMObjects[]; + +extern const char sStrings[]; + +////////////////////////////////////// +// Helper Methods for fetching data // +////////////////////////////////////// + +inline const nsXPTInterfaceInfo* GetInterface(uint16_t aIndex) { + if (aIndex > 0 && aIndex <= sInterfacesSize) { + return &sInterfaces[aIndex - 1]; // 1-based as 0 is a marker. + } + return nullptr; +} + +inline const nsXPTType& GetType(uint16_t aIndex) { return sTypes[aIndex]; } + +inline const nsXPTParamInfo& GetParam(uint16_t aIndex) { + return sParams[aIndex]; +} + +inline const nsXPTMethodInfo& GetMethod(uint16_t aIndex) { + return sMethods[aIndex]; +} + +inline const nsXPTConstantInfo& GetConstant(uint16_t aIndex) { + return sConsts[aIndex]; +} + +inline const nsXPTDOMObjectInfo& GetDOMObjectInfo(uint16_t aIndex) { + return sDOMObjects[aIndex]; +} + +inline const char* GetString(uint32_t aIndex) { return &sStrings[aIndex]; } + +} // namespace detail +} // namespace xpt + +#define XPT_FOR_EACH_ARITHMETIC_TYPE(MACRO) \ + MACRO(TD_INT8, int8_t) \ + MACRO(TD_INT16, int16_t) \ + MACRO(TD_INT32, int32_t) \ + MACRO(TD_INT64, int64_t) \ + MACRO(TD_UINT8, uint8_t) \ + MACRO(TD_UINT16, uint16_t) \ + MACRO(TD_UINT32, uint32_t) \ + MACRO(TD_UINT64, uint64_t) \ + MACRO(TD_FLOAT, float) \ + MACRO(TD_DOUBLE, double) \ + MACRO(TD_BOOL, bool) \ + MACRO(TD_CHAR, char) \ + MACRO(TD_WCHAR, char16_t) + +#define XPT_FOR_EACH_POINTER_TYPE(MACRO) \ + MACRO(TD_VOID, void*) \ + MACRO(TD_NSIDPTR, nsID*) \ + MACRO(TD_PSTRING, char*) \ + MACRO(TD_PWSTRING, wchar_t*) \ + MACRO(TD_INTERFACE_TYPE, nsISupports*) \ + MACRO(TD_INTERFACE_IS_TYPE, nsISupports*) \ + MACRO(TD_LEGACY_ARRAY, void*) \ + MACRO(TD_PSTRING_SIZE_IS, char*) \ + MACRO(TD_PWSTRING_SIZE_IS, wchar_t*) \ + MACRO(TD_DOMOBJECT, void*) \ + MACRO(TD_PROMISE, mozilla::dom::Promise*) + +#define XPT_FOR_EACH_COMPLEX_TYPE(MACRO) \ + MACRO(TD_UTF8STRING, nsCString) \ + MACRO(TD_CSTRING, nsCString) \ + MACRO(TD_ASTRING, nsString) \ + MACRO(TD_NSID, nsID) \ + MACRO(TD_JSVAL, JS::Value) \ + MACRO(TD_ARRAY, xpt::detail::UntypedTArray) + +#define XPT_FOR_EACH_TYPE(MACRO) \ + XPT_FOR_EACH_ARITHMETIC_TYPE(MACRO) \ + XPT_FOR_EACH_POINTER_TYPE(MACRO) \ + XPT_FOR_EACH_COMPLEX_TYPE(MACRO) + +inline size_t nsXPTType::Stride() const { + // Compute the stride to use when walking an array of the given type. + switch (Tag()) { +#define XPT_TYPE_STRIDE(tag, type) \ + case tag: \ + return sizeof(type); + XPT_FOR_EACH_TYPE(XPT_TYPE_STRIDE) +#undef XPT_TYPE_STRIDE + } + + MOZ_CRASH("Unknown type"); +} + +#endif /* xptinfo_h */ diff --git a/xpcom/rust/gecko_logger/Cargo.toml b/xpcom/rust/gecko_logger/Cargo.toml new file mode 100644 index 0000000000..2f8ca84a97 --- /dev/null +++ b/xpcom/rust/gecko_logger/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "gecko_logger" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +edition = "2018" +license = "MPL-2.0" + +[dependencies] +lazy_static = "1" +log = {version = "0.4", features = ["release_max_level_info"]} +env_logger = {version = "0.10", default-features = false, features = ["color"]} # disable `regex` to reduce code size +app_services_logger = { path = "../../../services/common/app_services_logger" } diff --git a/xpcom/rust/gecko_logger/src/lib.rs b/xpcom/rust/gecko_logger/src/lib.rs new file mode 100644 index 0000000000..5da85b9d89 --- /dev/null +++ b/xpcom/rust/gecko_logger/src/lib.rs @@ -0,0 +1,256 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! This provides a way to direct rust logging into the gecko logger. + +#[macro_use] +extern crate lazy_static; + +use app_services_logger::{AppServicesLogger, LOGGERS_BY_TARGET}; +use log::Log; +use log::{Level, LevelFilter}; +use std::boxed::Box; +use std::collections::HashMap; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::os::raw::c_int; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::RwLock; +use std::{cmp, env}; + +extern "C" { + fn ExternMozLog(tag: *const c_char, prio: c_int, text: *const c_char); + fn gfx_critical_note(msg: *const c_char); + #[cfg(target_os = "android")] + fn __android_log_write(prio: c_int, tag: *const c_char, text: *const c_char) -> c_int; +} + +lazy_static! { + // This could be a proper static once [1] is fixed or parking_lot's const fn + // support is not nightly-only. + // + // [1]: https://github.com/rust-lang/rust/issues/73714 + static ref LOG_MODULE_MAP: RwLock> = RwLock::new(HashMap::new()); +} + +/// This tells us whether LOG_MODULE_MAP is possibly non-empty. +static LOGGING_ACTIVE: AtomicBool = AtomicBool::new(false); + +/// This function searches for the module's name in the hashmap. If that is not +/// found, it proceeds to search for the parent modules. +/// It returns a tuple containing the matched string, if the matched module +/// was a pattern match, and the level we found in the hashmap. +/// If none is found, it will return the module's name and LevelFilter::Off +fn get_level_for_module<'a>( + map: &HashMap, + key: &'a str, +) -> (&'a str, bool, LevelFilter) { + if let Some((level, is_pattern_match)) = map.get(key) { + return (key, *is_pattern_match, level.clone()); + } + + let mut mod_name = &key[..]; + while let Some(pos) = mod_name.rfind("::") { + mod_name = &mod_name[..pos]; + if let Some((level, is_pattern_match)) = map.get(mod_name) { + return (mod_name, *is_pattern_match, level.clone()); + } + } + + return (key, false, LevelFilter::Off); +} + +/// This function takes a record to maybe log to Gecko. +/// It returns true if the record was handled by Gecko's logging, and false +/// otherwise. +pub fn log_to_gecko(record: &log::Record) -> bool { + if !LOGGING_ACTIVE.load(Ordering::Relaxed) { + return false; + } + + let key = match record.module_path() { + Some(key) => key, + None => return false, + }; + + let (mod_name, is_pattern_match, level) = { + let map = LOG_MODULE_MAP.read().unwrap(); + get_level_for_module(&map, &key) + }; + + if level == LevelFilter::Off { + return false; + } + + if level < record.metadata().level() { + return false; + } + + // Map the log::Level to mozilla::LogLevel. + let moz_log_level = match record.metadata().level() { + Level::Error => 1, // Error + Level::Warn => 2, // Warning + Level::Info => 3, // Info + Level::Debug => 4, // Debug + Level::Trace => 5, // Verbose + }; + + // If it was a pattern match, we need to append ::* to the matched string. + let (tag, msg) = if is_pattern_match { + ( + CString::new(format!("{}::*", mod_name)).unwrap(), + CString::new(format!("[{}] {}", key, record.args())).unwrap(), + ) + } else { + ( + CString::new(key).unwrap(), + CString::new(format!("{}", record.args())).unwrap(), + ) + }; + + unsafe { + ExternMozLog(tag.as_ptr(), moz_log_level, msg.as_ptr()); + } + + return true; +} + +#[no_mangle] +pub unsafe extern "C" fn set_rust_log_level(module: *const c_char, level: u8) { + // Convert the Gecko level to a rust LevelFilter. + let rust_level = match level { + 1 => LevelFilter::Error, + 2 => LevelFilter::Warn, + 3 => LevelFilter::Info, + 4 => LevelFilter::Debug, + 5 => LevelFilter::Trace, + _ => LevelFilter::Off, + }; + + // This is the name of the rust module that we're trying to log in Gecko. + let mut mod_name = CStr::from_ptr(module).to_string_lossy().into_owned(); + + let is_pattern_match = mod_name.ends_with("::*"); + + // If this is a pattern, remove the last "::*" from it so we can search it + // in the map. + if is_pattern_match { + let len = mod_name.len() - 3; + mod_name.truncate(len); + } + + LOGGING_ACTIVE.store(true, Ordering::Relaxed); + let mut map = LOG_MODULE_MAP.write().unwrap(); + map.insert(mod_name, (rust_level, is_pattern_match)); + + // Figure out the max level of all the modules. + let max = map + .values() + .map(|(lvl, _)| lvl) + .max() + .unwrap_or(&LevelFilter::Off); + log::set_max_level(*max); +} + +pub struct GeckoLogger { + logger: env_logger::Logger, +} + +impl GeckoLogger { + pub fn new() -> GeckoLogger { + let mut builder = env_logger::Builder::new(); + let default_level = if cfg!(debug_assertions) { + "warn" + } else { + "error" + }; + let logger = match env::var("RUST_LOG") { + Ok(v) => builder.parse_filters(&v).build(), + _ => builder.parse_filters(default_level).build(), + }; + + GeckoLogger { logger } + } + + pub fn init() -> Result<(), log::SetLoggerError> { + let gecko_logger = Self::new(); + + // The max level may have already been set by gecko_logger. Don't + // set it to a lower level. + let level = cmp::max(log::max_level(), gecko_logger.logger.filter()); + log::set_max_level(level); + log::set_boxed_logger(Box::new(gecko_logger)) + } + + fn should_log_to_app_services(target: &str) -> bool { + return AppServicesLogger::is_app_services_logger_registered(target.into()); + } + + fn maybe_log_to_app_services(&self, record: &log::Record) { + if Self::should_log_to_app_services(record.target()) { + if let Some(l) = LOGGERS_BY_TARGET.read().unwrap().get(record.target()) { + l.log(record); + } + } + } + + fn should_log_to_gfx_critical_note(record: &log::Record) -> bool { + record.level() == log::Level::Error && record.target().contains("webrender") + } + + fn maybe_log_to_gfx_critical_note(&self, record: &log::Record) { + if Self::should_log_to_gfx_critical_note(record) { + let msg = CString::new(format!("{}", record.args())).unwrap(); + unsafe { + gfx_critical_note(msg.as_ptr()); + } + } + } + + #[cfg(not(target_os = "android"))] + fn log_out(&self, record: &log::Record) { + // If the log wasn't handled by the gecko platform logger, just pass it + // to the env_logger. + if !log_to_gecko(record) { + self.logger.log(record); + } + } + + #[cfg(target_os = "android")] + fn log_out(&self, record: &log::Record) { + if !self.logger.matches(record) { + return; + } + + let msg = CString::new(format!("{}", record.args())).unwrap(); + let tag = CString::new(record.module_path().unwrap()).unwrap(); + let prio = match record.metadata().level() { + Level::Error => 6, /* ERROR */ + Level::Warn => 5, /* WARN */ + Level::Info => 4, /* INFO */ + Level::Debug => 3, /* DEBUG */ + Level::Trace => 2, /* VERBOSE */ + }; + // Output log directly to android log, since env_logger can output log + // only to stderr or stdout. + unsafe { + __android_log_write(prio, tag.as_ptr(), msg.as_ptr()); + } + } +} + +impl log::Log for GeckoLogger { + fn enabled(&self, metadata: &log::Metadata) -> bool { + self.logger.enabled(metadata) || GeckoLogger::should_log_to_app_services(metadata.target()) + } + + fn log(&self, record: &log::Record) { + // Forward log to gfxCriticalNote, if the log should be in gfx crash log. + self.maybe_log_to_gfx_critical_note(record); + self.maybe_log_to_app_services(record); + self.log_out(record); + } + + fn flush(&self) {} +} diff --git a/xpcom/rust/gkrust_utils/Cargo.toml b/xpcom/rust/gkrust_utils/Cargo.toml new file mode 100644 index 0000000000..e398725260 --- /dev/null +++ b/xpcom/rust/gkrust_utils/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "gkrust_utils" +version = "0.1.0" +authors = ["Jonathan Kingston "] +license = "MPL-2.0" + +[dependencies] +semver = "1.0" +nsstring = { path = "../nsstring" } diff --git a/xpcom/rust/gkrust_utils/cbindgen.toml b/xpcom/rust/gkrust_utils/cbindgen.toml new file mode 100644 index 0000000000..967fbefd47 --- /dev/null +++ b/xpcom/rust/gkrust_utils/cbindgen.toml @@ -0,0 +1,31 @@ +header = """/* 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/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. + * To generate this file: + * 1. Get the latest cbindgen using `cargo install --force cbindgen` + * a. Alternatively, you can clone `https://github.com/eqrion/cbindgen` and use a tagged release + * 2. Run `rustup run nightly cbindgen xpcom/rust/gkrust_utils --lockfile Cargo.lock --crate gkrust_utils -o xpcom/base/gk_rust_utils_ffi_generated.h` + */ +#include "nsError.h" +#include "nsString.h" +""" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +namespaces = ["mozilla"] + +[export] +# Skip constants because we don't have any +item_types = ["globals", "enums", "structs", "unions", "typedefs", "opaque", "functions"] + +[enum] +add_sentinel = true +derive_helper_methods = true + +[defines] +"target_os = windows" = "XP_WIN" +"target_os = macos" = "XP_MACOSX" +"target_os = android" = "ANDROID" diff --git a/xpcom/rust/gkrust_utils/src/lib.rs b/xpcom/rust/gkrust_utils/src/lib.rs new file mode 100644 index 0000000000..c519d7e002 --- /dev/null +++ b/xpcom/rust/gkrust_utils/src/lib.rs @@ -0,0 +1,24 @@ +/* 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/. */ + +extern crate nsstring; +extern crate semver; +use nsstring::nsACString; + +#[no_mangle] +pub unsafe extern "C" fn GkRustUtils_ParseSemVer( + ver: &nsACString, + out_major: *mut u64, + out_minor: *mut u64, + out_patch: *mut u64, +) -> bool { + let version = match semver::Version::parse(&ver.to_utf8()) { + Ok(ver) => ver, + Err(_) => return false, + }; + *out_major = version.major; + *out_minor = version.minor; + *out_patch = version.patch; + true +} diff --git a/xpcom/rust/gtest/bench-collections/Bench.cpp b/xpcom/rust/gtest/bench-collections/Bench.cpp new file mode 100644 index 0000000000..41152470c7 --- /dev/null +++ b/xpcom/rust/gtest/bench-collections/Bench.cpp @@ -0,0 +1,297 @@ +/* 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/. */ + +// Overview +// -------- +// This file measures the speed of various implementations of C++ and Rust +// collections (hash tables, etc.) used within the codebase. There are a small +// number of benchmarks for each collection type; each benchmark tests certain +// operations (insertion, lookup, iteration, etc.) More benchmarks could easily +// be envisioned, but this small number is enough to characterize the major +// differences between implementations, while keeping the file size and +// complexity low. +// +// Details +// ------- +// The file uses `MOZ_GTEST_BENCH_F` so that results are integrated into +// PerfHerder. It is also designed so that individual test benchmarks can be +// run under a profiler. +// +// The C++ code uses `MOZ_RELEASE_ASSERT` extensively to check values and +// ensure operations aren't optimized away by the compiler. The Rust code uses +// `assert!()`. These should be roughly equivalent, but aren't guaranteed to be +// the same. As a result, the intra-C++ comparisons should be reliable, and the +// intra-Rust comparisons should be reliable, but the C++ vs. Rust comparisons +// may be less reliable. +// +// Note that the Rust implementations run very slowly without --enable-release. +// +// Profiling +// --------- +// If you want to measure a particular implementation under a profiler such as +// Callgrind, do something like this: +// +// MOZ_RUN_GTEST=1 GTEST_FILTER='*BenchCollections*$IMPL*' +// valgrind --tool=callgrind --callgrind-out-file=clgout +// $OBJDIR/dist/bin/firefox -unittest +// callgrind_annotate --auto=yes clgout > clgann +// +// where $IMPL is part of an implementation name in a test (e.g. "PLDHash", +// "MozHash") and $OBJDIR is an objdir containing a --enable-release build. +// +// Note that multiple processes are spawned, so `clgout` gets overwritten +// multiple times, but the last process to write its profiling data to file is +// the one of interest. (Alternatively, use --callgrind-out-file=clgout.%p to +// get separate output files for each process, with a PID suffix.) + +#include "gtest/gtest.h" +#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH +#include "mozilla/AllocPolicy.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/HashTable.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TimeStamp.h" +#include "PLDHashTable.h" +#include + +using namespace mozilla; + +// This function gives a pseudo-random sequence with the following properties: +// - Deterministic and platform-independent. +// - No duplicates in the first VALS_LEN results, which is useful for ensuring +// the tables get to a particular size, and also for guaranteeing lookups +// that fail. +static uintptr_t MyRand() { + static uintptr_t s = 0; + s = s * 1103515245 + 12345; + return s; +} + +// Keep this in sync with Params in bench.rs. +struct Params { + const char* mConfigName; + size_t mNumInserts; // Insert this many unique keys + size_t mNumSuccessfulLookups; // Does mNumInserts lookups each time + size_t mNumFailingLookups; // Does mNumInserts lookups each time + size_t mNumIterations; // Iterates the full table each time + bool mRemoveInserts; // Remove all entries at end? +}; + +// We don't use std::unordered_{set,map}, but it's an interesting thing to +// benchmark against. +// +// Keep this in sync with all the other Bench_*() functions. +static void Bench_Cpp_unordered_set(const Params* aParams, void** aVals, + size_t aLen) { + std::unordered_set hs; + + for (size_t j = 0; j < aParams->mNumInserts; j++) { + hs.insert(aVals[j]); + } + + for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.find(aVals[j]) != hs.end()); + } + } + + for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { + for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) { + MOZ_RELEASE_ASSERT(hs.find(aVals[j]) == hs.end()); + } + } + + for (size_t i = 0; i < aParams->mNumIterations; i++) { + size_t n = 0; + for (const auto& elem : hs) { + (void)elem; + n++; + } + MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); + MOZ_RELEASE_ASSERT(hs.size() == n); + } + + if (aParams->mRemoveInserts) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.erase(aVals[j]) == 1); + } + MOZ_RELEASE_ASSERT(hs.size() == 0); + } else { + MOZ_RELEASE_ASSERT(hs.size() == aParams->mNumInserts); + } +} + +// Keep this in sync with all the other Bench_*() functions. +static void Bench_Cpp_PLDHashTable(const Params* aParams, void** aVals, + size_t aLen) { + PLDHashTable hs(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub)); + + for (size_t j = 0; j < aParams->mNumInserts; j++) { + auto entry = static_cast(hs.Add(aVals[j])); + MOZ_RELEASE_ASSERT(!entry->key); + entry->key = aVals[j]; + } + + for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.Search(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { + for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) { + MOZ_RELEASE_ASSERT(!hs.Search(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumIterations; i++) { + size_t n = 0; + for (auto iter = hs.Iter(); !iter.Done(); iter.Next()) { + n++; + } + MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); + MOZ_RELEASE_ASSERT(hs.EntryCount() == n); + } + + if (aParams->mRemoveInserts) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + hs.Remove(aVals[j]); + } + MOZ_RELEASE_ASSERT(hs.EntryCount() == 0); + } else { + MOZ_RELEASE_ASSERT(hs.EntryCount() == aParams->mNumInserts); + } +} + +// Keep this in sync with all the other Bench_*() functions. +static void Bench_Cpp_MozHashSet(const Params* aParams, void** aVals, + size_t aLen) { + mozilla::HashSet, MallocAllocPolicy> hs; + + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.put(aVals[j])); + } + + for (size_t i = 0; i < aParams->mNumSuccessfulLookups; i++) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + MOZ_RELEASE_ASSERT(hs.has(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumFailingLookups; i++) { + for (size_t j = aParams->mNumInserts; j < aParams->mNumInserts * 2; j++) { + MOZ_RELEASE_ASSERT(!hs.has(aVals[j])); + } + } + + for (size_t i = 0; i < aParams->mNumIterations; i++) { + size_t n = 0; + for (auto iter = hs.iter(); !iter.done(); iter.next()) { + n++; + } + MOZ_RELEASE_ASSERT(aParams->mNumInserts == n); + MOZ_RELEASE_ASSERT(hs.count() == n); + } + + if (aParams->mRemoveInserts) { + for (size_t j = 0; j < aParams->mNumInserts; j++) { + hs.remove(aVals[j]); + } + MOZ_RELEASE_ASSERT(hs.count() == 0); + } else { + MOZ_RELEASE_ASSERT(hs.count() == aParams->mNumInserts); + } +} + +extern "C" { +void Bench_Rust_HashSet(const Params* params, void** aVals, size_t aLen); +void Bench_Rust_FnvHashSet(const Params* params, void** aVals, size_t aLen); +void Bench_Rust_FxHashSet(const Params* params, void** aVals, size_t aLen); +} + +static const size_t VALS_LEN = 131072; + +// Each benchmark measures a different aspect of performance. +// Note that no "Inserts" value can exceed VALS_LEN. +// Also, if any failing lookups are done, Inserts must be <= VALS_LEN/2. +const Params gParamsList[] = { + // clang-format off + // Successful Failing Remove + // Inserts lookups lookups Iterations inserts + { "succ_lookups", 1024, 5000, 0, 0, false }, + { "fail_lookups", 1024, 0, 5000, 0, false }, + { "insert_remove", VALS_LEN, 0, 0, 0, true }, + { "iterate", 1024, 0, 0, 5000, false }, + // clang-format on +}; + +class BenchCollections : public ::testing::Test { + protected: + void SetUp() override { + StaticMutexAutoLock lock(sValsMutex); + + if (!sVals) { + sVals = (void**)malloc(VALS_LEN * sizeof(void*)); + for (size_t i = 0; i < VALS_LEN; i++) { + // This leaves the high 32 bits zero on 64-bit platforms, but that + // should still be enough randomness to get typical behaviour. + sVals[i] = reinterpret_cast(uintptr_t(MyRand())); + } + } + + printf("\n"); + for (size_t i = 0; i < ArrayLength(gParamsList); i++) { + const Params* params = &gParamsList[i]; + printf("%14s", params->mConfigName); + } + printf("%14s\n", "total"); + } + + public: + void BenchImpl(void (*aBench)(const Params*, void**, size_t)) { + StaticMutexAutoLock lock(sValsMutex); + + double total = 0; + for (size_t i = 0; i < ArrayLength(gParamsList); i++) { + const Params* params = &gParamsList[i]; + TimeStamp t1 = TimeStamp::Now(); + aBench(params, sVals, VALS_LEN); + TimeStamp t2 = TimeStamp::Now(); + double t = (t2 - t1).ToMilliseconds(); + printf("%11.1f ms", t); + total += t; + } + printf("%11.1f ms\n", total); + } + + private: + // Random values used in the benchmarks. + static void** sVals; + + // A mutex that protects all benchmark operations, ensuring that two + // benchmarks never run concurrently. + static StaticMutex sValsMutex MOZ_UNANNOTATED; +}; + +void** BenchCollections::sVals; +StaticMutex BenchCollections::sValsMutex; + +MOZ_GTEST_BENCH_F(BenchCollections, unordered_set, + [this] { BenchImpl(Bench_Cpp_unordered_set); }); + +MOZ_GTEST_BENCH_F(BenchCollections, PLDHash, + [this] { BenchImpl(Bench_Cpp_PLDHashTable); }); + +MOZ_GTEST_BENCH_F(BenchCollections, MozHash, + [this] { BenchImpl(Bench_Cpp_MozHashSet); }); + +MOZ_GTEST_BENCH_F(BenchCollections, RustHash, + [this] { BenchImpl(Bench_Rust_HashSet); }); + +MOZ_GTEST_BENCH_F(BenchCollections, RustFnvHash, + [this] { BenchImpl(Bench_Rust_FnvHashSet); }); + +MOZ_GTEST_BENCH_F(BenchCollections, RustFxHash, + [this] { BenchImpl(Bench_Rust_FxHashSet); }); diff --git a/xpcom/rust/gtest/bench-collections/Cargo.toml b/xpcom/rust/gtest/bench-collections/Cargo.toml new file mode 100644 index 0000000000..857d87cffd --- /dev/null +++ b/xpcom/rust/gtest/bench-collections/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bench-collections-gtest" +version = "0.1.0" +license = "MPL-2.0" +description = "Benchmarks for various collections" + +[dependencies] +fnv = "1.0" +fxhash = "0.2.1" + +[lib] +path = "bench.rs" diff --git a/xpcom/rust/gtest/bench-collections/bench.rs b/xpcom/rust/gtest/bench-collections/bench.rs new file mode 100644 index 0000000000..1aba69f01f --- /dev/null +++ b/xpcom/rust/gtest/bench-collections/bench.rs @@ -0,0 +1,101 @@ +/* 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/. */ + +#![allow(non_snake_case)] + +extern crate fnv; +extern crate fxhash; + +use fnv::FnvHashSet; +use fxhash::FxHashSet; +use std::collections::HashSet; +use std::os::raw::{c_char, c_void}; +use std::slice; + +/// Keep this in sync with Params in Bench.cpp. +#[derive(Debug)] +#[repr(C)] +pub struct Params { + config_name: *const c_char, + num_inserts: usize, + num_successful_lookups: usize, + num_failing_lookups: usize, + num_iterations: usize, + remove_inserts: bool, +} + +#[no_mangle] +pub extern "C" fn Bench_Rust_HashSet( + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let hs: HashSet<_> = std::collections::HashSet::default(); + Bench_Rust(hs, params, vals, len); +} + +#[no_mangle] +pub extern "C" fn Bench_Rust_FnvHashSet( + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let hs = FnvHashSet::default(); + Bench_Rust(hs, params, vals, len); +} + +#[no_mangle] +pub extern "C" fn Bench_Rust_FxHashSet( + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let hs = FxHashSet::default(); + Bench_Rust(hs, params, vals, len); +} + +// Keep this in sync with all the other Bench_*() functions. +fn Bench_Rust( + mut hs: HashSet<*const c_void, H>, + params: *const Params, + vals: *const *const c_void, + len: usize, +) { + let params = unsafe { &*params }; + let vals = unsafe { slice::from_raw_parts(vals, len) }; + + for j in 0..params.num_inserts { + hs.insert(vals[j]); + } + + for _i in 0..params.num_successful_lookups { + for j in 0..params.num_inserts { + assert!(hs.contains(&vals[j])); + } + } + + for _i in 0..params.num_failing_lookups { + for j in params.num_inserts..params.num_inserts * 2 { + assert!(!hs.contains(&vals[j])); + } + } + + for _i in 0..params.num_iterations { + let mut n = 0; + for _ in hs.iter() { + n += 1; + } + assert!(params.num_inserts == n); + assert!(hs.len() == n); + } + + if params.remove_inserts { + for j in 0..params.num_inserts { + assert!(hs.remove(&vals[j])); + } + assert!(hs.is_empty()); + } else { + assert!(hs.len() == params.num_inserts); + } +} diff --git a/xpcom/rust/gtest/moz.build b/xpcom/rust/gtest/moz.build new file mode 100644 index 0000000000..d3457930c8 --- /dev/null +++ b/xpcom/rust/gtest/moz.build @@ -0,0 +1,14 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + "bench-collections/Bench.cpp", + "moz_task/TestMozTask.cpp", + "nsstring/TestnsString.cpp", + "xpcom/TestXpcom.cpp", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/xpcom/rust/gtest/moz_task/Cargo.toml b/xpcom/rust/gtest/moz_task/Cargo.toml new file mode 100644 index 0000000000..09d240c309 --- /dev/null +++ b/xpcom/rust/gtest/moz_task/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "moz_task-gtest" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Tests for rust bindings to xpcom event target types" +edition = "2018" + +[dependencies] +moz_task = { path = "../../moz_task" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/gtest/moz_task/TestMozTask.cpp b/xpcom/rust/gtest/moz_task/TestMozTask.cpp new file mode 100644 index 0000000000..6c40f1bc97 --- /dev/null +++ b/xpcom/rust/gtest/moz_task/TestMozTask.cpp @@ -0,0 +1,14 @@ +/* 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 "gtest/gtest.h" + +extern "C" void Rust_Future(bool* aItWorked); + +TEST(RustMozTask, Future) +{ + bool itWorked = false; + Rust_Future(&itWorked); + EXPECT_TRUE(itWorked); +} diff --git a/xpcom/rust/gtest/moz_task/test.rs b/xpcom/rust/gtest/moz_task/test.rs new file mode 100644 index 0000000000..fa4c0af852 --- /dev/null +++ b/xpcom/rust/gtest/moz_task/test.rs @@ -0,0 +1,78 @@ +/* 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/. */ + +use moz_task; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll, Waker}, +}; + +/// Demo `Future` to demonstrate executing futures to completion via `nsIEventTarget`. +struct MyFuture { + poll_count: u32, + waker: Option, + expect_main_thread: bool, +} + +impl MyFuture { + fn new(expect_main_thread: bool) -> Self { + Self { + poll_count: 0, + waker: None, + expect_main_thread, + } + } +} + +impl Future for MyFuture { + type Output = u32; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + assert_eq!(moz_task::is_main_thread(), self.expect_main_thread); + + self.poll_count += 1; + + if let Some(waker) = &mut self.waker { + if !waker.will_wake(cx.waker()) { + *waker = cx.waker().clone(); + } + } else { + let waker = cx.waker().clone(); + self.waker = Some(waker); + } + + println!("Poll count = {}", self.poll_count); + if self.poll_count > 5 { + Poll::Ready(self.poll_count) + } else { + // Just notify the task that we need to re-polled. + if let Some(waker) = &self.waker { + waker.wake_by_ref(); + } + Poll::Pending + } + } +} + +#[no_mangle] +pub extern "C" fn Rust_Future(it_worked: *mut bool) { + let future = async move { + assert_eq!(MyFuture::new(true).await, 6); + assert_eq!( + moz_task::spawn_local("Rust_Future inner spawn_local", MyFuture::new(true)).await, + 6 + ); + assert_eq!( + moz_task::spawn("Rust_Future inner spawn", MyFuture::new(false)).await, + 6 + ); + unsafe { + *it_worked = true; + } + }; + unsafe { + moz_task::gtest_only::spin_event_loop_until("Rust_Future", future).unwrap(); + }; +} diff --git a/xpcom/rust/gtest/nsstring/Cargo.toml b/xpcom/rust/gtest/nsstring/Cargo.toml new file mode 100644 index 0000000000..fd997926a7 --- /dev/null +++ b/xpcom/rust/gtest/nsstring/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "nsstring-gtest" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Tests for rust bindings to xpcom string types" + +[dependencies] +nsstring = { path = "../../nsstring" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/gtest/nsstring/TestnsString.cpp b/xpcom/rust/gtest/nsstring/TestnsString.cpp new file mode 100644 index 0000000000..4e14c7bca5 --- /dev/null +++ b/xpcom/rust/gtest/nsstring/TestnsString.cpp @@ -0,0 +1,177 @@ +#include "gtest/gtest.h" +#include +#include +#include "nsString.h" + +extern "C" { +// This function is called by the rust code in test.rs if a non-fatal test +// failure occurs. +void GTest_ExpectFailure(const char* aMessage) { EXPECT_STREQ(aMessage, ""); } +} + +#define SIZE_ALIGN_CHECK(Clazz) \ + extern "C" void Rust_Test_ReprSizeAlign_##Clazz(size_t* size, \ + size_t* align); \ + TEST(RustNsString, ReprSizeAlign_##Clazz) \ + { \ + size_t size, align; \ + Rust_Test_ReprSizeAlign_##Clazz(&size, &align); \ + EXPECT_EQ(size, sizeof(Clazz)); \ + EXPECT_EQ(align, alignof(Clazz)); \ + } + +SIZE_ALIGN_CHECK(nsString) +SIZE_ALIGN_CHECK(nsCString) + +#define MEMBER_CHECK(Clazz, Member) \ + extern "C" void Rust_Test_Member_##Clazz##_##Member( \ + size_t* size, size_t* align, size_t* offset); \ + TEST(RustNsString, ReprMember_##Clazz##_##Member) \ + { \ + class Hack : public Clazz { \ + public: \ + static void RunTest() { \ + size_t size, align, offset; \ + Rust_Test_Member_##Clazz##_##Member(&size, &align, &offset); \ + EXPECT_EQ(size, sizeof(std::declval().Member)); \ + EXPECT_EQ(align, alignof(decltype(std::declval().Member))); \ + EXPECT_EQ(offset, offsetof(Hack, Member)); \ + } \ + }; \ + static_assert(sizeof(Clazz) == sizeof(Hack), "Hack matches class"); \ + Hack::RunTest(); \ + } + +MEMBER_CHECK(nsString, mData) +MEMBER_CHECK(nsString, mLength) +MEMBER_CHECK(nsString, mDataFlags) +MEMBER_CHECK(nsString, mClassFlags) +MEMBER_CHECK(nsCString, mData) +MEMBER_CHECK(nsCString, mLength) +MEMBER_CHECK(nsCString, mDataFlags) +MEMBER_CHECK(nsCString, mClassFlags) + +extern "C" void Rust_Test_NsStringFlags( + uint16_t* f_terminated, uint16_t* f_voided, uint16_t* f_refcounted, + uint16_t* f_owned, uint16_t* f_inline, uint16_t* f_literal, + uint16_t* f_class_inline, uint16_t* f_class_null_terminated); +TEST(RustNsString, NsStringFlags) +{ + uint16_t f_terminated, f_voided, f_refcounted, f_owned, f_inline, f_literal, + f_class_inline, f_class_null_terminated; + Rust_Test_NsStringFlags(&f_terminated, &f_voided, &f_refcounted, &f_owned, + &f_inline, &f_literal, &f_class_inline, + &f_class_null_terminated); + EXPECT_EQ(f_terminated, uint16_t(nsAString::DataFlags::TERMINATED)); + EXPECT_EQ(f_terminated, uint16_t(nsACString::DataFlags::TERMINATED)); + EXPECT_EQ(f_voided, uint16_t(nsAString::DataFlags::VOIDED)); + EXPECT_EQ(f_voided, uint16_t(nsACString::DataFlags::VOIDED)); + EXPECT_EQ(f_refcounted, uint16_t(nsAString::DataFlags::REFCOUNTED)); + EXPECT_EQ(f_refcounted, uint16_t(nsACString::DataFlags::REFCOUNTED)); + EXPECT_EQ(f_owned, uint16_t(nsAString::DataFlags::OWNED)); + EXPECT_EQ(f_owned, uint16_t(nsACString::DataFlags::OWNED)); + EXPECT_EQ(f_inline, uint16_t(nsAString::DataFlags::INLINE)); + EXPECT_EQ(f_inline, uint16_t(nsACString::DataFlags::INLINE)); + EXPECT_EQ(f_literal, uint16_t(nsAString::DataFlags::LITERAL)); + EXPECT_EQ(f_literal, uint16_t(nsACString::DataFlags::LITERAL)); + EXPECT_EQ(f_class_inline, uint16_t(nsAString::ClassFlags::INLINE)); + EXPECT_EQ(f_class_inline, uint16_t(nsACString::ClassFlags::INLINE)); + EXPECT_EQ(f_class_null_terminated, + uint16_t(nsAString::ClassFlags::NULL_TERMINATED)); + EXPECT_EQ(f_class_null_terminated, + uint16_t(nsACString::ClassFlags::NULL_TERMINATED)); +} + +extern "C" void Rust_StringFromCpp(const nsACString* aCStr, + const nsAString* aStr); +TEST(RustNsString, StringFromCpp) +{ + nsAutoCString foo; + foo.AssignASCII("Hello, World!"); + + nsAutoString bar; + bar.AssignASCII("Hello, World!"); + + Rust_StringFromCpp(&foo, &bar); +} + +extern "C" void Rust_AssignFromRust(nsACString* aCStr, nsAString* aStr); +TEST(RustNsString, AssignFromRust) +{ + nsAutoCString cs; + nsAutoString s; + Rust_AssignFromRust(&cs, &s); + EXPECT_TRUE(cs.EqualsASCII("Hello, World!")); + EXPECT_TRUE(s.EqualsASCII("Hello, World!")); +} + +extern "C" { +void Cpp_AssignFromCpp(nsACString* aCStr, nsAString* aStr) { + aCStr->AssignASCII("Hello, World!"); + aStr->AssignASCII("Hello, World!"); +} +} +extern "C" void Rust_AssignFromCpp(); +TEST(RustNsString, AssignFromCpp) +{ Rust_AssignFromCpp(); } + +extern "C" void Rust_StringWrite(); +TEST(RustNsString, StringWrite) +{ Rust_StringWrite(); } + +extern "C" void Rust_FromEmptyRustString(); +TEST(RustNsString, FromEmptyRustString) +{ Rust_FromEmptyRustString(); } + +extern "C" void Rust_WriteToBufferFromRust(nsACString* aCStr, nsAString* aStr, + nsACString* aFallibleCStr, + nsAString* aFallibleStr); +TEST(RustNsString, WriteToBufferFromRust) +{ + nsAutoCString cStr; + nsAutoString str; + nsAutoCString fallibleCStr; + nsAutoString fallibleStr; + + cStr.AssignLiteral("abc"); + str.AssignLiteral("abc"); + fallibleCStr.AssignLiteral("abc"); + fallibleStr.AssignLiteral("abc"); + + Rust_WriteToBufferFromRust(&cStr, &str, &fallibleCStr, &fallibleStr); + + EXPECT_TRUE(cStr.EqualsASCII("ABC")); + EXPECT_TRUE(str.EqualsASCII("ABC")); + EXPECT_TRUE(fallibleCStr.EqualsASCII("ABC")); + EXPECT_TRUE(fallibleStr.EqualsASCII("ABC")); +} + +extern "C" void Rust_InlineCapacityFromRust(const nsACString* aCStr, + const nsAString* aStr, + size_t* aCStrCapacity, + size_t* aStrCapacity); +TEST(RustNsString, InlineCapacityFromRust) +{ + size_t cStrCapacity; + size_t strCapacity; + nsAutoCStringN<93> cs; + nsAutoStringN<93> s; + Rust_InlineCapacityFromRust(&cs, &s, &cStrCapacity, &strCapacity); + EXPECT_EQ(cStrCapacity, 92U); + EXPECT_EQ(strCapacity, 92U); +} + +extern "C" void Rust_VoidStringFromRust(nsACString* aCStr, nsAString* aStr); +TEST(RustNsString, VoidStringFromRust) +{ + nsAutoCString cs; + nsAutoString s; + + EXPECT_FALSE(cs.IsVoid()); + EXPECT_FALSE(s.IsVoid()); + + Rust_VoidStringFromRust(&cs, &s); + + EXPECT_TRUE(cs.IsVoid()); + EXPECT_TRUE(s.IsVoid()); +} diff --git a/xpcom/rust/gtest/nsstring/test.rs b/xpcom/rust/gtest/nsstring/test.rs new file mode 100644 index 0000000000..a5d142f2b2 --- /dev/null +++ b/xpcom/rust/gtest/nsstring/test.rs @@ -0,0 +1,131 @@ +#![allow(non_snake_case)] + +extern crate nsstring; + +use nsstring::*; +use std::ffi::CString; +use std::fmt::Write; +use std::os::raw::c_char; + +fn nonfatal_fail(msg: String) { + extern "C" { + fn GTest_ExpectFailure(message: *const c_char); + } + unsafe { + let msg = CString::new(msg).unwrap(); + GTest_ExpectFailure(msg.as_ptr()); + } +} + +/// This macro checks if the two arguments are equal, and causes a non-fatal +/// GTest test failure if they are not. +macro_rules! expect_eq { + ($x:expr, $y:expr) => { + match (&$x, &$y) { + (x, y) => { + if *x != *y { + nonfatal_fail(format!( + "check failed: (`{:?}` == `{:?}`) at {}:{}", + x, + y, + file!(), + line!() + )) + } + } + } + }; +} + +#[no_mangle] +pub extern "C" fn Rust_StringFromCpp(cs: *const nsACString, s: *const nsAString) { + unsafe { + expect_eq!(&*cs, "Hello, World!"); + expect_eq!(&*s, "Hello, World!"); + } +} + +#[no_mangle] +pub extern "C" fn Rust_AssignFromRust(cs: *mut nsACString, s: *mut nsAString) { + unsafe { + (*cs).assign(&nsCString::from("Hello, World!")); + expect_eq!(&*cs, "Hello, World!"); + (*s).assign(&nsString::from("Hello, World!")); + expect_eq!(&*s, "Hello, World!"); + } +} + +extern "C" { + fn Cpp_AssignFromCpp(cs: *mut nsACString, s: *mut nsAString); +} + +#[no_mangle] +pub extern "C" fn Rust_AssignFromCpp() { + let mut cs = nsCString::new(); + let mut s = nsString::new(); + unsafe { + Cpp_AssignFromCpp(&mut *cs, &mut *s); + } + expect_eq!(cs, "Hello, World!"); + expect_eq!(s, "Hello, World!"); +} + +#[no_mangle] +pub extern "C" fn Rust_StringWrite() { + let mut cs = nsCString::new(); + let mut s = nsString::new(); + + write!(s, "a").unwrap(); + write!(cs, "a").unwrap(); + expect_eq!(s, "a"); + expect_eq!(cs, "a"); + write!(s, "bc").unwrap(); + write!(cs, "bc").unwrap(); + expect_eq!(s, "abc"); + expect_eq!(cs, "abc"); + write!(s, "{}", 123).unwrap(); + write!(cs, "{}", 123).unwrap(); + expect_eq!(s, "abc123"); + expect_eq!(cs, "abc123"); +} + +#[no_mangle] +pub extern "C" fn Rust_FromEmptyRustString() { + let mut test = nsString::from("Blah"); + test.assign_utf8(&nsCString::from(String::new())); + assert!(test.is_empty()); +} + +#[no_mangle] +pub extern "C" fn Rust_WriteToBufferFromRust( + cs: *mut nsACString, + s: *mut nsAString, + fallible_cs: *mut nsACString, + fallible_s: *mut nsAString, +) { + unsafe { + let cs_buf = (*cs).to_mut(); + let s_buf = (*s).to_mut(); + let fallible_cs_buf = (*fallible_cs).fallible_to_mut().unwrap(); + let fallible_s_buf = (*fallible_s).fallible_to_mut().unwrap(); + + cs_buf[0] = b'A'; + cs_buf[1] = b'B'; + cs_buf[2] = b'C'; + s_buf[0] = b'A' as u16; + s_buf[1] = b'B' as u16; + s_buf[2] = b'C' as u16; + fallible_cs_buf[0] = b'A'; + fallible_cs_buf[1] = b'B'; + fallible_cs_buf[2] = b'C'; + fallible_s_buf[0] = b'A' as u16; + fallible_s_buf[1] = b'B' as u16; + fallible_s_buf[2] = b'C' as u16; + } +} + +#[no_mangle] +pub extern "C" fn Rust_VoidStringFromRust(cs: &mut nsACString, s: &mut nsAString) { + cs.set_is_void(true); + s.set_is_void(true); +} diff --git a/xpcom/rust/gtest/xpcom/Cargo.toml b/xpcom/rust/gtest/xpcom/Cargo.toml new file mode 100644 index 0000000000..777080b33b --- /dev/null +++ b/xpcom/rust/gtest/xpcom/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "xpcom-gtest" +version = "0.1.0" +authors = ["michael@thelayzells.com"] +license = "MPL-2.0" +description = "Tests for rust bindings to xpcom interfaces" + +[dependencies] +xpcom = { path = "../../xpcom" } +nserror = { path = "../../nserror" } +nsstring = { path = "../../nsstring" } + +[lib] +path = "test.rs" diff --git a/xpcom/rust/gtest/xpcom/TestXpcom.cpp b/xpcom/rust/gtest/xpcom/TestXpcom.cpp new file mode 100644 index 0000000000..69425274f6 --- /dev/null +++ b/xpcom/rust/gtest/xpcom/TestXpcom.cpp @@ -0,0 +1,66 @@ +/* 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 "gtest/gtest.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsIObserver.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" + +extern "C" nsIObserverService* Rust_ObserveFromRust(); + +TEST(RustXpcom, ObserverFromRust) +{ + nsCOMPtr rust = Rust_ObserveFromRust(); + nsCOMPtr cpp = mozilla::services::GetObserverService(); + EXPECT_EQ(rust, cpp); +} + +extern "C" void Rust_ImplementRunnableInRust(bool* aItWorked, + nsIRunnable** aRunnable); + +TEST(RustXpcom, ImplementRunnableInRust) +{ + bool itWorked = false; + nsCOMPtr runnable; + Rust_ImplementRunnableInRust(&itWorked, getter_AddRefs(runnable)); + + EXPECT_TRUE(runnable); + EXPECT_FALSE(itWorked); + runnable->Run(); + EXPECT_TRUE(itWorked); +} + +extern "C" void Rust_GetMultipleInterfaces(nsIRunnable** aRunnable, + nsIObserver** aObserver); + +TEST(RustXpcom, DynamicCastVoid) +{ + nsCOMPtr runnable; + nsCOMPtr observer; + Rust_GetMultipleInterfaces(getter_AddRefs(runnable), + getter_AddRefs(observer)); + + // They should have different addresses when `static_cast` to void* + EXPECT_NE(static_cast(runnable.get()), + static_cast(observer.get())); + + // These should be the same object + nsCOMPtr runnableSupports = do_QueryInterface(runnable); + nsCOMPtr observerSupports = do_QueryInterface(observer); + EXPECT_EQ(runnableSupports.get(), observerSupports.get()); + +#ifndef XP_WIN + // They should have the same address when dynamic_cast to void* + // dynamic_cast is not supported without rtti on windows. + EXPECT_EQ(dynamic_cast(runnable.get()), + dynamic_cast(observer.get())); + + // The nsISupports pointer from `do_QueryInterface` should match + // `dynamic_cast` + EXPECT_EQ(dynamic_cast(observer.get()), + static_cast(observerSupports.get())); +#endif +} diff --git a/xpcom/rust/gtest/xpcom/test.rs b/xpcom/rust/gtest/xpcom/test.rs new file mode 100644 index 0000000000..f26a0140f3 --- /dev/null +++ b/xpcom/rust/gtest/xpcom/test.rs @@ -0,0 +1,131 @@ +/* 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/. */ + +#![allow(non_snake_case)] + +#[macro_use] +extern crate xpcom; + +extern crate nserror; + +use nserror::{nsresult, NS_OK}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; +use std::ptr; +use xpcom::{interfaces, RefPtr}; + +#[no_mangle] +pub unsafe extern "C" fn Rust_ObserveFromRust() -> *const interfaces::nsIObserverService { + let obssvc: RefPtr = + xpcom::components::Observer::service().unwrap(); + + // Define an observer + #[xpcom(implement(nsIObserver), nonatomic)] + struct Observer { + run: *mut bool, + } + impl Observer { + unsafe fn Observe( + &self, + _subject: *const interfaces::nsISupports, + topic: *const c_char, + _data: *const u16, + ) -> nsresult { + *self.run = true; + assert!(CStr::from_ptr(topic).to_str() == Ok("test-rust-observe")); + NS_OK + } + } + + let topic = CString::new("test-rust-observe").unwrap(); + + let mut run = false; + let observer = Observer::allocate(InitObserver { run: &mut run }); + let rv = obssvc.AddObserver( + observer.coerce::(), + topic.as_ptr(), + false, + ); + assert!(rv.succeeded()); + + let rv = obssvc.NotifyObservers(ptr::null(), topic.as_ptr(), ptr::null()); + assert!(rv.succeeded()); + assert!(run, "The observer should have been run!"); + + let rv = obssvc.RemoveObserver(observer.coerce::(), topic.as_ptr()); + assert!(rv.succeeded()); + + assert!( + observer.coerce::() as *const _ + == &*observer + .query_interface::() + .unwrap() as *const _ + ); + + &*obssvc +} + +#[no_mangle] +pub unsafe extern "C" fn Rust_ImplementRunnableInRust( + it_worked: *mut bool, + runnable: *mut *const interfaces::nsIRunnable, +) { + // Define a type which implements nsIRunnable in rust. + #[xpcom(implement(nsIRunnable), atomic)] + struct RunnableFn { + run: F, + } + + impl RunnableFn { + unsafe fn Run(&self) -> nsresult { + (self.run)(); + NS_OK + } + } + + let my_runnable = RunnableFn::allocate(InitRunnableFn { + run: move || { + *it_worked = true; + }, + }); + my_runnable + .query_interface::() + .unwrap() + .forget(&mut *runnable); +} + +#[no_mangle] +pub unsafe extern "C" fn Rust_GetMultipleInterfaces( + runnable: *mut *const interfaces::nsIRunnable, + observer: *mut *const interfaces::nsIObserver, +) { + // Define a type which implements nsIRunnable and nsIObserver in rust, and + // hand both references back to c++ + #[xpcom(implement(nsIRunnable, nsIObserver), atomic)] + struct MultipleInterfaces {} + + impl MultipleInterfaces { + unsafe fn Run(&self) -> nsresult { + NS_OK + } + unsafe fn Observe( + &self, + _subject: *const interfaces::nsISupports, + _topic: *const c_char, + _data: *const u16, + ) -> nsresult { + NS_OK + } + } + + let instance = MultipleInterfaces::allocate(InitMultipleInterfaces {}); + instance + .query_interface::() + .unwrap() + .forget(&mut *runnable); + instance + .query_interface::() + .unwrap() + .forget(&mut *observer); +} diff --git a/xpcom/rust/moz_task/Cargo.toml b/xpcom/rust/moz_task/Cargo.toml new file mode 100644 index 0000000000..2e3e43ef7a --- /dev/null +++ b/xpcom/rust/moz_task/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "moz_task" +version = "0.1.0" +authors = ["Myk Melez "] +license = "MPL-2.0" +description = "Rust wrappers around XPCOM threading functions" +edition = "2018" + +[dependencies] +log = "0.4" +cstr = "0.2" +libc = "0.2" +async-task = { version = "4.3" } +nserror = { path = "../nserror" } +nsstring = { path = "../nsstring" } +xpcom = { path = "../xpcom" } diff --git a/xpcom/rust/moz_task/src/dispatcher.rs b/xpcom/rust/moz_task/src/dispatcher.rs new file mode 100644 index 0000000000..17ad9ceb81 --- /dev/null +++ b/xpcom/rust/moz_task/src/dispatcher.rs @@ -0,0 +1,153 @@ +/* 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/. */ + +use crate::{ + dispatch_background_task_runnable, dispatch_runnable, get_current_thread, DispatchOptions, +}; +use nserror::{nsresult, NS_OK}; +use nsstring::nsACString; +use std::sync::Mutex; +use xpcom::interfaces::{nsIEventTarget, nsIRunnablePriority}; +use xpcom::xpcom; + +/// Basic wrapper to convert a FnOnce callback into a `nsIRunnable` to be +/// dispatched using XPCOM. +#[xpcom(implement(nsIRunnable, nsINamed, nsIRunnablePriority), atomic)] +struct RunnableFunction { + name: &'static str, + priority: u32, + function: Mutex>, +} + +impl RunnableFunction { + #[allow(non_snake_case)] + fn Run(&self) -> nsresult { + let function = self.function.lock().unwrap().take(); + debug_assert!(function.is_some(), "runnable invoked twice?"); + if let Some(function) = function { + function(); + } + NS_OK + } + + #[allow(non_snake_case)] + unsafe fn GetName(&self, result: *mut nsACString) -> nsresult { + (*result).assign(self.name); + NS_OK + } + + #[allow(non_snake_case)] + unsafe fn GetPriority(&self, result: *mut u32) -> nsresult { + *result = self.priority; + NS_OK + } +} + +pub struct RunnableBuilder { + name: &'static str, + function: F, + priority: u32, + options: DispatchOptions, +} + +impl RunnableBuilder { + pub fn new(name: &'static str, function: F) -> Self { + RunnableBuilder { + name, + function, + priority: nsIRunnablePriority::PRIORITY_NORMAL, + options: DispatchOptions::default(), + } + } + + pub fn priority(mut self, priority: u32) -> Self { + self.priority = priority; + self + } + + pub fn options(mut self, options: DispatchOptions) -> Self { + self.options = options; + self + } + + pub fn may_block(mut self, may_block: bool) -> Self { + self.options = self.options.may_block(may_block); + self + } + + pub unsafe fn at_end(mut self, at_end: bool) -> Self { + self.options = self.options.at_end(at_end); + self + } +} + +impl RunnableBuilder +where + F: FnOnce() + Send + 'static, +{ + /// Dispatch this Runnable to the specified EventTarget. The runnable function must be `Send`. + pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), nsresult> { + let runnable = RunnableFunction::allocate(InitRunnableFunction { + name: self.name, + priority: self.priority, + function: Mutex::new(Some(self.function)), + }); + unsafe { dispatch_runnable(runnable.coerce(), target, self.options) } + } + + /// Dispatch this Runnable to the specified EventTarget as a background + /// task. The runnable function must be `Send`. + pub fn dispatch_background_task(self) -> Result<(), nsresult> { + let runnable = RunnableFunction::allocate(InitRunnableFunction { + name: self.name, + priority: self.priority, + function: Mutex::new(Some(self.function)), + }); + unsafe { dispatch_background_task_runnable(runnable.coerce(), self.options) } + } +} + +impl RunnableBuilder +where + F: FnOnce() + 'static, +{ + /// Dispatch this Runnable to the current thread. + /// + /// Unlike `dispatch` and `dispatch_background_task`, the runnable does not + /// need to be `Send` to dispatch to the current thread. + pub fn dispatch_local(self) -> Result<(), nsresult> { + let target = get_current_thread()?; + let runnable = RunnableFunction::allocate(InitRunnableFunction { + name: self.name, + priority: self.priority, + function: Mutex::new(Some(self.function)), + }); + unsafe { dispatch_runnable(runnable.coerce(), target.coerce(), self.options) } + } +} + +pub fn dispatch_onto( + name: &'static str, + target: &nsIEventTarget, + function: F, +) -> Result<(), nsresult> +where + F: FnOnce() + Send + 'static, +{ + RunnableBuilder::new(name, function).dispatch(target) +} + +pub fn dispatch_background_task(name: &'static str, function: F) -> Result<(), nsresult> +where + F: FnOnce() + Send + 'static, +{ + RunnableBuilder::new(name, function).dispatch_background_task() +} + +pub fn dispatch_local(name: &'static str, function: F) -> Result<(), nsresult> +where + F: FnOnce() + 'static, +{ + RunnableBuilder::new(name, function).dispatch_local() +} diff --git a/xpcom/rust/moz_task/src/event_loop.rs b/xpcom/rust/moz_task/src/event_loop.rs new file mode 100644 index 0000000000..f8d113ed57 --- /dev/null +++ b/xpcom/rust/moz_task/src/event_loop.rs @@ -0,0 +1,66 @@ +/* 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/. */ + +extern crate nsstring; + +use cstr::cstr; +use nserror::{nsresult, NS_ERROR_SERVICE_NOT_AVAILABLE, NS_ERROR_UNEXPECTED, NS_OK}; +use nsstring::*; +use std::cell::RefCell; +use std::future::Future; +use xpcom::{interfaces::nsIThreadManager, xpcom, xpcom_method}; + +#[xpcom(implement(nsINestedEventLoopCondition), nonatomic)] +struct FutureCompleteCondition { + value: RefCell>, +} + +impl FutureCompleteCondition { + xpcom_method!(is_done => IsDone() -> bool); + fn is_done(&self) -> Result { + Ok(self.value.borrow().is_some()) + } +} + +/// Spin the event loop on the current thread until `future` is resolved. +/// +/// # Safety +/// +/// Spinning a nested event loop should always be avoided when possible, as it +/// can cause hangs, break JS run-to-completion guarantees, and break other C++ +/// code currently on the stack relying on heap invariants. While in a pure-rust +/// codebase this method would only be ill-advised and not technically "unsafe", +/// it is marked as unsafe due to the potential for triggering unsafety in +/// unrelated C++ code. +pub unsafe fn spin_event_loop_until( + reason: &'static str, + future: F, +) -> Result +where + F: Future + 'static, + F::Output: 'static, +{ + let thread_manager = + xpcom::get_service::(cstr!("@mozilla.org/thread-manager;1")) + .ok_or(NS_ERROR_SERVICE_NOT_AVAILABLE)?; + + let cond = FutureCompleteCondition::::allocate(InitFutureCompleteCondition { + value: RefCell::new(None), + }); + + // Spawn our future onto the current thread event loop, and record the + // completed value as it completes. + let cond2 = cond.clone(); + crate::spawn_local(reason, async move { + let rv = future.await; + *cond2.value.borrow_mut() = Some(rv); + }) + .detach(); + + thread_manager + .SpinEventLoopUntil(&*nsCStr::from(reason), cond.coerce()) + .to_result()?; + let rv = cond.value.borrow_mut().take(); + rv.ok_or(NS_ERROR_UNEXPECTED) +} diff --git a/xpcom/rust/moz_task/src/executor.rs b/xpcom/rust/moz_task/src/executor.rs new file mode 100644 index 0000000000..0016839373 --- /dev/null +++ b/xpcom/rust/moz_task/src/executor.rs @@ -0,0 +1,291 @@ +/* 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/. */ + +use crate::{get_current_thread, DispatchOptions, RunnableBuilder}; +use std::{ + cell::Cell, + fmt::Debug, + future::Future, + pin::Pin, + ptr, + sync::Arc, + task::{Context, Poll}, +}; +use xpcom::interfaces::{nsIEventTarget, nsIRunnablePriority}; +use xpcom::RefPtr; + +/// A spawned task. +/// +/// A [`AsyncTask`] can be awaited to retrieve the output of its future. +/// +/// Dropping an [`AsyncTask`] cancels it, which means its future won't be polled +/// again. To drop the [`AsyncTask`] handle without canceling it, use +/// [`detach()`][`AsyncTask::detach()`] instead. To cancel a task gracefully and +/// wait until it is fully destroyed, use the [`cancel()`][AsyncTask::cancel()] +/// method. +/// +/// A task which is cancelled due to the nsIEventTarget it was dispatched to no +/// longer accepting events will never be resolved. +#[derive(Debug)] +#[must_use = "tasks get canceled when dropped, use `.detach()` to run them in the background"] +pub struct AsyncTask { + task: async_task::FallibleTask, +} + +impl AsyncTask { + fn new(task: async_task::Task) -> Self { + AsyncTask { + task: task.fallible(), + } + } + + /// Detaches the task to let it keep running in the background. + pub fn detach(self) { + self.task.detach() + } + + /// Cancels the task and waits for it to stop running. + /// + /// Returns the task's output if it was completed just before it got canceled, or [`None`] if + /// it didn't complete. + /// + /// While it's possible to simply drop the [`Task`] to cancel it, this is a cleaner way of + /// canceling because it also waits for the task to stop running. + pub async fn cancel(self) -> Option { + self.task.cancel().await + } +} + +impl Future for AsyncTask { + type Output = T; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + // Wrap the future produced by `AsyncTask` to never resolve if the + // Runnable was dropped, and the task was cancelled. + match Pin::new(&mut self.task).poll(cx) { + Poll::Ready(Some(t)) => Poll::Ready(t), + Poll::Ready(None) | Poll::Pending => Poll::Pending, + } + } +} + +enum SpawnTarget { + BackgroundTask, + EventTarget(RefPtr), +} + +// SAFETY: All XPCOM interfaces are considered !Send + !Sync, however all +// well-behaved nsIEventTarget instances must be threadsafe. +unsafe impl Send for SpawnTarget {} +unsafe impl Sync for SpawnTarget {} + +/// Information used by tasks as they are spawned. Stored in an Arc such that +/// their identity can be used for `POLLING_TASK`. +struct TaskSpawnConfig { + name: &'static str, + priority: u32, + options: DispatchOptions, + target: SpawnTarget, +} + +thread_local! { + /// Raw pointer to the TaskSpawnConfig for the currently polling task. Used + /// to detect scheduling callbacks for a runnable while it is polled, to set + /// `DISPATCH_AT_END` on the notification. + static POLLING_TASK: Cell<*const TaskSpawnConfig> = Cell::new(ptr::null()); +} + +fn schedule(config: Arc, runnable: async_task::Runnable) { + // If we're dispatching this task while it is currently running on the same + // thread, set the `DISPATCH_AT_END` flag in the dispatch options to tell + // our threadpool target to not bother to spin up another thread. + let currently_polling = POLLING_TASK.with(|t| t.get() == Arc::as_ptr(&config)); + + // SAFETY: We use the POLLING_TASK thread local to check if we meet the + // requirements for `at_end`. + let options = unsafe { config.options.at_end(currently_polling) }; + + // Build the RunnableBuilder for our task to be dispatched. + let config2 = config.clone(); + let builder = RunnableBuilder::new(config.name, move || { + // Record the pointer for the currently executing task in the + // POLLING_TASK thread-local so that nested dispatches can detect it. + POLLING_TASK.with(|t| { + let prev = t.get(); + t.set(Arc::as_ptr(&config2)); + runnable.run(); + t.set(prev); + }); + }) + .priority(config.priority) + .options(options); + + let rv = match &config.target { + SpawnTarget::BackgroundTask => builder.dispatch_background_task(), + SpawnTarget::EventTarget(target) => builder.dispatch(&*target), + }; + if let Err(err) = rv { + log::warn!( + "dispatch for spawned task '{}' failed: {:?}", + config.name, + err + ); + } +} + +/// Helper for starting an async task which will run a future to completion. +#[derive(Debug)] +pub struct TaskBuilder { + name: &'static str, + future: F, + priority: u32, + options: DispatchOptions, +} + +impl TaskBuilder { + pub fn new(name: &'static str, future: F) -> TaskBuilder { + TaskBuilder { + name, + future, + priority: nsIRunnablePriority::PRIORITY_NORMAL, + options: DispatchOptions::default(), + } + } + + /// Specify the priority of the task's runnables. + pub fn priority(mut self, priority: u32) -> Self { + self.priority = priority; + self + } + + /// Specify options to use when dispatching the task. + pub fn options(mut self, options: DispatchOptions) -> Self { + self.options = options; + self + } + + /// Set whether or not the event may block, and should be run on the IO + /// thread pool. + pub fn may_block(mut self, may_block: bool) -> Self { + self.options = self.options.may_block(may_block); + self + } +} + +impl TaskBuilder +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + /// Run the future on the background task pool. + pub fn spawn(self) -> AsyncTask { + let config = Arc::new(TaskSpawnConfig { + name: self.name, + priority: self.priority, + options: self.options, + target: SpawnTarget::BackgroundTask, + }); + let (runnable, task) = async_task::spawn(self.future, move |runnable| { + schedule(config.clone(), runnable) + }); + runnable.schedule(); + AsyncTask::new(task) + } + + /// Run the future on the specified nsIEventTarget. + pub fn spawn_onto(self, target: &nsIEventTarget) -> AsyncTask { + let config = Arc::new(TaskSpawnConfig { + name: self.name, + priority: self.priority, + options: self.options, + target: SpawnTarget::EventTarget(RefPtr::new(target)), + }); + let (runnable, task) = async_task::spawn(self.future, move |runnable| { + schedule(config.clone(), runnable) + }); + runnable.schedule(); + AsyncTask::new(task) + } +} + +impl TaskBuilder +where + F: Future + 'static, + F::Output: 'static, +{ + /// Run the future on the current thread. + /// + /// Unlike the other `spawn` methods, this method supports non-Send futures. + /// + /// # Panics + /// + /// This method may panic if run on a thread which cannot run local futures + /// (e.g. due to it is not being an XPCOM thread, or if we are very late + /// during shutdown). + pub fn spawn_local(self) -> AsyncTask { + let current_thread = get_current_thread().expect("cannot get current thread"); + let config = Arc::new(TaskSpawnConfig { + name: self.name, + priority: self.priority, + options: self.options, + target: SpawnTarget::EventTarget(RefPtr::new(current_thread.coerce())), + }); + let (runnable, task) = async_task::spawn_local(self.future, move |runnable| { + schedule(config.clone(), runnable) + }); + runnable.schedule(); + AsyncTask::new(task) + } +} + +/// Spawn a future onto the background task pool. The future will not be run on +/// the main thread. +pub fn spawn(name: &'static str, future: F) -> AsyncTask +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + TaskBuilder::new(name, future).spawn() +} + +/// Spawn a potentially-blocking future onto the background task pool. The +/// future will not be run on the main thread. +pub fn spawn_blocking(name: &'static str, future: F) -> AsyncTask +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + TaskBuilder::new(name, future).may_block(true).spawn() +} + +/// Spawn a local future onto the current thread. +pub fn spawn_local(name: &'static str, future: F) -> AsyncTask +where + F: Future + 'static, + F::Output: 'static, +{ + TaskBuilder::new(name, future).spawn_local() +} + +pub fn spawn_onto(name: &'static str, target: &nsIEventTarget, future: F) -> AsyncTask +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + TaskBuilder::new(name, future).spawn_onto(target) +} + +pub fn spawn_onto_blocking( + name: &'static str, + target: &nsIEventTarget, + future: F, +) -> AsyncTask +where + F: Future + Send + 'static, + F::Output: Send + 'static, +{ + TaskBuilder::new(name, future) + .may_block(true) + .spawn_onto(target) +} diff --git a/xpcom/rust/moz_task/src/lib.rs b/xpcom/rust/moz_task/src/lib.rs new file mode 100644 index 0000000000..2f0c0cfd0a --- /dev/null +++ b/xpcom/rust/moz_task/src/lib.rs @@ -0,0 +1,378 @@ +/* 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/. */ + +//! This module wraps XPCOM threading functions with Rust functions +//! to make it safer and more convenient to call the XPCOM functions. +//! It also provides the Task trait and TaskRunnable struct, +//! which make it easier to dispatch tasks to threads. + +mod dispatcher; +pub use dispatcher::{dispatch_background_task, dispatch_local, dispatch_onto, RunnableBuilder}; +mod event_loop; +mod executor; +pub use executor::{ + spawn, spawn_blocking, spawn_local, spawn_onto, spawn_onto_blocking, AsyncTask, TaskBuilder, +}; + +// Expose functions intended to be used only in gtest via this module. +// We don't use a feature gate here to stop the need to compile all crates that +// depend upon `moz_task` twice. +pub mod gtest_only { + pub use crate::event_loop::spin_event_loop_until; +} + +use nserror::nsresult; +use nsstring::{nsACString, nsCString}; +use std::{ffi::CStr, marker::PhantomData, mem, ptr}; +use xpcom::{ + getter_addrefs, + interfaces::{nsIEventTarget, nsIRunnable, nsISerialEventTarget, nsISupports, nsIThread}, + AtomicRefcnt, RefCounted, RefPtr, XpCom, +}; + +extern "C" { + fn NS_GetCurrentThreadRust(result: *mut *const nsIThread) -> nsresult; + fn NS_GetMainThreadRust(result: *mut *const nsIThread) -> nsresult; + fn NS_IsMainThread() -> bool; + fn NS_NewNamedThreadWithDefaultStackSize( + name: *const nsACString, + result: *mut *const nsIThread, + event: *const nsIRunnable, + ) -> nsresult; + fn NS_IsOnCurrentThread(target: *const nsIEventTarget) -> bool; + fn NS_ProxyReleaseISupports( + name: *const libc::c_char, + target: *const nsIEventTarget, + doomed: *const nsISupports, + always_proxy: bool, + ); + fn NS_CreateBackgroundTaskQueue( + name: *const libc::c_char, + target: *mut *const nsISerialEventTarget, + ) -> nsresult; + fn NS_DispatchBackgroundTask(event: *const nsIRunnable, flags: u32) -> nsresult; +} + +pub fn get_current_thread() -> Result, nsresult> { + getter_addrefs(|p| unsafe { NS_GetCurrentThreadRust(p) }) +} + +pub fn get_main_thread() -> Result, nsresult> { + getter_addrefs(|p| unsafe { NS_GetMainThreadRust(p) }) +} + +pub fn is_main_thread() -> bool { + unsafe { NS_IsMainThread() } +} + +// There's no OS requirement that thread names be static, but dynamic thread +// names tend to conceal more than they reveal when processing large numbers of +// crash reports. +pub fn create_thread(name: &'static str) -> Result, nsresult> { + getter_addrefs(|p| unsafe { + NS_NewNamedThreadWithDefaultStackSize(&*nsCString::from(name), p, ptr::null()) + }) +} + +pub fn is_on_current_thread(target: &nsIEventTarget) -> bool { + unsafe { NS_IsOnCurrentThread(target) } +} + +/// Creates a queue that runs tasks on the background thread pool. The tasks +/// will run in the order they're dispatched, one after the other. +pub fn create_background_task_queue( + name: &'static CStr, +) -> Result, nsresult> { + getter_addrefs(|p| unsafe { NS_CreateBackgroundTaskQueue(name.as_ptr(), p) }) +} + +/// Dispatches a one-shot runnable to an event target, like a thread or a +/// task queue, with the given options. +/// +/// This function leaks the runnable if dispatch fails. +/// +/// # Safety +/// +/// As there is no guarantee that the runnable is actually `Send + Sync`, we +/// can't know that it's safe to dispatch an `nsIRunnable` to any +/// `nsIEventTarget`. +pub unsafe fn dispatch_runnable( + runnable: &nsIRunnable, + target: &nsIEventTarget, + options: DispatchOptions, +) -> Result<(), nsresult> { + // NOTE: DispatchFromScript performs an AddRef on `runnable` which is + // why this function leaks on failure. + target + .DispatchFromScript(runnable, options.flags()) + .to_result() +} + +/// Dispatches a one-shot task runnable to the background thread pool with the +/// given options. The task may run concurrently with other background tasks. +/// If you need tasks to run in a specific order, please create a background +/// task queue using `create_background_task_queue`, and dispatch tasks to it +/// instead. +/// +/// This function leaks the runnable if dispatch fails. This avoids a race where +/// a runnable can be destroyed on either the original or target thread, which +/// is important if the runnable holds thread-unsafe members. +/// +/// ### Safety +/// +/// As there is no guarantee that the runnable is actually `Send + Sync`, we +/// can't know that it's safe to dispatch an `nsIRunnable` to any +/// `nsIEventTarget`. +pub unsafe fn dispatch_background_task_runnable( + runnable: &nsIRunnable, + options: DispatchOptions, +) -> Result<(), nsresult> { + // This eventually calls the non-`already_AddRefed` overload of + // `nsIEventTarget::Dispatch` (see xpcom/threads/nsIEventTarget.idl#20-25), + // which adds an owning reference and leaks if dispatch fails. + NS_DispatchBackgroundTask(runnable, options.flags()).to_result() +} + +/// Options to control how task runnables are dispatched. +#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)] +pub struct DispatchOptions(u32); + +impl Default for DispatchOptions { + #[inline] + fn default() -> Self { + DispatchOptions(nsIEventTarget::DISPATCH_NORMAL) + } +} + +impl DispatchOptions { + /// Creates a blank set of options. The runnable will be dispatched using + /// the default mode. + #[inline] + pub fn new() -> Self { + DispatchOptions::default() + } + + /// Indicates whether or not the dispatched runnable may block its target + /// thread by waiting on I/O. If `true`, the runnable may be dispatched to a + /// dedicated thread pool, leaving the main pool free for CPU-bound tasks. + #[inline] + pub fn may_block(self, may_block: bool) -> DispatchOptions { + const FLAG: u32 = nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK; + if may_block { + DispatchOptions(self.flags() | FLAG) + } else { + DispatchOptions(self.flags() & !FLAG) + } + } + + /// Specifies that the dispatch is occurring from a running event that was + /// dispatched to the same event target, and that event is about to finish. + /// + /// A thread pool can use this as an optimization hint to not spin up + /// another thread, since the current thread is about to become idle. + /// + /// Setting this flag is unsafe, as it may only be used from the target + /// event target when the event is about to finish. + #[inline] + pub unsafe fn at_end(self, may_block: bool) -> DispatchOptions { + const FLAG: u32 = nsIEventTarget::DISPATCH_AT_END; + if may_block { + DispatchOptions(self.flags() | FLAG) + } else { + DispatchOptions(self.flags() & !FLAG) + } + } + + /// Returns the set of bitflags to pass to `DispatchFromScript`. + #[inline] + fn flags(self) -> u32 { + self.0 + } +} + +/// A task represents an operation that asynchronously executes on a target +/// thread, and returns its result to the original thread. +/// +/// # Alternatives +/// +/// This trait is no longer necessary for basic tasks to be dispatched to +/// another thread with a callback on the originating thread. `moz_task` now has +/// a series of more rust-like primitives which can be used instead. For +/// example, it may be preferable to use the async executor over `Task`: +/// +/// ```ignore +/// // Spawn a task onto the background task pool, and capture the result of its +/// // execution. +/// let bg_task = moz_task::spawn("Example", async move { +/// do_background_work(captured_state) +/// }); +/// +/// // Spawn another task on the calling thread which will await on the result +/// // of the async operation, and invoke a non-Send callback. This task won't +/// // be awaited on, so needs to be `detach`-ed. +/// moz_task::spawn_local("Example", async move { +/// callback.completed(bg_task.await); +/// }) +/// .detach(); +/// ``` +/// +/// If no result is needed, the task returned from `spawn` may be also detached +/// directly. +pub trait Task { + // FIXME: These could accept `&mut`. + fn run(&self); + fn done(&self) -> Result<(), nsresult>; +} + +pub struct TaskRunnable { + name: &'static str, + task: Box, +} + +impl TaskRunnable { + // XXX: Fixme: clean up this old API. (bug 1744312) + pub fn new( + name: &'static str, + task: Box, + ) -> Result { + Ok(TaskRunnable { name, task }) + } + + pub fn dispatch(self, target: &nsIEventTarget) -> Result<(), nsresult> { + self.dispatch_with_options(target, DispatchOptions::default()) + } + + pub fn dispatch_with_options( + self, + target: &nsIEventTarget, + options: DispatchOptions, + ) -> Result<(), nsresult> { + // Perform `task.run()` on a background thread. + let task = self.task; + let handle = TaskBuilder::new(self.name, async move { + task.run(); + task + }) + .options(options) + .spawn_onto(target); + + // Run `task.done()` on the starting thread once the background thread + // is done with the task. + spawn_local(self.name, async move { + let task = handle.await; + let _ = task.done(); + }) + .detach(); + Ok(()) + } + + pub fn dispatch_background_task_with_options( + self, + options: DispatchOptions, + ) -> Result<(), nsresult> { + // Perform `task.run()` on a background thread. + let task = self.task; + let handle = TaskBuilder::new(self.name, async move { + task.run(); + task + }) + .options(options) + .spawn(); + + // Run `task.done()` on the starting thread once the background thread + // is done with the task. + spawn_local(self.name, async move { + let task = handle.await; + let _ = task.done(); + }) + .detach(); + Ok(()) + } +} + +pub type ThreadPtrHandle = RefPtr>; + +/// A Rust analog to `nsMainThreadPtrHolder` that wraps an `nsISupports` object +/// with thread-safe refcounting. The holder keeps one reference to the wrapped +/// object that's released when the holder's refcount reaches zero. +pub struct ThreadPtrHolder { + ptr: *const T, + marker: PhantomData, + name: &'static CStr, + owning_thread: RefPtr, + refcnt: AtomicRefcnt, +} + +unsafe impl Send for ThreadPtrHolder {} +unsafe impl Sync for ThreadPtrHolder {} + +unsafe impl RefCounted for ThreadPtrHolder { + unsafe fn addref(&self) { + self.refcnt.inc(); + } + + unsafe fn release(&self) { + let rc = self.refcnt.dec(); + if rc == 0 { + // Once the holder's count reaches zero, release the wrapped + // object... + if !self.ptr.is_null() { + // The holder can be released on any thread. If we're on the + // owning thread, we can release the object directly. Otherwise, + // we need to post a proxy release event to release the object + // on the owning thread. + if is_on_current_thread(&self.owning_thread) { + (*self.ptr).release() + } else { + NS_ProxyReleaseISupports( + self.name.as_ptr(), + self.owning_thread.coerce(), + self.ptr as *const T as *const nsISupports, + false, + ); + } + } + // ...And deallocate the holder. + mem::drop(Box::from_raw(self as *const Self as *mut Self)); + } + } +} + +impl ThreadPtrHolder { + /// Creates a new owning thread pointer holder. Returns an error if the + /// thread manager has shut down. Panics if `name` isn't a valid C string. + pub fn new(name: &'static CStr, ptr: RefPtr) -> Result, nsresult> { + let owning_thread = get_current_thread()?; + // Take ownership of the `RefPtr`. This does _not_ decrement its + // refcount, which is what we want. Once we've released all references + // to the holder, we'll release the wrapped `RefPtr`. + let raw: *const T = &*ptr; + mem::forget(ptr); + unsafe { + let boxed = Box::new(ThreadPtrHolder { + name, + ptr: raw, + marker: PhantomData, + owning_thread, + refcnt: AtomicRefcnt::new(), + }); + Ok(RefPtr::from_raw(Box::into_raw(boxed)).unwrap()) + } + } + + /// Returns the wrapped object's owning thread. + pub fn owning_thread(&self) -> &nsIThread { + &self.owning_thread + } + + /// Returns the wrapped object if called from the owning thread, or + /// `None` if called from any other thread. + pub fn get(&self) -> Option<&T> { + if is_on_current_thread(&self.owning_thread) && !self.ptr.is_null() { + unsafe { Some(&*self.ptr) } + } else { + None + } + } +} diff --git a/xpcom/rust/nserror/Cargo.toml b/xpcom/rust/nserror/Cargo.toml new file mode 100644 index 0000000000..4fca5a4c2c --- /dev/null +++ b/xpcom/rust/nserror/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "nserror" +version = "0.1.0" +authors = ["Nika Layzell "] +license = "MPL-2.0" +description = "Rust bindings to xpcom nsresult and NS_ERROR_ values" +edition = "2018" + +[dependencies] +nsstring = { path = "../nsstring" } +mozbuild = "0.1" diff --git a/xpcom/rust/nserror/src/lib.rs b/xpcom/rust/nserror/src/lib.rs new file mode 100644 index 0000000000..e4107d1b57 --- /dev/null +++ b/xpcom/rust/nserror/src/lib.rs @@ -0,0 +1,79 @@ +/* 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/. */ + +use nsstring::{nsACString, nsCString}; +use std::error::Error; +use std::fmt; + +/// The type of errors in gecko. Uses a newtype to provide additional type +/// safety in Rust and #[repr(transparent)] to ensure the same representation +/// as the C++ equivalent. +#[repr(transparent)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub struct nsresult(pub u32); + +impl nsresult { + pub fn failed(self) -> bool { + (self.0 >> 31) != 0 + } + + pub fn succeeded(self) -> bool { + !self.failed() + } + + pub fn to_result(self) -> Result<(), nsresult> { + if self.failed() { + Err(self) + } else { + Ok(()) + } + } + + /// Get a printable name for the nsresult error code. This function returns + /// a nsCString<'static>, which implements `Display`. + pub fn error_name(self) -> nsCString { + let mut cstr = nsCString::new(); + unsafe { + Gecko_GetErrorName(self, &mut *cstr); + } + cstr + } +} + +impl fmt::Display for nsresult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.error_name()) + } +} + +impl fmt::Debug for nsresult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.error_name()) + } +} + +impl From> for nsresult +where + E: Into, +{ + fn from(result: Result) -> nsresult { + match result { + Ok(_) => NS_OK, + Err(e) => e.into(), + } + } +} + +impl Error for nsresult {} + +extern "C" { + fn Gecko_GetErrorName(rv: nsresult, cstr: *mut nsACString); +} + +mod error_list { + include!(mozbuild::objdir_path!("xpcom/base/error_list.rs")); +} + +pub use error_list::*; diff --git a/xpcom/rust/nsstring/Cargo.toml b/xpcom/rust/nsstring/Cargo.toml new file mode 100644 index 0000000000..c2f8e34b45 --- /dev/null +++ b/xpcom/rust/nsstring/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "nsstring" +version = "0.1.0" +authors = ["nobody@mozilla.com"] +license = "MPL-2.0" +description = "Rust bindings to xpcom string types" +edition = "2018" + +[features] +gecko_debug = [] + +[dependencies] +bitflags = "1.0" +encoding_rs = "0.8.0" diff --git a/xpcom/rust/nsstring/src/conversions.rs b/xpcom/rust/nsstring/src/conversions.rs new file mode 100644 index 0000000000..c72c195c08 --- /dev/null +++ b/xpcom/rust/nsstring/src/conversions.rs @@ -0,0 +1,751 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use crate::{ + nsACString, nsAString, nsCStringLike, BulkWriteOk, Gecko_FallibleAssignCString, + Latin1StringLike, +}; +use encoding_rs::mem::*; +use encoding_rs::Encoding; +use std::slice; + +/// Required math stated in the docs of +/// `convert_utf16_to_utf8()`. +#[inline(always)] +fn times_three(a: usize) -> Option { + a.checked_mul(3) +} + +#[inline(always)] +fn identity(a: usize) -> Option { + Some(a) +} + +#[inline(always)] +fn plus_one(a: usize) -> Option { + a.checked_add(1) +} + +/// Typical cache line size per +/// https://stackoverflow.com/questions/14707803/line-size-of-l1-and-l2-caches +/// +/// For consistent behavior, not trying to use 128 on aarch64 +/// or other fanciness like that. +const CACHE_LINE: usize = 64; + +const CACHE_LINE_MASK: usize = CACHE_LINE - 1; + +/// Returns true if the string is both longer than a cache line +/// and the first cache line is ASCII. +#[inline(always)] +fn long_string_starts_with_ascii(buffer: &[u8]) -> bool { + // We examine data only up to the end of the cache line + // to make this check minimally disruptive. + if buffer.len() <= CACHE_LINE { + return false; + } + let bound = CACHE_LINE - ((buffer.as_ptr() as usize) & CACHE_LINE_MASK); + is_ascii(&buffer[..bound]) +} + +/// Returns true if the string is both longer than two cache lines +/// and the first two cache lines are Basic Latin. +#[inline(always)] +fn long_string_stars_with_basic_latin(buffer: &[u16]) -> bool { + // We look at two cache lines with code unit size of two. There is need + // to look at more than one cache line in the UTF-16 case, because looking + // at just one cache line wouldn't catch non-ASCII Latin with high enough + // probability with Latin-script languages that have relatively infrequent + // non-ASCII characters. + if buffer.len() <= CACHE_LINE { + return false; + } + let bound = (CACHE_LINE * 2 - ((buffer.as_ptr() as usize) & CACHE_LINE_MASK)) / 2; + is_basic_latin(&buffer[..bound]) +} + +// Ignoring the copy avoidance complications of conversions between Latin1 and +// UTF-8, a conversion function has the outward form of +// `fn F(&mut self, other: &[T], old_len: usize) -> Result`, +// where `T` is either `u8` or `u16`. `other` is the slice whose converted +// content are to be appended to `self` and `old_len` indicates how many +// code unit of `self` are to be preserved (0 for the assignment case and +// `self.len()` for the appending case). +// +// As implementation parameters a conversion function needs to know the +// math for computing the worst case conversion length in code units given +// the input length in code units. For a _constant conversion_ the number +// of code units the conversion produces equals the number of code units +// in the input. For a _shinking conversion_ the maximum number of code +// units the conversion can produce equals the number of code units in +// the input, but the conversion can produce fewer code units. Still, due +// to implementation details, the function might want _one_ unit more of +// output space. For an _expanding conversion_ (no need for macro), the +// minimum number of code units produced by the conversion is the number +// of code units in the input, but the conversion can produce more. +// +// Copy avoidance conversions avoid copying a refcounted buffer when it's +// ASCII-only. +// +// Internally, a conversion function needs to know the underlying +// encoding_rs conversion function, the math for computing the required +// output buffer size and, depending on the case, the underlying +// encoding_rs ASCII prefix handling function. + +/// A conversion where the number of code units in the output is potentially +/// smaller than the number of code units in the input. +/// +/// Takes the name of the method to be generated, the name of the conversion +/// function and the type of the input slice. +/// +/// `$name` is the name of the function to generate +/// `$convert` is the underlying `encoding_rs::mem` function to use +/// `$other_ty` is the type of the input slice +/// `$math` is the worst-case length math that `$convert` expects +macro_rules! shrinking_conversion { + (name = $name:ident, + convert = $convert:ident, + other_ty = $other_ty:ty, + math = $math:ident) => { + fn $name(&mut self, other: $other_ty, old_len: usize) -> Result { + let needed = $math(other.len()).ok_or(())?; + let mut handle = + unsafe { self.bulk_write(old_len.checked_add(needed).ok_or(())?, old_len, false)? }; + let written = $convert(other, &mut handle.as_mut_slice()[old_len..]); + let new_len = old_len + written; + Ok(handle.finish(new_len, new_len > CACHE_LINE)) + } + }; +} + +/// A conversion where the number of code units in the output is always equal +/// to the number of code units in the input. +/// +/// Takes the name of the method to be generated, the name of the conversion +/// function and the type of the input slice. +/// +/// `$name` is the name of the function to generate +/// `$convert` is the underlying `encoding_rs::mem` function to use +/// `$other_ty` is the type of the input slice +macro_rules! constant_conversion { + (name = $name:ident, + convert = $convert:ident, + other_ty = $other_ty:ty) => { + fn $name( + &mut self, + other: $other_ty, + old_len: usize, + allow_shrinking: bool, + ) -> Result { + let new_len = old_len.checked_add(other.len()).ok_or(())?; + let mut handle = unsafe { self.bulk_write(new_len, old_len, allow_shrinking)? }; + $convert(other, &mut handle.as_mut_slice()[old_len..]); + Ok(handle.finish(new_len, false)) + } + }; +} + +/// An intermediate check for avoiding a copy and having an `nsStringBuffer` +/// refcount increment instead when both `self` and `other` are `nsACString`s, +/// `other` is entirely ASCII and all old data in `self` is discarded. +/// +/// `$name` is the name of the function to generate +/// `$impl` is the underlying conversion that takes a slice and that is used +/// when we can't just adopt the incoming buffer as-is +/// `$string_like` is the kind of input taken +macro_rules! ascii_copy_avoidance { + (name = $name:ident, + implementation = $implementation:ident, + string_like = $string_like:ident) => { + fn $name( + &mut self, + other: &T, + old_len: usize, + ) -> Result { + let adapter = other.adapt(); + let other_slice = adapter.as_ref(); + let num_ascii = if adapter.is_abstract() && old_len == 0 { + let up_to = Encoding::ascii_valid_up_to(other_slice); + if up_to == other_slice.len() { + // Calling something whose argument can be obtained from + // the adapter rather than an nsStringLike avoids a huge + // lifetime mess by keeping nsStringLike and + // Latin1StringLike free of lifetime interdependencies. + if unsafe { Gecko_FallibleAssignCString(self, other.adapt().as_ptr()) } { + return Ok(BulkWriteOk {}); + } else { + return Err(()); + } + } + Some(up_to) + } else { + None + }; + self.$implementation(other_slice, old_len, num_ascii) + } + }; +} + +impl nsAString { + // Valid UTF-8 to UTF-16 + + // Documentation says the destination buffer needs to have + // as many code units as the input. + shrinking_conversion!( + name = fallible_append_str_impl, + convert = convert_str_to_utf16, + other_ty = &str, + math = identity + ); + + /// Convert a valid UTF-8 string into valid UTF-16 and replace the content + /// of this string with the conversion result. + pub fn assign_str(&mut self, other: &str) { + self.fallible_append_str_impl(other, 0) + .expect("Out of memory"); + } + + /// Convert a valid UTF-8 string into valid UTF-16 and fallibly replace the + /// content of this string with the conversion result. + pub fn fallible_assign_str(&mut self, other: &str) -> Result<(), ()> { + self.fallible_append_str_impl(other, 0).map(|_| ()) + } + + /// Convert a valid UTF-8 string into valid UTF-16 and append the conversion + /// to this string. + pub fn append_str(&mut self, other: &str) { + let len = self.len(); + self.fallible_append_str_impl(other, len) + .expect("Out of memory"); + } + + /// Convert a valid UTF-8 string into valid UTF-16 and fallibly append the + /// conversion to this string. + pub fn fallible_append_str(&mut self, other: &str) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_str_impl(other, len).map(|_| ()) + } + + // Potentially-invalid UTF-8 to UTF-16 + + // Documentation says the destination buffer needs to have + // one more code unit than the input. + shrinking_conversion!( + name = fallible_append_utf8_impl, + convert = convert_utf8_to_utf16, + other_ty = &[u8], + math = plus_one + ); + + /// Convert a potentially-invalid UTF-8 string into valid UTF-16 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// replace the content of this string with the conversion result. + pub fn assign_utf8(&mut self, other: &[u8]) { + self.fallible_append_utf8_impl(other, 0) + .expect("Out of memory"); + } + + /// Convert a potentially-invalid UTF-8 string into valid UTF-16 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// fallibly replace the content of this string with the conversion result. + pub fn fallible_assign_utf8(&mut self, other: &[u8]) -> Result<(), ()> { + self.fallible_append_utf8_impl(other, 0).map(|_| ()) + } + + /// Convert a potentially-invalid UTF-8 string into valid UTF-16 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// append the conversion result to this string. + pub fn append_utf8(&mut self, other: &[u8]) { + let len = self.len(); + self.fallible_append_utf8_impl(other, len) + .expect("Out of memory"); + } + + /// Convert a potentially-invalid UTF-8 string into valid UTF-16 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// fallibly append the conversion result to this string. + pub fn fallible_append_utf8(&mut self, other: &[u8]) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_utf8_impl(other, len).map(|_| ()) + } + + // Latin1 to UTF-16 + + constant_conversion!( + name = fallible_append_latin1_impl, + convert = convert_latin1_to_utf16, + other_ty = &[u8] + ); + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-16 and replace the content of this string with the conversion result. + pub fn assign_latin1(&mut self, other: &[u8]) { + self.fallible_append_latin1_impl(other, 0, true) + .expect("Out of memory"); + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-16 and fallibly replace the content of this string with the + /// conversion result. + pub fn fallible_assign_latin1(&mut self, other: &[u8]) -> Result<(), ()> { + self.fallible_append_latin1_impl(other, 0, true).map(|_| ()) + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-16 and append the conversion result to this string. + pub fn append_latin1(&mut self, other: &[u8]) { + let len = self.len(); + self.fallible_append_latin1_impl(other, len, false) + .expect("Out of memory"); + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-16 and fallibly append the conversion result to this string. + pub fn fallible_append_latin1(&mut self, other: &[u8]) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_latin1_impl(other, len, false) + .map(|_| ()) + } +} + +impl nsACString { + // UTF-16 to UTF-8 + + fn fallible_append_utf16_to_utf8_impl( + &mut self, + other: &[u16], + old_len: usize, + ) -> Result { + // We first size the buffer for ASCII if the first two cache lines are ASCII. If that turns out + // not to be enough, we size for the worst case given the length of the remaining input at that + // point. BUT if the worst case fits inside the inline capacity of an autostring, we skip + // the ASCII stuff. + let worst_case_needed = if let Some(inline_capacity) = self.inline_capacity() { + let worst_case = times_three(other.len()).ok_or(())?; + if worst_case <= inline_capacity { + Some(worst_case) + } else { + None + } + } else { + None + }; + let (filled, read, mut handle) = + if worst_case_needed.is_none() && long_string_stars_with_basic_latin(other) { + let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?; + let mut handle = unsafe { self.bulk_write(new_len_with_ascii, old_len, false)? }; + let (read, written) = + convert_utf16_to_utf8_partial(other, &mut handle.as_mut_slice()[old_len..]); + let left = other.len() - read; + if left == 0 { + return Ok(handle.finish(old_len + written, true)); + } + let filled = old_len + written; + let needed = times_three(left).ok_or(())?; + let new_len = filled.checked_add(needed).ok_or(())?; + unsafe { + handle.restart_bulk_write(new_len, filled, false)?; + } + (filled, read, handle) + } else { + // Started with non-ASCII. Compute worst case + let needed = if let Some(n) = worst_case_needed { + n + } else { + times_three(other.len()).ok_or(())? + }; + let new_len = old_len.checked_add(needed).ok_or(())?; + let handle = unsafe { self.bulk_write(new_len, old_len, false)? }; + (old_len, 0, handle) + }; + let written = convert_utf16_to_utf8(&other[read..], &mut handle.as_mut_slice()[filled..]); + Ok(handle.finish(filled + written, true)) + } + + /// Convert a potentially-invalid UTF-16 string into valid UTF-8 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// replace the content of this string with the conversion result. + pub fn assign_utf16_to_utf8(&mut self, other: &[u16]) { + self.fallible_append_utf16_to_utf8_impl(other, 0) + .expect("Out of memory"); + } + + /// Convert a potentially-invalid UTF-16 string into valid UTF-8 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// fallibly replace the content of this string with the conversion result. + pub fn fallible_assign_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> { + self.fallible_append_utf16_to_utf8_impl(other, 0) + .map(|_| ()) + } + + /// Convert a potentially-invalid UTF-16 string into valid UTF-8 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// append the conversion result to this string. + pub fn append_utf16_to_utf8(&mut self, other: &[u16]) { + let len = self.len(); + self.fallible_append_utf16_to_utf8_impl(other, len) + .expect("Out of memory"); + } + + /// Convert a potentially-invalid UTF-16 string into valid UTF-8 + /// (replacing invalid sequences with the REPLACEMENT CHARACTER) and + /// fallibly append the conversion result to this string. + pub fn fallible_append_utf16_to_utf8(&mut self, other: &[u16]) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_utf16_to_utf8_impl(other, len) + .map(|_| ()) + } + + // UTF-16 to Latin1 + + constant_conversion!( + name = fallible_append_utf16_to_latin1_lossy_impl, + convert = convert_utf16_to_latin1_lossy, + other_ty = &[u16] + ); + + /// Convert a UTF-16 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// replace the content of this string with the conversion result. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-16, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn assign_utf16_to_latin1_lossy(&mut self, other: &[u16]) { + self.fallible_append_utf16_to_latin1_lossy_impl(other, 0, true) + .expect("Out of memory"); + } + + /// Convert a UTF-16 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// fallibly replace the content of this string with the conversion result. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-16, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn fallible_assign_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> { + self.fallible_append_utf16_to_latin1_lossy_impl(other, 0, true) + .map(|_| ()) + } + + /// Convert a UTF-16 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// append the conversion result to this string. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-16, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn append_utf16_to_latin1_lossy(&mut self, other: &[u16]) { + let len = self.len(); + self.fallible_append_utf16_to_latin1_lossy_impl(other, len, false) + .expect("Out of memory"); + } + + /// Convert a UTF-16 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// fallibly append the conversion result to this string. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-16, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn fallible_append_utf16_to_latin1_lossy(&mut self, other: &[u16]) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_utf16_to_latin1_lossy_impl(other, len, false) + .map(|_| ()) + } + + // UTF-8 to Latin1 + + ascii_copy_avoidance!( + name = fallible_append_utf8_to_latin1_lossy_check, + implementation = fallible_append_utf8_to_latin1_lossy_impl, + string_like = nsCStringLike + ); + + fn fallible_append_utf8_to_latin1_lossy_impl( + &mut self, + other: &[u8], + old_len: usize, + maybe_num_ascii: Option, + ) -> Result { + let new_len = old_len.checked_add(other.len()).ok_or(())?; + let num_ascii = maybe_num_ascii.unwrap_or(0); + // Already checked for overflow above, so this can't overflow. + let old_len_plus_num_ascii = old_len + num_ascii; + let mut handle = unsafe { self.bulk_write(new_len, old_len, false)? }; + let written = { + let buffer = handle.as_mut_slice(); + if num_ascii != 0 { + (&mut buffer[old_len..old_len_plus_num_ascii]).copy_from_slice(&other[..num_ascii]); + } + convert_utf8_to_latin1_lossy(&other[num_ascii..], &mut buffer[old_len_plus_num_ascii..]) + }; + Ok(handle.finish(old_len_plus_num_ascii + written, true)) + } + + /// Convert a UTF-8 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// replace the content of this string with the conversion result. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-8, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn assign_utf8_to_latin1_lossy(&mut self, other: &T) { + self.fallible_append_utf8_to_latin1_lossy_check(other, 0) + .expect("Out of memory"); + } + + /// Convert a UTF-8 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// fallibly replace the content of this string with the conversion result. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-8, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn fallible_assign_utf8_to_latin1_lossy( + &mut self, + other: &T, + ) -> Result<(), ()> { + self.fallible_append_utf8_to_latin1_lossy_check(other, 0) + .map(|_| ()) + } + + /// Convert a UTF-8 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// append the conversion result to this string. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-8, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn append_utf8_to_latin1_lossy(&mut self, other: &T) { + let len = self.len(); + self.fallible_append_utf8_to_latin1_lossy_check(other, len) + .expect("Out of memory"); + } + + /// Convert a UTF-8 string whose all code points are below U+0100 into + /// a Latin1 (scalar value is byte value; not windows-1252!) string and + /// fallibly append the conversion result to this string. + /// + /// # Panics + /// + /// If the input contains code points above U+00FF or is not valid UTF-8, + /// panics in debug mode and produces garbage in a memory-safe way in + /// release builds. The nature of the garbage may differ based on CPU + /// architecture and must not be relied upon. + pub fn fallible_append_utf8_to_latin1_lossy( + &mut self, + other: &T, + ) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_utf8_to_latin1_lossy_check(other, len) + .map(|_| ()) + } + + // Latin1 to UTF-8 CString + + ascii_copy_avoidance!( + name = fallible_append_latin1_to_utf8_check, + implementation = fallible_append_latin1_to_utf8_impl, + string_like = Latin1StringLike + ); + + fn fallible_append_latin1_to_utf8_impl( + &mut self, + other: &[u8], + old_len: usize, + maybe_num_ascii: Option, + ) -> Result { + let (filled, read, mut handle) = if let Some(num_ascii) = maybe_num_ascii { + // Wrapper checked for ASCII + let left = other.len() - num_ascii; + let filled = old_len + num_ascii; + let needed = left.checked_mul(2).ok_or(())?; + let new_len = filled.checked_add(needed).ok_or(())?; + let mut handle = unsafe { self.bulk_write(new_len, old_len, false)? }; + if num_ascii != 0 { + (&mut handle.as_mut_slice()[old_len..filled]).copy_from_slice(&other[..num_ascii]); + } + (filled, num_ascii, handle) + } else { + let worst_case_needed = if let Some(inline_capacity) = self.inline_capacity() { + let worst_case = other.len().checked_mul(2).ok_or(())?; + if worst_case <= inline_capacity { + Some(worst_case) + } else { + None + } + } else { + None + }; + if worst_case_needed.is_none() && long_string_starts_with_ascii(other) { + // Wrapper didn't check for ASCII, so let's see if `other` starts with ASCII + // `other` starts with ASCII, so let's first size the buffer + // with optimism that it's ASCII-only. + let new_len_with_ascii = old_len.checked_add(other.len()).ok_or(())?; + let mut handle = unsafe { self.bulk_write(new_len_with_ascii, old_len, false)? }; + let (read, written) = + convert_latin1_to_utf8_partial(other, &mut handle.as_mut_slice()[old_len..]); + let left = other.len() - read; + let filled = old_len + written; + if left == 0 { + // `other` fit in the initial allocation + return Ok(handle.finish(filled, true)); + } + let needed = left.checked_mul(2).ok_or(())?; + let new_len = filled.checked_add(needed).ok_or(())?; + unsafe { + handle.restart_bulk_write(new_len, filled, false)?; + } + (filled, read, handle) + } else { + // Started with non-ASCII. Assume worst case. + let needed = if let Some(n) = worst_case_needed { + n + } else { + other.len().checked_mul(2).ok_or(())? + }; + let new_len = old_len.checked_add(needed).ok_or(())?; + let handle = unsafe { self.bulk_write(new_len, old_len, false)? }; + (old_len, 0, handle) + } + }; + let written = convert_latin1_to_utf8(&other[read..], &mut handle.as_mut_slice()[filled..]); + Ok(handle.finish(filled + written, true)) + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-8 and replace the content of this string with the conversion result. + pub fn assign_latin1_to_utf8(&mut self, other: &T) { + self.fallible_append_latin1_to_utf8_check(other, 0) + .expect("Out of memory"); + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-8 and fallibly replace the content of this string with the + /// conversion result. + pub fn fallible_assign_latin1_to_utf8( + &mut self, + other: &T, + ) -> Result<(), ()> { + self.fallible_append_latin1_to_utf8_check(other, 0) + .map(|_| ()) + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-8 and append the conversion result to this string. + pub fn append_latin1_to_utf8(&mut self, other: &T) { + let len = self.len(); + self.fallible_append_latin1_to_utf8_check(other, len) + .expect("Out of memory"); + } + + /// Convert a Latin1 (i.e. byte value equals scalar value; not windows-1252!) + /// into UTF-8 and fallibly append the conversion result to this string. + pub fn fallible_append_latin1_to_utf8( + &mut self, + other: &T, + ) -> Result<(), ()> { + let len = self.len(); + self.fallible_append_latin1_to_utf8_check(other, len) + .map(|_| ()) + } +} + +#[no_mangle] +pub unsafe extern "C" fn nsstring_fallible_append_utf8_impl( + this: *mut nsAString, + other: *const u8, + other_len: usize, + old_len: usize, +) -> bool { + let other_slice = slice::from_raw_parts(other, other_len); + (*this) + .fallible_append_utf8_impl(other_slice, old_len) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nsstring_fallible_append_latin1_impl( + this: *mut nsAString, + other: *const u8, + other_len: usize, + old_len: usize, + allow_shrinking: bool, +) -> bool { + let other_slice = slice::from_raw_parts(other, other_len); + (*this) + .fallible_append_latin1_impl(other_slice, old_len, allow_shrinking) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nscstring_fallible_append_utf16_to_utf8_impl( + this: *mut nsACString, + other: *const u16, + other_len: usize, + old_len: usize, +) -> bool { + let other_slice = slice::from_raw_parts(other, other_len); + (*this) + .fallible_append_utf16_to_utf8_impl(other_slice, old_len) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nscstring_fallible_append_utf16_to_latin1_lossy_impl( + this: *mut nsACString, + other: *const u16, + other_len: usize, + old_len: usize, + allow_shrinking: bool, +) -> bool { + let other_slice = slice::from_raw_parts(other, other_len); + (*this) + .fallible_append_utf16_to_latin1_lossy_impl(other_slice, old_len, allow_shrinking) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nscstring_fallible_append_utf8_to_latin1_lossy_check( + this: *mut nsACString, + other: *const nsACString, + old_len: usize, +) -> bool { + (*this) + .fallible_append_utf8_to_latin1_lossy_check(&*other, old_len) + .is_ok() +} + +#[no_mangle] +pub unsafe extern "C" fn nscstring_fallible_append_latin1_to_utf8_check( + this: *mut nsACString, + other: *const nsACString, + old_len: usize, +) -> bool { + (*this) + .fallible_append_latin1_to_utf8_check(&*other, old_len) + .is_ok() +} diff --git a/xpcom/rust/nsstring/src/lib.rs b/xpcom/rust/nsstring/src/lib.rs new file mode 100644 index 0000000000..521c2c8c04 --- /dev/null +++ b/xpcom/rust/nsstring/src/lib.rs @@ -0,0 +1,1543 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +//! This module provides rust bindings for the XPCOM string types. +//! +//! # TL;DR (what types should I use) +//! +//! Use `&{mut,} nsA[C]String` for functions in rust which wish to take or +//! mutate XPCOM strings. The other string types `Deref` to this type. +//! +//! Use `ns[C]String` (`ns[C]String` in C++) for string struct members, and as +//! an intermediate between rust string data structures (such as `String` or +//! `Vec`) and `&{mut,} nsA[C]String` (using `ns[C]String::from(value)`). +//! These conversions will attempt to re-use the passed-in buffer, appending a +//! null. +//! +//! Use `ns[C]Str` (`nsDependent[C]String` in C++) as an intermediate between +//! borrowed rust data structures (such as `&str` and `&[u16]`) and `&{mut,} +//! nsA[C]String` (using `ns[C]Str::from(value)`). These conversions should not +//! perform any allocations. This type is not safe to share with `C++` as a +//! struct field, but passing the borrowed `&{mut,} nsA[C]String` over FFI is +//! safe. +//! +//! Use `*{const,mut} nsA[C]String` (`{const,} nsA[C]String*` in C++) for +//! function arguments passed across the rust/C++ language boundary. +//! +//! There is currently no Rust equivalent to `nsAuto[C]String`. Implementing a +//! type that contains a pointer to an inline buffer is difficult in Rust due +//! to its move semantics, which require that it be safe to move a value by +//! copying its bits. If such a type is genuinely needed at some point, +//! has a sketch of +//! how to emulate it via macros. +//! +//! # String Types +//! +//! ## `nsA[C]String` +//! +//! The core types in this module are `nsAString` and `nsACString`. These types +//! are zero-sized as far as rust is concerned, and are safe to pass around +//! behind both references (in rust code), and pointers (in C++ code). They +//! represent a handle to a XPCOM string which holds either `u16` or `u8` +//! characters respectively. The backing character buffer is guaranteed to live +//! as long as the reference to the `nsAString` or `nsACString`. +//! +//! These types in rust are simply used as dummy types. References to them +//! represent a pointer to the beginning of a variable-sized `#[repr(C)]` struct +//! which is common between both C++ and Rust implementations. In C++, their +//! corresponding types are also named `nsAString` or `nsACString`, and they are +//! defined within the `nsTSubstring.{cpp,h}` file. +//! +//! ### Valid Operations +//! +//! An `&nsA[C]String` acts like rust's `&str`, in that it is a borrowed +//! reference to the backing data. When used as an argument to other functions +//! on `&mut nsA[C]String`, optimizations can be performed to avoid copying +//! buffers, as information about the backing storage is preserved. +//! +//! An `&mut nsA[C]String` acts like rust's `&mut Cow`, in that it is a +//! mutable reference to a potentially borrowed string, which when modified will +//! ensure that it owns its own backing storage. This type can be appended to +//! with the methods `.append`, `.append_utf{8,16}`, and with the `write!` +//! macro, and can be assigned to with `.assign`. +//! +//! ## `ns[C]Str<'a>` +//! +//! This type is an maybe-owned string type. It acts similarially to a +//! `Cow<[{u8,u16}]>`. This type provides `Deref` and `DerefMut` implementations +//! to `nsA[C]String`, which provides the methods for manipulating this type. +//! This type's lifetime parameter, `'a`, represents the lifetime of the backing +//! storage. When modified this type may re-allocate in order to ensure that it +//! does not mutate its backing storage. +//! +//! `ns[C]Str`s can be constructed either with `ns[C]Str::new()`, which creates +//! an empty `ns[C]Str<'static>`, or through one of the provided `From` +//! implementations. Only `nsCStr` can be constructed `From<'a str>`, as +//! constructing a `nsStr` would require transcoding. Use `ns[C]String` instead. +//! +//! When passing this type by reference, prefer passing a `&nsA[C]String` or +//! `&mut nsA[C]String`. to passing this type. +//! +//! When passing this type across the language boundary, pass it as `*const +//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a +//! mutable reference. +//! +//! ## `ns[C]String` +//! +//! This type is an owned, null-terminated string type. This type provides +//! `Deref` and `DerefMut` implementations to `nsA[C]String`, which provides the +//! methods for manipulating this type. +//! +//! `ns[C]String`s can be constructed either with `ns[C]String::new()`, which +//! creates an empty `ns[C]String`, or through one of the provided `From` +//! implementations, which will try to avoid reallocating when possible, +//! although a terminating `null` will be added. +//! +//! When passing this type by reference, prefer passing a `&nsA[C]String` or +//! `&mut nsA[C]String`. to passing this type. +//! +//! When passing this type across the language boundary, pass it as `*const +//! nsA[C]String` for an immutable reference, or `*mut nsA[C]String` for a +//! mutable reference. This struct may also be included in `#[repr(C)]` structs +//! shared with C++. +//! +//! ## `ns[C]StringRepr` +//! +//! This crate also provides the type `ns[C]StringRepr` which acts conceptually +//! similar to an `ns[C]String`, however, it does not have a `Drop` +//! implementation. +//! +//! If this type is dropped in rust, it will not free its backing storage. This +//! can be useful when implementing FFI types which contain `ns[C]String` members +//! which invoke their member's destructors through C++ code. + +#![allow(non_camel_case_types)] +#![allow(clippy::missing_safety_doc)] +#![allow(clippy::new_without_default)] +#![allow(clippy::result_unit_err)] + +use bitflags::bitflags; +use std::borrow; +use std::cmp; +use std::fmt; +use std::marker::PhantomData; +use std::mem; +use std::ops::{Deref, DerefMut}; +use std::os::raw::c_void; +use std::ptr; +use std::slice; +use std::str; + +mod conversions; + +pub use self::conversions::nscstring_fallible_append_latin1_to_utf8_check; +pub use self::conversions::nscstring_fallible_append_utf16_to_latin1_lossy_impl; +pub use self::conversions::nscstring_fallible_append_utf16_to_utf8_impl; +pub use self::conversions::nscstring_fallible_append_utf8_to_latin1_lossy_check; +pub use self::conversions::nsstring_fallible_append_latin1_impl; +pub use self::conversions::nsstring_fallible_append_utf8_impl; + +/// A type for showing that `finish()` was called on a `BulkWriteHandle`. +/// Instantiating this type from elsewhere is basically an assertion that +/// there is no `BulkWriteHandle` around, so be very careful with instantiating +/// this type! +pub struct BulkWriteOk; + +/// Semi-arbitrary threshold below which we don't care about shrinking +/// buffers to size. Currently matches `CACHE_LINE` in the `conversions` +/// module. +const SHRINKING_THRESHOLD: usize = 64; + +/////////////////////////////////// +// Internal Implementation Flags // +/////////////////////////////////// + +bitflags! { + // While this has the same layout as u16, it cannot be passed + // over FFI safely as a u16. + #[repr(C)] + struct DataFlags: u16 { + const TERMINATED = 1 << 0; // IsTerminated returns true + const VOIDED = 1 << 1; // IsVoid returns true + const REFCOUNTED = 1 << 2; // mData points to a heap-allocated, shareable, refcounted + // buffer + const OWNED = 1 << 3; // mData points to a heap-allocated, raw buffer + const INLINE = 1 << 4; // mData points to a writable, inline buffer + const LITERAL = 1 << 5; // mData points to a string literal; TERMINATED will also be set + } +} + +bitflags! { + // While this has the same layout as u16, it cannot be passed + // over FFI safely as a u16. + #[repr(C)] + struct ClassFlags: u16 { + const INLINE = 1 << 0; // |this|'s buffer is inline + const NULL_TERMINATED = 1 << 1; // |this| requires its buffer is null-terminated + } +} + +//////////////////////////////////// +// Generic String Bindings Macros // +//////////////////////////////////// + +macro_rules! string_like { + { + char_t = $char_t: ty; + + AString = $AString: ident; + String = $String: ident; + Str = $Str: ident; + + StringLike = $StringLike: ident; + StringAdapter = $StringAdapter: ident; + } => { + /// This trait is implemented on types which are `ns[C]String`-like, in + /// that they can at very low cost be converted to a borrowed + /// `&nsA[C]String`. Unfortunately, the intermediate type + /// `ns[C]StringAdapter` is required as well due to types like `&[u8]` + /// needing to be (cheaply) wrapped in a `nsCString` on the stack to + /// create the `&nsACString`. + /// + /// This trait is used to DWIM when calling the methods on + /// `nsA[C]String`. + pub trait $StringLike { + fn adapt(&self) -> $StringAdapter; + } + + impl<'a, T: $StringLike + ?Sized> $StringLike for &'a T { + fn adapt(&self) -> $StringAdapter { + ::adapt(*self) + } + } + + impl<'a, T> $StringLike for borrow::Cow<'a, T> + where T: $StringLike + borrow::ToOwned + ?Sized { + fn adapt(&self) -> $StringAdapter { + ::adapt(self.as_ref()) + } + } + + impl $StringLike for $AString { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Abstract(self) + } + } + + impl<'a> $StringLike for $Str<'a> { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Abstract(self) + } + } + + impl $StringLike for $String { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Abstract(self) + } + } + + impl $StringLike for [$char_t] { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Borrowed($Str::from(self)) + } + } + + impl $StringLike for Vec<$char_t> { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Borrowed($Str::from(&self[..])) + } + } + + impl $StringLike for Box<[$char_t]> { + fn adapt(&self) -> $StringAdapter { + $StringAdapter::Borrowed($Str::from(&self[..])) + } + } + } +} + +impl<'a> Drop for nsAStringBulkWriteHandle<'a> { + /// This only runs in error cases. In success cases, `finish()` + /// calls `forget(self)`. + fn drop(&mut self) { + if self.capacity == 0 { + // If capacity is 0, the string is a zero-length + // string, so we have nothing to do. + return; + } + // The old zero terminator may be gone by now, so we need + // to write a new one somewhere and make length match. + // We can use a length between 1 and self.capacity. + // Seems prudent to overwrite the uninitialized memory. + // Using the length 1 leaves the shortest memory to overwrite. + // U+FFFD is the safest placeholder. Merely truncating the + // string to a zero-length string might be dangerous in some + // scenarios. See + // https://www.unicode.org/reports/tr36/#Substituting_for_Ill_Formed_Subsequences + // for closely related scenario. + unsafe { + let mut this = self.string.as_repr_mut(); + this.as_mut().length = 1u32; + *(this.as_mut().data.as_mut()) = 0xFFFDu16; + *(this.as_mut().data.as_ptr().add(1)) = 0; + } + } +} + +impl<'a> Drop for nsACStringBulkWriteHandle<'a> { + /// This only runs in error cases. In success cases, `finish()` + /// calls `forget(self)`. + fn drop(&mut self) { + if self.capacity == 0 { + // If capacity is 0, the string is a zero-length + // string, so we have nothing to do. + return; + } + // The old zero terminator may be gone by now, so we need + // to write a new one somewhere and make length match. + // We can use a length between 1 and self.capacity. + // Seems prudent to overwrite the uninitialized memory. + // Using the length 1 leaves the shortest memory to overwrite. + // U+FFFD is the safest placeholder, but when it doesn't fit, + // let's use ASCII substitute. Merely truncating the + // string to a zero-length string might be dangerous in some + // scenarios. See + // https://www.unicode.org/reports/tr36/#Substituting_for_Ill_Formed_Subsequences + // for closely related scenario. + unsafe { + let mut this = self.string.as_repr_mut(); + if self.capacity >= 3 { + this.as_mut().length = 3u32; + *(this.as_mut().data.as_mut()) = 0xEFu8; + *(this.as_mut().data.as_ptr().add(1)) = 0xBFu8; + *(this.as_mut().data.as_ptr().add(2)) = 0xBDu8; + *(this.as_mut().data.as_ptr().add(3)) = 0; + } else { + this.as_mut().length = 1u32; + *(this.as_mut().data.as_mut()) = 0x1Au8; // U+FFFD doesn't fit + *(this.as_mut().data.as_ptr().add(1)) = 0; + } + } + } +} + +macro_rules! define_string_types { + { + char_t = $char_t: ty; + + AString = $AString: ident; + String = $String: ident; + Str = $Str: ident; + + StringLike = $StringLike: ident; + StringAdapter = $StringAdapter: ident; + + StringRepr = $StringRepr: ident; + AutoStringRepr = $AutoStringRepr: ident; + + BulkWriteHandle = $BulkWriteHandle: ident; + + drop = $drop: ident; + assign = $assign: ident, $fallible_assign: ident; + take_from = $take_from: ident, $fallible_take_from: ident; + append = $append: ident, $fallible_append: ident; + set_length = $set_length: ident, $fallible_set_length: ident; + begin_writing = $begin_writing: ident, $fallible_begin_writing: ident; + start_bulk_write = $start_bulk_write: ident; + } => { + /// The representation of a `ns[C]String` type in C++. This type is + /// used internally by our definition of `ns[C]String` to ensure layout + /// compatibility with the C++ `ns[C]String` type. + /// + /// This type may also be used in place of a C++ `ns[C]String` inside of + /// struct definitions which are shared with C++, as it has identical + /// layout to our `ns[C]String` type. + /// + /// This struct will leak its data if dropped from rust. See the module + /// documentation for more information on this type. + #[repr(C)] + #[derive(Debug)] + pub struct $StringRepr { + data: ptr::NonNull<$char_t>, + length: u32, + dataflags: DataFlags, + classflags: ClassFlags, + } + + impl $StringRepr { + fn new(classflags: ClassFlags) -> $StringRepr { + static NUL: $char_t = 0; + $StringRepr { + data: unsafe { ptr::NonNull::new_unchecked(&NUL as *const _ as *mut _) }, + length: 0, + dataflags: DataFlags::TERMINATED | DataFlags::LITERAL, + classflags, + } + } + } + + impl Deref for $StringRepr { + type Target = $AString; + fn deref(&self) -> &$AString { + unsafe { + &*(self as *const _ as *const $AString) + } + } + } + + impl DerefMut for $StringRepr { + fn deref_mut(&mut self) -> &mut $AString { + unsafe { + &mut *(self as *mut _ as *mut $AString) + } + } + } + + #[repr(C)] + #[derive(Debug)] + pub struct $AutoStringRepr { + super_repr: $StringRepr, + inline_capacity: u32, + } + + pub struct $BulkWriteHandle<'a> { + string: &'a mut $AString, + capacity: usize, + } + + impl<'a> $BulkWriteHandle<'a> { + fn new(string: &'a mut $AString, capacity: usize) -> Self { + $BulkWriteHandle{ string, capacity } + } + + pub unsafe fn restart_bulk_write(&mut self, + capacity: usize, + units_to_preserve: usize, + allow_shrinking: bool) -> Result<(), ()> { + self.capacity = + self.string.start_bulk_write_impl(capacity, + units_to_preserve, + allow_shrinking)?; + Ok(()) + } + + pub fn finish(mut self, length: usize, allow_shrinking: bool) -> BulkWriteOk { + // NOTE: Drop is implemented outside the macro earlier in this file, + // because it needs to deal with different code unit representations + // for the REPLACEMENT CHARACTER in the UTF-16 and UTF-8 cases and + // needs to deal with a REPLACEMENT CHARACTER not fitting in the + // buffer in the UTF-8 case. + assert!(length <= self.capacity); + if length == 0 { + // `truncate()` is OK even when the string + // is in invalid state. + self.string.truncate(); + mem::forget(self); // Don't run the failure path in drop() + return BulkWriteOk{}; + } + if allow_shrinking && length > SHRINKING_THRESHOLD { + unsafe { + let _ = self.restart_bulk_write(length, length, true); + } + } + unsafe { + let mut this = self.string.as_repr_mut(); + this.as_mut().length = length as u32; + *(this.as_mut().data.as_ptr().add(length)) = 0; + if cfg!(debug_assertions) { + // Overwrite the unused part in debug builds. Note + // that capacity doesn't include space for the zero + // terminator, so starting after the zero-terminator + // we wrote ends up overwriting the terminator space + // not reflected in the capacity number. + // write_bytes() takes care of multiplying the length + // by the size of T. + ptr::write_bytes(this.as_mut().data.as_ptr().add(length + 1), + 0xE4u8, + self.capacity - length); + } + // We don't have a Rust interface for mozilla/MemoryChecking.h, + // so let's just not communicate with MSan/Valgrind here. + } + mem::forget(self); // Don't run the failure path in drop() + BulkWriteOk{} + } + + pub fn as_mut_slice(&mut self) -> &mut [$char_t] { + unsafe { + let mut this = self.string.as_repr_mut(); + slice::from_raw_parts_mut(this.as_mut().data.as_ptr(), self.capacity) + } + } + } + + /// This type is the abstract type which is used for interacting with + /// strings in rust. Each string type can derefence to an instance of + /// this type, which provides the useful operations on strings. + /// + /// NOTE: Rust thinks this type has a size of 0, because the data + /// associated with it is not necessarially safe to move. It is not safe + /// to construct a nsAString yourself, unless it is received by + /// dereferencing one of these types. + /// + /// NOTE: The `[u8; 0]` member is zero sized, and only exists to prevent + /// the construction by code outside of this module. It is used instead + /// of a private `()` member because the `improper_ctypes` lint complains + /// about some ZST members in `extern "C"` function declarations. + #[repr(C)] + pub struct $AString { + _prohibit_constructor: [u8; 0], + } + + impl $AString { + /// Assign the value of `other` into self, overwriting any value + /// currently stored. Performs an optimized assignment when possible + /// if `other` is a `nsA[C]String`. + pub fn assign(&mut self, other: &T) { + unsafe { $assign(self, other.adapt().as_ptr()) }; + } + + /// Assign the value of `other` into self, overwriting any value + /// currently stored. Performs an optimized assignment when possible + /// if `other` is a `nsA[C]String`. + /// + /// Returns Ok(()) on success, and Err(()) if the allocation failed. + pub fn fallible_assign(&mut self, other: &T) -> Result<(), ()> { + if unsafe { $fallible_assign(self, other.adapt().as_ptr()) } { + Ok(()) + } else { + Err(()) + } + } + + /// Take the value of `other` and set `self`, overwriting any value + /// currently stored. The passed-in string will be truncated. + pub fn take_from(&mut self, other: &mut $AString) { + unsafe { $take_from(self, other) }; + } + + /// Take the value of `other` and set `self`, overwriting any value + /// currently stored. If this function fails, the source string will + /// be left untouched, otherwise it will be truncated. + /// + /// Returns Ok(()) on success, and Err(()) if the allocation failed. + pub fn fallible_take_from(&mut self, other: &mut $AString) -> Result<(), ()> { + if unsafe { $fallible_take_from(self, other) } { + Ok(()) + } else { + Err(()) + } + } + + /// Append the value of `other` into self. + pub fn append(&mut self, other: &T) { + unsafe { $append(self, other.adapt().as_ptr()) }; + } + + /// Append the value of `other` into self. + /// + /// Returns Ok(()) on success, and Err(()) if the allocation failed. + pub fn fallible_append(&mut self, other: &T) -> Result<(), ()> { + if unsafe { $fallible_append(self, other.adapt().as_ptr()) } { + Ok(()) + } else { + Err(()) + } + } + + /// Mark the string's data as void. If `true`, the string will be truncated. + /// + /// A void string is generally converted to a `null` JS value by bindings code. + pub fn set_is_void(&mut self, is_void: bool) { + if is_void { + self.truncate(); + } + unsafe { + self.as_repr_mut().as_mut().dataflags.set(DataFlags::VOIDED, is_void); + } + } + + /// Returns whether the string's data is voided. + pub fn is_void(&self) -> bool { + self.as_repr().dataflags.contains(DataFlags::VOIDED) + } + + /// Set the length of the string to the passed-in length, and expand + /// the backing capacity to match. This method is unsafe as it can + /// expose uninitialized memory when len is greater than the current + /// length of the string. + pub unsafe fn set_length(&mut self, len: u32) { + $set_length(self, len); + } + + /// Set the length of the string to the passed-in length, and expand + /// the backing capacity to match. This method is unsafe as it can + /// expose uninitialized memory when len is greater than the current + /// length of the string. + /// + /// Returns Ok(()) on success, and Err(()) if the allocation failed. + pub unsafe fn fallible_set_length(&mut self, len: u32) -> Result<(), ()> { + if $fallible_set_length(self, len) { + Ok(()) + } else { + Err(()) + } + } + + pub fn truncate(&mut self) { + unsafe { + self.set_length(0); + } + } + + /// Get a `&mut` reference to the backing data for this string. + /// This method will allocate and copy if the current backing buffer + /// is immutable or shared. + pub fn to_mut(&mut self) -> &mut [$char_t] { + unsafe { + let len = self.len(); + if len == 0 { + // Use an arbitrary but aligned non-null value as the pointer + slice::from_raw_parts_mut(ptr::NonNull::<$char_t>::dangling().as_ptr(), 0) + } else { + slice::from_raw_parts_mut($begin_writing(self), len) + } + } + } + + /// Get a `&mut` reference to the backing data for this string. + /// This method will allocate and copy if the current backing buffer + /// is immutable or shared. + /// + /// Returns `Ok(&mut [T])` on success, and `Err(())` if the + /// allocation failed. + pub fn fallible_to_mut(&mut self) -> Result<&mut [$char_t], ()> { + unsafe { + let len = self.len(); + if len == 0 { + // Use an arbitrary but aligned non-null value as the pointer + Ok(slice::from_raw_parts_mut( + ptr::NonNull::<$char_t>::dangling().as_ptr() as *mut $char_t, 0)) + } else { + let ptr = $fallible_begin_writing(self); + if ptr.is_null() { + Err(()) + } else { + Ok(slice::from_raw_parts_mut(ptr, len)) + } + } + } + } + + /// Unshares the buffer of the string and returns a handle + /// from which a writable slice whose length is the rounded-up + /// capacity can be obtained. + /// + /// Fails also if the new length doesn't fit in 32 bits. + /// + /// # Safety + /// + /// Unsafe because of exposure of uninitialized memory. + pub unsafe fn bulk_write(&mut self, + capacity: usize, + units_to_preserve: usize, + allow_shrinking: bool) -> Result<$BulkWriteHandle, ()> { + let capacity = + self.start_bulk_write_impl(capacity, units_to_preserve, allow_shrinking)?; + Ok($BulkWriteHandle::new(self, capacity)) + } + + unsafe fn start_bulk_write_impl(&mut self, + capacity: usize, + units_to_preserve: usize, + allow_shrinking: bool) -> Result { + if capacity > u32::MAX as usize { + Err(()) + } else { + let capacity32 = capacity as u32; + let rounded = $start_bulk_write(self, + capacity32, + units_to_preserve as u32, + allow_shrinking && capacity > SHRINKING_THRESHOLD); + if rounded == u32::MAX { + return Err(()) + } + Ok(rounded as usize) + } + } + + fn as_repr(&self) -> &$StringRepr { + // All $AString values point to a struct prefix which is + // identical to $StringRepr, thus we can cast `self` + // into *const $StringRepr to get the reference to the + // underlying data. + unsafe { + &*(self as *const _ as *const $StringRepr) + } + } + + fn as_repr_mut(&mut self) -> ptr::NonNull<$StringRepr> { + unsafe { ptr::NonNull::new_unchecked(self as *mut _ as *mut $StringRepr)} + } + + fn as_auto_string_repr(&self) -> Option<&$AutoStringRepr> { + if !self.as_repr().classflags.contains(ClassFlags::INLINE) { + return None; + } + + unsafe { + Some(&*(self as *const _ as *const $AutoStringRepr)) + } + } + + /// If this is an autostring, returns the capacity (excluding the + /// zero terminator) of the inline buffer within `Some()`. Otherwise + /// returns `None`. + pub fn inline_capacity(&self) -> Option { + Some(self.as_auto_string_repr()?.inline_capacity as usize) + } + } + + impl Deref for $AString { + type Target = [$char_t]; + fn deref(&self) -> &[$char_t] { + unsafe { + // All $AString values point to a struct prefix which is + // identical to $StringRepr, thus we can cast `self` + // into *const $StringRepr to get the reference to the + // underlying data. + let this = &*(self as *const _ as *const $StringRepr); + slice::from_raw_parts(this.data.as_ptr(), this.length as usize) + } + } + } + + impl AsRef<[$char_t]> for $AString { + fn as_ref(&self) -> &[$char_t] { + self + } + } + + impl cmp::PartialEq for $AString { + fn eq(&self, other: &$AString) -> bool { + &self[..] == &other[..] + } + } + + impl cmp::PartialEq<[$char_t]> for $AString { + fn eq(&self, other: &[$char_t]) -> bool { + &self[..] == other + } + } + + impl cmp::PartialEq<$String> for $AString { + fn eq(&self, other: &$String) -> bool { + self.eq(&**other) + } + } + + impl<'a> cmp::PartialEq<$Str<'a>> for $AString { + fn eq(&self, other: &$Str<'a>) -> bool { + self.eq(&**other) + } + } + + #[repr(C)] + pub struct $Str<'a> { + hdr: $StringRepr, + _marker: PhantomData<&'a [$char_t]>, + } + + impl $Str<'static> { + pub fn new() -> $Str<'static> { + $Str { + hdr: $StringRepr::new(ClassFlags::empty()), + _marker: PhantomData, + } + } + } + + impl<'a> Drop for $Str<'a> { + fn drop(&mut self) { + unsafe { + $drop(&mut **self); + } + } + } + + impl<'a> Deref for $Str<'a> { + type Target = $AString; + fn deref(&self) -> &$AString { + &self.hdr + } + } + + impl<'a> DerefMut for $Str<'a> { + fn deref_mut(&mut self) -> &mut $AString { + &mut self.hdr + } + } + + impl<'a> AsRef<[$char_t]> for $Str<'a> { + fn as_ref(&self) -> &[$char_t] { + &self + } + } + + impl<'a> From<&'a [$char_t]> for $Str<'a> { + fn from(s: &'a [$char_t]) -> $Str<'a> { + assert!(s.len() < (u32::MAX as usize)); + if s.is_empty() { + return $Str::new(); + } + $Str { + hdr: $StringRepr { + data: unsafe { ptr::NonNull::new_unchecked(s.as_ptr() as *mut _) }, + length: s.len() as u32, + dataflags: DataFlags::empty(), + classflags: ClassFlags::empty(), + }, + _marker: PhantomData, + } + } + } + + impl<'a> From<&'a Vec<$char_t>> for $Str<'a> { + fn from(s: &'a Vec<$char_t>) -> $Str<'a> { + $Str::from(&s[..]) + } + } + + impl<'a> From<&'a $AString> for $Str<'a> { + fn from(s: &'a $AString) -> $Str<'a> { + $Str::from(&s[..]) + } + } + + impl<'a> fmt::Write for $Str<'a> { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + $AString::write_str(self, s) + } + } + + impl<'a> fmt::Display for $Str<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Display>::fmt(self, f) + } + } + + impl<'a> fmt::Debug for $Str<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Debug>::fmt(self, f) + } + } + + impl<'a> cmp::PartialEq for $Str<'a> { + fn eq(&self, other: &$Str<'a>) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<[$char_t]> for $Str<'a> { + fn eq(&self, other: &[$char_t]) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b [$char_t]> for $Str<'a> { + fn eq(&self, other: &&'b [$char_t]) -> bool { + $AString::eq(self, *other) + } + } + + impl<'a> cmp::PartialEq for $Str<'a> { + fn eq(&self, other: &str) -> bool { + $AString::eq(self, other) + } + } + + impl<'a, 'b> cmp::PartialEq<&'b str> for $Str<'a> { + fn eq(&self, other: &&'b str) -> bool { + $AString::eq(self, *other) + } + } + + #[repr(C)] + pub struct $String { + hdr: $StringRepr, + } + + unsafe impl Send for $String {} + unsafe impl Sync for $String {} + + impl $String { + pub fn new() -> $String { + $String { + hdr: $StringRepr::new(ClassFlags::NULL_TERMINATED), + } + } + + /// Converts this String into a StringRepr, which will leak if the + /// repr is not passed to something that knows how to free it. + pub fn into_repr(mut self) -> $StringRepr { + mem::replace(&mut self.hdr, $StringRepr::new(ClassFlags::NULL_TERMINATED)) + } + } + + impl Drop for $String { + fn drop(&mut self) { + unsafe { + $drop(&mut **self); + } + } + } + + impl Deref for $String { + type Target = $AString; + fn deref(&self) -> &$AString { + &self.hdr + } + } + + impl DerefMut for $String { + fn deref_mut(&mut self) -> &mut $AString { + &mut self.hdr + } + } + + impl Clone for $String { + fn clone(&self) -> Self { + let mut copy = $String::new(); + copy.assign(self); + copy + } + } + + impl AsRef<[$char_t]> for $String { + fn as_ref(&self) -> &[$char_t] { + &self + } + } + + impl<'a> From<&'a [$char_t]> for $String { + fn from(s: &'a [$char_t]) -> $String { + let mut res = $String::new(); + res.assign(&$Str::from(&s[..])); + res + } + } + + impl<'a> From<&'a Vec<$char_t>> for $String { + fn from(s: &'a Vec<$char_t>) -> $String { + $String::from(&s[..]) + } + } + + impl<'a> From<&'a $AString> for $String { + fn from(s: &'a $AString) -> $String { + $String::from(&s[..]) + } + } + + impl From> for $String { + fn from(s: Box<[$char_t]>) -> $String { + s.into_vec().into() + } + } + + impl From> for $String { + fn from(mut s: Vec<$char_t>) -> $String { + assert!(s.len() < (u32::MAX as usize)); + if s.is_empty() { + return $String::new(); + } + + let length = s.len() as u32; + s.push(0); // null terminator + + // SAFETY NOTE: This method produces an data_flags::OWNED + // ns[C]String from a Box<[$char_t]>. this is only safe + // because in the Gecko tree, we use the same allocator for + // Rust code as for C++ code, meaning that our box can be + // legally freed with libc::free(). + let ptr = s.as_mut_ptr(); + mem::forget(s); + unsafe { + Gecko_IncrementStringAdoptCount(ptr as *mut _); + } + $String { + hdr: $StringRepr { + data: unsafe { ptr::NonNull::new_unchecked(ptr) }, + length, + dataflags: DataFlags::OWNED | DataFlags::TERMINATED, + classflags: ClassFlags::NULL_TERMINATED, + } + } + } + } + + impl fmt::Write for $String { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + $AString::write_str(self, s) + } + } + + impl fmt::Display for $String { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Display>::fmt(self, f) + } + } + + impl fmt::Debug for $String { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + <$AString as fmt::Debug>::fmt(self, f) + } + } + + impl cmp::PartialEq for $String { + fn eq(&self, other: &$String) -> bool { + $AString::eq(self, other) + } + } + + impl cmp::PartialEq<[$char_t]> for $String { + fn eq(&self, other: &[$char_t]) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<&'a [$char_t]> for $String { + fn eq(&self, other: &&'a [$char_t]) -> bool { + $AString::eq(self, *other) + } + } + + impl cmp::PartialEq for $String { + fn eq(&self, other: &str) -> bool { + $AString::eq(self, other) + } + } + + impl<'a> cmp::PartialEq<&'a str> for $String { + fn eq(&self, other: &&'a str) -> bool { + $AString::eq(self, *other) + } + } + + /// An adapter type to allow for passing both types which coerce to + /// &[$char_type], and &$AString to a function, while still performing + /// optimized operations when passed the $AString. + pub enum $StringAdapter<'a> { + Borrowed($Str<'a>), + Abstract(&'a $AString), + } + + impl<'a> $StringAdapter<'a> { + fn as_ptr(&self) -> *const $AString { + &**self + } + } + + impl<'a> Deref for $StringAdapter<'a> { + type Target = $AString; + + fn deref(&self) -> &$AString { + match *self { + $StringAdapter::Borrowed(ref s) => s, + $StringAdapter::Abstract(ref s) => s, + } + } + } + + impl<'a> $StringAdapter<'a> { + #[allow(dead_code)] + fn is_abstract(&self) -> bool { + match *self { + $StringAdapter::Borrowed(_) => false, + $StringAdapter::Abstract(_) => true, + } + } + } + + string_like! { + char_t = $char_t; + + AString = $AString; + String = $String; + Str = $Str; + + StringLike = $StringLike; + StringAdapter = $StringAdapter; + } + } +} + +/////////////////////////////////////////// +// Bindings for nsCString (u8 char type) // +/////////////////////////////////////////// + +define_string_types! { + char_t = u8; + + AString = nsACString; + String = nsCString; + Str = nsCStr; + + StringLike = nsCStringLike; + StringAdapter = nsCStringAdapter; + + StringRepr = nsCStringRepr; + AutoStringRepr = nsAutoCStringRepr; + + BulkWriteHandle = nsACStringBulkWriteHandle; + + drop = Gecko_FinalizeCString; + assign = Gecko_AssignCString, Gecko_FallibleAssignCString; + take_from = Gecko_TakeFromCString, Gecko_FallibleTakeFromCString; + append = Gecko_AppendCString, Gecko_FallibleAppendCString; + set_length = Gecko_SetLengthCString, Gecko_FallibleSetLengthCString; + begin_writing = Gecko_BeginWritingCString, Gecko_FallibleBeginWritingCString; + start_bulk_write = Gecko_StartBulkWriteCString; +} + +impl nsACString { + /// Gets a CString as an utf-8 str or a String, trying to avoid copies, and + /// replacing invalid unicode sequences with replacement characters. + #[inline] + pub fn to_utf8(&self) -> borrow::Cow { + String::from_utf8_lossy(&self[..]) + } + + #[inline] + pub unsafe fn as_str_unchecked(&self) -> &str { + if cfg!(debug_assertions) { + str::from_utf8(self).expect("Should be utf-8") + } else { + str::from_utf8_unchecked(self) + } + } +} + +impl<'a> From<&'a str> for nsCStr<'a> { + fn from(s: &'a str) -> nsCStr<'a> { + s.as_bytes().into() + } +} + +impl<'a> From<&'a String> for nsCStr<'a> { + fn from(s: &'a String) -> nsCStr<'a> { + nsCStr::from(&s[..]) + } +} + +impl<'a> From<&'a str> for nsCString { + fn from(s: &'a str) -> nsCString { + s.as_bytes().into() + } +} + +impl<'a> From<&'a String> for nsCString { + fn from(s: &'a String) -> nsCString { + nsCString::from(&s[..]) + } +} + +impl From> for nsCString { + fn from(s: Box) -> nsCString { + s.into_string().into() + } +} + +impl From for nsCString { + fn from(s: String) -> nsCString { + s.into_bytes().into() + } +} + +// Support for the write!() macro for appending to nsACStrings +impl fmt::Write for nsACString { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + self.append(s); + Ok(()) + } +} + +impl fmt::Display for nsACString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Display::fmt(&self.to_utf8(), f) + } +} + +impl fmt::Debug for nsACString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&self.to_utf8(), f) + } +} + +impl cmp::PartialEq for nsACString { + fn eq(&self, other: &str) -> bool { + &self[..] == other.as_bytes() + } +} + +impl nsCStringLike for str { + fn adapt(&self) -> nsCStringAdapter { + nsCStringAdapter::Borrowed(nsCStr::from(self)) + } +} + +impl nsCStringLike for String { + fn adapt(&self) -> nsCStringAdapter { + nsCStringAdapter::Borrowed(nsCStr::from(&self[..])) + } +} + +impl nsCStringLike for Box { + fn adapt(&self) -> nsCStringAdapter { + nsCStringAdapter::Borrowed(nsCStr::from(&self[..])) + } +} + +// This trait is implemented on types which are Latin1 `nsCString`-like, +// in that they can at very low cost be converted to a borrowed +// `&nsACString` and do not denote UTF-8ness in the Rust type system. +// +// This trait is used to DWIM when calling the methods on +// `nsACString`. +string_like! { + char_t = u8; + + AString = nsACString; + String = nsCString; + Str = nsCStr; + + StringLike = Latin1StringLike; + StringAdapter = nsCStringAdapter; +} + +/////////////////////////////////////////// +// Bindings for nsString (u16 char type) // +/////////////////////////////////////////// + +define_string_types! { + char_t = u16; + + AString = nsAString; + String = nsString; + Str = nsStr; + + StringLike = nsStringLike; + StringAdapter = nsStringAdapter; + + StringRepr = nsStringRepr; + AutoStringRepr = nsAutoStringRepr; + + BulkWriteHandle = nsAStringBulkWriteHandle; + + drop = Gecko_FinalizeString; + assign = Gecko_AssignString, Gecko_FallibleAssignString; + take_from = Gecko_TakeFromString, Gecko_FallibleTakeFromString; + append = Gecko_AppendString, Gecko_FallibleAppendString; + set_length = Gecko_SetLengthString, Gecko_FallibleSetLengthString; + begin_writing = Gecko_BeginWritingString, Gecko_FallibleBeginWritingString; + start_bulk_write = Gecko_StartBulkWriteString; +} + +// NOTE: The From impl for a string slice for nsString produces a <'static> +// lifetime, as it allocates. +impl<'a> From<&'a str> for nsString { + fn from(s: &'a str) -> nsString { + s.encode_utf16().collect::>().into() + } +} + +impl<'a> From<&'a String> for nsString { + fn from(s: &'a String) -> nsString { + nsString::from(&s[..]) + } +} + +// Support for the write!() macro for writing to nsStrings +impl fmt::Write for nsAString { + fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> { + // Directly invoke gecko's routines for appending utf8 strings to + // nsAString values, to avoid as much overhead as possible + self.append_str(s); + Ok(()) + } +} + +impl nsAString { + /// Turns this utf-16 string into a string, replacing invalid unicode + /// sequences with replacement characters. + /// + /// This is needed because the default ToString implementation goes through + /// fmt::Display, and thus allocates the string twice. + #[allow(clippy::inherent_to_string_shadow_display)] + pub fn to_string(&self) -> String { + String::from_utf16_lossy(&self[..]) + } +} + +impl fmt::Display for nsAString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Display::fmt(&self.to_string(), f) + } +} + +impl fmt::Debug for nsAString { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fmt::Debug::fmt(&self.to_string(), f) + } +} + +impl cmp::PartialEq for nsAString { + fn eq(&self, other: &str) -> bool { + other.encode_utf16().eq(self.iter().cloned()) + } +} + +#[cfg(not(feature = "gecko_debug"))] +#[allow(non_snake_case)] +unsafe fn Gecko_IncrementStringAdoptCount(_: *mut c_void) {} + +extern "C" { + #[cfg(feature = "gecko_debug")] + fn Gecko_IncrementStringAdoptCount(data: *mut c_void); + + // Gecko implementation in nsSubstring.cpp + fn Gecko_FinalizeCString(this: *mut nsACString); + + fn Gecko_AssignCString(this: *mut nsACString, other: *const nsACString); + fn Gecko_TakeFromCString(this: *mut nsACString, other: *mut nsACString); + fn Gecko_AppendCString(this: *mut nsACString, other: *const nsACString); + fn Gecko_SetLengthCString(this: *mut nsACString, length: u32); + fn Gecko_BeginWritingCString(this: *mut nsACString) -> *mut u8; + fn Gecko_FallibleAssignCString(this: *mut nsACString, other: *const nsACString) -> bool; + fn Gecko_FallibleTakeFromCString(this: *mut nsACString, other: *mut nsACString) -> bool; + fn Gecko_FallibleAppendCString(this: *mut nsACString, other: *const nsACString) -> bool; + fn Gecko_FallibleSetLengthCString(this: *mut nsACString, length: u32) -> bool; + fn Gecko_FallibleBeginWritingCString(this: *mut nsACString) -> *mut u8; + fn Gecko_StartBulkWriteCString( + this: *mut nsACString, + capacity: u32, + units_to_preserve: u32, + allow_shrinking: bool, + ) -> u32; + + fn Gecko_FinalizeString(this: *mut nsAString); + + fn Gecko_AssignString(this: *mut nsAString, other: *const nsAString); + fn Gecko_TakeFromString(this: *mut nsAString, other: *mut nsAString); + fn Gecko_AppendString(this: *mut nsAString, other: *const nsAString); + fn Gecko_SetLengthString(this: *mut nsAString, length: u32); + fn Gecko_BeginWritingString(this: *mut nsAString) -> *mut u16; + fn Gecko_FallibleAssignString(this: *mut nsAString, other: *const nsAString) -> bool; + fn Gecko_FallibleTakeFromString(this: *mut nsAString, other: *mut nsAString) -> bool; + fn Gecko_FallibleAppendString(this: *mut nsAString, other: *const nsAString) -> bool; + fn Gecko_FallibleSetLengthString(this: *mut nsAString, length: u32) -> bool; + fn Gecko_FallibleBeginWritingString(this: *mut nsAString) -> *mut u16; + fn Gecko_StartBulkWriteString( + this: *mut nsAString, + capacity: u32, + units_to_preserve: u32, + allow_shrinking: bool, + ) -> u32; +} + +////////////////////////////////////// +// Repr Validation Helper Functions // +////////////////////////////////////// + +pub mod test_helpers { + //! This module only exists to help with ensuring that the layout of the + //! structs inside of rust and C++ are identical. + //! + //! It is public to ensure that these testing functions are avaliable to + //! gtest code. + + use super::{nsACString, nsAString}; + use super::{nsCStr, nsCString, nsCStringRepr}; + use super::{nsStr, nsString, nsStringRepr}; + use super::{ClassFlags, DataFlags}; + use std::mem; + + /// Generates an #[no_mangle] extern "C" function which returns the size and + /// alignment of the given type with the given name. + macro_rules! size_align_check { + ($T:ty, $fname:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn $fname(size: *mut usize, align: *mut usize) { + *size = mem::size_of::<$T>(); + *align = mem::align_of::<$T>(); + } + }; + ($T:ty, $U:ty, $V:ty, $fname:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn $fname(size: *mut usize, align: *mut usize) { + *size = mem::size_of::<$T>(); + *align = mem::align_of::<$T>(); + + assert_eq!(*size, mem::size_of::<$U>()); + assert_eq!(*align, mem::align_of::<$U>()); + assert_eq!(*size, mem::size_of::<$V>()); + assert_eq!(*align, mem::align_of::<$V>()); + } + }; + } + + size_align_check!( + nsStringRepr, + nsString, + nsStr<'static>, + Rust_Test_ReprSizeAlign_nsString + ); + size_align_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + Rust_Test_ReprSizeAlign_nsCString + ); + + /// Generates a $[no_mangle] extern "C" function which returns the size, + /// alignment and offset in the parent struct of a given member, with the + /// given name. + /// + /// This method can trigger Undefined Behavior if the accessing the member + /// $member on a given type would use that type's `Deref` implementation. + macro_rules! member_check { + ($T:ty, $U:ty, $V:ty, $member:ident, $method:ident) => { + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn $method( + size: *mut usize, + align: *mut usize, + offset: *mut usize, + ) { + // Create a temporary value of type T to get offsets, sizes + // and alignments from. + let tmp: mem::MaybeUninit<$T> = mem::MaybeUninit::uninit(); + // FIXME: This should use &raw references when available, + // this is technically UB as it creates a reference to + // uninitialized memory, but there's no better way to do + // this right now. + let tmp = &*tmp.as_ptr(); + *size = mem::size_of_val(&tmp.$member); + *align = mem::align_of_val(&tmp.$member); + *offset = (&tmp.$member as *const _ as usize) - (tmp as *const $T as usize); + + let tmp: mem::MaybeUninit<$U> = mem::MaybeUninit::uninit(); + let tmp = &*tmp.as_ptr(); + assert_eq!(*size, mem::size_of_val(&tmp.hdr.$member)); + assert_eq!(*align, mem::align_of_val(&tmp.hdr.$member)); + assert_eq!( + *offset, + (&tmp.hdr.$member as *const _ as usize) - (tmp as *const $U as usize) + ); + + let tmp: mem::MaybeUninit<$V> = mem::MaybeUninit::uninit(); + let tmp = &*tmp.as_ptr(); + assert_eq!(*size, mem::size_of_val(&tmp.hdr.$member)); + assert_eq!(*align, mem::align_of_val(&tmp.hdr.$member)); + assert_eq!( + *offset, + (&tmp.hdr.$member as *const _ as usize) - (tmp as *const $V as usize) + ); + } + }; + } + + member_check!( + nsStringRepr, + nsString, + nsStr<'static>, + data, + Rust_Test_Member_nsString_mData + ); + member_check!( + nsStringRepr, + nsString, + nsStr<'static>, + length, + Rust_Test_Member_nsString_mLength + ); + member_check!( + nsStringRepr, + nsString, + nsStr<'static>, + dataflags, + Rust_Test_Member_nsString_mDataFlags + ); + member_check!( + nsStringRepr, + nsString, + nsStr<'static>, + classflags, + Rust_Test_Member_nsString_mClassFlags + ); + member_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + data, + Rust_Test_Member_nsCString_mData + ); + member_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + length, + Rust_Test_Member_nsCString_mLength + ); + member_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + dataflags, + Rust_Test_Member_nsCString_mDataFlags + ); + member_check!( + nsCStringRepr, + nsCString, + nsCStr<'static>, + classflags, + Rust_Test_Member_nsCString_mClassFlags + ); + + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn Rust_Test_NsStringFlags( + f_terminated: *mut u16, + f_voided: *mut u16, + f_refcounted: *mut u16, + f_owned: *mut u16, + f_inline: *mut u16, + f_literal: *mut u16, + f_class_inline: *mut u16, + f_class_null_terminated: *mut u16, + ) { + *f_terminated = DataFlags::TERMINATED.bits(); + *f_voided = DataFlags::VOIDED.bits(); + *f_refcounted = DataFlags::REFCOUNTED.bits(); + *f_owned = DataFlags::OWNED.bits(); + *f_inline = DataFlags::INLINE.bits(); + *f_literal = DataFlags::LITERAL.bits(); + *f_class_inline = ClassFlags::INLINE.bits(); + *f_class_null_terminated = ClassFlags::NULL_TERMINATED.bits(); + } + + #[no_mangle] + #[allow(non_snake_case)] + pub unsafe extern "C" fn Rust_InlineCapacityFromRust( + cstring: *const nsACString, + string: *const nsAString, + cstring_capacity: *mut usize, + string_capacity: *mut usize, + ) { + *cstring_capacity = (*cstring).inline_capacity().unwrap(); + *string_capacity = (*string).inline_capacity().unwrap(); + } +} diff --git a/xpcom/rust/xpcom/Cargo.toml b/xpcom/rust/xpcom/Cargo.toml new file mode 100644 index 0000000000..9c8a1ec83b --- /dev/null +++ b/xpcom/rust/xpcom/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "xpcom" +version = "0.1.0" +authors = ["Nika Layzell "] +edition = "2018" +license = "MPL-2.0" + +[dependencies] +cstr = "0.2" +libc = "0.2" +nsstring = { path = "../nsstring" } +nserror = { path = "../nserror" } +threadbound = "0.1" +xpcom_macros = { path = "xpcom_macros" } +thin-vec = { version = "0.2.1", features = ["gecko-ffi"] } +mozbuild = "0.1" + +[features] +thread_sanitizer = [] +gecko_refcount_logging = [] diff --git a/xpcom/rust/xpcom/src/base.rs b/xpcom/rust/xpcom/src/base.rs new file mode 100644 index 0000000000..556768a179 --- /dev/null +++ b/xpcom/rust/xpcom/src/base.rs @@ -0,0 +1,59 @@ +/* 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/. */ + +use crate::interfaces::{nsIInterfaceRequestor, nsISupports}; +use crate::{GetterAddrefs, RefCounted, RefPtr}; + +#[repr(C)] +#[derive(Copy, Clone, Eq, PartialEq)] +/// A "unique identifier". This is modeled after OSF DCE UUIDs. +pub struct nsID(pub u32, pub u16, pub u16, pub [u8; 8]); + +/// Interface IDs +pub type nsIID = nsID; +/// Class IDs +pub type nsCID = nsID; + +/// A type which implements XpCom must follow the following rules: +/// +/// * It must be a legal XPCOM interface. +/// * The result of a QueryInterface or similar call, passing IID, must return a +/// valid reference to an object of the given type. +/// * It must be valid to cast a &self reference to a &nsISupports reference. +pub unsafe trait XpCom: RefCounted { + const IID: nsIID; + + /// Perform a QueryInterface call on this object, attempting to dynamically + /// cast it to the requested interface type. Returns Some(RefPtr) if the + /// cast succeeded, and None otherwise. + fn query_interface(&self) -> Option> { + let mut ga = GetterAddrefs::::new(); + unsafe { + if (*(self as *const Self as *const nsISupports)) + .QueryInterface(&T::IID, ga.void_ptr()) + .succeeded() + { + ga.refptr() + } else { + None + } + } + } + + /// Perform a `GetInterface` call on this object, returning `None` if the + /// object doesn't implement `nsIInterfaceRequestor`, or can't access the + /// interface `T`. + fn get_interface(&self) -> Option> { + let ireq = self.query_interface::()?; + + let mut ga = GetterAddrefs::::new(); + unsafe { + if ireq.GetInterface(&T::IID, ga.void_ptr()).succeeded() { + ga.refptr() + } else { + None + } + } + } +} diff --git a/xpcom/rust/xpcom/src/components.rs b/xpcom/rust/xpcom/src/components.rs new file mode 100644 index 0000000000..c83e2df705 --- /dev/null +++ b/xpcom/rust/xpcom/src/components.rs @@ -0,0 +1,23 @@ +/* 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/. */ + +//! This module contains convenient accessors for static XPCOM components. +//! +//! The contents of this file are generated from +//! `xpcom/components/gen_static_components.py`. + +extern "C" { + fn Gecko_GetServiceByModuleID( + id: ModuleID, + iid: &crate::nsIID, + result: *mut *mut libc::c_void, + ) -> nserror::nsresult; + fn Gecko_CreateInstanceByModuleID( + id: ModuleID, + iid: &crate::nsIID, + result: *mut *mut libc::c_void, + ) -> nserror::nsresult; +} + +include!(mozbuild::objdir_path!("xpcom/components/components.rs")); diff --git a/xpcom/rust/xpcom/src/interfaces/idl.rs b/xpcom/rust/xpcom/src/interfaces/idl.rs new file mode 100644 index 0000000000..c8d62ce716 --- /dev/null +++ b/xpcom/rust/xpcom/src/interfaces/idl.rs @@ -0,0 +1,12 @@ +/* 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/. */ + +#![allow(bad_style)] + +use crate::interfaces::*; +use crate::*; + +// NOTE: This file contains a series of `include!()` invocations, defining all +// idl interfaces directly within this module. +include!(mozbuild::objdir_path!("dist/xpcrs/rt/all.rs")); diff --git a/xpcom/rust/xpcom/src/interfaces/mod.rs b/xpcom/rust/xpcom/src/interfaces/mod.rs new file mode 100644 index 0000000000..5b9de5cef5 --- /dev/null +++ b/xpcom/rust/xpcom/src/interfaces/mod.rs @@ -0,0 +1,31 @@ +/* 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/. */ + +//! This module contains the xpcom interfaces exposed to rust code. +//! +//! The items in this module come in a few flavours: +//! +//! 1. `nsI*`: These are the types for XPCOM interfaces. They should always be +//! passed behind a reference, pointer, or `RefPtr`. They may be coerced to +//! their base interfaces using the `coerce` method. +//! +//! 2. `nsI*Coerce`: These traits provide the implementation mechanics for the +//! `coerce` method, and can usually be ignored. *These traits are hidden in +//! rustdoc* +//! +//! 3. `nsI*VTable`: These structs are the vtable definitions for each type. +//! They contain the base interface's vtable, followed by pointers for each +//! of the vtable's methods. If direct access is needed, a `*const nsI*` can +//! be safely transmuted to a `*const nsI*VTable`. *These structs are hidden +//! in rustdoc* +//! +//! 4. Typedefs used in idl file definitions. + +// Interfaces defined in .idl files +mod idl; +pub use self::idl::*; + +// Other interfaces which are needed to compile +mod nonidl; +pub use self::nonidl::*; diff --git a/xpcom/rust/xpcom/src/interfaces/nonidl.rs b/xpcom/rust/xpcom/src/interfaces/nonidl.rs new file mode 100644 index 0000000000..b9e3f1abe2 --- /dev/null +++ b/xpcom/rust/xpcom/src/interfaces/nonidl.rs @@ -0,0 +1,180 @@ +/* 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/. */ + +//! This module contains definitions of interfaces which are used in idl files +//! as forward declarations, but are not actually defined in an idl file. +//! +//! NOTE: The IIDs in these files must be kept in sync with the IDL definitions +//! in the corresponding C++ files. + +use crate::nsID; + +// XXX: This macro should have an option for a custom base interface instead of +// nsISupports, such that Document can have nsINode as a base, etc. For now, +// query_interface should be sufficient. +macro_rules! nonidl { + ($name:ident, $iid:expr) => { + /// This interface is referenced from idl files, but not defined in + /// them. It exports no methods to rust code. + #[repr(C)] + pub struct $name { + _vtable: *const $crate::interfaces::nsISupportsVTable, + } + + unsafe impl $crate::XpCom for $name { + const IID: $crate::nsIID = $iid; + } + + unsafe impl $crate::RefCounted for $name { + #[inline] + unsafe fn addref(&self) { + self.AddRef(); + } + #[inline] + unsafe fn release(&self) { + self.Release(); + } + } + + impl ::std::ops::Deref for $name { + type Target = $crate::interfaces::nsISupports; + #[inline] + fn deref(&self) -> &$crate::interfaces::nsISupports { + unsafe { ::std::mem::transmute(self) } + } + } + }; +} + +// Must be kept in sync with Document.h +nonidl!( + Document, + nsID( + 0xce1f7627, + 0x7109, + 0x4977, + [0xba, 0x77, 0x49, 0x0f, 0xfd, 0xe0, 0x7a, 0xaa] + ) +); + +// Must be kept in sync with nsINode.h +nonidl!( + nsINode, + nsID( + 0x70ba4547, + 0x7699, + 0x44fc, + [0xb3, 0x20, 0x52, 0xdb, 0xe3, 0xd1, 0xf9, 0x0a] + ) +); + +// Must be kept in sync with nsIContent.h +nonidl!( + nsIContent, + nsID( + 0x8e1bab9d, + 0x8815, + 0x4d2c, + [0xa2, 0x4d, 0x7a, 0xba, 0x52, 0x39, 0xdc, 0x22] + ) +); + +// Must be kept in sync with nsIConsoleReportCollector.h +nonidl!( + nsIConsoleReportCollector, + nsID( + 0xdd98a481, + 0xd2c4, + 0x4203, + [0x8d, 0xfa, 0x85, 0xbf, 0xd7, 0xdc, 0xd7, 0x05] + ) +); + +// Must be kept in sync with nsIGlobalObject.h +nonidl!( + nsIGlobalObject, + nsID( + 0x11afa8be, + 0xd997, + 0x4e07, + [0xa6, 0xa3, 0x6f, 0x87, 0x2e, 0xc3, 0xee, 0x7f] + ) +); + +// Must be kept in sync with nsIScriptElement.h +nonidl!( + nsIScriptElement, + nsID( + 0xe60fca9b, + 0x1b96, + 0x4e4e, + [0xa9, 0xb4, 0xdc, 0x98, 0x4f, 0x88, 0x3f, 0x9c] + ) +); + +// Must be kept in sync with nsPIDOMWindow.h +nonidl!( + nsPIDOMWindowOuter, + nsID( + 0x769693d4, + 0xb009, + 0x4fe2, + [0xaf, 0x18, 0x7d, 0xc8, 0xdf, 0x74, 0x96, 0xdf] + ) +); + +// Must be kept in sync with nsPIDOMWindow.h +nonidl!( + nsPIDOMWindowInner, + nsID( + 0x775dabc9, + 0x8f43, + 0x4277, + [0x9a, 0xdb, 0xf1, 0x99, 0x0d, 0x77, 0xcf, 0xfb] + ) +); + +// Must be kept in sync with nsIScriptContext.h +nonidl!( + nsIScriptContext, + nsID( + 0x54cbe9cf, + 0x7282, + 0x421a, + [0x91, 0x6f, 0xd0, 0x70, 0x73, 0xde, 0xb8, 0xc0] + ) +); + +// Must be kept in sync with nsIScriptGlobalObject.h +nonidl!( + nsIScriptGlobalObject, + nsID( + 0x876f83bd, + 0x6314, + 0x460a, + [0xa0, 0x45, 0x1c, 0x8f, 0x46, 0x2f, 0xb8, 0xe1] + ) +); + +// Must be kept in sync with nsIScrollObserver.h +nonidl!( + nsIScrollObserver, + nsID( + 0xaa5026eb, + 0x2f88, + 0x4026, + [0xa4, 0x6b, 0xf4, 0x59, 0x6b, 0x4e, 0xdf, 0x00] + ) +); + +// Must be kept in sync with nsIWidget.h +nonidl!( + nsIWidget, + nsID( + 0x06396bf6, + 0x2dd8, + 0x45e5, + [0xac, 0x45, 0x75, 0x26, 0x53, 0xb1, 0xc9, 0x80] + ) +); diff --git a/xpcom/rust/xpcom/src/lib.rs b/xpcom/rust/xpcom/src/lib.rs new file mode 100644 index 0000000000..ac039ebe76 --- /dev/null +++ b/xpcom/rust/xpcom/src/lib.rs @@ -0,0 +1,43 @@ +/* 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/. */ + +//! This crate contains the functionality required in order to both implement +//! and call XPCOM methods from rust code. +//! +//! For documentation on how to implement XPCOM methods, see the documentation +//! for the [`xpcom_macros`](../xpcom_macros/index.html) crate. + +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] + +// re-export the xpcom_macros macro +pub use xpcom_macros::xpcom; + +// Helper functions and data structures are exported in the root of the crate. +mod base; +pub use base::*; + +// Declarative macro to generate XPCOM method stubs. +mod method; +pub use method::*; + +// dom::Promise resolving. +mod promise; +pub use promise::*; + +mod refptr; +pub use refptr::*; + +mod statics; +pub use statics::*; + +// XPCOM interface definitions. +pub mod interfaces; + +// XPCOM component getters. +pub mod components; + +// Implementation details of the xpcom_macros crate. +#[doc(hidden)] +pub mod reexports; diff --git a/xpcom/rust/xpcom/src/method.rs b/xpcom/rust/xpcom/src/method.rs new file mode 100644 index 0000000000..66c0510bd9 --- /dev/null +++ b/xpcom/rust/xpcom/src/method.rs @@ -0,0 +1,241 @@ +/* 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/. */ + +use nserror::{nsresult, NS_ERROR_NULL_POINTER}; + +/// The xpcom_method macro generates a Rust XPCOM method stub that converts +/// raw pointer arguments to references, calls a Rustic implementation +/// of the method, writes its return value into the XPCOM method's outparameter, +/// and returns an nsresult. +/// +/// In other words, given an XPCOM method like: +/// +/// ```ignore +/// interface nsIFooBarBaz : nsISupports { +/// nsIVariant foo(in AUTF8String bar, [optional] in bool baz); +/// } +/// ``` +/// +/// And a Rust implementation that uses #[xpcom] to implement it: +/// +/// ```ignore +/// #[xpcom(implement(nsIFooBarBaz), atomic)] +/// struct FooBarBaz { +/// // … +/// } +/// ``` +/// +/// With the appropriate extern crate and use declarations +/// +/// ```ignore +/// extern crate xpcom; +/// use xpcom::xpcom_method; +/// ``` +/// +/// Invoking the macro with the name of the XPCOM method, the name of its +/// Rustic implementation, the set of its arguments, and its return value: +/// +/// ```ignore +/// impl FooBarBaz { +/// xpcom_method!( +/// foo => Foo(bar: *const nsACString, baz: bool) -> *const nsIVariant +/// ); +/// } +/// ``` +/// +/// Results in the macro generating an XPCOM stub like the following: +/// +/// ```ignore +/// unsafe fn Foo(&self, bar: *const nsACString, baz: bool, retval: *mut *const nsIVariant) -> nsresult { +/// let bar = match Ensure::ensure(bar) { +/// Ok(val) => val, +/// Err(result) => return result, +/// }; +/// let baz = match Ensure::ensure(baz) { +/// Ok(val) => val, +/// Err(result) => return result, +/// }; +/// +/// match self.foo(bar, baz) { +/// Ok(val) => { +/// val.forget(&mut *retval); +/// NS_OK +/// } +/// Err(error) => { +/// error!("{}", error); +/// error.into() +/// } +/// } +/// } +/// ``` +/// +/// Which calls a Rustic implementation (that you implement) like the following: +/// +/// ```ignore +/// impl FooBarBaz { +/// fn foo(&self, bar: &nsACString, baz: bool) -> Result, nsresult> { +/// // … +/// } +/// } +/// ``` +/// +/// Notes: +/// +/// On error, the Rustic implementation can return an Err(nsresult) or any +/// other type that implements Into. So you can define and return +/// a custom error type, which the XPCOM stub will convert to nsresult. +/// +/// This macro assumes that all non-null pointer arguments are valid! +/// It does ensure that they aren't null, using the `ensure_param` macro. +/// But it doesn't otherwise check their validity. That makes the function +/// unsafe, so callers must ensure that they only call it with valid pointer +/// arguments. +#[macro_export] +macro_rules! xpcom_method { + // This rule is provided to ensure external modules don't need to import + // internal implementation details of xpcom_method. + // The @ensure_param rule converts raw pointer arguments to references, + // returning NS_ERROR_NULL_POINTER if the argument is_null(). + // + // Notes: + // + // This rule can be called on a non-pointer copy parameter, but there's no + // benefit to doing so. The macro will just set the value of the parameter + // to itself. (This macro does this anyway due to limitations in declarative + // macros; it isn't currently possible to distinguish between pointer and + // copy types when processing a set of parameters.) + // + // The macro currently supports only in-parameters (*const nsIFoo); It + // doesn't (yet?) support out-parameters (*mut nsIFoo). The xpcom_method + // macro itself does, however, support the return value out-parameter. + (@ensure_param $name:ident) => { + let $name = match $crate::Ensure::ensure($name) { + Ok(val) => val, + Err(result) => return result, + }; + }; + + // `#[allow(non_snake_case)]` is used for each method because `$xpcom_name` + // is almost always UpperCamelCase, and Rust gives a warning that it should + // be snake_case. It isn't reasonable to rename the XPCOM methods, so + // silence the warning. + + // A method whose return value is a *mut *const nsISomething type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> *const nsIVariant + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> *const $retval:ty) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut *const $retval) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + val.forget(&mut *retval); + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method whose return value is a *mut nsAString type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> nsAString + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> nsAString) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut nsAString) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + (*retval).assign(&val); + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method whose return value is a *mut nsACString type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> nsACString + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> nsACString) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut nsACString) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + (*retval).assign(&val); + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method whose return value is a non-nsA[C]String *mut type. + // Example: foo => Foo(bar: *const nsACString, baz: bool) -> bool + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*) -> $retval:ty) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)* retval: *mut $retval) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(val) => { + *retval = val; + NS_OK + } + Err(error) => { + error.into() + } + } + } + }; + + // A method that doesn't have a return value. + // Example: foo => Foo(bar: *const nsACString, baz: bool) + ($rust_name:ident => $xpcom_name:ident($($param_name:ident: $param_type:ty),*)) => { + #[allow(non_snake_case)] + unsafe fn $xpcom_name(&self, $($param_name: $param_type,)*) -> nsresult { + $(xpcom_method!(@ensure_param $param_name);)* + match self.$rust_name($($param_name, )*) { + Ok(_) => NS_OK, + Err(error) => { + error.into() + } + } + } + }; +} + +/// A trait that ensures that a raw pointer isn't null and converts it to +/// a reference. Because of limitations in declarative macros, this includes an +/// implementation for types that are Copy, which simply returns the value +/// itself. +#[doc(hidden)] +pub trait Ensure { + unsafe fn ensure(value: T) -> Self; +} + +impl<'a, T: 'a> Ensure<*const T> for Result<&'a T, nsresult> { + unsafe fn ensure(ptr: *const T) -> Result<&'a T, nsresult> { + if ptr.is_null() { + Err(NS_ERROR_NULL_POINTER) + } else { + Ok(&*ptr) + } + } +} + +impl<'a, T: 'a> Ensure<*const T> for Result, nsresult> { + unsafe fn ensure(ptr: *const T) -> Result, nsresult> { + Ok(if ptr.is_null() { None } else { Some(&*ptr) }) + } +} + +impl Ensure for Result { + unsafe fn ensure(copyable: T) -> Result { + Ok(copyable) + } +} diff --git a/xpcom/rust/xpcom/src/promise.rs b/xpcom/rust/xpcom/src/promise.rs new file mode 100644 index 0000000000..0fdab9b6aa --- /dev/null +++ b/xpcom/rust/xpcom/src/promise.rs @@ -0,0 +1,62 @@ +/* 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 https://mozilla.org/MPL/2.0/. */ + +use crate::{ + create_instance, + interfaces::{nsIVariant, nsIWritableVariant}, + RefCounted, +}; + +use cstr::*; + +mod ffi { + use super::*; + + extern "C" { + // These are implemented in dom/promise/Promise.cpp + pub fn DomPromise_AddRef(promise: *const Promise); + pub fn DomPromise_Release(promise: *const Promise); + pub fn DomPromise_RejectWithVariant(promise: *const Promise, variant: *const nsIVariant); + pub fn DomPromise_ResolveWithVariant(promise: *const Promise, variant: *const nsIVariant); + } +} + +#[repr(C)] +pub struct Promise { + private: [u8; 0], + + /// This field is a phantomdata to ensure that the Promise type and any + /// struct containing it is not safe to send across threads, as DOM is + /// generally not threadsafe. + __nosync: ::std::marker::PhantomData<::std::rc::Rc>, +} + +impl Promise { + pub fn reject_with_undefined(&self) { + let variant = create_instance::(cstr!("@mozilla.org/variant;1")) + .expect("Failed to create writable variant"); + unsafe { + variant.SetAsVoid(); + } + self.reject_with_variant(&variant); + } + + pub fn reject_with_variant(&self, variant: &nsIVariant) { + unsafe { ffi::DomPromise_RejectWithVariant(self, variant) } + } + + pub fn resolve_with_variant(&self, variant: &nsIVariant) { + unsafe { ffi::DomPromise_ResolveWithVariant(self, variant) } + } +} + +unsafe impl RefCounted for Promise { + unsafe fn addref(&self) { + ffi::DomPromise_AddRef(self) + } + + unsafe fn release(&self) { + ffi::DomPromise_Release(self) + } +} diff --git a/xpcom/rust/xpcom/src/reexports.rs b/xpcom/rust/xpcom/src/reexports.rs new file mode 100644 index 0000000000..d198899497 --- /dev/null +++ b/xpcom/rust/xpcom/src/reexports.rs @@ -0,0 +1,52 @@ +/* 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/. */ + +//! The automatically generated code from `xpcom_macros` depends on some types +//! which are defined in other libraries which `xpcom` depends on, but which may +//! not be `extern crate`-ed into the crate the macros are expanded into. This +//! module re-exports those types from `xpcom` so that they can be used from the +//! macro. +// re-export libc so it can be used by the procedural macro. +pub extern crate libc; + +pub use nsstring::{nsACString, nsAString, nsCString, nsString}; + +pub use nserror::{nsresult, NS_ERROR_NO_INTERFACE, NS_OK}; + +pub use std::ops::Deref; + +/// Helper method used by the xpcom codegen, it is not public API or meant for +/// calling outside of that context. +/// +/// Takes a reference to the `this` pointer received from XPIDL, and offsets and +/// casts it to a reference to the concrete rust `struct` type, `U`. +/// +/// `vtable_index` is the index, and therefore the offset in pointers, of the +/// vtable for `T` in `U`. +/// +/// A reference to `this` is taken, instead of taking `*const T` by value, to use +/// as a lifetime bound, such that the returned `&U` reference has a bounded +/// lifetime when used to call the implementation method. +#[inline] +pub unsafe fn transmute_from_vtable_ptr<'a, T, U>( + this: &'a *const T, + vtable_index: usize, +) -> &'a U { + &*((*this as *const *const ()).sub(vtable_index) as *const U) +} + +/// On some ABIs, extra information is included before the vtable's function +/// pointers which are used to implement RTTI. We build Gecko with RTTI +/// disabled, however these fields may still be present to support +/// `dynamic_cast` on our rust VTables in case they are accessed. +/// +/// Itanium ABI Layout: https://refspecs.linuxbase.org/cxxabi-1.83.html#vtable +#[repr(C)] +pub struct VTableExtra { + #[cfg(not(windows))] + pub offset: isize, + #[cfg(not(windows))] + pub typeinfo: *const libc::c_void, + pub vtable: T, +} diff --git a/xpcom/rust/xpcom/src/refptr.rs b/xpcom/rust/xpcom/src/refptr.rs new file mode 100644 index 0000000000..8549b6d2f0 --- /dev/null +++ b/xpcom/rust/xpcom/src/refptr.rs @@ -0,0 +1,388 @@ +/* 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/. */ + +use crate::interfaces::nsISupports; +use libc; +use nserror::{nsresult, NS_OK}; +use std::cell::Cell; +use std::convert::TryInto; +use std::fmt; +use std::marker::PhantomData; +use std::mem; +use std::ops::Deref; +use std::ptr::{self, NonNull}; +use std::sync::atomic::{self, AtomicUsize, Ordering}; +use threadbound::ThreadBound; + +// This should match the definition in mfbt/RefCountType.h, modulo the delicate +// effort at maintaining binary compatibility with Microsoft COM on Windows. +pub type MozExternalRefCountType = u32; + +/// A trait representing a type which can be reference counted invasively. +/// The object is responsible for freeing its backing memory when its +/// reference count reaches 0. +pub unsafe trait RefCounted { + /// Increment the reference count. + unsafe fn addref(&self); + /// Decrement the reference count, potentially freeing backing memory. + unsafe fn release(&self); +} + +/// A smart pointer holding a RefCounted object. The object itself manages its +/// own memory. RefPtr will invoke the addref and release methods at the +/// appropriate times to facilitate the bookkeeping. +#[repr(transparent)] +pub struct RefPtr { + _ptr: NonNull, + // Tell dropck that we own an instance of T. + _marker: PhantomData, +} + +impl RefPtr { + /// Construct a new RefPtr from a reference to the refcounted object. + #[inline] + pub fn new(p: &T) -> RefPtr { + unsafe { + p.addref(); + } + RefPtr { + _ptr: p.into(), + _marker: PhantomData, + } + } + + /// Construct a RefPtr from a raw pointer, addrefing it. + #[inline] + pub unsafe fn from_raw(p: *const T) -> Option> { + let ptr = NonNull::new(p as *mut T)?; + ptr.as_ref().addref(); + Some(RefPtr { + _ptr: ptr, + _marker: PhantomData, + }) + } + + /// Construct a RefPtr from a raw pointer, without addrefing it. + #[inline] + pub unsafe fn from_raw_dont_addref(p: *const T) -> Option> { + Some(RefPtr { + _ptr: NonNull::new(p as *mut T)?, + _marker: PhantomData, + }) + } + + /// Write this RefPtr's value into an outparameter. + #[inline] + pub fn forget(self, into: &mut *const T) { + *into = Self::forget_into_raw(self); + } + + #[inline] + pub fn forget_into_raw(this: RefPtr) -> *const T { + let into = &*this as *const T; + mem::forget(this); + into + } +} + +impl Deref for RefPtr { + type Target = T; + #[inline] + fn deref(&self) -> &T { + unsafe { self._ptr.as_ref() } + } +} + +impl Drop for RefPtr { + #[inline] + fn drop(&mut self) { + unsafe { + self._ptr.as_ref().release(); + } + } +} + +impl Clone for RefPtr { + #[inline] + fn clone(&self) -> RefPtr { + RefPtr::new(self) + } +} + +impl fmt::Debug for RefPtr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "RefPtr<{:?}>", self.deref()) + } +} + +// Both `Send` and `Sync` bounds are required for `RefPtr` to implement +// either, as sharing a `RefPtr` also allows transferring ownership, and +// vice-versa. +unsafe impl Send for RefPtr {} +unsafe impl Sync for RefPtr {} + +macro_rules! assert_layout_eq { + ($T:ty, $U:ty) => { + const _: [(); std::mem::size_of::<$T>()] = [(); std::mem::size_of::<$U>()]; + const _: [(); std::mem::align_of::<$T>()] = [(); std::mem::align_of::<$U>()]; + }; +} + +// Assert that `RefPtr` has the correct memory layout. +assert_layout_eq!(RefPtr, *const nsISupports); +// Assert that the null-pointer optimization applies to `RefPtr`. +assert_layout_eq!(RefPtr, Option>); + +/// A wrapper that binds a RefCounted value to its original thread, +/// preventing retrieval from other threads and panicking if the value +/// is dropped on a different thread. +/// +/// These limitations enable values of this type to be Send + Sync, which is +/// useful when creating a struct that holds a RefPtr type while being +/// Send + Sync. Such a struct can hold a ThreadBoundRefPtr type instead. +pub struct ThreadBoundRefPtr(ThreadBound<*const T>); + +impl ThreadBoundRefPtr { + pub fn new(ptr: RefPtr) -> Self { + let raw: *const T = &*ptr; + mem::forget(ptr); + ThreadBoundRefPtr(ThreadBound::new(raw)) + } + + pub fn get_ref(&self) -> Option<&T> { + self.0.get_ref().map(|raw| unsafe { &**raw }) + } +} + +impl Drop for ThreadBoundRefPtr { + fn drop(&mut self) { + unsafe { + RefPtr::from_raw_dont_addref(self.get_ref().expect("drop() called on wrong thread!")); + } + } +} + +/// A helper struct for constructing `RefPtr` from raw pointer outparameters. +/// Holds a `*const T` internally which will be released if non null when +/// destructed, and can be easily transformed into an `Option>`. +/// +/// It many cases it may be easier to use the `getter_addrefs` method. +pub struct GetterAddrefs { + _ptr: *const T, + _marker: PhantomData, +} + +impl GetterAddrefs { + /// Create a `GetterAddrefs`, initializing it with the null pointer. + #[inline] + pub fn new() -> GetterAddrefs { + GetterAddrefs { + _ptr: ptr::null(), + _marker: PhantomData, + } + } + + /// Get a reference to the internal `*const T`. This method is unsafe, + /// as the destructor of this class depends on the internal `*const T` + /// being either a valid reference to a value of type `T`, or null. + #[inline] + pub unsafe fn ptr(&mut self) -> &mut *const T { + &mut self._ptr + } + + /// Get a reference to the internal `*const T` as a `*mut libc::c_void`. + /// This is useful to pass to functions like `GetInterface` which take a + /// void pointer outparameter. + #[inline] + pub unsafe fn void_ptr(&mut self) -> *mut *mut libc::c_void { + &mut self._ptr as *mut *const T as *mut *mut libc::c_void + } + + /// Transform this `GetterAddrefs` into an `Option>`, without + /// performing any addrefs or releases. + #[inline] + pub fn refptr(self) -> Option> { + let p = self._ptr; + // Don't run the destructor because we don't want to release the stored + // pointer. + mem::forget(self); + unsafe { RefPtr::from_raw_dont_addref(p) } + } +} + +impl Drop for GetterAddrefs { + #[inline] + fn drop(&mut self) { + if !self._ptr.is_null() { + unsafe { + (*self._ptr).release(); + } + } + } +} + +/// Helper method for calling XPCOM methods which return a reference counted +/// value through an outparameter. Takes a lambda, which is called with a valid +/// outparameter argument (`*mut *const T`), and returns a `nsresult`. Returns +/// either a `RefPtr` with the value returned from the outparameter, or a +/// `nsresult`. +/// +/// # NOTE: +/// +/// Can return `Err(NS_OK)` if the call succeeded, but the outparameter was set +/// to NULL. +/// +/// # Usage +/// +/// ``` +/// let x: Result, nsresult> = +/// getter_addrefs(|p| iosvc.NewURI(uri, ptr::null(), ptr::null(), p)); +/// ``` +#[inline] +pub fn getter_addrefs(f: F) -> Result, nsresult> +where + F: FnOnce(*mut *const T) -> nsresult, +{ + let mut ga = GetterAddrefs::::new(); + let rv = f(unsafe { ga.ptr() }); + if rv.failed() { + return Err(rv); + } + ga.refptr().ok_or(NS_OK) +} + +/// The type of the reference count type for xpcom structs. +/// +/// `#[xpcom(nonatomic)]` will use this type for the `__refcnt` field. +#[derive(Debug)] +pub struct Refcnt(Cell); +impl Refcnt { + /// Create a new reference count value. This is unsafe as manipulating + /// Refcnt values is an easy footgun. + pub unsafe fn new() -> Self { + Refcnt(Cell::new(0)) + } + + /// Increment the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn inc(&self) -> MozExternalRefCountType { + // XXX: Checked add? + let new = self.0.get() + 1; + self.0.set(new); + new.try_into().unwrap() + } + + /// Decrement the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn dec(&self) -> MozExternalRefCountType { + // XXX: Checked sub? + let new = self.0.get() - 1; + self.0.set(new); + new.try_into().unwrap() + } + + /// Get the current value of the reference count. + pub fn get(&self) -> usize { + self.0.get() + } +} + +/// The type of the atomic reference count used for xpcom structs. +/// +/// `#[xpcom(atomic)]` will use this type for the `__refcnt` field. +/// +/// See `nsISupportsImpl.h`'s `ThreadSafeAutoRefCnt` class for reasoning behind +/// memory ordering decisions. +#[derive(Debug)] +pub struct AtomicRefcnt(AtomicUsize); +impl AtomicRefcnt { + /// Create a new reference count value. This is unsafe as manipulating + /// Refcnt values is an easy footgun. + pub unsafe fn new() -> Self { + AtomicRefcnt(AtomicUsize::new(0)) + } + + /// Increment the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn inc(&self) -> MozExternalRefCountType { + let result = self.0.fetch_add(1, Ordering::Relaxed) + 1; + result.try_into().unwrap() + } + + /// Decrement the reference count. Returns the new reference count. This is + /// unsafe as modifying this value can cause a use-after-free. + pub unsafe fn dec(&self) -> MozExternalRefCountType { + let result = self.0.fetch_sub(1, Ordering::Release) - 1; + if result == 0 { + // We're going to destroy the object on this thread, so we need + // acquire semantics to synchronize with the memory released by + // the last release on other threads, that is, to ensure that + // writes prior to that release are now visible on this thread. + if cfg!(feature = "thread_sanitizer") { + // TSan doesn't understand atomic::fence, so in order to avoid + // a false positive for every time a refcounted object is + // deleted, we replace the fence with an atomic operation. + self.0.load(Ordering::Acquire); + } else { + atomic::fence(Ordering::Acquire); + } + } + result.try_into().unwrap() + } + + /// Get the current value of the reference count. + pub fn get(&self) -> usize { + self.0.load(Ordering::Acquire) + } +} + +#[cfg(feature = "gecko_refcount_logging")] +pub mod trace_refcnt { + extern "C" { + pub fn NS_LogCtor(aPtr: *mut libc::c_void, aTypeName: *const libc::c_char, aSize: u32); + pub fn NS_LogDtor(aPtr: *mut libc::c_void, aTypeName: *const libc::c_char, aSize: u32); + pub fn NS_LogAddRef( + aPtr: *mut libc::c_void, + aRefcnt: usize, + aClass: *const libc::c_char, + aClassSize: u32, + ); + pub fn NS_LogRelease( + aPtr: *mut libc::c_void, + aRefcnt: usize, + aClass: *const libc::c_char, + aClassSize: u32, + ); + } +} + +// stub inline methods for the refcount logging functions for when the feature +// is disabled. +#[cfg(not(feature = "gecko_refcount_logging"))] +pub mod trace_refcnt { + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogCtor(_: *mut libc::c_void, _: *const libc::c_char, _: u32) {} + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogDtor(_: *mut libc::c_void, _: *const libc::c_char, _: u32) {} + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogAddRef( + _: *mut libc::c_void, + _: usize, + _: *const libc::c_char, + _: u32, + ) { + } + #[inline] + #[allow(non_snake_case)] + pub unsafe extern "C" fn NS_LogRelease( + _: *mut libc::c_void, + _: usize, + _: *const libc::c_char, + _: u32, + ) { + } +} diff --git a/xpcom/rust/xpcom/src/statics.rs b/xpcom/rust/xpcom/src/statics.rs new file mode 100644 index 0000000000..27f66fdb4b --- /dev/null +++ b/xpcom/rust/xpcom/src/statics.rs @@ -0,0 +1,75 @@ +/* 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/. */ + +use crate::interfaces::{nsIComponentManager, nsIComponentRegistrar, nsIServiceManager}; +use crate::{GetterAddrefs, RefPtr, XpCom}; +use std::ffi::CStr; + +/// Get a reference to the global `nsIComponentManager`. +/// +/// Can return `None` during shutdown. +#[inline] +pub fn component_manager() -> Option> { + unsafe { RefPtr::from_raw(Gecko_GetComponentManager()) } +} + +/// Get a reference to the global `nsIServiceManager`. +/// +/// Can return `None` during shutdown. +#[inline] +pub fn service_manager() -> Option> { + unsafe { RefPtr::from_raw(Gecko_GetServiceManager()) } +} + +/// Get a reference to the global `nsIComponentRegistrar` +/// +/// Can return `None` during shutdown. +#[inline] +pub fn component_registrar() -> Option> { + unsafe { RefPtr::from_raw(Gecko_GetComponentRegistrar()) } +} + +/// Helper for calling `nsIComponentManager::CreateInstanceByContractID` on the +/// global `nsIComponentRegistrar`. +/// +/// This method is similar to `do_CreateInstance` in C++. +#[inline] +pub fn create_instance(id: &CStr) -> Option> { + unsafe { + let mut ga = GetterAddrefs::::new(); + if component_manager()? + .CreateInstanceByContractID(id.as_ptr(), &T::IID, ga.void_ptr()) + .succeeded() + { + ga.refptr() + } else { + None + } + } +} + +/// Helper for calling `nsIServiceManager::GetServiceByContractID` on the global +/// `nsIServiceManager`. +/// +/// This method is similar to `do_GetService` in C++. +#[inline] +pub fn get_service(id: &CStr) -> Option> { + unsafe { + let mut ga = GetterAddrefs::::new(); + if service_manager()? + .GetServiceByContractID(id.as_ptr(), &T::IID, ga.void_ptr()) + .succeeded() + { + ga.refptr() + } else { + None + } + } +} + +extern "C" { + fn Gecko_GetComponentManager() -> *const nsIComponentManager; + fn Gecko_GetServiceManager() -> *const nsIServiceManager; + fn Gecko_GetComponentRegistrar() -> *const nsIComponentRegistrar; +} diff --git a/xpcom/rust/xpcom/xpcom_macros/Cargo.toml b/xpcom/rust/xpcom/xpcom_macros/Cargo.toml new file mode 100644 index 0000000000..4b5bcdc315 --- /dev/null +++ b/xpcom/rust/xpcom/xpcom_macros/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "xpcom_macros" +version = "0.1.0" +authors = ["Nika Layzell "] +edition = "2018" +license = "MPL-2.0" + +[lib] +proc-macro = true + +[dependencies] +syn = "1" +quote = "1" +proc-macro2 = "1" +lazy_static = "1.0" +mozbuild = "0.1" diff --git a/xpcom/rust/xpcom/xpcom_macros/src/lib.rs b/xpcom/rust/xpcom/xpcom_macros/src/lib.rs new file mode 100644 index 0000000000..2511ba9103 --- /dev/null +++ b/xpcom/rust/xpcom/xpcom_macros/src/lib.rs @@ -0,0 +1,818 @@ +/* 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/. */ + +//! This crate provides the `#[xpcom]` custom attribute. This custom attribute +//! is used in order to implement [`xpcom`] interfaces. +//! +//! # Usage +//! +//! The easiest way to explain this crate is probably with a usage example. I'll +//! show you the example, and then we'll destructure it and walk through what +//! each component is doing. +//! +//! ```ignore +//! // Declaring an XPCOM Struct +//! #[xpcom(implement(nsIRunnable), atomic)] +//! struct ImplRunnable { +//! i: i32, +//! } +//! +//! // Implementing methods on an XPCOM Struct +//! impl ImplRunnable { +//! unsafe fn Run(&self) -> nsresult { +//! println!("{}", self.i); +//! NS_OK +//! } +//! } +//! ``` +//! +//! ## Declaring an XPCOM Struct +//! +//! ```ignore +//! // This derive should be placed on the initialization struct in order to +//! // trigger the procedural macro. +//! #[xpcom( +//! // The implement argument should be passed the names of the IDL +//! // interfaces which you want to implement. These can be separated by +//! // commas if you want to implement multiple interfaces. +//! // +//! // Some methods use types which we cannot bind to in rust. Interfaces +//! // like those cannot be implemented, and a compile-time error will occur +//! // if they are listed in this attribute. +//! implement(nsIRunnable), +//! +//! // The refcount kind can be specified as one of the following values: +//! // * `atomic` == atomic reference count +//! // ~= NS_DECL_THREADSAFE_ISUPPORTS in C++ +//! // * `nonatomic` == non atomic reference count +//! // ~= NS_DECL_ISUPPORTS in C++ +//! atomic, +//! )] +//! +//! // It is a compile time error to put the `#[xpcom]` attribute on +//! // an enum, union, or tuple struct. +//! // +//! // The macro will generate both the named struct, as well as a version with +//! // its name prefixed with `Init` which can be used to initialize the type. +//! struct ImplRunnable { +//! i: i32, +//! } +//! ``` +//! +//! The above example will generate `ImplRunnable` and `InitImplRunnable` +//! structs. The `ImplRunnable` struct will implement the [`nsIRunnable`] XPCOM +//! interface, and cannot be constructed directly. +//! +//! The following methods will be automatically implemented on it: +//! +//! ```ignore +//! // Automatic nsISupports implementation +//! unsafe fn AddRef(&self) -> MozExternalRefCountType; +//! unsafe fn Release(&self) -> MozExternalRefCountType; +//! unsafe fn QueryInterface(&self, uuid: &nsIID, result: *mut *mut libc::c_void) -> nsresult; +//! +//! // Allocates and initializes a new instance of this type. The values will +//! // be moved from the `Init` struct which is passed in. +//! fn allocate(init: InitImplRunnable) -> RefPtr; +//! +//! // Helper for performing the `query_interface` operation to case to a +//! // specific interface. +//! fn query_interface(&self) -> Option>; +//! +//! // Coerce function for cheaply casting to our base interfaces. +//! fn coerce(&self) -> &T; +//! ``` +//! +//! The [`RefCounted`] interface will also be implemented, so that the type can +//! be used within the [`RefPtr`] type. +//! +//! The `coerce` and `query_interface` methods are backed by the generated +//! `*Coerce` trait. This trait is impl-ed for every interface implemented by +//! the trait. For example: +//! +//! ```ignore +//! pub trait ImplRunnableCoerce { +//! fn coerce_from(x: &ImplRunnable) -> &Self; +//! } +//! impl ImplRunnableCoerce for nsIRunnable { .. } +//! impl ImplRunnableCoerce for nsISupports { .. } +//! ``` +//! +//! ## Implementing methods on an XPCOM Struct +//! +//! ```ignore +//! // Methods should be implemented directly on the generated struct. All +//! // methods other than `AddRef`, `Release`, and `QueryInterface` must be +//! // implemented manually. +//! impl ImplRunnable { +//! // The method should have the same name as the corresponding C++ method. +//! unsafe fn Run(&self) -> nsresult { +//! // Fields defined on the template struct will be directly on the +//! // generated struct. +//! println!("{}", self.i); +//! NS_OK +//! } +//! } +//! ``` +//! +//! XPCOM methods implemented in Rust have signatures similar to methods +//! implemented in C++. +//! +//! ```ignore +//! // nsISupports foo(in long long bar, in AString baz); +//! unsafe fn Foo(&self, bar: i64, baz: *const nsAString, +//! _retval: *mut *const nsISupports) -> nsresult; +//! +//! // AString qux(in nsISupports ham); +//! unsafe fn Qux(&self, ham: *const nsISupports, +//! _retval: *mut nsAString) -> nsresult; +//! ``` +//! +//! This is a little tedious, so the `xpcom_method!` macro provides a convenient +//! way to generate wrappers around more idiomatic Rust methods. +//! +//! [`xpcom`]: ../xpcom/index.html +//! [`nsIRunnable`]: ../xpcom/struct.nsIRunnable.html +//! [`RefCounted`]: ../xpcom/struct.RefCounted.html +//! [`RefPtr`]: ../xpcom/struct.RefPtr.html + +use lazy_static::lazy_static; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use std::collections::{HashMap, HashSet}; +use syn::punctuated::Punctuated; +use syn::{ + parse_macro_input, parse_quote, AttributeArgs, Field, Fields, Ident, ItemStruct, Meta, + MetaList, NestedMeta, Path, Token, Type, +}; + +macro_rules! bail { + (@($t:expr), $s:expr) => { + return Err(syn::Error::new_spanned(&$t, &$s[..])) + }; + (@($t:expr), $f:expr, $($e:expr),*) => { + return Err(syn::Error::new_spanned(&$t, &format!($f, $($e),*)[..])) + }; + ($s:expr) => { + return Err(syn::Error::new(Span::call_site(), &$s[..])) + }; + ($f:expr, $($e:expr),*) => { + return Err(syn::Error::new(Span::call_site(), &format!($f, $($e),*)[..])) + }; +} + +/* These are the structs generated by the rust_macros.py script */ + +/// A single parameter to an XPCOM method. +#[derive(Debug)] +struct Param { + name: &'static str, + ty: &'static str, +} + +/// A single method on an XPCOM interface. +#[derive(Debug)] +struct Method { + name: &'static str, + params: &'static [Param], + ret: &'static str, +} + +/// An XPCOM interface. `methods` will be `Err("reason")` if the interface +/// cannot be implemented in rust code. +#[derive(Debug)] +struct Interface { + name: &'static str, + base: Option<&'static str>, + methods: Result<&'static [Method], &'static str>, +} + +impl Interface { + fn base(&self) -> Option<&'static Interface> { + Some(IFACES[self.base?]) + } + + fn methods(&self) -> Result<&'static [Method], syn::Error> { + match self.methods { + Ok(methods) => Ok(methods), + Err(reason) => Err(syn::Error::new( + Span::call_site(), + &format!( + "Interface {} cannot be implemented in rust \ + because {} is not supported yet", + self.name, reason + ), + )), + } + } +} + +lazy_static! { + /// This item contains the information generated by the procedural macro in + /// the form of a `HashMap` from interface names to their descriptions. + static ref IFACES: HashMap<&'static str, &'static Interface> = { + let lists: &[&[Interface]] = + include!(mozbuild::objdir_path!("dist/xpcrs/bt/all.rs")); + + let mut hm = HashMap::new(); + for &list in lists { + for iface in list { + hm.insert(iface.name, iface); + } + } + hm + }; +} + +/// The type of the reference count to use for the struct. +#[derive(Debug, Eq, PartialEq, Copy, Clone)] +enum RefcntKind { + Atomic, + NonAtomic, +} + +/// Produces the tokens for the type representation. +impl ToTokens for RefcntKind { + fn to_tokens(&self, tokens: &mut TokenStream) { + match *self { + RefcntKind::NonAtomic => quote!(xpcom::Refcnt).to_tokens(tokens), + RefcntKind::Atomic => quote!(xpcom::AtomicRefcnt).to_tokens(tokens), + } + } +} + +/// Extract the fields list from the input struct. +fn get_fields(si: &ItemStruct) -> Result<&Punctuated, syn::Error> { + match si.fields { + Fields::Named(ref named) => Ok(&named.named), + _ => bail!(@(si), "The initializer struct must be a standard named \ + value struct definition"), + } +} + +/// Takes the template struct in, and generates `ItemStruct` for the "real" and +/// "init" structs. +fn gen_structs( + template: &ItemStruct, + bases: &[&Interface], + refcnt_ty: RefcntKind, +) -> Result<(ItemStruct, ItemStruct), syn::Error> { + let real_ident = &template.ident; + let init_ident = format_ident!("Init{}", real_ident); + let vis = &template.vis; + + let bases = bases.iter().map(|base| { + let ident = format_ident!("__base_{}", base.name); + let vtable = format_ident!("{}VTable", base.name); + quote!(#ident : *const xpcom::interfaces::#vtable) + }); + + let fields = get_fields(template)?; + let (impl_generics, _, where_clause) = template.generics.split_for_impl(); + Ok(( + parse_quote! { + #[repr(C)] + #vis struct #real_ident #impl_generics #where_clause { + #(#bases,)* + __refcnt: #refcnt_ty, + #fields + } + }, + parse_quote! { + #vis struct #init_ident #impl_generics #where_clause { + #fields + } + }, + )) +} + +/// Generates the `extern "system"` methods which are actually included in the +/// VTable for the given interface. +/// +/// `idx` must be the offset in pointers of the pointer to this vtable in the +/// struct `real`. This is soundness-critical, as it will be used to offset +/// pointers received from xpcom back to the concrete implementation. +fn gen_vtable_methods( + real: &ItemStruct, + iface: &Interface, + vtable_index: usize, +) -> Result { + let base_ty = format_ident!("{}", iface.name); + + let base_methods = if let Some(base) = iface.base() { + gen_vtable_methods(real, base, vtable_index)? + } else { + quote! {} + }; + + let ty_name = &real.ident; + let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); + + let mut method_defs = Vec::new(); + for method in iface.methods()? { + let ret = syn::parse_str::(method.ret)?; + + let mut params = Vec::new(); + let mut args = Vec::new(); + for param in method.params { + let name = format_ident!("{}", param.name); + let ty = syn::parse_str::(param.ty)?; + + params.push(quote! {#name : #ty,}); + args.push(quote! {#name,}); + } + + let name = format_ident!("{}", method.name); + method_defs.push(quote! { + unsafe extern "system" fn #name #impl_generics ( + this: *const #base_ty, #(#params)* + ) -> #ret #where_clause { + let this: &#ty_name #ty_generics = + ::xpcom::reexports::transmute_from_vtable_ptr(&this, #vtable_index); + this.#name(#(#args)*) + } + }); + } + + Ok(quote! { + #base_methods + #(#method_defs)* + }) +} + +/// Generates the VTable for a given base interface. This assumes that the +/// implementations of each of the `extern "system"` methods are in scope. +fn gen_inner_vtable(real: &ItemStruct, iface: &Interface) -> Result { + let vtable_ty = format_ident!("{}VTable", iface.name); + + // Generate the vtable for the base interface. + let base_vtable = if let Some(base) = iface.base() { + let vt = gen_inner_vtable(real, base)?; + quote! {__base: #vt,} + } else { + quote! {} + }; + + // Include each of the method definitions for this interface. + let (_, ty_generics, _) = real.generics.split_for_impl(); + let turbofish = ty_generics.as_turbofish(); + let vtable_init = iface + .methods()? + .into_iter() + .map(|method| { + let name = format_ident!("{}", method.name); + quote! { #name : #name #turbofish, } + }) + .collect::>(); + + Ok(quote!(#vtable_ty { + #base_vtable + #(#vtable_init)* + })) +} + +fn gen_root_vtable( + real: &ItemStruct, + base: &Interface, + idx: usize, +) -> Result { + let field = format_ident!("__base_{}", base.name); + let vtable_ty = format_ident!("{}VTable", base.name); + + let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); + let turbofish = ty_generics.as_turbofish(); + + let methods = gen_vtable_methods(real, base, idx)?; + let vtable = gen_inner_vtable(real, base)?; + + // Define the `recover_self` method. This performs an offset calculation to + // recover a pointer to the original struct from a pointer to the given + // VTable field. + Ok(quote! {#field: { + // The method implementations which will be used to build the vtable. + #methods + + // The actual VTable definition. This is in a separate method in order + // to allow it to be generic. + #[inline] + fn get_vtable #impl_generics () -> &'static ::xpcom::reexports::VTableExtra<#vtable_ty> #where_clause { + &::xpcom::reexports::VTableExtra { + #[cfg(not(windows))] + offset: { + // NOTE: workaround required to avoid depending on the + // unstable const expression feature `const {}`. + const OFFSET: isize = -((::std::mem::size_of::() * #idx) as isize); + OFFSET + }, + #[cfg(not(windows))] + typeinfo: 0 as *const _, + vtable: #vtable, + } + } + &get_vtable #turbofish ().vtable + },}) +} + +/// Generate the cast implementations. This generates the implementation details +/// for the `Coerce` trait, and the `QueryInterface` method. The first return +/// value is the `QueryInterface` implementation, and the second is the `Coerce` +/// implementation. +fn gen_casts( + seen: &mut HashSet<&'static str>, + iface: &Interface, + real: &ItemStruct, + coerce_name: &Ident, + vtable_field: &Ident, +) -> Result<(TokenStream, TokenStream), syn::Error> { + if !seen.insert(iface.name) { + return Ok((quote! {}, quote! {})); + } + + // Generate the cast implementations for the base interfaces. + let (base_qi, base_coerce) = if let Some(base) = iface.base() { + gen_casts(seen, base, real, coerce_name, vtable_field)? + } else { + (quote! {}, quote! {}) + }; + + // Add the if statment to QueryInterface for the base class. + let base_name = format_ident!("{}", iface.name); + + let qi = quote! { + #base_qi + if *uuid == #base_name::IID { + // Implement QueryInterface in terms of coercions. + self.addref(); + *result = self.coerce::<#base_name>() + as *const #base_name + as *const ::xpcom::reexports::libc::c_void + as *mut ::xpcom::reexports::libc::c_void; + return ::xpcom::reexports::NS_OK; + } + }; + + // Add an implementation of the `*Coerce` trait for the base interface. + let name = &real.ident; + let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); + let coerce = quote! { + #base_coerce + + impl #impl_generics #coerce_name #ty_generics for ::xpcom::interfaces::#base_name #where_clause { + fn coerce_from(v: &#name #ty_generics) -> &Self { + unsafe { + // Get the address of the VTable field. This should be a + // pointer to a pointer to a vtable, which we can then cast + // into a pointer to our interface. + &*(&(v.#vtable_field) + as *const *const _ + as *const ::xpcom::interfaces::#base_name) + } + } + } + }; + + Ok((qi, coerce)) +} + +fn check_generics(generics: &syn::Generics) -> Result<(), syn::Error> { + for param in &generics.params { + let tp = match param { + syn::GenericParam::Type(tp) => tp, + syn::GenericParam::Lifetime(lp) => bail!( + @(lp), + "Cannot use #[xpcom] on types with lifetime parameters. \ + Implementors of XPCOM interfaces must not contain non-'static \ + lifetimes.", + ), + // XXX: Once const generics become stable, it may be as simple as + // removing this bail! to support them. + syn::GenericParam::Const(cp) => { + bail!(@(cp), "Cannot use #[xpcom] on types with const generics.") + } + }; + + let mut static_lt = false; + for bound in &tp.bounds { + match bound { + syn::TypeParamBound::Lifetime(lt) if lt.ident == "static" => { + static_lt = true; + break; + } + _ => {} + } + } + + if !static_lt { + bail!( + @(param), + "Every generic parameter for xpcom implementation must have a \ + 'static lifetime bound declared in the generics. Implicit \ + lifetime bounds or lifetime bounds in where clauses are not \ + detected by the macro and will be ignored. \ + Implementors of XPCOM interfaces must not contain non-'static \ + lifetimes.", + ); + } + } + Ok(()) +} + +#[derive(Default)] +struct Options { + bases: Vec<&'static Interface>, + refcnt: Option, +} + +impl Options { + fn parse_path_arg(&mut self, path: &Path) -> Result<(), syn::Error> { + if path.is_ident("atomic") || path.is_ident("nonatomic") { + if self.refcnt.is_some() { + bail!(@(path), "Duplicate refcnt atomicity specifier"); + } + self.refcnt = Some(if path.is_ident("atomic") { + RefcntKind::Atomic + } else { + RefcntKind::NonAtomic + }); + return Ok(()); + } + bail!(@(path), "Unexpected path argument to #[xpcom]"); + } + + fn parse_list_arg(&mut self, list: &MetaList) -> Result<(), syn::Error> { + if list.path.is_ident("implement") { + for item in list.nested.iter() { + let iface = match *item { + NestedMeta::Meta(syn::Meta::Path(ref iface)) => iface, + _ => bail!(@(item), "Expected interface name to implement"), + }; + let ident = match iface.get_ident() { + Some(ref iface) => iface.to_string(), + _ => bail!(@(iface), "Interface name must be unqualified"), + }; + if let Some(&iface) = IFACES.get(ident.as_str()) { + self.bases.push(iface); + } else { + bail!(@(item), "Invalid base interface `{}`", ident); + } + } + return Ok(()); + } + bail!(@(list), "Unexpected list argument to #[xpcom]"); + } + + fn parse(&mut self, args: &AttributeArgs) -> Result<(), syn::Error> { + for arg in args { + match arg { + NestedMeta::Meta(Meta::Path(path)) => self.parse_path_arg(path)?, + NestedMeta::Meta(Meta::List(list)) => self.parse_list_arg(list)?, + NestedMeta::Meta(Meta::NameValue(name_value)) => { + bail!(@(name_value), "Unexpected name-value argument to #[xpcom]") + } + NestedMeta::Lit(lit) => bail!(@(lit), "Unexpected literal argument to #[xpcom]"), + } + } + + if self.bases.is_empty() { + bail!( + "Types with #[xpcom(..)] must implement at least one \ + interface. Interfaces can be implemented by adding an \ + implements(nsIFoo, nsIBar) parameter to the #[xpcom] attribute" + ); + } + + if self.refcnt.is_none() { + bail!("Must specify refcnt kind in #[xpcom] attribute"); + } + + Ok(()) + } +} + +/// The root xpcom procedural macro definition. +fn xpcom_impl(args: AttributeArgs, template: ItemStruct) -> Result { + let mut options = Options::default(); + options.parse(&args)?; + + check_generics(&template.generics)?; + + let bases = options.bases; + + // Ensure that all our base interface methods have unique names. + let mut method_names = HashMap::new(); + for base in &bases { + for method in base.methods()? { + if let Some(existing) = method_names.insert(method.name, base.name) { + bail!( + "The method `{0}` is declared on both `{1}` and `{2}`, + but a Rust type cannot implement two methods with the \ + same name. You can add the `[binaryname(Renamed{0})]` \ + XPIDL attribute to one of the declarations to rename it.", + method.name, + existing, + base.name + ); + } + } + } + + // Determine what reference count type to use, and generate the real struct. + let refcnt_ty = options.refcnt.unwrap(); + let (real, init) = gen_structs(&template, &bases, refcnt_ty)?; + + let name_init = &init.ident; + let name = &real.ident; + let coerce_name = format_ident!("{}Coerce", name); + + // Generate a VTable for each of the base interfaces. + let mut vtables = Vec::new(); + for (idx, base) in bases.iter().enumerate() { + vtables.push(gen_root_vtable(&real, base, idx)?); + } + + // Generate the field initializers for the final struct, moving each field + // out of the original __init struct. + let inits = get_fields(&init)?.iter().map(|field| { + let id = &field.ident; + quote! { #id : __init.#id, } + }); + + let vis = &real.vis; + + // Generate the implementation for QueryInterface and Coerce. + let mut seen = HashSet::new(); + let mut qi_impl = Vec::new(); + let mut coerce_impl = Vec::new(); + for base in &bases { + let (qi, coerce) = gen_casts( + &mut seen, + base, + &real, + &coerce_name, + &format_ident!("__base_{}", base.name), + )?; + qi_impl.push(qi); + coerce_impl.push(coerce); + } + + let size_for_logs = if real.generics.params.is_empty() { + quote!(::std::mem::size_of::() as u32) + } else { + // Refcount logging requires all types with the same name to have the + // same size, and generics aren't taken into account when creating our + // name string, so we need to make sure that all possible instantiations + // report the same size. To do that, we fake a size based on the number + // of vtable pointers and the known refcount field. + let fake_size_npointers = bases.len() + 1; + quote!((::std::mem::size_of::() * #fake_size_npointers) as u32) + }; + + let (impl_generics, ty_generics, where_clause) = real.generics.split_for_impl(); + let name_for_logs = quote!( + concat!(module_path!(), "::", stringify!(#name #ty_generics), "\0").as_ptr() + as *const ::xpcom::reexports::libc::c_char + ); + Ok(quote! { + #init + + #real + + impl #impl_generics #name #ty_generics #where_clause { + /// This method is used for + fn allocate(__init: #name_init #ty_generics) -> ::xpcom::RefPtr { + #[allow(unused_imports)] + use ::xpcom::*; + #[allow(unused_imports)] + use ::xpcom::interfaces::*; + #[allow(unused_imports)] + use ::xpcom::reexports::{ + libc, nsACString, nsAString, nsCString, nsString, nsresult + }; + + // Helper for asserting that for all instantiations, this + // object has the 'static lifetime. + fn xpcom_types_must_be_static(t: &T) {} + + unsafe { + // NOTE: This is split into multiple lines to make the + // output more readable. + let value = #name { + #(#vtables)* + __refcnt: #refcnt_ty::new(), + #(#inits)* + }; + let boxed = ::std::boxed::Box::new(value); + xpcom_types_must_be_static(&*boxed); + let raw = ::std::boxed::Box::into_raw(boxed); + ::xpcom::RefPtr::from_raw(raw).unwrap() + } + } + + /// Automatically generated implementation of AddRef for nsISupports. + #vis unsafe fn AddRef(&self) -> ::xpcom::MozExternalRefCountType { + let new = self.__refcnt.inc(); + ::xpcom::trace_refcnt::NS_LogAddRef( + self as *const _ as *mut ::xpcom::reexports::libc::c_void, + new as usize, + #name_for_logs, + #size_for_logs, + ); + new + } + + /// Automatically generated implementation of Release for nsISupports. + #vis unsafe fn Release(&self) -> ::xpcom::MozExternalRefCountType { + let new = self.__refcnt.dec(); + ::xpcom::trace_refcnt::NS_LogRelease( + self as *const _ as *mut ::xpcom::reexports::libc::c_void, + new as usize, + #name_for_logs, + #size_for_logs, + ); + if new == 0 { + // dealloc + ::std::mem::drop(::std::boxed::Box::from_raw(self as *const Self as *mut Self)); + } + new + } + + /// Automatically generated implementation of QueryInterface for + /// nsISupports. + #vis unsafe fn QueryInterface(&self, + uuid: *const ::xpcom::nsIID, + result: *mut *mut ::xpcom::reexports::libc::c_void) + -> ::xpcom::reexports::nsresult { + #[allow(unused_imports)] + use ::xpcom::*; + #[allow(unused_imports)] + use ::xpcom::interfaces::*; + + #(#qi_impl)* + + ::xpcom::reexports::NS_ERROR_NO_INTERFACE + } + + /// Perform a QueryInterface call on this object, attempting to + /// dynamically cast it to the requested interface type. Returns + /// Some(RefPtr) if the cast succeeded, and None otherwise. + #vis fn query_interface(&self) + -> ::std::option::Option<::xpcom::RefPtr> + { + let mut ga = ::xpcom::GetterAddrefs::::new(); + unsafe { + if self.QueryInterface(&XPCOM_InterfaceType::IID, ga.void_ptr()).succeeded() { + ga.refptr() + } else { + None + } + } + } + + /// Coerce this type safely to any of the interfaces which it + /// implements without `AddRef`ing it. + #vis fn coerce(&self) -> &XPCOM_InterfaceType { + XPCOM_InterfaceType::coerce_from(self) + } + } + + /// This trait is implemented on the interface types which this + /// `#[xpcom]` type can be safely ane cheaply coerced to using the + /// `coerce` method. + /// + /// The trait and its method should usually not be used directly, but + /// rather acts as a trait bound and implementation for the `coerce` + /// methods. + #[doc(hidden)] + #vis trait #coerce_name #impl_generics #where_clause { + /// Convert a value of the `#[xpcom]` type into the implementing + /// interface type. + fn coerce_from(v: &#name #ty_generics) -> &Self; + } + + #(#coerce_impl)* + + unsafe impl #impl_generics ::xpcom::RefCounted for #name #ty_generics #where_clause { + unsafe fn addref(&self) { + self.AddRef(); + } + + unsafe fn release(&self) { + self.Release(); + } + } + }) +} + +#[proc_macro_attribute] +pub fn xpcom( + args: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + let args = parse_macro_input!(args as AttributeArgs); + let input = parse_macro_input!(input as ItemStruct); + match xpcom_impl(args, input) { + Ok(ts) => ts.into(), + Err(err) => err.to_compile_error().into(), + } +} diff --git a/xpcom/string/README.html b/xpcom/string/README.html new file mode 100644 index 0000000000..ea81688121 --- /dev/null +++ b/xpcom/string/README.html @@ -0,0 +1,11 @@ + + + +

managing sequences of characters

+

+ +

+ + diff --git a/xpcom/string/RustRegex.h b/xpcom/string/RustRegex.h new file mode 100644 index 0000000000..80b8140bb5 --- /dev/null +++ b/xpcom/string/RustRegex.h @@ -0,0 +1,707 @@ +/* -*- 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/. */ + +#ifndef mozilla_RustRegex_h +#define mozilla_RustRegex_h + +#include "nsPrintfCString.h" +#include "nsTArray.h" +#include "rure.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +// This header is a thin wrapper around the `rure.h` header file, which declares +// the C API for interacting with the rust `regex` crate. This is intended to +// make the type more ergonomic to use with mozilla types. + +class RustRegex; +class RustRegexSet; +class RustRegexOptions; +class RustRegexCaptures; +class RustRegexIter; +class RustRegexIterCaptureNames; + +using RustRegexMatch = rure_match; + +/* + * RustRegexCaptures represents storage for sub-capture locations of a match. + * + * Computing the capture groups of a match can carry a significant performance + * penalty, so their use in the API is optional. + * + * A RustRegexCaptures value may outlive its corresponding RustRegex and can be + * freed independently. + * + * It is not safe to use from multiple threads simultaneously. + */ +class RustRegexCaptures final { + public: + RustRegexCaptures() = default; + + // Check if the `RustRegexCaptures` object is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * CaptureAt returns Some if and only if the capturing group at the + * index given was part of the match. If so, the returned RustRegexMatch + * object contains the start and end offsets (in bytes) of the match. + * + * If no capture group with the index aIdx exists, or the group was not part + * of the match, then Nothing is returned. (A capturing group exists if and + * only if aIdx is less than Length().) + * + * Note that index 0 corresponds to the full match. + */ + Maybe CaptureAt(size_t aIdx) const { + RustRegexMatch match; + if (mPtr && rure_captures_at(mPtr.get(), aIdx, &match)) { + return Some(match); + } + return Nothing(); + } + Maybe operator[](size_t aIdx) const { + return CaptureAt(aIdx); + } + + /* + * Returns the number of capturing groups in this `RustRegexCaptures`. + */ + size_t Length() const { return mPtr ? rure_captures_len(mPtr.get()) : 0; } + + private: + friend class RustRegex; + friend class RustRegexIter; + + explicit RustRegexCaptures(rure* aRe) + : mPtr(aRe ? rure_captures_new(aRe) : nullptr) {} + + struct Deleter { + void operator()(rure_captures* ptr) const { rure_captures_free(ptr); } + }; + UniquePtr mPtr; +}; + +/* + * RustRegexIterCaptureNames is an iterator over the list of capture group names + * in this particular RustRegex. + * + * A RustRegexIterCaptureNames value may not outlive its corresponding + * RustRegex, and should be destroyed before its corresponding RustRegex is + * destroyed. + * + * It is not safe to use from multiple threads simultaneously. + */ +class RustRegexIterCaptureNames { + public: + RustRegexIterCaptureNames() = delete; + + // Check if the `RustRegexIterCaptureNames` object is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * Advances the iterator and returns true if and only if another capture group + * name exists. + * + * The value of the capture group name is written to the provided pointer. + */ + mozilla::Maybe Next() { + char* next = nullptr; + if (mPtr && rure_iter_capture_names_next(mPtr.get(), &next)) { + return Some(next); + } + return Nothing(); + } + + private: + friend class RustRegex; + + explicit RustRegexIterCaptureNames(rure* aRe) + : mPtr(aRe ? rure_iter_capture_names_new(aRe) : nullptr) {} + + struct Deleter { + void operator()(rure_iter_capture_names* ptr) const { + rure_iter_capture_names_free(ptr); + } + }; + UniquePtr mPtr; +}; + +/* + * RustRegexIter is an iterator over successive non-overlapping matches in a + * particular haystack. + * + * A RustRegexIter value may not outlive its corresponding RustRegex and should + * be destroyed before its corresponding RustRegex is destroyed. + * + * It is not safe to use from multiple threads simultaneously. + */ +class RustRegexIter { + public: + RustRegexIter() = delete; + + // Check if the `RustRegexIter` object is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * Next() returns Some if and only if this regex matches anywhere in haystack. + * The returned RustRegexMatch object contains the start and end offsets (in + * bytes) of the match. + * + * If no match is found, then subsequent calls will return Nothing() + * indefinitely. + * + * Next() should be preferred to NextCaptures() since it may be faster. + * + * N.B. The performance of this search is not impacted by the presence of + * capturing groups in your regular expression. + */ + mozilla::Maybe Next() { + RustRegexMatch match{}; + if (mPtr && + rure_iter_next(mPtr.get(), mHaystackPtr, mHaystackSize, &match)) { + return Some(match); + } + return Nothing(); + } + + /* + * NextCaptures returns a valid RustRegexCaptures if and only if this regex + * matches anywhere in haystack. If a match is found, then all of its capture + * locations are stored in the returned RustRegexCaptures object. + * + * If no match is found, then subsequent calls will return an invalid + * `RustRegexCaptures` indefinitely. + * + * Only use this function if you specifically need access to capture + * locations. It is not necessary to use this function just because your + * regular expression contains capturing groups. + * + * Capture locations can be accessed using the methods on RustRegexCaptures. + * + * N.B. The performance of this search can be impacted by the number of + * capturing groups. If you're using this function, it may be beneficial to + * use non-capturing groups (e.g., `(?:re)`) where possible. + */ + RustRegexCaptures NextCaptures() { + RustRegexCaptures captures(mRe); + if (mPtr && rure_iter_next_captures(mPtr.get(), mHaystackPtr, mHaystackSize, + captures.mPtr.get())) { + return captures; + } + return {}; + } + + private: + friend class RustRegex; + RustRegexIter(rure* aRe, const std::string_view& aHaystack) + : mRe(aRe), + mHaystackPtr(reinterpret_cast(aHaystack.data())), + mHaystackSize(aHaystack.size()), + mPtr(aRe ? rure_iter_new(aRe) : nullptr) {} + + rure* MOZ_NON_OWNING_REF mRe; + const uint8_t* MOZ_NON_OWNING_REF mHaystackPtr; + size_t mHaystackSize; + + struct Deleter { + void operator()(rure_iter* ptr) const { rure_iter_free(ptr); } + }; + UniquePtr mPtr; +}; + +/* + * RustRegexOptions is the set of configuration options for compiling a regular + * expression. + * + * All flags on this type can be used to set default flags while compiling, and + * can be toggled in the expression itself using standard syntax, e.g. `(?i)` + * turns case-insensitive matching on, and `(?-i)` disables it. + * + * In addition, two non-flag options are available: setting the size limit of + * the compiled program and setting the size limit of the cache of states that + * the DFA uses while searching. + * + * For most uses, the default settings will work fine, and a default-constructed + * RustRegexOptions can be passed. + */ +class RustRegexOptions { + public: + RustRegexOptions() = default; + + /* + * Set the value for the case insensitive (i) flag. + * + * When enabled, letters in the pattern will match both upper case and lower + * case variants. + */ + RustRegexOptions& CaseInsensitive(bool aYes) { + return SetFlag(aYes, RURE_FLAG_CASEI); + } + + /* + * Set the value for the multi-line matching (m) flag. + * + * When enabled, ^ matches the beginning of lines and $ matches the end of + * lines. + * + * By default, they match beginning/end of the input. + */ + RustRegexOptions& MultiLine(bool aYes) { + return SetFlag(aYes, RURE_FLAG_MULTI); + } + + /* + * Set the value for the any character (s) flag, where in . matches anything + * when s is set and matches anything except for new line when it is not set + * (the default). + * + * N.B. “matches anything†means “any byte†when Unicode is disabled and means + * “any valid UTF-8 encoding of any Unicode scalar value†when Unicode is + * enabled. + */ + RustRegexOptions& DotMatchesNewLine(bool aYes) { + return SetFlag(aYes, RURE_FLAG_DOTNL); + } + + /* + * Set the value for the greedy swap (U) flag. + * + * When enabled, a pattern like a* is lazy (tries to find shortest match) and + * a*? is greedy (tries to find longest match). + * + * By default, a* is greedy and a*? is lazy. + */ + RustRegexOptions& SwapGreed(bool aYes) { + return SetFlag(aYes, RURE_FLAG_SWAP_GREED); + } + + /* + * Set the value for the ignore whitespace (x) flag. + * + * When enabled, whitespace such as new lines and spaces will be ignored + * between expressions of the pattern, and # can be used to start a comment + * until the next new line. + */ + RustRegexOptions& IgnoreWhitespace(bool aYes) { + return SetFlag(aYes, RURE_FLAG_SPACE); + } + + /* + * Set the value for the Unicode (u) flag. + * + * Enabled by default. When disabled, character classes such as \w only match + * ASCII word characters instead of all Unicode word characters. + */ + RustRegexOptions& Unicode(bool aYes) { + return SetFlag(aYes, RURE_FLAG_UNICODE); + } + + /* + * SizeLimit sets the appoximate size limit of the compiled regular + * expression. + * + * This size limit roughly corresponds to the number of bytes occupied by + * a single compiled program. If the program would exceed this number, + * then an invalid RustRegex will be constructed. + */ + RustRegexOptions& SizeLimit(size_t aLimit) { + mSizeLimit = Some(aLimit); + return *this; + } + + /* + * DFASizeLimit sets the approximate size of the cache used by the DFA during + * search. + * + * This roughly corresponds to the number of bytes that the DFA will use while + * searching. + * + * Note that this is a *per thread* limit. There is no way to set a global + * limit. In particular, if a regular expression is used from multiple threads + * simultaneously, then each thread may use up to the number of bytes + * specified here. + */ + RustRegexOptions& DFASizeLimit(size_t aLimit) { + mDFASizeLimit = Some(aLimit); + return *this; + } + + private: + friend class RustRegex; + friend class RustRegexSet; + + struct OptionsDeleter { + void operator()(rure_options* ptr) const { rure_options_free(ptr); } + }; + + UniquePtr GetOptions() const { + UniquePtr options; + if (mSizeLimit || mDFASizeLimit) { + options.reset(rure_options_new()); + if (mSizeLimit) { + rure_options_size_limit(options.get(), *mSizeLimit); + } + if (mDFASizeLimit) { + rure_options_dfa_size_limit(options.get(), *mDFASizeLimit); + } + } + return options; + } + + uint32_t GetFlags() const { return mFlags; } + + RustRegexOptions& SetFlag(bool aYes, uint32_t aFlag) { + if (aYes) { + mFlags |= aFlag; + } else { + mFlags &= ~aFlag; + } + return *this; + } + + uint32_t mFlags = RURE_DEFAULT_FLAGS; + Maybe mSizeLimit; + Maybe mDFASizeLimit; +}; + +/* + * RustRegex is the type of a compiled regular expression. + * + * A RustRegex can be safely used from multiple threads simultaneously. + * + * When calling the matching methods on this type, they will generally have the + * following parameters: + * + * aHaystack + * may contain arbitrary bytes, but ASCII compatible text is more useful. + * UTF-8 is even more useful. Other text encodings aren't supported. + * + * aStart + * the position in bytes at which to start searching. Note that setting the + * start position is distinct from using a substring for `aHaystack`, since + * the regex engine may look at bytes before the start position to determine + * match information. For example, if the start position is greater than 0, + * then the \A ("begin text") anchor can never match. + */ +class RustRegex final { + public: + // Create a new invalid RustRegex object + RustRegex() = default; + + /* + * Compiles the given pattern into a regular expression. The pattern must be + * valid UTF-8 and the length corresponds to the number of bytes in the + * pattern. + * + * If an error occurs, the constructed RustRegex will be `!IsValid()`. + * + * The compiled expression returned may be used from multiple threads + * simultaneously. + */ + explicit RustRegex(const std::string_view& aPattern, + const RustRegexOptions& aOptions = {}) { +#ifdef DEBUG + rure_error* error = rure_error_new(); +#else + rure_error* error = nullptr; +#endif + mPtr.reset(rure_compile(reinterpret_cast(aPattern.data()), + aPattern.size(), aOptions.GetFlags(), + aOptions.GetOptions().get(), error)); +#ifdef DEBUG + if (!mPtr) { + NS_WARNING(nsPrintfCString("RustRegex compile failed: %s", + rure_error_message(error)) + .get()); + } + rure_error_free(error); +#endif + } + + // Check if the compiled `RustRegex` is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * IsMatch returns true if and only if this regex matches anywhere in + * aHaystack. + * + * See the type-level comment for details on aHaystack and aStart. + * + * IsMatch() should be preferred to Find() since it may be faster. + * + * N.B. The performance of this search is not impacted by the presence of + * capturing groups in your regular expression. + */ + bool IsMatch(const std::string_view& aHaystack, size_t aStart = 0) const { + return mPtr && + rure_is_match(mPtr.get(), + reinterpret_cast(aHaystack.data()), + aHaystack.size(), aStart); + } + + /* + * Find returns Some if and only if this regex matches anywhere in + * haystack. The returned RustRegexMatch object contains the start and end + * offsets (in bytes) of the match. + * + * See the type-level comment for details on aHaystack and aStart. + * + * Find() should be preferred to FindCaptures() since it may be faster. + * + * N.B. The performance of this search is not impacted by the presence of + * capturing groups in your regular expression. + */ + Maybe Find(const std::string_view& aHaystack, + size_t aStart = 0) const { + RustRegexMatch match{}; + if (mPtr && rure_find(mPtr.get(), + reinterpret_cast(aHaystack.data()), + aHaystack.size(), aStart, &match)) { + return Some(match); + } + return Nothing(); + } + + /* + * FindCaptures() returns a valid RustRegexCaptures if and only if this + * regex matches anywhere in haystack. If a match is found, then all of its + * capture locations are stored in the returned RustRegexCaptures object. + * + * See the type-level comment for details on aHaystack and aStart. + * + * Only use this function if you specifically need access to capture + * locations. It is not necessary to use this function just because your + * regular expression contains capturing groups. + * + * Capture locations can be accessed using the methods on RustRegexCaptures. + * + * N.B. The performance of this search can be impacted by the number of + * capturing groups. If you're using this function, it may be beneficial to + * use non-capturing groups (e.g., `(?:re)`) where possible. + */ + RustRegexCaptures FindCaptures(const std::string_view& aHaystack, + size_t aStart = 0) const { + RustRegexCaptures captures(mPtr.get()); + if (mPtr && + rure_find_captures(mPtr.get(), + reinterpret_cast(aHaystack.data()), + aHaystack.size(), aStart, captures.mPtr.get())) { + return captures; + } + return {}; + } + + /* + * ShortestMatch() returns Some if and only if this regex matches anywhere + * in haystack. If a match is found, then its end location is stored in the + * pointer given. The end location is the place at which the regex engine + * determined that a match exists, but may occur before the end of the + * proper leftmost-first match. + * + * See the type-level comment for details on aHaystack and aStart. + * + * ShortestMatch should be preferred to Find since it may be faster. + * + * N.B. The performance of this search is not impacted by the presence of + * capturing groups in your regular expression. + */ + Maybe ShortestMatch(const std::string_view& aHaystack, + size_t aStart = 0) const { + size_t end = 0; + if (mPtr && + rure_shortest_match(mPtr.get(), + reinterpret_cast(aHaystack.data()), + aHaystack.size(), aStart, &end)) { + return Some(end); + } + return Nothing(); + } + + /* + * Create an iterator over all successive non-overlapping matches of this + * regex in aHaystack. + * + * See the type-level comment for details on aHaystack. + * + * Both aHaystack and this regex must remain valid until the returned + * `RustRegexIter` is destroyed. + */ + RustRegexIter IterMatches(const std::string_view& aHaystack) const { + return RustRegexIter(mPtr.get(), aHaystack); + } + + /* + * Returns the capture index for the name given. If no such named capturing + * group exists in this regex, then -1 is returned. + * + * The capture index may be used with RustRegexCaptures::CaptureAt. + * + * This function never returns 0 since the first capture group always + * corresponds to the entire match and is always unnamed. + */ + int32_t CaptureNameIndex(const char* aName) const { + return mPtr ? rure_capture_name_index(mPtr.get(), aName) : -1; + } + + /* + * Create an iterator over the list of capture group names in this particular + * regex. + * + * This regex must remain valid until the returned `RustRegexIterCaptureNames` + * is destroyed. + */ + RustRegexIterCaptureNames IterCaptureNames() const { + return RustRegexIterCaptureNames(mPtr.get()); + } + + /* + * Count the number of successive non-overlapping matches of this regex in + * aHaystack. + * + * See the type-level comment for details on aHaystack. + */ + size_t CountMatches(const std::string_view& aHaystack) const { + size_t count = 0; + auto iter = IterMatches(aHaystack); + while (iter.Next()) { + count++; + } + return count; + } + + private: + struct Deleter { + void operator()(rure* ptr) const { rure_free(ptr); } + }; + UniquePtr mPtr; +}; + +/* + * RustRegexSet is the type of a set of compiled regular expression. + * + * A RustRegexSet can be safely used from multiple threads simultaneously. + * + * When calling the matching methods on this type, they will generally have the + * following parameters: + * + * aHaystack + * may contain arbitrary bytes, but ASCII compatible text is more useful. + * UTF-8 is even more useful. Other text encodings aren't supported. + * + * aStart + * the position in bytes at which to start searching. Note that setting the + * start position is distinct from using a substring for `aHaystack`, since + * the regex engine may look at bytes before the start position to determine + * match information. For example, if the start position is greater than 0, + * then the \A ("begin text") anchor can never match. + */ +class RustRegexSet final { + public: + /* + * Compiles the given range of patterns into a single regular expression which + * can be matched in a linear-scan. Each pattern in aPatterns must be valid + * UTF-8, and implicitly coerce to `std::string_view`. + * + * If an error occurs, the constructed RustRegexSet will be `!IsValid()`. + * + * The compiled expression returned may be used from multiple threads + * simultaneously. + */ + template + explicit RustRegexSet(Patterns&& aPatterns, + const RustRegexOptions& aOptions = {}) { +#ifdef DEBUG + rure_error* error = rure_error_new(); +#else + rure_error* error = nullptr; +#endif + AutoTArray patternPtrs; + AutoTArray patternSizes; + for (auto&& pattern : std::forward(aPatterns)) { + std::string_view view = pattern; + patternPtrs.AppendElement(reinterpret_cast(view.data())); + patternSizes.AppendElement(view.size()); + } + mPtr.reset(rure_compile_set(patternPtrs.Elements(), patternSizes.Elements(), + patternPtrs.Length(), aOptions.GetFlags(), + aOptions.GetOptions().get(), error)); +#ifdef DEBUG + if (!mPtr) { + NS_WARNING(nsPrintfCString("RustRegexSet compile failed: %s", + rure_error_message(error)) + .get()); + } + rure_error_free(error); +#endif + } + + // Check if the `RustRegexSet` object is valid. + bool IsValid() const { return mPtr != nullptr; } + explicit operator bool() const { return IsValid(); } + + /* + * IsMatch returns true if and only if any regexes within the set + * match anywhere in the haystack. Once a match has been located, the + * matching engine will quit immediately. + * + * See the type-level comment for details on aHaystack and aStart. + */ + bool IsMatch(const std::string_view& aHaystack, size_t aStart = 0) const { + return mPtr && + rure_set_is_match(mPtr.get(), + reinterpret_cast(aHaystack.data()), + aHaystack.size(), aStart); + } + + struct SetMatches { + bool matchedAny = false; + nsTArray matches; + }; + + /* + * Matches() compares each regex in the set against the haystack and + * returns a list with the match result of each pattern. Match results are + * ordered in the same way as the regex set was compiled. For example, index 0 + * of matches corresponds to the first pattern passed to the constructor. + * + * See the type-level comment for details on aHaystack and aStart. + * + * Only use this function if you specifically need to know which regexes + * matched within the set. To determine if any of the regexes matched without + * caring which, use IsMatch. + */ + SetMatches Matches(const std::string_view& aHaystack, + size_t aStart = 0) const { + nsTArray matches; + matches.SetLength(Length()); + bool any = mPtr && rure_set_matches( + mPtr.get(), + reinterpret_cast(aHaystack.data()), + aHaystack.size(), aStart, matches.Elements()); + return SetMatches{any, std::move(matches)}; + } + + /* + * Returns the number of patterns the regex set was compiled with. + */ + size_t Length() const { return mPtr ? rure_set_len(mPtr.get()) : 0; } + + private: + struct Deleter { + void operator()(rure_set* ptr) const { rure_set_free(ptr); } + }; + UniquePtr mPtr; +}; + +} // namespace mozilla + +#endif // mozilla_RustRegex_h diff --git a/xpcom/string/RustStringAPI.cpp b/xpcom/string/RustStringAPI.cpp new file mode 100644 index 0000000000..55ce6b9eeb --- /dev/null +++ b/xpcom/string/RustStringAPI.cpp @@ -0,0 +1,123 @@ +/* -*- 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 "nsISupports.h" +#include "nsString.h" + +// Extern "C" utilities used by the rust nsString bindings. + +// Provide rust bindings to the nsA[C]String types +extern "C" { + +// This is a no-op on release, so we ifdef it out such that using it in release +// results in a linker error. +#ifdef DEBUG +void Gecko_IncrementStringAdoptCount(void* aData) { + MOZ_LOG_CTOR(aData, "StringAdopt", 1); +} +#elif defined(MOZ_DEBUG_RUST) +void Gecko_IncrementStringAdoptCount(void* aData) {} +#endif + +void Gecko_FinalizeCString(nsACString* aThis) { aThis->~nsACString(); } + +void Gecko_AssignCString(nsACString* aThis, const nsACString* aOther) { + aThis->Assign(*aOther); +} + +void Gecko_TakeFromCString(nsACString* aThis, nsACString* aOther) { + aThis->Assign(std::move(*aOther)); +} + +void Gecko_AppendCString(nsACString* aThis, const nsACString* aOther) { + aThis->Append(*aOther); +} + +void Gecko_SetLengthCString(nsACString* aThis, uint32_t aLength) { + aThis->SetLength(aLength); +} + +bool Gecko_FallibleAssignCString(nsACString* aThis, const nsACString* aOther) { + return aThis->Assign(*aOther, mozilla::fallible); +} + +bool Gecko_FallibleTakeFromCString(nsACString* aThis, nsACString* aOther) { + return aThis->Assign(std::move(*aOther), mozilla::fallible); +} + +bool Gecko_FallibleAppendCString(nsACString* aThis, const nsACString* aOther) { + return aThis->Append(*aOther, mozilla::fallible); +} + +bool Gecko_FallibleSetLengthCString(nsACString* aThis, uint32_t aLength) { + return aThis->SetLength(aLength, mozilla::fallible); +} + +char* Gecko_BeginWritingCString(nsACString* aThis) { + return aThis->BeginWriting(); +} + +char* Gecko_FallibleBeginWritingCString(nsACString* aThis) { + return aThis->BeginWriting(mozilla::fallible); +} + +uint32_t Gecko_StartBulkWriteCString(nsACString* aThis, uint32_t aCapacity, + uint32_t aUnitsToPreserve, + bool aAllowShrinking) { + return aThis->StartBulkWriteImpl(aCapacity, aUnitsToPreserve, aAllowShrinking) + .unwrapOr(UINT32_MAX); +} + +void Gecko_FinalizeString(nsAString* aThis) { aThis->~nsAString(); } + +void Gecko_AssignString(nsAString* aThis, const nsAString* aOther) { + aThis->Assign(*aOther); +} + +void Gecko_TakeFromString(nsAString* aThis, nsAString* aOther) { + aThis->Assign(std::move(*aOther)); +} + +void Gecko_AppendString(nsAString* aThis, const nsAString* aOther) { + aThis->Append(*aOther); +} + +void Gecko_SetLengthString(nsAString* aThis, uint32_t aLength) { + aThis->SetLength(aLength); +} + +bool Gecko_FallibleAssignString(nsAString* aThis, const nsAString* aOther) { + return aThis->Assign(*aOther, mozilla::fallible); +} + +bool Gecko_FallibleTakeFromString(nsAString* aThis, nsAString* aOther) { + return aThis->Assign(std::move(*aOther), mozilla::fallible); +} + +bool Gecko_FallibleAppendString(nsAString* aThis, const nsAString* aOther) { + return aThis->Append(*aOther, mozilla::fallible); +} + +bool Gecko_FallibleSetLengthString(nsAString* aThis, uint32_t aLength) { + return aThis->SetLength(aLength, mozilla::fallible); +} + +char16_t* Gecko_BeginWritingString(nsAString* aThis) { + return aThis->BeginWriting(); +} + +char16_t* Gecko_FallibleBeginWritingString(nsAString* aThis) { + return aThis->BeginWriting(mozilla::fallible); +} + +uint32_t Gecko_StartBulkWriteString(nsAString* aThis, uint32_t aCapacity, + uint32_t aUnitsToPreserve, + bool aAllowShrinking) { + return aThis->StartBulkWriteImpl(aCapacity, aUnitsToPreserve, aAllowShrinking) + .unwrapOr(UINT32_MAX); +} + +} // extern "C" diff --git a/xpcom/string/crashtests/1113005-frame.html b/xpcom/string/crashtests/1113005-frame.html new file mode 100644 index 0000000000..505fc22f1e --- /dev/null +++ b/xpcom/string/crashtests/1113005-frame.html @@ -0,0 +1,5 @@ +
+ diff --git a/xpcom/string/crashtests/1113005.html b/xpcom/string/crashtests/1113005.html new file mode 100644 index 0000000000..e377bb637f --- /dev/null +++ b/xpcom/string/crashtests/1113005.html @@ -0,0 +1,2 @@ + + diff --git a/xpcom/string/crashtests/394275-1.html b/xpcom/string/crashtests/394275-1.html new file mode 100644 index 0000000000..b589c4d359 --- /dev/null +++ b/xpcom/string/crashtests/394275-1.html @@ -0,0 +1,9 @@ + + + + + diff --git a/xpcom/string/crashtests/395651-1.html b/xpcom/string/crashtests/395651-1.html new file mode 100644 index 0000000000..bbed371fd6 --- /dev/null +++ b/xpcom/string/crashtests/395651-1.html @@ -0,0 +1,30 @@ + + + + + +
+ + diff --git a/xpcom/string/crashtests/crashtests.list b/xpcom/string/crashtests/crashtests.list new file mode 100644 index 0000000000..d464166e85 --- /dev/null +++ b/xpcom/string/crashtests/crashtests.list @@ -0,0 +1,3 @@ +load 394275-1.html +load 395651-1.html +skip-if(gtkWidget||winWidget) load 1113005.html # Bug 1683062 diff --git a/xpcom/string/moz.build b/xpcom/string/moz.build new file mode 100644 index 0000000000..c0f8091b8f --- /dev/null +++ b/xpcom/string/moz.build @@ -0,0 +1,62 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "String") + +EXPORTS += [ + "nsASCIIMask.h", + "nsAString.h", + "nsCharTraits.h", + "nsDependentString.h", + "nsDependentSubstring.h", + "nsLiteralString.h", + "nsPrintfCString.h", + "nsPromiseFlatString.h", + "nsReadableUtils.h", + "nsString.h", + "nsStringBuffer.h", + "nsStringFlags.h", + "nsStringFwd.h", + "nsStringIterator.h", + "nsTDependentString.h", + "nsTDependentSubstring.h", + "nsTextFormatter.h", + "nsTLiteralString.h", + "nsTPromiseFlatString.h", + "nsTString.h", + "nsTStringHasher.h", + "nsTStringRepr.h", + "nsTSubstring.h", + "nsTSubstringTuple.h", + "nsUTF8Utils.h", +] + +EXPORTS.mozilla += [ + "RustRegex.h", +] + +UNIFIED_SOURCES += [ + "nsASCIIMask.cpp", + "nsReadableUtils.cpp", + "nsStringBuffer.cpp", + "nsTDependentString.cpp", + "nsTDependentSubstring.cpp", + "nsTextFormatter.cpp", + "nsTLiteralString.cpp", + "nsTPromiseFlatString.cpp", + "nsTString.cpp", + "nsTStringComparator.cpp", + "nsTStringRepr.cpp", + "nsTSubstring.cpp", + "nsTSubstringTuple.cpp", + "RustStringAPI.cpp", +] + +if CONFIG["MOZ_DEBUG"]: + UNIFIED_SOURCES += ["nsStringStats.cpp"] + +FINAL_LIBRARY = "xul" diff --git a/xpcom/string/nsASCIIMask.cpp b/xpcom/string/nsASCIIMask.cpp new file mode 100644 index 0000000000..abcff70306 --- /dev/null +++ b/xpcom/string/nsASCIIMask.cpp @@ -0,0 +1,38 @@ +/* -*- 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 "nsASCIIMask.h" + +namespace mozilla { + +constexpr bool TestWhitespace(char c) { + return c == '\f' || c == '\t' || c == '\r' || c == '\n' || c == ' '; +} +constexpr ASCIIMaskArray sWhitespaceMask = CreateASCIIMask(TestWhitespace); + +constexpr bool TestCRLF(char c) { return c == '\r' || c == '\n'; } +constexpr ASCIIMaskArray sCRLFMask = CreateASCIIMask(TestCRLF); + +constexpr bool TestCRLFTab(char c) { + return c == '\r' || c == '\n' || c == '\t'; +} +constexpr ASCIIMaskArray sCRLFTabMask = CreateASCIIMask(TestCRLFTab); + +constexpr bool TestZeroToNine(char c) { + return c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || + c == '6' || c == '7' || c == '8' || c == '9'; +} +constexpr ASCIIMaskArray sZeroToNineMask = CreateASCIIMask(TestZeroToNine); + +const ASCIIMaskArray& ASCIIMask::MaskWhitespace() { return sWhitespaceMask; } + +const ASCIIMaskArray& ASCIIMask::MaskCRLF() { return sCRLFMask; } + +const ASCIIMaskArray& ASCIIMask::MaskCRLFTab() { return sCRLFTabMask; } + +const ASCIIMaskArray& ASCIIMask::Mask0to9() { return sZeroToNineMask; } + +} // namespace mozilla diff --git a/xpcom/string/nsASCIIMask.h b/xpcom/string/nsASCIIMask.h new file mode 100644 index 0000000000..54f51d8957 --- /dev/null +++ b/xpcom/string/nsASCIIMask.h @@ -0,0 +1,70 @@ +/* -*- 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/. */ + +#ifndef nsASCIIMask_h_ +#define nsASCIIMask_h_ + +#include +#include + +#include "mozilla/Attributes.h" + +typedef std::array ASCIIMaskArray; + +namespace mozilla { + +// Boolean arrays, fixed size and filled in at compile time, meant to +// record something about each of the (standard) ASCII characters. +// No extended ASCII for now, there has been no use case. +// If you have loops that go through a string character by character +// and test for equality to a certain set of characters before deciding +// on a course of action, chances are building up one of these arrays +// and using it is going to be faster, especially if the set of +// characters is more than one long, and known at compile time. +class ASCIIMask { + public: + // Preset masks for some common character groups + // When testing, you must check if the index is < 128 or use IsMasked() + // + // if (someChar < 128 && MaskCRLF()[someChar]) this is \r or \n + + static const ASCIIMaskArray& MaskCRLF(); + static const ASCIIMaskArray& Mask0to9(); + static const ASCIIMaskArray& MaskCRLFTab(); + static const ASCIIMaskArray& MaskWhitespace(); + + static MOZ_ALWAYS_INLINE bool IsMasked(const ASCIIMaskArray& aMask, + uint32_t aChar) { + return aChar < 128 && aMask[aChar]; + } +}; + +// Outside of the preset ones, use these templates to create more masks. +// +// The example creation will look like this: +// +// constexpr bool TestABC(char c) { return c == 'A' || c == 'B' || c == 'C'; } +// constexpr std::array sABCMask = CreateASCIIMask(TestABC); +// ... +// if (someChar < 128 && sABCMask[someChar]) this is A or B or C + +namespace asciimask_details { +template +constexpr std::array CreateASCIIMask( + F fun, std::index_sequence) { + return {{fun(Indices)...}}; +} +} // namespace asciimask_details + +template +constexpr std::array CreateASCIIMask(F fun) { + return asciimask_details::CreateASCIIMask(fun, + std::make_index_sequence<128>{}); +} + +} // namespace mozilla + +#endif // nsASCIIMask_h_ diff --git a/xpcom/string/nsAString.h b/xpcom/string/nsAString.h new file mode 100644 index 0000000000..3893ff8e37 --- /dev/null +++ b/xpcom/string/nsAString.h @@ -0,0 +1,38 @@ +/* -*- 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/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsAString_h___ +#define nsAString_h___ + +#include "nsStringFwd.h" +#include "nsStringIterator.h" +#include "mozilla/TypedEnumBits.h" + +#include +#include + +#include "nsStringFlags.h" +#include "nsTStringRepr.h" +#include "nsTSubstring.h" +#include "nsTSubstringTuple.h" + +/** + * ASCII case-insensitive comparator. (for Unicode case-insensitive + * comparision, see nsUnicharUtils.h) + */ +int nsCaseInsensitiveCStringComparator(const char*, const char*, size_t, + size_t); + +class nsCaseInsensitiveCStringArrayComparator { + public: + template + bool Equals(const A& aStrA, const B& aStrB) const { + return aStrA.Equals(aStrB, nsCaseInsensitiveCStringComparator); + } +}; + +#endif // !defined(nsAString_h___) diff --git a/xpcom/string/nsCharTraits.h b/xpcom/string/nsCharTraits.h new file mode 100644 index 0000000000..c81c2f5b2d --- /dev/null +++ b/xpcom/string/nsCharTraits.h @@ -0,0 +1,486 @@ +/* -*- 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/. */ + +#ifndef nsCharTraits_h___ +#define nsCharTraits_h___ + +#include // for |EOF|, |WEOF| +#include // for |uint32_t| +#include // for |memcpy|, et al +#include "mozilla/MemoryChecking.h" + +// This file may be used (through nsUTF8Utils.h) from non-XPCOM code, in +// particular the standalone software updater. In that case stub out +// the macros provided by nsDebug.h which are only usable when linking XPCOM + +#ifdef NS_NO_XPCOM +# define NS_WARNING(msg) +# define NS_ASSERTION(cond, msg) +# define NS_ERROR(msg) +#else +# include "nsDebug.h" // for NS_ASSERTION +#endif + +/* + * Some macros for converting char16_t (UTF-16) to and from Unicode scalar + * values. + * + * Note that UTF-16 represents all Unicode scalar values up to U+10FFFF by + * using "surrogate pairs". These consist of a high surrogate, i.e. a code + * point in the range U+D800 - U+DBFF, and a low surrogate, i.e. a code point + * in the range U+DC00 - U+DFFF, like this: + * + * U+D800 U+DC00 = U+10000 + * U+D800 U+DC01 = U+10001 + * ... + * U+DBFF U+DFFE = U+10FFFE + * U+DBFF U+DFFF = U+10FFFF + * + * These surrogate code points U+D800 - U+DFFF are not themselves valid Unicode + * scalar values and are not well-formed UTF-16 except as high-surrogate / + * low-surrogate pairs. + */ + +#define PLANE1_BASE uint32_t(0x00010000) +// High surrogates are in the range 0xD800 -- OxDBFF +#define NS_IS_HIGH_SURROGATE(u) ((uint32_t(u) & 0xFFFFFC00) == 0xD800) +// Low surrogates are in the range 0xDC00 -- 0xDFFF +#define NS_IS_LOW_SURROGATE(u) ((uint32_t(u) & 0xFFFFFC00) == 0xDC00) +// Easier to type than NS_IS_HIGH_SURROGATE && NS_IS_LOW_SURROGATE +#define NS_IS_SURROGATE_PAIR(h, l) \ + (NS_IS_HIGH_SURROGATE(h) && NS_IS_LOW_SURROGATE(l)) +// Faster than testing NS_IS_HIGH_SURROGATE || NS_IS_LOW_SURROGATE +#define IS_SURROGATE(u) ((uint32_t(u) & 0xFFFFF800) == 0xD800) + +// Everything else is not a surrogate: 0x000 -- 0xD7FF, 0xE000 -- 0xFFFF + +// N = (H - 0xD800) * 0x400 + 0x10000 + (L - 0xDC00) +// I wonder whether we could somehow assert that H is a high surrogate +// and L is a low surrogate +#define SURROGATE_TO_UCS4(h, l) \ + (((uint32_t(h) & 0x03FF) << 10) + (uint32_t(l) & 0x03FF) + PLANE1_BASE) + +// Extract surrogates from a UCS4 char +// Reference: the Unicode standard 4.0, section 3.9 +// Since (c - 0x10000) >> 10 == (c >> 10) - 0x0080 and +// 0xD7C0 == 0xD800 - 0x0080, +// ((c - 0x10000) >> 10) + 0xD800 can be simplified to +#define H_SURROGATE(c) char16_t(char16_t(uint32_t(c) >> 10) + char16_t(0xD7C0)) +// where it's to be noted that 0xD7C0 is not bitwise-OR'd +// but added. + +// Since 0x10000 & 0x03FF == 0, +// (c - 0x10000) & 0x03FF == c & 0x03FF so that +// ((c - 0x10000) & 0x03FF) | 0xDC00 is equivalent to +#define L_SURROGATE(c) \ + char16_t(char16_t(uint32_t(c) & uint32_t(0x03FF)) | char16_t(0xDC00)) + +#define IS_IN_BMP(ucs) (uint32_t(ucs) < PLANE1_BASE) +#define UCS2_REPLACEMENT_CHAR char16_t(0xFFFD) + +#define UCS_END uint32_t(0x00110000) +#define IS_VALID_CHAR(c) ((uint32_t(c) < UCS_END) && !IS_SURROGATE(c)) +#define ENSURE_VALID_CHAR(c) (IS_VALID_CHAR(c) ? (c) : UCS2_REPLACEMENT_CHAR) + +template +struct nsCharTraits {}; + +template <> +struct nsCharTraits { + typedef char16_t char_type; + typedef uint16_t unsigned_char_type; + typedef char incompatible_char_type; + + static char_type* const sEmptyBuffer; + + // integer representation of characters: + typedef int int_type; + + static char_type to_char_type(int_type aChar) { return char_type(aChar); } + + static int_type to_int_type(char_type aChar) { + return int_type(static_cast(aChar)); + } + + static bool eq_int_type(int_type aLhs, int_type aRhs) { return aLhs == aRhs; } + + // |char_type| comparisons: + + static bool eq(char_type aLhs, char_type aRhs) { return aLhs == aRhs; } + + static bool lt(char_type aLhs, char_type aRhs) { return aLhs < aRhs; } + + // operations on s[n] arrays: + + static char_type* move(char_type* aStr1, const char_type* aStr2, size_t aN) { + return static_cast( + memmove(aStr1, aStr2, aN * sizeof(char_type))); + } + + static char_type* copy(char_type* aStr1, const char_type* aStr2, size_t aN) { + return static_cast( + memcpy(aStr1, aStr2, aN * sizeof(char_type))); + } + + static void uninitialize(char_type* aStr, size_t aN) { +#ifdef DEBUG + memset(aStr, 0xE4, aN * sizeof(char_type)); +#endif + MOZ_MAKE_MEM_UNDEFINED(aStr, aN * sizeof(char_type)); + } + + static char_type* copyASCII(char_type* aStr1, const char* aStr2, size_t aN) { + for (char_type* s = aStr1; aN--; ++s, ++aStr2) { + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + *s = static_cast(*aStr2); + } + return aStr1; + } + + static int compare(const char_type* aStr1, const char_type* aStr2, + size_t aN) { + for (; aN--; ++aStr1, ++aStr2) { + if (!eq(*aStr1, *aStr2)) { + return to_int_type(*aStr1) - to_int_type(*aStr2); + } + } + + return 0; + } + + static int compareASCII(const char_type* aStr1, const char* aStr2, + size_t aN) { + for (; aN--; ++aStr1, ++aStr2) { + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + if (!eq_int_type(to_int_type(*aStr1), + to_int_type(static_cast(*aStr2)))) { + return to_int_type(*aStr1) - + to_int_type(static_cast(*aStr2)); + } + } + + return 0; + } + + static bool equalsLatin1(const char_type* aStr1, const char* aStr2, + const size_t aN) { + for (size_t i = aN; i > 0; --i, ++aStr1, ++aStr2) { + if (*aStr1 != static_cast(*aStr2)) { + return false; + } + } + + return true; + } + + // this version assumes that s2 is null-terminated and s1 has length n. + // if s1 is shorter than s2 then we return -1; if s1 is longer than s2, + // we return 1. + static int compareASCIINullTerminated(const char_type* aStr1, size_t aN, + const char* aStr2) { + for (; aN--; ++aStr1, ++aStr2) { + if (!*aStr2) { + return 1; + } + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + if (!eq_int_type(to_int_type(*aStr1), + to_int_type(static_cast(*aStr2)))) { + return to_int_type(*aStr1) - + to_int_type(static_cast(*aStr2)); + } + } + + if (*aStr2) { + return -1; + } + + return 0; + } + + /** + * Convert c to its lower-case form, but only if c is in the ASCII + * range. Otherwise leave it alone. + */ + static char_type ASCIIToLower(char_type aChar) { + if (aChar >= 'A' && aChar <= 'Z') { + return char_type(aChar + ('a' - 'A')); + } + + return aChar; + } + + static int compareLowerCaseToASCII(const char_type* aStr1, const char* aStr2, + size_t aN) { + for (; aN--; ++aStr1, ++aStr2) { + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'), + "Unexpected uppercase character"); + char_type lower_s1 = ASCIIToLower(*aStr1); + if (lower_s1 != static_cast(*aStr2)) { + return to_int_type(lower_s1) - + to_int_type(static_cast(*aStr2)); + } + } + + return 0; + } + + // this version assumes that s2 is null-terminated and s1 has length n. + // if s1 is shorter than s2 then we return -1; if s1 is longer than s2, + // we return 1. + static int compareLowerCaseToASCIINullTerminated(const char_type* aStr1, + size_t aN, + const char* aStr2) { + for (; aN--; ++aStr1, ++aStr2) { + if (!*aStr2) { + return 1; + } + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'), + "Unexpected uppercase character"); + char_type lower_s1 = ASCIIToLower(*aStr1); + if (lower_s1 != static_cast(*aStr2)) { + return to_int_type(lower_s1) - + to_int_type(static_cast(*aStr2)); + } + } + + if (*aStr2) { + return -1; + } + + return 0; + } + + static size_t length(const char_type* aStr) { + size_t result = 0; + while (!eq(*aStr++, char_type(0))) { + ++result; + } + return result; + } + + static const char_type* find(const char_type* aStr, size_t aN, + char_type aChar) { + while (aN--) { + if (eq(*aStr, aChar)) { + return aStr; + } + ++aStr; + } + + return 0; + } +}; + +template <> +struct nsCharTraits { + typedef char char_type; + typedef unsigned char unsigned_char_type; + typedef char16_t incompatible_char_type; + + static char_type* const sEmptyBuffer; + + // integer representation of characters: + + typedef int int_type; + + static char_type to_char_type(int_type aChar) { return char_type(aChar); } + + static int_type to_int_type(char_type aChar) { + return int_type(static_cast(aChar)); + } + + static bool eq_int_type(int_type aLhs, int_type aRhs) { return aLhs == aRhs; } + + // |char_type| comparisons: + + static bool eq(char_type aLhs, char_type aRhs) { return aLhs == aRhs; } + + static bool lt(char_type aLhs, char_type aRhs) { return aLhs < aRhs; } + + // operations on s[n] arrays: + + static char_type* move(char_type* aStr1, const char_type* aStr2, size_t aN) { + return static_cast( + memmove(aStr1, aStr2, aN * sizeof(char_type))); + } + + static char_type* copy(char_type* aStr1, const char_type* aStr2, size_t aN) { + return static_cast( + memcpy(aStr1, aStr2, aN * sizeof(char_type))); + } + + static void uninitialize(char_type* aStr, size_t aN) { +#ifdef DEBUG + memset(aStr, 0xE4, aN * sizeof(char_type)); +#endif + MOZ_MAKE_MEM_UNDEFINED(aStr, aN * sizeof(char_type)); + } + + static char_type* copyASCII(char_type* aStr1, const char* aStr2, size_t aN) { + return copy(aStr1, aStr2, aN); + } + + static int compare(const char_type* aStr1, const char_type* aStr2, + size_t aN) { + return memcmp(aStr1, aStr2, aN); + } + + static int compareASCII(const char_type* aStr1, const char* aStr2, + size_t aN) { +#ifdef DEBUG + for (size_t i = 0; i < aN; ++i) { + NS_ASSERTION(!(aStr2[i] & ~0x7F), "Unexpected non-ASCII character"); + } +#endif + return compare(aStr1, aStr2, aN); + } + + static bool equalsLatin1(const char_type* aStr1, const char* aStr2, + size_t aN) { + return memcmp(aStr1, aStr2, aN) == 0; + } + + // this version assumes that s2 is null-terminated and s1 has length n. + // if s1 is shorter than s2 then we return -1; if s1 is longer than s2, + // we return 1. + static int compareASCIINullTerminated(const char_type* aStr1, size_t aN, + const char* aStr2) { + // can't use strcmp here because we don't want to stop when aStr1 + // contains a null + for (; aN--; ++aStr1, ++aStr2) { + if (!*aStr2) { + return 1; + } + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + if (*aStr1 != *aStr2) { + return to_int_type(*aStr1) - to_int_type(*aStr2); + } + } + + if (*aStr2) { + return -1; + } + + return 0; + } + + /** + * Convert c to its lower-case form, but only if c is ASCII. + */ + static char_type ASCIIToLower(char_type aChar) { + if (aChar >= 'A' && aChar <= 'Z') { + return char_type(aChar + ('a' - 'A')); + } + + return aChar; + } + + static int compareLowerCaseToASCII(const char_type* aStr1, const char* aStr2, + size_t aN) { + for (; aN--; ++aStr1, ++aStr2) { + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'), + "Unexpected uppercase character"); + char_type lower_s1 = ASCIIToLower(*aStr1); + if (lower_s1 != *aStr2) { + return to_int_type(lower_s1) - to_int_type(*aStr2); + } + } + return 0; + } + + // this version assumes that s2 is null-terminated and s1 has length n. + // if s1 is shorter than s2 then we return -1; if s1 is longer than s2, + // we return 1. + static int compareLowerCaseToASCIINullTerminated(const char_type* aStr1, + size_t aN, + const char* aStr2) { + for (; aN--; ++aStr1, ++aStr2) { + if (!*aStr2) { + return 1; + } + NS_ASSERTION(!(*aStr2 & ~0x7F), "Unexpected non-ASCII character"); + NS_ASSERTION(!(*aStr2 >= 'A' && *aStr2 <= 'Z'), + "Unexpected uppercase character"); + char_type lower_s1 = ASCIIToLower(*aStr1); + if (lower_s1 != *aStr2) { + return to_int_type(lower_s1) - to_int_type(*aStr2); + } + } + + if (*aStr2) { + return -1; + } + + return 0; + } + + static size_t length(const char_type* aStr) { return strlen(aStr); } + + static const char_type* find(const char_type* aStr, size_t aN, + char_type aChar) { + return reinterpret_cast( + memchr(aStr, to_int_type(aChar), aN)); + } +}; + +template +struct nsCharSourceTraits { + typedef typename InputIterator::difference_type difference_type; + + static difference_type readable_distance(const InputIterator& aFirst, + const InputIterator& aLast) { + // assumes single fragment + return aLast.get() - aFirst.get(); + } + + static const typename InputIterator::value_type* read( + const InputIterator& aIter) { + return aIter.get(); + } + + static void advance(InputIterator& aStr, difference_type aN) { + aStr.advance(aN); + } +}; + +template +struct nsCharSourceTraits { + typedef ptrdiff_t difference_type; + + static difference_type readable_distance(CharT* aStr) { + return nsCharTraits::length(aStr); + } + + static difference_type readable_distance(CharT* aFirst, CharT* aLast) { + return aLast - aFirst; + } + + static const CharT* read(CharT* aStr) { return aStr; } + + static void advance(CharT*& aStr, difference_type aN) { aStr += aN; } +}; + +template +struct nsCharSinkTraits { + static void write(OutputIterator& aIter, + const typename OutputIterator::value_type* aStr, + size_t aN) { + aIter.write(aStr, aN); + } +}; + +template +struct nsCharSinkTraits { + static void write(CharT*& aIter, const CharT* aStr, size_t aN) { + nsCharTraits::move(aIter, aStr, aN); + aIter += aN; + } +}; + +#endif // !defined(nsCharTraits_h___) diff --git a/xpcom/string/nsDependentString.h b/xpcom/string/nsDependentString.h new file mode 100644 index 0000000000..4896c8d086 --- /dev/null +++ b/xpcom/string/nsDependentString.h @@ -0,0 +1,15 @@ +/* -*- 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/. */ + +#ifndef nsDependentString_h___ +#define nsDependentString_h___ + +#include "nsString.h" +#include "nsDebug.h" + +#include "nsTDependentString.h" + +#endif /* !defined(nsDependentString_h___) */ diff --git a/xpcom/string/nsDependentSubstring.h b/xpcom/string/nsDependentSubstring.h new file mode 100644 index 0000000000..cb6cef5d77 --- /dev/null +++ b/xpcom/string/nsDependentSubstring.h @@ -0,0 +1,13 @@ +/* -*- 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/. */ + +#ifndef nsDependentSubstring_h___ +#define nsDependentSubstring_h___ + +#include "nsAString.h" +#include "nsTDependentSubstring.h" + +#endif /* !defined(nsDependentSubstring_h___) */ diff --git a/xpcom/string/nsLiteralString.h b/xpcom/string/nsLiteralString.h new file mode 100644 index 0000000000..f982724ce4 --- /dev/null +++ b/xpcom/string/nsLiteralString.h @@ -0,0 +1,31 @@ +/* -*- 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/. */ + +#ifndef nsLiteralString_h___ +#define nsLiteralString_h___ + +#include "nscore.h" +#include "nsString.h" + +#include "nsTLiteralString.h" + +#include "mozilla/Char16.h" + +#define NS_CSTRING_LITERAL_AS_STRING_LITERAL(s) u"" s + +#define NS_LITERAL_STRING_FROM_CSTRING(s) \ + static_cast( \ + nsLiteralString(NS_CSTRING_LITERAL_AS_STRING_LITERAL(s))) + +constexpr auto operator""_ns(const char* aStr, std::size_t aLen) { + return nsLiteralCString{aStr, aLen}; +} + +constexpr auto operator""_ns(const char16_t* aStr, std::size_t aLen) { + return nsLiteralString{aStr, aLen}; +} + +#endif /* !defined(nsLiteralString_h___) */ diff --git a/xpcom/string/nsPrintfCString.h b/xpcom/string/nsPrintfCString.h new file mode 100644 index 0000000000..f722888705 --- /dev/null +++ b/xpcom/string/nsPrintfCString.h @@ -0,0 +1,64 @@ +/* -*- 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/. */ + +#ifndef nsPrintfCString_h___ +#define nsPrintfCString_h___ + +#include "nsString.h" + +/** + * nsPrintfCString lets you create a nsCString using a printf-style format + * string. For example: + * + * NS_WARNING(nsPrintfCString("Unexpected value: %f", 13.917).get()); + * + * nsPrintfCString has a small built-in auto-buffer. For larger strings, it + * will allocate on the heap. + * + * See also nsCString::AppendPrintf(). + */ +class nsPrintfCString : public nsAutoCStringN<16> { + typedef nsCString string_type; + + public: + explicit nsPrintfCString(const char_type* aFormat, ...) + MOZ_FORMAT_PRINTF(2, 3) { + va_list ap; + va_start(ap, aFormat); + AppendVprintf(aFormat, ap); + va_end(ap); + } +}; + +/** + * + * + * nsVPrintfCString is like nsPrinfCString but is created using vprintf style + * args. This is useful for functions that have already received variadic + * arguments and want to create a nsPrintfCString. For example: + * + * void LogToSeveralLocations(const char* aFormat,...) { + * va_list ap; + * va_start(ap, aFormat); + * nsPrintfCString logString(aFormat, ap); + * va_end(ap); + * // Use logString + * } + * + * See also nsCString::AppendVprintf(). + */ + +class nsVprintfCString : public nsAutoCStringN<16> { + typedef nsCString string_type; + + public: + nsVprintfCString(const char_type* aFormat, va_list aArgs) + MOZ_FORMAT_PRINTF(2, 0) { + AppendVprintf(aFormat, aArgs); + } +}; + +#endif // !defined(nsPrintfCString_h___) diff --git a/xpcom/string/nsPromiseFlatString.h b/xpcom/string/nsPromiseFlatString.h new file mode 100644 index 0000000000..98541ceb4a --- /dev/null +++ b/xpcom/string/nsPromiseFlatString.h @@ -0,0 +1,14 @@ +/* -*- 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/. */ + +#ifndef nsPromiseFlatString_h___ +#define nsPromiseFlatString_h___ + +#include "nsString.h" + +#include "nsTPromiseFlatString.h" + +#endif /* !defined(nsPromiseFlatString_h___) */ diff --git a/xpcom/string/nsReadableUtils.cpp b/xpcom/string/nsReadableUtils.cpp new file mode 100644 index 0000000000..fa4c4bc69b --- /dev/null +++ b/xpcom/string/nsReadableUtils.cpp @@ -0,0 +1,630 @@ +/* -*- 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 "nsReadableUtils.h" + +#include + +#include "mozilla/CheckedInt.h" +#include "mozilla/Utf8.h" + +#include "nscore.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsUTF8Utils.h" + +using mozilla::Span; + +/** + * A helper function that allocates a buffer of the desired character type big + * enough to hold a copy of the supplied string (plus a zero terminator). + * + * @param aSource an string you will eventually be making a copy of + * @return a new buffer which you must free with |free|. + * + */ +template +inline CharT* AllocateStringCopy(const FromStringT& aSource, CharT*) { + return static_cast( + malloc((size_t(aSource.Length()) + 1) * sizeof(CharT))); +} + +char* ToNewCString(const nsAString& aSource) { + char* str = ToNewCString(aSource, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char* ToNewCString(const nsAString& aSource, + const mozilla::fallible_t& aFallible) { + char* dest = AllocateStringCopy(aSource, (char*)nullptr); + if (!dest) { + return nullptr; + } + + auto len = aSource.Length(); + LossyConvertUtf16toLatin1(aSource, Span(dest, len)); + dest[len] = 0; + return dest; +} + +char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count, + const mozilla::fallible_t& aFallible) { + auto len = aSource.Length(); + // The uses of this function seem temporary enough that it's not + // worthwhile to be fancy about the allocation size. Let's just use + // the worst case. + // Times 3 plus 1, because ConvertUTF16toUTF8 requires times 3 and + // then we have the terminator. + // Using CheckedInt, because aUTF8Count is uint32_t* for + // historical reasons. + mozilla::CheckedInt destLen(len); + destLen *= 3; + destLen += 1; + if (!destLen.isValid()) { + return nullptr; + } + size_t destLenVal = destLen.value(); + char* dest = static_cast(malloc(destLenVal)); + if (!dest) { + return nullptr; + } + + size_t written = ConvertUtf16toUtf8(aSource, Span(dest, destLenVal)); + dest[written] = 0; + + if (aUTF8Count) { + *aUTF8Count = written; + } + + return dest; +} + +char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count) { + char* str = ToNewUTF8String(aSource, aUTF8Count, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char* ToNewCString(const nsACString& aSource) { + char* str = ToNewCString(aSource, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char* ToNewCString(const nsACString& aSource, + const mozilla::fallible_t& aFallible) { + // no conversion needed, just allocate a buffer of the correct length and copy + // into it + + char* dest = AllocateStringCopy(aSource, (char*)nullptr); + if (!dest) { + return nullptr; + } + + auto len = aSource.Length(); + memcpy(dest, aSource.BeginReading(), len * sizeof(char)); + dest[len] = 0; + return dest; +} + +char16_t* ToNewUnicode(const nsAString& aSource) { + char16_t* str = ToNewUnicode(aSource, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char16_t* ToNewUnicode(const nsAString& aSource, + const mozilla::fallible_t& aFallible) { + // no conversion needed, just allocate a buffer of the correct length and copy + // into it + + char16_t* dest = AllocateStringCopy(aSource, (char16_t*)nullptr); + if (!dest) { + return nullptr; + } + + auto len = aSource.Length(); + memcpy(dest, aSource.BeginReading(), len * sizeof(char16_t)); + dest[len] = 0; + return dest; +} + +char16_t* ToNewUnicode(const nsACString& aSource) { + char16_t* str = ToNewUnicode(aSource, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char16_t* ToNewUnicode(const nsACString& aSource, + const mozilla::fallible_t& aFallible) { + char16_t* dest = AllocateStringCopy(aSource, (char16_t*)nullptr); + if (!dest) { + return nullptr; + } + + auto len = aSource.Length(); + ConvertLatin1toUtf16(aSource, Span(dest, len)); + dest[len] = 0; + return dest; +} + +char16_t* UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count, + const mozilla::fallible_t& aFallible) { + // Compute length plus one as required by ConvertUTF8toUTF16 + uint32_t lengthPlusOne = aSource.Length() + 1; // Can't overflow + + mozilla::CheckedInt allocLength(lengthPlusOne); + // Add space for zero-termination + allocLength += 1; + // We need UTF-16 units + allocLength *= sizeof(char16_t); + + if (!allocLength.isValid()) { + return nullptr; + } + + char16_t* dest = (char16_t*)malloc(allocLength.value()); + if (!dest) { + return nullptr; + } + + size_t written = ConvertUtf8toUtf16(aSource, Span(dest, lengthPlusOne)); + dest[written] = 0; + + if (aUTF16Count) { + *aUTF16Count = written; + } + + return dest; +} + +char16_t* UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count) { + char16_t* str = UTF8ToNewUnicode(aSource, aUTF16Count, mozilla::fallible); + if (!str) { + MOZ_CRASH("Unable to allocate memory"); + } + return str; +} + +char16_t* CopyUnicodeTo(const nsAString& aSource, uint32_t aSrcOffset, + char16_t* aDest, uint32_t aLength) { + MOZ_ASSERT(aSrcOffset + aLength <= aSource.Length()); + memcpy(aDest, aSource.BeginReading() + aSrcOffset, + size_t(aLength) * sizeof(char16_t)); + return aDest; +} + +void ToUpperCase(nsACString& aCString) { + char* cp = aCString.BeginWriting(); + char* end = cp + aCString.Length(); + while (cp != end) { + char ch = *cp; + if (ch >= 'a' && ch <= 'z') { + *cp = ch - ('a' - 'A'); + } + ++cp; + } +} + +void ToUpperCase(const nsACString& aSource, nsACString& aDest) { + aDest.SetLength(aSource.Length()); + const char* src = aSource.BeginReading(); + const char* end = src + aSource.Length(); + char* dst = aDest.BeginWriting(); + while (src != end) { + char ch = *src; + if (ch >= 'a' && ch <= 'z') { + *dst = ch - ('a' - 'A'); + } else { + *dst = ch; + } + ++src; + ++dst; + } +} + +void ToLowerCase(nsACString& aCString) { + char* cp = aCString.BeginWriting(); + char* end = cp + aCString.Length(); + while (cp != end) { + char ch = *cp; + if (ch >= 'A' && ch <= 'Z') { + *cp = ch + ('a' - 'A'); + } + ++cp; + } +} + +void ToLowerCase(const nsACString& aSource, nsACString& aDest) { + aDest.SetLength(aSource.Length()); + const char* src = aSource.BeginReading(); + const char* end = src + aSource.Length(); + char* dst = aDest.BeginWriting(); + while (src != end) { + char ch = *src; + if (ch >= 'A' && ch <= 'Z') { + *dst = ch + ('a' - 'A'); + } else { + *dst = ch; + } + ++src; + ++dst; + } +} + +void ParseString(const nsACString& aSource, char aDelimiter, + nsTArray& aArray) { + nsACString::const_iterator start, end; + aSource.BeginReading(start); + aSource.EndReading(end); + + for (;;) { + nsACString::const_iterator delimiter = start; + FindCharInReadable(aDelimiter, delimiter, end); + + if (delimiter != start) { + aArray.AppendElement(Substring(start, delimiter)); + } + + if (delimiter == end) { + break; + } + start = ++delimiter; + if (start == end) { + break; + } + } +} + +template +bool FindInReadable_Impl( + const StringT& aPattern, IteratorT& aSearchStart, IteratorT& aSearchEnd, + nsTStringComparator aCompare) { + bool found_it = false; + + // only bother searching at all if we're given a non-empty range to search + if (aSearchStart != aSearchEnd) { + IteratorT aPatternStart, aPatternEnd; + aPattern.BeginReading(aPatternStart); + aPattern.EndReading(aPatternEnd); + + // outer loop keeps searching till we find it or run out of string to search + while (!found_it) { + // fast inner loop (that's what it's called, not what it is) looks for a + // potential match + while (aSearchStart != aSearchEnd && + aCompare(aPatternStart.get(), aSearchStart.get(), 1, 1)) { + ++aSearchStart; + } + + // if we broke out of the `fast' loop because we're out of string ... + // we're done: no match + if (aSearchStart == aSearchEnd) { + break; + } + + // otherwise, we're at a potential match, let's see if we really hit one + IteratorT testPattern(aPatternStart); + IteratorT testSearch(aSearchStart); + + // slow inner loop verifies the potential match (found by the `fast' loop) + // at the current position + for (;;) { + // we already compared the first character in the outer loop, + // so we'll advance before the next comparison + ++testPattern; + ++testSearch; + + // if we verified all the way to the end of the pattern, then we found + // it! + if (testPattern == aPatternEnd) { + found_it = true; + aSearchEnd = testSearch; // return the exact found range through the + // parameters + break; + } + + // if we got to end of the string we're searching before we hit the end + // of the + // pattern, we'll never find what we're looking for + if (testSearch == aSearchEnd) { + aSearchStart = aSearchEnd; + break; + } + + // else if we mismatched ... it's time to advance to the next search + // position + // and get back into the `fast' loop + if (aCompare(testPattern.get(), testSearch.get(), 1, 1)) { + ++aSearchStart; + break; + } + } + } + } + + return found_it; +} + +/** + * This searches the entire string from right to left, and returns the first + * match found, if any. + */ +template +bool RFindInReadable_Impl( + const StringT& aPattern, IteratorT& aSearchStart, IteratorT& aSearchEnd, + nsTStringComparator aCompare) { + IteratorT patternStart, patternEnd, searchEnd = aSearchEnd; + aPattern.BeginReading(patternStart); + aPattern.EndReading(patternEnd); + + // Point to the last character in the pattern + --patternEnd; + // outer loop keeps searching till we run out of string to search + while (aSearchStart != searchEnd) { + // Point to the end position of the next possible match + --searchEnd; + + // Check last character, if a match, explore further from here + if (aCompare(patternEnd.get(), searchEnd.get(), 1, 1) == 0) { + // We're at a potential match, let's see if we really hit one + IteratorT testPattern(patternEnd); + IteratorT testSearch(searchEnd); + + // inner loop verifies the potential match at the current position + do { + // if we verified all the way to the end of the pattern, then we found + // it! + if (testPattern == patternStart) { + aSearchStart = testSearch; // point to start of match + aSearchEnd = ++searchEnd; // point to end of match + return true; + } + + // if we got to end of the string we're searching before we hit the end + // of the + // pattern, we'll never find what we're looking for + if (testSearch == aSearchStart) { + aSearchStart = aSearchEnd; + return false; + } + + // test previous character for a match + --testPattern; + --testSearch; + } while (aCompare(testPattern.get(), testSearch.get(), 1, 1) == 0); + } + } + + aSearchStart = aSearchEnd; + return false; +} + +bool FindInReadable(const nsAString& aPattern, + nsAString::const_iterator& aSearchStart, + nsAString::const_iterator& aSearchEnd, + nsStringComparator aComparator) { + return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator); +} + +bool FindInReadable(const nsACString& aPattern, + nsACString::const_iterator& aSearchStart, + nsACString::const_iterator& aSearchEnd, + nsCStringComparator aComparator) { + return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator); +} + +bool CaseInsensitiveFindInReadable(const nsACString& aPattern, + nsACString::const_iterator& aSearchStart, + nsACString::const_iterator& aSearchEnd) { + return FindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, + nsCaseInsensitiveCStringComparator); +} + +bool RFindInReadable(const nsAString& aPattern, + nsAString::const_iterator& aSearchStart, + nsAString::const_iterator& aSearchEnd, + const nsStringComparator aComparator) { + return RFindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator); +} + +bool RFindInReadable(const nsACString& aPattern, + nsACString::const_iterator& aSearchStart, + nsACString::const_iterator& aSearchEnd, + const nsCStringComparator aComparator) { + return RFindInReadable_Impl(aPattern, aSearchStart, aSearchEnd, aComparator); +} + +bool FindCharInReadable(char16_t aChar, nsAString::const_iterator& aSearchStart, + const nsAString::const_iterator& aSearchEnd) { + ptrdiff_t fragmentLength = aSearchEnd.get() - aSearchStart.get(); + + const char16_t* charFoundAt = + nsCharTraits::find(aSearchStart.get(), fragmentLength, aChar); + if (charFoundAt) { + aSearchStart.advance(charFoundAt - aSearchStart.get()); + return true; + } + + aSearchStart.advance(fragmentLength); + return false; +} + +bool FindCharInReadable(char aChar, nsACString::const_iterator& aSearchStart, + const nsACString::const_iterator& aSearchEnd) { + ptrdiff_t fragmentLength = aSearchEnd.get() - aSearchStart.get(); + + const char* charFoundAt = + nsCharTraits::find(aSearchStart.get(), fragmentLength, aChar); + if (charFoundAt) { + aSearchStart.advance(charFoundAt - aSearchStart.get()); + return true; + } + + aSearchStart.advance(fragmentLength); + return false; +} + +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring) { + nsAString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, 0, sub_len).Equals(aSubstring); +} + +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring, + nsStringComparator aComparator) { + nsAString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, 0, sub_len).Equals(aSubstring, aComparator); +} + +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring) { + nsACString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, 0, sub_len).Equals(aSubstring); +} + +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring, + nsCStringComparator aComparator) { + nsACString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, 0, sub_len).Equals(aSubstring, aComparator); +} + +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring) { + nsAString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, src_len - sub_len, sub_len).Equals(aSubstring); +} + +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring, + nsStringComparator aComparator) { + nsAString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, src_len - sub_len, sub_len) + .Equals(aSubstring, aComparator); +} + +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring) { + nsACString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, src_len - sub_len, sub_len).Equals(aSubstring); +} + +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring, + nsCStringComparator aComparator) { + nsACString::size_type src_len = aSource.Length(), + sub_len = aSubstring.Length(); + if (sub_len > src_len) { + return false; + } + return Substring(aSource, src_len - sub_len, sub_len) + .Equals(aSubstring, aComparator); +} + +static const char16_t empty_buffer[1] = {'\0'}; + +const nsString& EmptyString() { + static const nsDependentString sEmpty(empty_buffer); + + return sEmpty; +} + +const nsCString& EmptyCString() { + static const nsDependentCString sEmpty((const char*)empty_buffer); + + return sEmpty; +} + +const nsString& VoidString() { + static const nsString sNull(mozilla::detail::StringDataFlags::VOIDED); + + return sNull; +} + +const nsCString& VoidCString() { + static const nsCString sNull(mozilla::detail::StringDataFlags::VOIDED); + + return sNull; +} + +int32_t CompareUTF8toUTF16(const nsACString& aUTF8String, + const nsAString& aUTF16String, bool* aErr) { + const char* u8; + const char* u8end; + aUTF8String.BeginReading(u8); + aUTF8String.EndReading(u8end); + + const char16_t* u16; + const char16_t* u16end; + aUTF16String.BeginReading(u16); + aUTF16String.EndReading(u16end); + + for (;;) { + if (u8 == u8end) { + if (u16 == u16end) { + return 0; + } + return -1; + } + if (u16 == u16end) { + return 1; + } + // No need for ASCII optimization, since both NextChar() + // calls get inlined. + uint32_t scalar8 = UTF8CharEnumerator::NextChar(&u8, u8end, aErr); + uint32_t scalar16 = UTF16CharEnumerator::NextChar(&u16, u16end, aErr); + if (scalar16 == scalar8) { + continue; + } + if (scalar8 < scalar16) { + return -1; + } + return 1; + } +} + +void AppendUCS4ToUTF16(const uint32_t aSource, nsAString& aDest) { + NS_ASSERTION(IS_VALID_CHAR(aSource), "Invalid UCS4 char"); + if (IS_IN_BMP(aSource)) { + aDest.Append(char16_t(aSource)); + } else { + aDest.Append(H_SURROGATE(aSource)); + aDest.Append(L_SURROGATE(aSource)); + } +} diff --git a/xpcom/string/nsReadableUtils.h b/xpcom/string/nsReadableUtils.h new file mode 100644 index 0000000000..803c6b5d2f --- /dev/null +++ b/xpcom/string/nsReadableUtils.h @@ -0,0 +1,610 @@ +/* -*- 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/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsReadableUtils_h___ +#define nsReadableUtils_h___ + +/** + * I guess all the routines in this file are all mis-named. + * According to our conventions, they should be |NS_xxx|. + */ + +#include "mozilla/Assertions.h" +#include "nsAString.h" +#include "mozilla/TextUtils.h" + +#include "nsTArrayForwardDeclare.h" + +// From the nsstring crate +extern "C" { +bool nsstring_fallible_append_utf8_impl(nsAString* aThis, const char* aOther, + size_t aOtherLen, size_t aOldLen); + +bool nsstring_fallible_append_latin1_impl(nsAString* aThis, const char* aOther, + size_t aOtherLen, size_t aOldLen, + bool aAllowShrinking); + +bool nscstring_fallible_append_utf16_to_utf8_impl(nsACString* aThis, + const char16_t*, + size_t aOtherLen, + size_t aOldLen); + +bool nscstring_fallible_append_utf16_to_latin1_lossy_impl(nsACString* aThis, + const char16_t*, + size_t aOtherLen, + size_t aOldLen, + bool aAllowShrinking); + +bool nscstring_fallible_append_utf8_to_latin1_lossy_check( + nsACString* aThis, const nsACString* aOther, size_t aOldLen); + +bool nscstring_fallible_append_latin1_to_utf8_check(nsACString* aThis, + const nsACString* aOther, + size_t aOldLen); +} + +inline size_t Distance(const nsReadingIterator& aStart, + const nsReadingIterator& aEnd) { + MOZ_ASSERT(aStart.get() <= aEnd.get()); + return static_cast(aEnd.get() - aStart.get()); +} + +inline size_t Distance(const nsReadingIterator& aStart, + const nsReadingIterator& aEnd) { + MOZ_ASSERT(aStart.get() <= aEnd.get()); + return static_cast(aEnd.get() - aStart.get()); +} + +// NOTE: Operations that don't need an operand to be an XPCOM string +// are in mozilla/TextUtils.h and mozilla/Utf8.h. + +// UTF-8 to UTF-16 +// Invalid UTF-8 byte sequences are replaced with the REPLACEMENT CHARACTER. + +[[nodiscard]] inline bool CopyUTF8toUTF16(mozilla::Span aSource, + nsAString& aDest, + const mozilla::fallible_t&) { + return nsstring_fallible_append_utf8_impl(&aDest, aSource.Elements(), + aSource.Length(), 0); +} + +inline void CopyUTF8toUTF16(mozilla::Span aSource, + nsAString& aDest) { + if (MOZ_UNLIKELY(!CopyUTF8toUTF16(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool AppendUTF8toUTF16(mozilla::Span aSource, + nsAString& aDest, + const mozilla::fallible_t&) { + return nsstring_fallible_append_utf8_impl(&aDest, aSource.Elements(), + aSource.Length(), aDest.Length()); +} + +inline void AppendUTF8toUTF16(mozilla::Span aSource, + nsAString& aDest) { + if (MOZ_UNLIKELY(!AppendUTF8toUTF16(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// Latin1 to UTF-16 +// Interpret each incoming unsigned byte value as a Unicode scalar value (not +// windows-1252!). The function names say "ASCII" instead of "Latin1" for +// legacy reasons. + +[[nodiscard]] inline bool CopyASCIItoUTF16(mozilla::Span aSource, + nsAString& aDest, + const mozilla::fallible_t&) { + return nsstring_fallible_append_latin1_impl(&aDest, aSource.Elements(), + aSource.Length(), 0, true); +} + +inline void CopyASCIItoUTF16(mozilla::Span aSource, + nsAString& aDest) { + if (MOZ_UNLIKELY(!CopyASCIItoUTF16(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool AppendASCIItoUTF16(mozilla::Span aSource, + nsAString& aDest, + const mozilla::fallible_t&) { + return nsstring_fallible_append_latin1_impl( + &aDest, aSource.Elements(), aSource.Length(), aDest.Length(), false); +} + +inline void AppendASCIItoUTF16(mozilla::Span aSource, + nsAString& aDest) { + if (MOZ_UNLIKELY(!AppendASCIItoUTF16(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// UTF-16 to UTF-8 +// Unpaired surrogates are replaced with the REPLACEMENT CHARACTER. + +[[nodiscard]] inline bool CopyUTF16toUTF8(mozilla::Span aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf16_to_utf8_impl( + &aDest, aSource.Elements(), aSource.Length(), 0); +} + +inline void CopyUTF16toUTF8(mozilla::Span aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY(!CopyUTF16toUTF8(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool AppendUTF16toUTF8( + mozilla::Span aSource, nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf16_to_utf8_impl( + &aDest, aSource.Elements(), aSource.Length(), aDest.Length()); +} + +inline void AppendUTF16toUTF8(mozilla::Span aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY(!AppendUTF16toUTF8(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// UTF-16 to Latin1 +// If all code points in the input are below U+0100, represents each scalar +// value as an unsigned byte. (This is not windows-1252!) If there are code +// points above U+00FF, memory-safely produces garbage and will likely start +// asserting in future debug builds. The nature of the garbage may differ +// based on CPU architecture and must not be relied upon. The names say +// "ASCII" instead of "Latin1" for legacy reasons. + +[[nodiscard]] inline bool LossyCopyUTF16toASCII( + mozilla::Span aSource, nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf16_to_latin1_lossy_impl( + &aDest, aSource.Elements(), aSource.Length(), 0, true); +} + +inline void LossyCopyUTF16toASCII(mozilla::Span aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY(!LossyCopyUTF16toASCII(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool LossyAppendUTF16toASCII( + mozilla::Span aSource, nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf16_to_latin1_lossy_impl( + &aDest, aSource.Elements(), aSource.Length(), aDest.Length(), false); +} + +inline void LossyAppendUTF16toASCII(mozilla::Span aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY( + !LossyAppendUTF16toASCII(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// Latin1 to UTF-8 +// Interpret each incoming unsigned byte value as a Unicode scalar value (not +// windows-1252!). +// If the input is ASCII, the heap-allocated nsStringBuffer is shared if +// possible. + +[[nodiscard]] inline bool CopyLatin1toUTF8(const nsACString& aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_latin1_to_utf8_check(&aDest, &aSource, 0); +} + +inline void CopyLatin1toUTF8(const nsACString& aSource, nsACString& aDest) { + if (MOZ_UNLIKELY(!CopyLatin1toUTF8(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool AppendLatin1toUTF8(const nsACString& aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_latin1_to_utf8_check(&aDest, &aSource, + aDest.Length()); +} + +inline void AppendLatin1toUTF8(const nsACString& aSource, nsACString& aDest) { + if (MOZ_UNLIKELY(!AppendLatin1toUTF8(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +// UTF-8 to Latin1 +// If all code points in the input are below U+0100, represents each scalar +// value as an unsigned byte. (This is not windows-1252!) If there are code +// points above U+00FF, memory-safely produces garbage in release builds and +// asserts in debug builds. The nature of the garbage may differ +// based on CPU architecture and must not be relied upon. +// If the input is ASCII, the heap-allocated nsStringBuffer is shared if +// possible. + +[[nodiscard]] inline bool LossyCopyUTF8toLatin1(const nsACString& aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf8_to_latin1_lossy_check(&aDest, &aSource, + 0); +} + +inline void LossyCopyUTF8toLatin1(const nsACString& aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY(!LossyCopyUTF8toLatin1(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aSource.Length()); + } +} + +[[nodiscard]] inline bool LossyAppendUTF8toLatin1(const nsACString& aSource, + nsACString& aDest, + const mozilla::fallible_t&) { + return nscstring_fallible_append_utf8_to_latin1_lossy_check(&aDest, &aSource, + aDest.Length()); +} + +inline void LossyAppendUTF8toLatin1(const nsACString& aSource, + nsACString& aDest) { + if (MOZ_UNLIKELY( + !LossyAppendUTF8toLatin1(aSource, aDest, mozilla::fallible))) { + aDest.AllocFailed(aDest.Length() + aSource.Length()); + } +} + +/** + * Returns a new |char| buffer containing a zero-terminated copy of |aSource|. + * + * Infallibly allocates and returns a new |char| buffer which you must + * free with |free|. + * Performs a conversion with LossyConvertUTF16toLatin1() writing into the + * newly-allocated buffer. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource a 16-bit wide string + * @return a new |char| buffer you must free with |free|. + */ +char* ToNewCString(const nsAString& aSource); + +/* A fallible version of ToNewCString. Returns nullptr on failure. */ +char* ToNewCString(const nsAString& aSource, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char| buffer containing a zero-terminated copy of |aSource|. + * + * Infallibly allocates and returns a new |char| buffer which you must + * free with |free|. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource an 8-bit wide string + * @return a new |char| buffer you must free with |free|. + */ +char* ToNewCString(const nsACString& aSource); + +/* A fallible version of ToNewCString. Returns nullptr on failure. */ +char* ToNewCString(const nsACString& aSource, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char| buffer containing a zero-terminated copy of |aSource|. + * + * Infallibly allocates and returns a new |char| buffer which you must + * free with |free|. + * Performs an encoding conversion from a UTF-16 string to a UTF-8 string with + * unpaired surrogates replaced with the REPLACEMENT CHARACTER copying + * |aSource| to your new buffer. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource a UTF-16 string (made of char16_t's) + * @param aUTF8Count the number of 8-bit units that was returned + * @return a new |char| buffer you must free with |free|. + */ +char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count = nullptr); + +/* A fallible version of ToNewUTF8String. Returns nullptr on failure. */ +char* ToNewUTF8String(const nsAString& aSource, uint32_t* aUTF8Count, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char16_t| buffer containing a zero-terminated copy + * of |aSource|. + * + * Infallibly allocates and returns a new |char16_t| buffer which you must + * free with |free|. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource a UTF-16 string + * @return a new |char16_t| buffer you must free with |free|. + */ +char16_t* ToNewUnicode(const nsAString& aSource); + +/* A fallible version of ToNewUnicode. Returns nullptr on failure. */ +char16_t* ToNewUnicode(const nsAString& aSource, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char16_t| buffer containing a zero-terminated copy + * of |aSource|. + * + * Infallibly allocates and returns a new |char16_t| buffer which you must + * free with|free|. + * + * Performs an encoding conversion by 0-padding 8-bit wide characters up to + * 16-bits wide (i.e. Latin1 to UTF-16 conversion) while copying |aSource| + * to your new buffer. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource a Latin1 string + * @return a new |char16_t| buffer you must free with |free|. + */ +char16_t* ToNewUnicode(const nsACString& aSource); + +/* A fallible version of ToNewUnicode. Returns nullptr on failure. */ +char16_t* ToNewUnicode(const nsACString& aSource, + const mozilla::fallible_t& aFallible); + +/** + * Returns a new |char16_t| buffer containing a zero-terminated copy + * of |aSource|. + * + * Infallibly allocates and returns a new |char| buffer which you must + * free with |free|. Performs an encoding conversion from UTF-8 to UTF-16 + * while copying |aSource| to your new buffer. Malformed byte sequences + * are replaced with the REPLACEMENT CHARACTER. + * + * The new buffer is zero-terminated, but that may not help you if |aSource| + * contains embedded nulls. + * + * @param aSource an 8-bit wide string, UTF-8 encoded + * @param aUTF16Count the number of 16-bit units that was returned + * @return a new |char16_t| buffer you must free with |free|. + * (UTF-16 encoded) + */ +char16_t* UTF8ToNewUnicode(const nsACString& aSource, + uint32_t* aUTF16Count = nullptr); + +/* A fallible version of UTF8ToNewUnicode. Returns nullptr on failure. */ +char16_t* UTF8ToNewUnicode(const nsACString& aSource, uint32_t* aUTF16Count, + const mozilla::fallible_t& aFallible); + +/** + * Copies |aLength| 16-bit code units from the start of |aSource| to the + * |char16_t| buffer |aDest|. + * + * After this operation |aDest| is not null terminated. + * + * @param aSource a UTF-16 string + * @param aSrcOffset start offset in the source string + * @param aDest a |char16_t| buffer + * @param aLength the number of 16-bit code units to copy + * @return pointer to destination buffer - identical to |aDest| + */ +char16_t* CopyUnicodeTo(const nsAString& aSource, uint32_t aSrcOffset, + char16_t* aDest, uint32_t aLength); + +/** + * Replaces unpaired surrogates with U+FFFD in the argument. + * + * Copies a shared string buffer or an otherwise read-only + * buffer only if there are unpaired surrogates. + */ +[[nodiscard]] inline bool EnsureUTF16Validity(nsAString& aString) { + size_t upTo = mozilla::Utf16ValidUpTo(aString); + size_t len = aString.Length(); + if (upTo == len) { + return true; + } + char16_t* ptr = aString.BeginWriting(mozilla::fallible); + if (!ptr) { + return false; + } + auto span = mozilla::Span(ptr, len); + span[upTo] = 0xFFFD; + mozilla::EnsureUtf16ValiditySpan(span.From(upTo + 1)); + return true; +} + +void ParseString(const nsACString& aSource, char aDelimiter, + nsTArray& aArray); + +namespace mozilla::detail { + +constexpr auto kStringJoinAppendDefault = + [](auto& aResult, const auto& aValue) { aResult.Append(aValue); }; + +} // namespace mozilla::detail + +/** + * Join a sequence of items, each optionally transformed to a string, with a + * given separator, appending to a given string. + * + * \tparam CharType char or char16_t + * \tparam InputRange a range usable with range-based for + * \tparam Func optionally, a functor accepting a nsTSubstring& and + * an item of InputRange which appends the latter to the former + */ +template < + typename CharType, typename InputRange, + typename Func = const decltype(mozilla::detail::kStringJoinAppendDefault)&> +void StringJoinAppend( + nsTSubstring& aOutput, + const nsTLiteralString& aSeparator, const InputRange& aInputRange, + Func&& aFunc = mozilla::detail::kStringJoinAppendDefault) { + bool first = true; + for (const auto& item : aInputRange) { + if (first) { + first = false; + } else { + aOutput.Append(aSeparator); + } + + aFunc(aOutput, item); + } +} + +/** + * Join a sequence of items, each optionally transformed to a string, with a + * given separator, returning a new string. + * + * \tparam CharType char or char16_t + * \tparam InputRange a range usable with range-based for + * \tparam Func optionally, a functor accepting a nsTSubstring& and + * an item of InputRange which appends the latter to the former + + */ +template < + typename CharType, typename InputRange, + typename Func = const decltype(mozilla::detail::kStringJoinAppendDefault)&> +auto StringJoin(const nsTLiteralString& aSeparator, + const InputRange& aInputRange, + Func&& aFunc = mozilla::detail::kStringJoinAppendDefault) { + nsTAutoString res; + StringJoinAppend(res, aSeparator, aInputRange, std::forward(aFunc)); + return res; +} + +/** + * Converts case in place in the argument string. + */ +void ToUpperCase(nsACString&); + +void ToLowerCase(nsACString&); + +void ToUpperCase(nsACString&); + +void ToLowerCase(nsACString&); + +/** + * Converts case from string aSource to aDest. + */ +void ToUpperCase(const nsACString& aSource, nsACString& aDest); + +void ToLowerCase(const nsACString& aSource, nsACString& aDest); + +/** + * Finds the leftmost occurrence of |aPattern|, if any in the range + * |aSearchStart|..|aSearchEnd|. + * + * Returns |true| if a match was found, and adjusts |aSearchStart| and + * |aSearchEnd| to point to the match. If no match was found, returns |false| + * and makes |aSearchStart == aSearchEnd|. + * + * Currently, this is equivalent to the O(m*n) implementation previously on + * |ns[C]String|. + * + * If we need something faster, then we can implement that later. + */ + +bool FindInReadable(const nsAString& aPattern, nsAString::const_iterator&, + nsAString::const_iterator&, + nsStringComparator = nsTDefaultStringComparator); +bool FindInReadable(const nsACString& aPattern, nsACString::const_iterator&, + nsACString::const_iterator&, + nsCStringComparator = nsTDefaultStringComparator); + +/* sometimes we don't care about where the string was, just that we + * found it or not */ +inline bool FindInReadable( + const nsAString& aPattern, const nsAString& aSource, + nsStringComparator aCompare = nsTDefaultStringComparator) { + nsAString::const_iterator start, end; + aSource.BeginReading(start); + aSource.EndReading(end); + return FindInReadable(aPattern, start, end, aCompare); +} + +inline bool FindInReadable( + const nsACString& aPattern, const nsACString& aSource, + nsCStringComparator aCompare = nsTDefaultStringComparator) { + nsACString::const_iterator start, end; + aSource.BeginReading(start); + aSource.EndReading(end); + return FindInReadable(aPattern, start, end, aCompare); +} + +bool CaseInsensitiveFindInReadable(const nsACString& aPattern, + nsACString::const_iterator&, + nsACString::const_iterator&); + +/** + * Finds the rightmost occurrence of |aPattern| + * Returns |true| if a match was found, and adjusts |aSearchStart| and + * |aSearchEnd| to point to the match. If no match was found, returns |false| + * and makes |aSearchStart == aSearchEnd|. + */ +bool RFindInReadable(const nsAString& aPattern, nsAString::const_iterator&, + nsAString::const_iterator&, + nsStringComparator = nsTDefaultStringComparator); +bool RFindInReadable(const nsACString& aPattern, nsACString::const_iterator&, + nsACString::const_iterator&, + nsCStringComparator = nsTDefaultStringComparator); + +/** + * Finds the leftmost occurrence of |aChar|, if any in the range + * |aSearchStart|..|aSearchEnd|. + * + * Returns |true| if a match was found, and adjusts |aSearchStart| to + * point to the match. If no match was found, returns |false| and + * makes |aSearchStart == aSearchEnd|. + */ +bool FindCharInReadable(char16_t aChar, nsAString::const_iterator& aSearchStart, + const nsAString::const_iterator& aSearchEnd); +bool FindCharInReadable(char aChar, nsACString::const_iterator& aSearchStart, + const nsACString::const_iterator& aSearchEnd); + +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring); +bool StringBeginsWith(const nsAString& aSource, const nsAString& aSubstring, + nsStringComparator); +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring); +bool StringBeginsWith(const nsACString& aSource, const nsACString& aSubstring, + nsCStringComparator); +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring); +bool StringEndsWith(const nsAString& aSource, const nsAString& aSubstring, + nsStringComparator); +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring); +bool StringEndsWith(const nsACString& aSource, const nsACString& aSubstring, + nsCStringComparator); + +const nsString& EmptyString(); +const nsCString& EmptyCString(); + +const nsString& VoidString(); +const nsCString& VoidCString(); + +/** + * Compare a UTF-8 string to an UTF-16 string. + * + * Returns 0 if the strings are equal, -1 if aUTF8String is less + * than aUTF16Count, and 1 in the reverse case. Errors are replaced + * with U+FFFD and then the U+FFFD is compared as if it had occurred + * in the input. If aErr is not nullptr, *aErr is set to true if + * either string had malformed sequences. + */ +int32_t CompareUTF8toUTF16(const nsACString& aUTF8String, + const nsAString& aUTF16String, bool* aErr = nullptr); + +void AppendUCS4ToUTF16(const uint32_t aSource, nsAString& aDest); + +#endif // !defined(nsReadableUtils_h___) diff --git a/xpcom/string/nsString.h b/xpcom/string/nsString.h new file mode 100644 index 0000000000..e86ea594ac --- /dev/null +++ b/xpcom/string/nsString.h @@ -0,0 +1,171 @@ +/* -*- 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/. */ + +#ifndef nsString_h___ +#define nsString_h___ + +#include + +#include "mozilla/Attributes.h" + +#include "nsStringFwd.h" + +#include "nsAString.h" +#include "nsDependentSubstring.h" +#include "nsReadableUtils.h" + +#include "nsTString.h" + +static_assert(sizeof(char16_t) == 2, "size of char16_t must be 2"); +static_assert(sizeof(nsString::char_type) == 2, + "size of nsString::char_type must be 2"); +static_assert(nsString::char_type(-1) > nsString::char_type(0), + "nsString::char_type must be unsigned"); +static_assert(sizeof(nsCString::char_type) == 1, + "size of nsCString::char_type must be 1"); + +static_assert(sizeof(nsTLiteralString) == sizeof(nsTString), + "nsLiteralCString can masquerade as nsCString, " + "so they must have identical layout"); + +static_assert(sizeof(nsTLiteralString) == sizeof(nsTString), + "nsTLiteralString can masquerade as nsString, " + "so they must have identical layout"); + +/** + * A helper class that converts a UTF-16 string to ASCII in a lossy manner + */ +class NS_LossyConvertUTF16toASCII : public nsAutoCString { + public: + explicit NS_LossyConvertUTF16toASCII(const char16ptr_t aString) { + LossyAppendUTF16toASCII(mozilla::MakeStringSpan(aString), *this); + } + + NS_LossyConvertUTF16toASCII(const char16ptr_t aString, size_t aLength) { + LossyAppendUTF16toASCII( + Substring(static_cast(aString), aLength), *this); + } + + explicit NS_LossyConvertUTF16toASCII(const nsAString& aString) { + LossyAppendUTF16toASCII(aString, *this); + } + + private: + // NOT TO BE IMPLEMENTED + NS_LossyConvertUTF16toASCII(char) = delete; +}; + +class NS_ConvertASCIItoUTF16 : public nsAutoString { + public: + explicit NS_ConvertASCIItoUTF16(const char* aCString) { + AppendASCIItoUTF16(mozilla::MakeStringSpan(aCString), *this); + } + + NS_ConvertASCIItoUTF16(const char* aCString, size_t aLength) { + AppendASCIItoUTF16(Substring(aCString, aLength), *this); + } + + explicit NS_ConvertASCIItoUTF16(const nsACString& aCString) { + AppendASCIItoUTF16(aCString, *this); + } + + explicit NS_ConvertASCIItoUTF16(mozilla::Span aCString) { + AppendASCIItoUTF16(aCString, *this); + } + + private: + // NOT TO BE IMPLEMENTED + NS_ConvertASCIItoUTF16(char16_t) = delete; +}; + +/** + * A helper class that converts a UTF-16 string to UTF-8 + */ +class NS_ConvertUTF16toUTF8 : public nsAutoCString { + public: + explicit NS_ConvertUTF16toUTF8(const char16ptr_t aString) { + AppendUTF16toUTF8(mozilla::MakeStringSpan(aString), *this); + } + + NS_ConvertUTF16toUTF8(const char16ptr_t aString, size_t aLength) { + AppendUTF16toUTF8(Substring(static_cast(aString), aLength), + *this); + } + + explicit NS_ConvertUTF16toUTF8(const nsAString& aString) { + AppendUTF16toUTF8(aString, *this); + } + + explicit NS_ConvertUTF16toUTF8(mozilla::Span aString) { + AppendUTF16toUTF8(aString, *this); + } + + private: + // NOT TO BE IMPLEMENTED + NS_ConvertUTF16toUTF8(char) = delete; +}; + +class NS_ConvertUTF8toUTF16 : public nsAutoString { + public: + explicit NS_ConvertUTF8toUTF16(const char* aCString) { + AppendUTF8toUTF16(mozilla::MakeStringSpan(aCString), *this); + } + + NS_ConvertUTF8toUTF16(const char* aCString, size_t aLength) { + AppendUTF8toUTF16(Substring(aCString, aLength), *this); + } + + explicit NS_ConvertUTF8toUTF16(const nsACString& aCString) { + AppendUTF8toUTF16(aCString, *this); + } + + explicit NS_ConvertUTF8toUTF16(mozilla::Span aCString) { + AppendUTF8toUTF16(aCString, *this); + } + + private: + // NOT TO BE IMPLEMENTED + NS_ConvertUTF8toUTF16(char16_t) = delete; +}; + +/** + * Converts an integer (signed/unsigned, 32/64bit) to its decimal string + * representation and returns it as an nsAutoCString/nsAutoString. + */ +template +nsTAutoString IntToTString(const U aInt, const int aRadix = 10) { + nsTAutoString string; + string.AppendInt(aInt, aRadix); + return string; +} + +template +nsAutoCString IntToCString(const U aInt, const int aRadix = 10) { + return IntToTString(aInt, aRadix); +} + +template +nsAutoString IntToString(const U aInt, const int aRadix = 10) { + return IntToTString(aInt, aRadix); +} + +// MOZ_DBG support + +inline std::ostream& operator<<(std::ostream& aOut, const nsACString& aString) { + aOut.write(aString.Data(), aString.Length()); + return aOut; +} + +inline std::ostream& operator<<(std::ostream& aOut, const nsAString& aString) { + return aOut << NS_ConvertUTF16toUTF8(aString); +} + +// the following are included/declared for backwards compatibility +#include "nsDependentString.h" +#include "nsLiteralString.h" +#include "nsPromiseFlatString.h" + +#endif // !defined(nsString_h___) diff --git a/xpcom/string/nsStringBuffer.cpp b/xpcom/string/nsStringBuffer.cpp new file mode 100644 index 0000000000..f32b51d3a6 --- /dev/null +++ b/xpcom/string/nsStringBuffer.cpp @@ -0,0 +1,162 @@ +/* -*- 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 "nsStringBuffer.h" + +#include "mozilla/MemoryReporting.h" +#include "nsISupportsImpl.h" +#include "nsString.h" + +#ifdef DEBUG +# include "nsStringStats.h" +#else +# define STRING_STAT_INCREMENT(_s) +#endif + +void nsStringBuffer::AddRef() { + // Memory synchronization is not required when incrementing a + // reference count. The first increment of a reference count on a + // thread is not important, since the first use of the object on a + // thread can happen before it. What is important is the transfer + // of the pointer to that thread, which may happen prior to the + // first increment on that thread. The necessary memory + // synchronization is done by the mechanism that transfers the + // pointer between threads. +#ifdef NS_BUILD_REFCNT_LOGGING + uint32_t count = +#endif + mRefCount.fetch_add(1, std::memory_order_relaxed) +#ifdef NS_BUILD_REFCNT_LOGGING + + 1 +#endif + ; + STRING_STAT_INCREMENT(Share); + NS_LOG_ADDREF(this, count, "nsStringBuffer", sizeof(*this)); +} + +void nsStringBuffer::Release() { + // Since this may be the last release on this thread, we need + // release semantics so that prior writes on this thread are visible + // to the thread that destroys the object when it reads mValue with + // acquire semantics. + uint32_t count = mRefCount.fetch_sub(1, std::memory_order_release) - 1; + NS_LOG_RELEASE(this, count, "nsStringBuffer"); + if (count == 0) { + // We're going to destroy the object on this thread, so we need + // acquire semantics to synchronize with the memory released by + // the last release on other threads, that is, to ensure that + // writes prior to that release are now visible on this thread. + count = mRefCount.load(std::memory_order_acquire); + + STRING_STAT_INCREMENT(Free); + free(this); // we were allocated with |malloc| + } +} + +/** + * Alloc returns a pointer to a new string header with set capacity. + */ +already_AddRefed nsStringBuffer::Alloc(size_t aSize) { + NS_ASSERTION(aSize != 0, "zero capacity allocation not allowed"); + NS_ASSERTION(sizeof(nsStringBuffer) + aSize <= size_t(uint32_t(-1)) && + sizeof(nsStringBuffer) + aSize > aSize, + "mStorageSize will truncate"); + + nsStringBuffer* hdr = (nsStringBuffer*)malloc(sizeof(nsStringBuffer) + aSize); + if (hdr) { + STRING_STAT_INCREMENT(Alloc); + + hdr->mRefCount = 1; + hdr->mStorageSize = aSize; + NS_LOG_ADDREF(hdr, 1, "nsStringBuffer", sizeof(*hdr)); + } + return already_AddRefed(hdr); +} + +nsStringBuffer* nsStringBuffer::Realloc(nsStringBuffer* aHdr, size_t aSize) { + STRING_STAT_INCREMENT(Realloc); + + NS_ASSERTION(aSize != 0, "zero capacity allocation not allowed"); + NS_ASSERTION(sizeof(nsStringBuffer) + aSize <= size_t(uint32_t(-1)) && + sizeof(nsStringBuffer) + aSize > aSize, + "mStorageSize will truncate"); + + // no point in trying to save ourselves if we hit this assertion + NS_ASSERTION(!aHdr->IsReadonly(), "|Realloc| attempted on readonly string"); + + // Treat this as a release and addref for refcounting purposes, since we + // just asserted that the refcount is 1. If we don't do that, refcount + // logging will claim we've leaked all sorts of stuff. + NS_LOG_RELEASE(aHdr, 0, "nsStringBuffer"); + + aHdr = (nsStringBuffer*)realloc(aHdr, sizeof(nsStringBuffer) + aSize); + if (aHdr) { + NS_LOG_ADDREF(aHdr, 1, "nsStringBuffer", sizeof(*aHdr)); + aHdr->mStorageSize = aSize; + } + + return aHdr; +} + +nsStringBuffer* nsStringBuffer::FromString(const nsAString& aStr) { + if (!(aStr.mDataFlags & nsAString::DataFlags::REFCOUNTED)) { + return nullptr; + } + + return FromData(aStr.mData); +} + +nsStringBuffer* nsStringBuffer::FromString(const nsACString& aStr) { + if (!(aStr.mDataFlags & nsACString::DataFlags::REFCOUNTED)) { + return nullptr; + } + + return FromData(aStr.mData); +} + +void nsStringBuffer::ToString(uint32_t aLen, nsAString& aStr, + bool aMoveOwnership) { + char16_t* data = static_cast(Data()); + + MOZ_DIAGNOSTIC_ASSERT(data[aLen] == char16_t(0), + "data should be null terminated"); + + nsAString::DataFlags flags = + nsAString::DataFlags::REFCOUNTED | nsAString::DataFlags::TERMINATED; + + if (!aMoveOwnership) { + AddRef(); + } + aStr.Finalize(); + aStr.SetData(data, aLen, flags); +} + +void nsStringBuffer::ToString(uint32_t aLen, nsACString& aStr, + bool aMoveOwnership) { + char* data = static_cast(Data()); + + MOZ_DIAGNOSTIC_ASSERT(data[aLen] == char(0), + "data should be null terminated"); + + nsACString::DataFlags flags = + nsACString::DataFlags::REFCOUNTED | nsACString::DataFlags::TERMINATED; + + if (!aMoveOwnership) { + AddRef(); + } + aStr.Finalize(); + aStr.SetData(data, aLen, flags); +} + +size_t nsStringBuffer::SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const { + return IsReadonly() ? 0 : aMallocSizeOf(this); +} + +size_t nsStringBuffer::SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this); +} diff --git a/xpcom/string/nsStringBuffer.h b/xpcom/string/nsStringBuffer.h new file mode 100644 index 0000000000..3c92959932 --- /dev/null +++ b/xpcom/string/nsStringBuffer.h @@ -0,0 +1,184 @@ +/* -*- 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/. */ + +#ifndef nsStringBuffer_h__ +#define nsStringBuffer_h__ + +#include +#include "mozilla/MemoryReporting.h" +#include "nsStringFwd.h" + +template +struct already_AddRefed; + +/** + * This structure precedes the string buffers "we" allocate. It may be the + * case that nsTAString::mData does not point to one of these special + * buffers. The mDataFlags member variable distinguishes the buffer type. + * + * When this header is in use, it enables reference counting, and capacity + * tracking. NOTE: A string buffer can be modified only if its reference + * count is 1. + */ +class nsStringBuffer { + private: + friend class CheckStaticAtomSizes; + + std::atomic mRefCount; + uint32_t mStorageSize; + + public: + /** + * Allocates a new string buffer, with given size in bytes and a + * reference count of one. When the string buffer is no longer needed, + * it should be released via Release. + * + * It is up to the caller to set the bytes corresponding to the string + * buffer by calling the Data method to fetch the raw data pointer. Care + * must be taken to properly null terminate the character array. The + * storage size can be greater than the length of the actual string + * (i.e., it is not required that the null terminator appear in the last + * storage unit of the string buffer's data). + * + * @return new string buffer or null if out of memory. + */ + static already_AddRefed Alloc(size_t aStorageSize); + + /** + * Resizes the given string buffer to the specified storage size. This + * method must not be called on a readonly string buffer. Use this API + * carefully!! + * + * This method behaves like the ANSI-C realloc function. (i.e., If the + * allocation fails, null will be returned and the given string buffer + * will remain unmodified.) + * + * @see IsReadonly + */ + static nsStringBuffer* Realloc(nsStringBuffer* aBuf, size_t aStorageSize); + + /** + * Increment the reference count on this string buffer. + */ + void NS_FASTCALL AddRef(); + + /** + * Decrement the reference count on this string buffer. The string + * buffer will be destroyed when its reference count reaches zero. + */ + void NS_FASTCALL Release(); + + /** + * This method returns the string buffer corresponding to the given data + * pointer. The data pointer must have been returned previously by a + * call to the nsStringBuffer::Data method. + */ + static nsStringBuffer* FromData(void* aData) { + return reinterpret_cast(aData) - 1; + } + + /** + * This method returns the data pointer for this string buffer. + */ + void* Data() const { + return const_cast(reinterpret_cast(this + 1)); + } + + /** + * This function returns the storage size of a string buffer in bytes. + * This value is the same value that was originally passed to Alloc (or + * Realloc). + */ + uint32_t StorageSize() const { return mStorageSize; } + + /** + * If this method returns false, then the caller can be sure that their + * reference to the string buffer is the only reference to the string + * buffer, and therefore it has exclusive access to the string buffer and + * associated data. However, if this function returns true, then other + * consumers may rely on the data in this buffer being immutable and + * other threads may access this buffer simultaneously. + */ + bool IsReadonly() const { + // This doesn't lead to the destruction of the buffer, so we don't + // need to perform acquire memory synchronization for the normal + // reason that a reference count needs acquire synchronization + // (ensuring that all writes to the object made on other threads are + // visible to the thread destroying the object). + // + // We then need to consider the possibility that there were prior + // writes to the buffer on a different thread: one that has either + // since released its reference count, or one that also has access + // to this buffer through the same reference. There are two ways + // for that to happen: either the buffer pointer or a data structure + // (e.g., string object) pointing to the buffer was transferred from + // one thread to another, or the data structure pointing to the + // buffer was already visible on both threads. In the first case + // (transfer), the transfer of data from one thread to another would + // have handled the memory synchronization. In the latter case + // (data structure visible on both threads), the caller needed some + // sort of higher level memory synchronization to protect against + // the string object being mutated at the same time on multiple + // threads. + + // See bug 1603504. TSan might complain about a race when using + // memory_order_relaxed, so use memory_order_acquire for making TSan + // happy. +#if defined(MOZ_TSAN) + return mRefCount.load(std::memory_order_acquire) > 1; +#else + return mRefCount.load(std::memory_order_relaxed) > 1; +#endif + } + + /** + * The FromString methods return a string buffer for the given string + * object or null if the string object does not have a string buffer. + * The reference count of the string buffer is NOT incremented by these + * methods. If the caller wishes to hold onto the returned value, then + * the returned string buffer must have its reference count incremented + * via a call to the AddRef method. + */ + static nsStringBuffer* FromString(const nsAString& aStr); + static nsStringBuffer* FromString(const nsACString& aStr); + + /** + * The ToString methods assign this string buffer to a given string + * object. If the string object does not support sharable string + * buffers, then its value will be set to a copy of the given string + * buffer. Otherwise, these methods increment the reference count of the + * given string buffer. It is important to specify the length (in + * storage units) of the string contained in the string buffer since the + * length of the string may be less than its storage size. The string + * must have a null terminator at the offset specified by |len|. + * + * NOTE: storage size is measured in bytes even for wide strings; + * however, string length is always measured in storage units + * (2-byte units for wide strings). + */ + void ToString(uint32_t aLen, nsAString& aStr, bool aMoveOwnership = false); + void ToString(uint32_t aLen, nsACString& aStr, bool aMoveOwnership = false); + + /** + * This measures the size only if the StringBuffer is unshared. + */ + size_t SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const; + + /** + * This measures the size regardless of whether the StringBuffer is + * unshared. + * + * WARNING: Only use this if you really know what you are doing, because + * it can easily lead to double-counting strings. If you do use them, + * please explain clearly in a comment why it's safe and won't lead to + * double-counting. + */ + size_t SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const; +}; + +#endif /* !defined(nsStringBuffer_h__ */ diff --git a/xpcom/string/nsStringFlags.h b/xpcom/string/nsStringFlags.h new file mode 100644 index 0000000000..d0ba05c8db --- /dev/null +++ b/xpcom/string/nsStringFlags.h @@ -0,0 +1,95 @@ +/* -*- 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/. */ + +#ifndef nsStringFlags_h +#define nsStringFlags_h + +#include +#include "mozilla/TypedEnumBits.h" + +namespace mozilla { +namespace detail { +// NOTE: these flags are declared public _only_ for convenience inside +// the string implementation. And they are outside of the string +// class so that the type is the same for both narrow and wide +// strings. + +// bits for mDataFlags +enum class StringDataFlags : uint16_t { + // Some terminology: + // + // "dependent buffer" A dependent buffer is one that the string class + // does not own. The string class relies on some + // external code to ensure the lifetime of the + // dependent buffer. + // + // "refcounted buffer" A refcounted buffer is one that the string class + // allocates. When it allocates a refcounted string + // buffer, it allocates some additional space at + // the beginning of the buffer for additional + // fields, including a reference count and a + // buffer length. See nsStringHeader. + // + // "adopted buffer" An adopted buffer is a raw string buffer + // allocated on the heap (using moz_xmalloc) + // of which the string class subsumes ownership. + // + // Some comments about the string data flags: + // + // REFCOUNTED, OWNED, and INLINE are all mutually exlusive. They + // indicate the allocation type of mData. If none of these flags + // are set, then the string buffer is dependent. + // + // REFCOUNTED, OWNED, or INLINE imply TERMINATED. This is because + // the string classes always allocate null-terminated buffers, and + // non-terminated substrings are always dependent. + // + // VOIDED implies TERMINATED, and moreover it implies that mData + // points to char_traits::sEmptyBuffer. Therefore, VOIDED is + // mutually exclusive with REFCOUNTED, OWNED, and INLINE. + // + // INLINE requires StringClassFlags::INLINE to be set on the type. + + // IsTerminated returns true + TERMINATED = 1 << 0, + + // IsVoid returns true + VOIDED = 1 << 1, + + // mData points to a heap-allocated, shareable, refcounted buffer + REFCOUNTED = 1 << 2, + + // mData points to a heap-allocated, raw buffer + OWNED = 1 << 3, + + // mData points to a writable, inline buffer + INLINE = 1 << 4, + + // mData points to a string literal; DataFlags::TERMINATED will also be set + LITERAL = 1 << 5, + + // used to check for invalid flags -- all bits above the last item + INVALID_MASK = (uint16_t) ~((LITERAL << 1) - 1) +}; + +// bits for mClassFlags +enum class StringClassFlags : uint16_t { + // |this|'s buffer is inline, and requires the type to be binary-compatible + // with nsTAutoStringN + INLINE = 1 << 0, + // |this| requires its buffer is null-terminated + NULL_TERMINATED = 1 << 1, + // used to check for invalid flags -- all bits above the last item + INVALID_MASK = (uint16_t) ~((NULL_TERMINATED << 1) - 1) +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StringDataFlags) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(StringClassFlags) + +} // namespace detail +} // namespace mozilla + +#endif diff --git a/xpcom/string/nsStringFwd.h b/xpcom/string/nsStringFwd.h new file mode 100644 index 0000000000..f737545163 --- /dev/null +++ b/xpcom/string/nsStringFwd.h @@ -0,0 +1,92 @@ +/* -*- 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/. */ + +/* nsStringFwd.h --- forward declarations for string classes */ + +#ifndef nsStringFwd_h +#define nsStringFwd_h + +#include "nscore.h" + +static constexpr int32_t kNotFound = -1; + +namespace mozilla { +namespace detail { + +template +class nsTStringRepr; + +using nsStringRepr = nsTStringRepr; +using nsCStringRepr = nsTStringRepr; + +} // namespace detail +} // namespace mozilla + +static const size_t AutoStringDefaultStorageSize = 64; + +template +class nsTSubstring; +template +class nsTSubstringTuple; +template +class nsTString; +template +class nsTAutoStringN; +template +class nsTDependentString; +template +class nsTDependentSubstring; +template +class nsTPromiseFlatString; +template +class nsTLiteralString; +template +class nsTSubstringSplitter; + +template +using nsTStringComparator = int (*)(const T*, const T*, size_t, size_t); + +// The default string comparator (case-sensitive comparision) +template +int nsTDefaultStringComparator(const T*, const T*, size_t, size_t); + +// We define this version without a size param instead of providing a +// default value for N so that so there is a default typename that doesn't +// require angle brackets. +template +using nsTAutoString = nsTAutoStringN; + +// Double-byte (char16_t) string types. + +using nsAString = nsTSubstring; +using nsSubstringTuple = nsTSubstringTuple; +using nsString = nsTString; +using nsAutoString = nsTAutoString; +template +using nsAutoStringN = nsTAutoStringN; +using nsDependentString = nsTDependentString; +using nsDependentSubstring = nsTDependentSubstring; +using nsPromiseFlatString = nsTPromiseFlatString; +using nsStringComparator = nsTStringComparator; +using nsLiteralString = nsTLiteralString; +using nsSubstringSplitter = nsTSubstringSplitter; + +// Single-byte (char) string types. + +using nsACString = nsTSubstring; +using nsCSubstringTuple = nsTSubstringTuple; +using nsCString = nsTString; +using nsAutoCString = nsTAutoString; +template +using nsAutoCStringN = nsTAutoStringN; +using nsDependentCString = nsTDependentString; +using nsDependentCSubstring = nsTDependentSubstring; +using nsPromiseFlatCString = nsTPromiseFlatString; +using nsCStringComparator = nsTStringComparator; +using nsLiteralCString = nsTLiteralString; +using nsCSubstringSplitter = nsTSubstringSplitter; + +#endif /* !defined(nsStringFwd_h) */ diff --git a/xpcom/string/nsStringIterator.h b/xpcom/string/nsStringIterator.h new file mode 100644 index 0000000000..db14efdaca --- /dev/null +++ b/xpcom/string/nsStringIterator.h @@ -0,0 +1,117 @@ +/* -*- 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/. */ + +#ifndef nsStringIterator_h___ +#define nsStringIterator_h___ + +#include "nsCharTraits.h" +#include "nsAlgorithm.h" +#include "nsDebug.h" + +/** + * @see nsTAString + */ + +template +class nsReadingIterator { + public: + typedef nsReadingIterator self_type; + typedef ptrdiff_t difference_type; + typedef size_t size_type; + typedef CharT value_type; + typedef const CharT* pointer; + typedef const CharT& reference; + + private: + friend class mozilla::detail::nsTStringRepr; + + // unfortunately, the API for nsReadingIterator requires that the + // iterator know its start and end positions. this was needed when + // we supported multi-fragment strings, but now it is really just + // extra baggage. we should remove mStart and mEnd at some point. + + const CharT* mStart; + const CharT* mEnd; + const CharT* mPosition; + + public: + nsReadingIterator() : mStart(nullptr), mEnd(nullptr), mPosition(nullptr) {} + // clang-format off + // nsReadingIterator( const nsReadingIterator& ); // auto-generated copy-constructor OK + // nsReadingIterator& operator=( const nsReadingIterator& ); // auto-generated copy-assignment operator OK + // clang-format on + + pointer get() const { return mPosition; } + + CharT operator*() const { return *get(); } + + self_type& operator++() { + ++mPosition; + return *this; + } + + self_type operator++(int) { + self_type result(*this); + ++mPosition; + return result; + } + + self_type& operator--() { + --mPosition; + return *this; + } + + self_type operator--(int) { + self_type result(*this); + --mPosition; + return result; + } + + self_type& advance(difference_type aN) { + if (aN > 0) { + difference_type step = XPCOM_MIN(aN, mEnd - mPosition); + + NS_ASSERTION( + step > 0, + "can't advance a reading iterator beyond the end of a string"); + + mPosition += step; + } else if (aN < 0) { + difference_type step = XPCOM_MAX(aN, -(mPosition - mStart)); + + NS_ASSERTION(step < 0, + "can't advance (backward) a reading iterator beyond the end " + "of a string"); + + mPosition += step; + } + return *this; + } + + // We return an unsigned type here (with corresponding assert) rather than + // the more usual difference_type because we want to make this class go + // away in favor of mozilla::RangedPtr. Since RangedPtr has the same + // requirement we are enforcing here, the transition ought to be much + // smoother. + size_type operator-(const self_type& aOther) const { + MOZ_ASSERT(mPosition >= aOther.mPosition); + return mPosition - aOther.mPosition; + } +}; + +template +inline bool operator==(const nsReadingIterator& aLhs, + const nsReadingIterator& aRhs) { + return aLhs.get() == aRhs.get(); +} + +template +inline bool operator!=(const nsReadingIterator& aLhs, + const nsReadingIterator& aRhs) { + return aLhs.get() != aRhs.get(); +} + +#endif /* !defined(nsStringIterator_h___) */ diff --git a/xpcom/string/nsStringStats.cpp b/xpcom/string/nsStringStats.cpp new file mode 100644 index 0000000000..7fc3d82ad5 --- /dev/null +++ b/xpcom/string/nsStringStats.cpp @@ -0,0 +1,66 @@ +/* -*- 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 "nsStringStats.h" + +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/MemoryReporting.h" +#include "nsString.h" + +#include +#include + +#ifdef XP_WIN +# include +# include +#else +# include +# include +#endif + +nsStringStats gStringStats; + +nsStringStats::~nsStringStats() { + // this is a hack to suppress duplicate string stats printing + // in seamonkey as a result of the string code being linked + // into seamonkey and libxpcom! :-( + if (!mAllocCount && !mAdoptCount) { + return; + } + + // Only print the stats if we detect a leak. + if (mAllocCount <= mFreeCount && mAdoptCount <= mAdoptFreeCount) { + return; + } + + printf("nsStringStats\n"); + printf(" => mAllocCount: % 10d\n", int(mAllocCount)); + printf(" => mReallocCount: % 10d\n", int(mReallocCount)); + printf(" => mFreeCount: % 10d", int(mFreeCount)); + if (mAllocCount > mFreeCount) { + printf(" -- LEAKED %d !!!\n", mAllocCount - mFreeCount); + } else { + printf("\n"); + } + printf(" => mShareCount: % 10d\n", int(mShareCount)); + printf(" => mAdoptCount: % 10d\n", int(mAdoptCount)); + printf(" => mAdoptFreeCount: % 10d", int(mAdoptFreeCount)); + if (mAdoptCount > mAdoptFreeCount) { + printf(" -- LEAKED %d !!!\n", mAdoptCount - mAdoptFreeCount); + } else { + printf("\n"); + } + +#ifdef XP_WIN + auto pid = uintptr_t(_getpid()); + auto tid = uintptr_t(GetCurrentThreadId()); +#else + auto pid = uintptr_t(getpid()); + auto tid = uintptr_t(pthread_self()); +#endif + + printf(" => Process ID: %" PRIuPTR ", Thread ID: %" PRIuPTR "\n", pid, tid); +} diff --git a/xpcom/string/nsStringStats.h b/xpcom/string/nsStringStats.h new file mode 100644 index 0000000000..a38304c2b7 --- /dev/null +++ b/xpcom/string/nsStringStats.h @@ -0,0 +1,32 @@ +/* -*- 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/. */ + +#ifndef nsStringStats_h +#define nsStringStats_h + +#include "mozilla/Atomics.h" + +class nsStringStats { + public: + nsStringStats() = default; + + ~nsStringStats(); + + using AtomicInt = mozilla::Atomic; + + AtomicInt mAllocCount{0}; + AtomicInt mReallocCount{0}; + AtomicInt mFreeCount{0}; + AtomicInt mShareCount{0}; + AtomicInt mAdoptCount{0}; + AtomicInt mAdoptFreeCount{0}; +}; + +extern nsStringStats gStringStats; + +#define STRING_STAT_INCREMENT(_s) (gStringStats.m##_s##Count)++ + +#endif // nsStringStats_h diff --git a/xpcom/string/nsTDependentString.cpp b/xpcom/string/nsTDependentString.cpp new file mode 100644 index 0000000000..83cfa39687 --- /dev/null +++ b/xpcom/string/nsTDependentString.cpp @@ -0,0 +1,50 @@ +/* -*- 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 "nsTDependentString.h" + +template +nsTDependentString::nsTDependentString(const char_type* aStart, + const char_type* aEnd) + : string_type(const_cast(aStart), aEnd - aStart, + DataFlags::TERMINATED, ClassFlags(0)) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); + this->AssertValidDependentString(); +} + +template +void nsTDependentString::Rebind(const string_type& str, + index_type startPos) { + MOZ_ASSERT(str.GetDataFlags() & DataFlags::TERMINATED, + "Unterminated flat string"); + + // If we currently own a buffer, release it. + this->Finalize(); + + size_type strLength = str.Length(); + + if (startPos > strLength) { + startPos = strLength; + } + + char_type* newData = + const_cast(static_cast(str.Data())) + + startPos; + size_type newLen = strLength - startPos; + DataFlags newDataFlags = + str.GetDataFlags() & (DataFlags::TERMINATED | DataFlags::LITERAL); + this->SetData(newData, newLen, newDataFlags); +} + +template +void nsTDependentString::Rebind(const char_type* aStart, + const char_type* aEnd) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); + this->Rebind(aStart, aEnd - aStart); +} + +template class nsTDependentString; +template class nsTDependentString; diff --git a/xpcom/string/nsTDependentString.h b/xpcom/string/nsTDependentString.h new file mode 100644 index 0000000000..c7194a677f --- /dev/null +++ b/xpcom/string/nsTDependentString.h @@ -0,0 +1,126 @@ +/* -*- 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/. */ + +#ifndef nsTDependentString_h +#define nsTDependentString_h + +#include "nsTString.h" + +/** + * nsTDependentString + * + * Stores a null-terminated, immutable sequence of characters. + * + * Subclass of nsTString that restricts string value to an immutable + * character sequence. This class does not own its data, so the creator + * of objects of this type must take care to ensure that a + * nsTDependentString continues to reference valid memory for the + * duration of its use. + */ +template +class nsTDependentString : public nsTString { + public: + typedef nsTDependentString self_type; + typedef nsTString base_string_type; + typedef typename base_string_type::string_type string_type; + + typedef typename base_string_type::fallible_t fallible_t; + + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::char_traits char_traits; + typedef + typename base_string_type::incompatible_char_type incompatible_char_type; + + typedef typename base_string_type::substring_tuple_type substring_tuple_type; + + typedef typename base_string_type::const_iterator const_iterator; + typedef typename base_string_type::iterator iterator; + + typedef typename base_string_type::comparator_type comparator_type; + + typedef typename base_string_type::const_char_iterator const_char_iterator; + + typedef typename base_string_type::string_view string_view; + + typedef typename base_string_type::index_type index_type; + typedef typename base_string_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + + public: + /** + * constructors + */ + + nsTDependentString(const char_type* aStart, const char_type* aEnd); + + nsTDependentString(const char_type* aData, size_type aLength) + : string_type(const_cast(aData), aLength, + DataFlags::TERMINATED, ClassFlags(0)) { + this->AssertValidDependentString(); + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + nsTDependentString(char16ptr_t aData, size_type aLength) + : nsTDependentString(static_cast(aData), aLength) {} +#endif + + explicit nsTDependentString(const char_type* aData) + : string_type(const_cast(aData), char_traits::length(aData), + DataFlags::TERMINATED, ClassFlags(0)) { + string_type::AssertValidDependentString(); + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + explicit nsTDependentString(char16ptr_t aData) + : nsTDependentString(static_cast(aData)) {} +#endif + + nsTDependentString(const string_type& aStr, index_type aStartPos) + : string_type() { + Rebind(aStr, aStartPos); + } + + // Create a nsTDependentSubstring to be bound later + nsTDependentString() : string_type() {} + + // auto-generated destructor OK + + nsTDependentString(self_type&& aStr) : string_type() { + Rebind(aStr, /* aStartPos = */ 0); + aStr.SetToEmptyBuffer(); + } + + explicit nsTDependentString(const self_type& aStr) : string_type() { + Rebind(aStr, /* aStartPos = */ 0); + } + + /** + * allow this class to be bound to a different string... + */ + + using nsTString::Rebind; + void Rebind(const char_type* aData) { + Rebind(aData, char_traits::length(aData)); + } + + void Rebind(const char_type* aStart, const char_type* aEnd); + void Rebind(const string_type&, index_type aStartPos); + + private: + // NOT USED + nsTDependentString(const substring_tuple_type&) = delete; + self_type& operator=(const self_type& aStr) = delete; +}; + +extern template class nsTDependentString; +extern template class nsTDependentString; + +#endif diff --git a/xpcom/string/nsTDependentSubstring.cpp b/xpcom/string/nsTDependentSubstring.cpp new file mode 100644 index 0000000000..ba1620f98b --- /dev/null +++ b/xpcom/string/nsTDependentSubstring.cpp @@ -0,0 +1,106 @@ +/* -*- 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/. */ + +// FIXME: Due to an include cycle, we need to include `nsTSubstring` first. +#include "nsTSubstring.h" +#include "nsTDependentSubstring.h" + +template +void nsTDependentSubstring::Rebind(const substring_type& str, + size_type startPos, size_type length) { + // If we currently own a buffer, release it. + this->Finalize(); + + size_type strLength = str.Length(); + + if (startPos > strLength) { + startPos = strLength; + } + + char_type* newData = + const_cast(static_cast(str.Data())) + + startPos; + size_type newLength = XPCOM_MIN(length, strLength - startPos); + DataFlags newDataFlags = DataFlags(0); + this->SetData(newData, newLength, newDataFlags); +} + +template +void nsTDependentSubstring::Rebind(const char_type* data, size_type length) { + NS_ASSERTION(data, "nsTDependentSubstring must wrap a non-NULL buffer"); + + // If we currently own a buffer, release it. + this->Finalize(); + + char_type* newData = + const_cast(static_cast(data)); + size_type newLength = length; + DataFlags newDataFlags = DataFlags(0); + this->SetData(newData, newLength, newDataFlags); +} + +template +void nsTDependentSubstring::Rebind(const char_type* aStart, + const char_type* aEnd) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); + this->Rebind(aStart, size_type(aEnd - aStart)); +} + +template +nsTDependentSubstring::nsTDependentSubstring(const char_type* aStart, + const char_type* aEnd) + : substring_type(const_cast(aStart), aEnd - aStart, + DataFlags(0), ClassFlags(0)) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); +} + +#if defined(MOZ_USE_CHAR16_WRAPPER) +template +template +nsTDependentSubstring::nsTDependentSubstring(char16ptr_t aStart, + char16ptr_t aEnd) + : substring_type(static_cast(aStart), + static_cast(aEnd)) { + MOZ_RELEASE_ASSERT(static_cast(aStart) <= + static_cast(aEnd), + "Overflow!"); +} +#endif + +template +nsTDependentSubstring::nsTDependentSubstring(const const_iterator& aStart, + const const_iterator& aEnd) + : substring_type(const_cast(aStart.get()), + aEnd.get() - aStart.get(), DataFlags(0), ClassFlags(0)) { + MOZ_RELEASE_ASSERT(aStart.get() <= aEnd.get(), "Overflow!"); +} + +template +const nsTDependentSubstring Substring(const T* aStart, const T* aEnd) { + MOZ_RELEASE_ASSERT(aStart <= aEnd, "Overflow!"); + return nsTDependentSubstring(aStart, aEnd); +} + +template nsTDependentSubstring const Substring(char const*, + char const*); +template nsTDependentSubstring const Substring( + char16_t const*, char16_t const*); + +#if defined(MOZ_USE_CHAR16_WRAPPER) +const nsTDependentSubstring Substring(char16ptr_t aData, + size_t aLength) { + return nsTDependentSubstring(aData, aLength); +} + +const nsTDependentSubstring Substring(char16ptr_t aStart, + char16ptr_t aEnd) { + return Substring(static_cast(aStart), + static_cast(aEnd)); +} +#endif + +template class nsTDependentSubstring; +template class nsTDependentSubstring; diff --git a/xpcom/string/nsTDependentSubstring.h b/xpcom/string/nsTDependentSubstring.h new file mode 100644 index 0000000000..b5198ff2b5 --- /dev/null +++ b/xpcom/string/nsTDependentSubstring.h @@ -0,0 +1,162 @@ +/* -*- 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/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsTDependentSubstring_h +#define nsTDependentSubstring_h + +#include "nsTSubstring.h" +#include "nsTLiteralString.h" +#include "mozilla/Span.h" + +/** + * nsTDependentSubstring_CharT + * + * A string class which wraps an external array of string characters. It + * is the client code's responsibility to ensure that the external buffer + * remains valid for a long as the string is alive. + * + * NAMES: + * nsDependentSubstring for wide characters + * nsDependentCSubstring for narrow characters + */ +template +class nsTDependentSubstring : public nsTSubstring { + public: + typedef nsTDependentSubstring self_type; + typedef nsTSubstring substring_type; + typedef typename substring_type::fallible_t fallible_t; + + typedef typename substring_type::char_type char_type; + typedef typename substring_type::char_traits char_traits; + typedef + typename substring_type::incompatible_char_type incompatible_char_type; + + typedef typename substring_type::substring_tuple_type substring_tuple_type; + + typedef typename substring_type::const_iterator const_iterator; + typedef typename substring_type::iterator iterator; + + typedef typename substring_type::comparator_type comparator_type; + + typedef typename substring_type::const_char_iterator const_char_iterator; + + typedef typename substring_type::string_view string_view; + + typedef typename substring_type::index_type index_type; + typedef typename substring_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename substring_type::DataFlags DataFlags; + typedef typename substring_type::ClassFlags ClassFlags; + + public: + void Rebind(const substring_type&, size_type aStartPos, + size_type aLength = size_type(-1)); + + void Rebind(const char_type* aData, size_type aLength); + + void Rebind(const char_type* aStart, const char_type* aEnd); + + nsTDependentSubstring(const substring_type& aStr, size_type aStartPos, + size_type aLength = size_type(-1)) + : substring_type() { + Rebind(aStr, aStartPos, aLength); + } + + nsTDependentSubstring(const char_type* aData, size_type aLength) + : substring_type(const_cast(aData), aLength, DataFlags(0), + ClassFlags(0)) {} + + explicit nsTDependentSubstring(mozilla::Span aData) + : nsTDependentSubstring(aData.Elements(), aData.Length()) {} + + nsTDependentSubstring(const char_type* aStart, const char_type* aEnd); + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + nsTDependentSubstring(char16ptr_t aData, size_type aLength) + : nsTDependentSubstring(static_cast(aData), aLength) {} + + template > + nsTDependentSubstring(char16ptr_t aStart, char16ptr_t aEnd); +#endif + + nsTDependentSubstring(const const_iterator& aStart, + const const_iterator& aEnd); + + // Create a nsTDependentSubstring to be bound later + nsTDependentSubstring() : substring_type() {} + + // auto-generated copy-constructor OK (XXX really?? what about base class + // copy-ctor?) + nsTDependentSubstring(const nsTDependentSubstring&) = default; + + private: + // NOT USED + void operator=(const self_type&) = + delete; // we're immutable, you can't assign into a substring +}; + +extern template class nsTDependentSubstring; +extern template class nsTDependentSubstring; + +template +inline const nsTDependentSubstring Substring(const nsTSubstring& aStr, + size_t aStartPos, + size_t aLength = size_t(-1)) { + return nsTDependentSubstring(aStr, aStartPos, aLength); +} + +template +inline const nsTDependentSubstring Substring(const nsTLiteralString& aStr, + size_t aStartPos, + size_t aLength = size_t(-1)) { + return nsTDependentSubstring(aStr, aStartPos, aLength); +} + +template +inline const nsTDependentSubstring Substring( + const nsReadingIterator& aStart, const nsReadingIterator& aEnd) { + return nsTDependentSubstring(aStart.get(), aEnd.get()); +} + +template +inline const nsTDependentSubstring Substring(const T* aData, + size_t aLength) { + return nsTDependentSubstring(aData, aLength); +} + +template +const nsTDependentSubstring Substring(const T* aStart, const T* aEnd); + +extern template const nsTDependentSubstring Substring(const char* aStart, + const char* aEnd); + +extern template const nsTDependentSubstring Substring( + const char16_t* aStart, const char16_t* aEnd); + +#if defined(MOZ_USE_CHAR16_WRAPPER) +inline const nsTDependentSubstring Substring(char16ptr_t aData, + size_t aLength); + +const nsTDependentSubstring Substring(char16ptr_t aStart, + char16ptr_t aEnd); +#endif + +template +inline const nsTDependentSubstring StringHead(const nsTSubstring& aStr, + size_t aCount) { + return nsTDependentSubstring(aStr, 0, aCount); +} + +template +inline const nsTDependentSubstring StringTail(const nsTSubstring& aStr, + size_t aCount) { + return nsTDependentSubstring(aStr, aStr.Length() - aCount, aCount); +} + +#endif diff --git a/xpcom/string/nsTLiteralString.cpp b/xpcom/string/nsTLiteralString.cpp new file mode 100644 index 0000000000..79454f7783 --- /dev/null +++ b/xpcom/string/nsTLiteralString.cpp @@ -0,0 +1,10 @@ +/* -*- 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 "nsTLiteralString.h" + +template class nsTLiteralString; +template class nsTLiteralString; diff --git a/xpcom/string/nsTLiteralString.h b/xpcom/string/nsTLiteralString.h new file mode 100644 index 0000000000..b233183a73 --- /dev/null +++ b/xpcom/string/nsTLiteralString.h @@ -0,0 +1,113 @@ +/* -*- 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/. */ + +#ifndef nsTLiteralString_h +#define nsTLiteralString_h + +#include "nsTStringRepr.h" + +/** + * nsTLiteralString_CharT + * + * Stores a null-terminated, immutable sequence of characters. + * + * nsTString-lookalike that restricts its string value to a literal character + * sequence. Can be implicitly cast to const nsTString& (the const is + * essential, since this class's data are not writable). The data are assumed + * to be static (permanent) and therefore, as an optimization, this class + * does not have a destructor. + */ +template +class nsTLiteralString : public mozilla::detail::nsTStringRepr { + public: + typedef nsTLiteralString self_type; + +#ifdef __clang__ + // bindgen w/ clang 3.9 at least chokes on a typedef, but using is okay. + using typename mozilla::detail::nsTStringRepr::base_string_type; +#else + // On the other hand msvc chokes on the using statement. It seems others + // don't care either way so we lump them in here. + typedef typename mozilla::detail::nsTStringRepr::base_string_type + base_string_type; +#endif + + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::size_type size_type; + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + + public: + /** + * constructor + */ + + template + explicit constexpr nsTLiteralString(const char_type (&aStr)[N]) + : nsTLiteralString(aStr, N - 1) {} + + nsTLiteralString(const nsTLiteralString&) = default; + + /** + * For compatibility with existing code that requires const ns[C]String*. + * Use sparingly. If possible, rewrite code to use const ns[C]String& + * and the implicit cast will just work. + */ + MOZ_LIFETIME_BOUND const nsTString& AsString() const { + return *reinterpret_cast*>(this); + } + + MOZ_LIFETIME_BOUND operator const nsTString&() const { return AsString(); } + + template + struct raw_type { + typedef N* type; + }; + +#ifdef MOZ_USE_CHAR16_WRAPPER + template + struct raw_type { + typedef char16ptr_t type; + }; +#endif + + /** + * Prohibit get() on temporaries as in "x"_ns.get(). + * These should be written as just "x", using a string literal directly. + */ + const typename raw_type::type get() const&& = delete; + const typename raw_type::type get() const& { return this->mData; } + +// At least older gcc versions do not accept these friend declarations, +// complaining about an "invalid argument list" here, but not where the actual +// operators are defined or used. We make the supposed-to-be-private constructor +// public when building with gcc, relying on the default clang builds to fail if +// any non-private use of that constructor would get into the codebase. +#if defined(__clang__) + private: + friend constexpr auto operator"" _ns(const char* aStr, std::size_t aLen); + friend constexpr auto operator"" _ns(const char16_t* aStr, std::size_t aLen); +#else + public: +#endif + // Only for use by operator"" + constexpr nsTLiteralString(const char_type* aStr, size_t aLen) + : base_string_type(const_cast(aStr), aLen, + DataFlags::TERMINATED | DataFlags::LITERAL, + ClassFlags::NULL_TERMINATED) {} + + public: + // NOT TO BE IMPLEMENTED + template + nsTLiteralString(char_type (&aStr)[N]) = delete; + + nsTLiteralString& operator=(const nsTLiteralString&) = delete; +}; + +extern template class nsTLiteralString; +extern template class nsTLiteralString; + +#endif diff --git a/xpcom/string/nsTPromiseFlatString.cpp b/xpcom/string/nsTPromiseFlatString.cpp new file mode 100644 index 0000000000..1243300033 --- /dev/null +++ b/xpcom/string/nsTPromiseFlatString.cpp @@ -0,0 +1,26 @@ +/* -*- 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 "nsTPromiseFlatString.h" + +template +void nsTPromiseFlatString::Init(const substring_type& str) { + if (str.IsTerminated()) { + char_type* newData = + const_cast(static_cast(str.Data())); + size_type newLength = str.Length(); + DataFlags newDataFlags = + str.GetDataFlags() & (DataFlags::TERMINATED | DataFlags::LITERAL); + // does not promote DataFlags::VOIDED + + this->SetData(newData, newLength, newDataFlags); + } else { + this->Assign(str); + } +} + +template class nsTPromiseFlatString; +template class nsTPromiseFlatString; diff --git a/xpcom/string/nsTPromiseFlatString.h b/xpcom/string/nsTPromiseFlatString.h new file mode 100644 index 0000000000..126362ec9c --- /dev/null +++ b/xpcom/string/nsTPromiseFlatString.h @@ -0,0 +1,136 @@ +/* -*- 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/. */ + +#ifndef nsTPromiseFlatString_h +#define nsTPromiseFlatString_h + +#include "nsTString.h" + +/** + * NOTE: + * + * Try to avoid flat strings. |PromiseFlat[C]String| will help you as a last + * resort, and this may be necessary when dealing with legacy or OS calls, + * but in general, requiring a null-terminated array of characters kills many + * of the performance wins the string classes offer. Write your own code to + * use |nsA[C]String&|s for parameters. Write your string proccessing + * algorithms to exploit iterators. If you do this, you will benefit from + * being able to chain operations without copying or allocating and your code + * will be significantly more efficient. Remember, a function that takes an + * |const nsA[C]String&| can always be passed a raw character pointer by + * wrapping it (for free) in a |nsDependent[C]String|. But a function that + * takes a character pointer always has the potential to force allocation and + * copying. + * + * + * How to use it: + * + * A |nsPromiseFlat[C]String| doesn't necessarily own the characters it + * promises. You must never use it to promise characters out of a string + * with a shorter lifespan. The typical use will be something like this: + * + * SomeOSFunction( PromiseFlatCString(aCSubstring).get() ); // GOOD + * + * Here's a BAD use: + * + * const char* buffer = PromiseFlatCString(aCSubstring).get(); + * SomeOSFunction(buffer); // BAD!! |buffer| is a dangling pointer + * + * The only way to make one is with the function |PromiseFlat[C]String|, + * which produce a |const| instance. ``What if I need to keep a promise + * around for a little while?'' you might ask. In that case, you can keep a + * reference, like so: + * + * const nsCString& flat = PromiseFlatString(aCSubstring); + * // Temporaries usually die after the full expression containing the + * // expression that created the temporary is evaluated. But when a + * // temporary is assigned to a local reference, the temporary's lifetime + * // is extended to the reference's lifetime (C++11 [class.temporary]p5). + * // + * // This reference holds the anonymous temporary alive. But remember: it + * // must _still_ have a lifetime shorter than that of |aCSubstring|, and + * // |aCSubstring| must not be changed while the PromiseFlatString lives. + * + * SomeOSFunction(flat.get()); + * SomeOtherOSFunction(flat.get()); + * + * + * How does it work? + * + * A |nsPromiseFlat[C]String| is just a wrapper for another string. If you + * apply it to a string that happens to be flat, your promise is just a + * dependent reference to the string's data. If you apply it to a non-flat + * string, then a temporary flat string is created for you, by allocating and + * copying. In the event that you end up assigning the result into a sharing + * string (e.g., |nsTString|), the right thing happens. + */ + +template +class nsTPromiseFlatString : public nsTString { + public: + typedef nsTPromiseFlatString self_type; + typedef nsTString base_string_type; + typedef typename base_string_type::substring_type substring_type; + typedef typename base_string_type::string_type string_type; + typedef typename base_string_type::substring_tuple_type substring_tuple_type; + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + + private: + void Init(const substring_type&); + + // NOT TO BE IMPLEMENTED + void operator=(const self_type&) = delete; + + // NOT TO BE IMPLEMENTED + nsTPromiseFlatString() = delete; + + // NOT TO BE IMPLEMENTED + nsTPromiseFlatString(const string_type& aStr) = delete; + + public: + explicit nsTPromiseFlatString(const substring_type& aStr) : string_type() { + Init(aStr); + } + + explicit nsTPromiseFlatString(const substring_tuple_type& aTuple) + : string_type() { + // nothing else to do here except assign the value of the tuple + // into ourselves. + this->Assign(aTuple); + } +}; + +extern template class nsTPromiseFlatString; +extern template class nsTPromiseFlatString; + +// We template this so that the constructor is chosen based on the type of the +// parameter. This allows us to reject attempts to promise a flat flat string. +template +const nsTPromiseFlatString TPromiseFlatString( + const typename nsTPromiseFlatString::substring_type& aString) { + return nsTPromiseFlatString(aString); +} + +template +const nsTPromiseFlatString TPromiseFlatString( + const typename nsTPromiseFlatString::substring_tuple_type& aString) { + return nsTPromiseFlatString(aString); +} + +#ifndef PromiseFlatCString +# define PromiseFlatCString TPromiseFlatString +#endif + +#ifndef PromiseFlatString +# define PromiseFlatString TPromiseFlatString +#endif + +#endif diff --git a/xpcom/string/nsTString.cpp b/xpcom/string/nsTString.cpp new file mode 100644 index 0000000000..4e845f62df --- /dev/null +++ b/xpcom/string/nsTString.cpp @@ -0,0 +1,42 @@ +/* -*- 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 "nsTString.h" +#include "nsString.h" +#include "prdtoa.h" + +/** + * nsTString::SetCharAt + */ + +template +bool nsTString::SetCharAt(char16_t aChar, index_type aIndex) { + if (aIndex >= this->mLength) { + return false; + } + + if (!this->EnsureMutable()) { + this->AllocFailed(this->mLength); + } + + this->mData[aIndex] = char_type(aChar); + return true; +} + +template +void nsTString::Rebind(const char_type* data, size_type length) { + // If we currently own a buffer, release it. + this->Finalize(); + + this->SetData(const_cast(data), length, DataFlags::TERMINATED); + this->AssertValidDependentString(); +} + +template class nsTString; +template class nsTString; + +template class nsTAutoStringN; +template class nsTAutoStringN; diff --git a/xpcom/string/nsTString.h b/xpcom/string/nsTString.h new file mode 100644 index 0000000000..9793f70e3b --- /dev/null +++ b/xpcom/string/nsTString.h @@ -0,0 +1,447 @@ +/* -*- 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/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsTString_h +#define nsTString_h + +#include "nsTSubstring.h" + +/** + * This is the canonical null-terminated string class. All subclasses + * promise null-terminated storage. Instances of this class allocate + * strings on the heap. + * + * NAMES: + * nsString for wide characters + * nsCString for narrow characters + * + * This class is also known as nsAFlat[C]String, where "flat" is used + * to denote a null-terminated string. + */ +template +class nsTString : public nsTSubstring { + public: + typedef nsTString self_type; + + using repr_type = mozilla::detail::nsTStringRepr; + +#ifdef __clang__ + // bindgen w/ clang 3.9 at least chokes on a typedef, but using is okay. + using typename nsTSubstring::substring_type; +#else + // On the other hand msvc chokes on the using statement. It seems others + // don't care either way so we lump them in here. + typedef typename nsTSubstring::substring_type substring_type; +#endif + + typedef typename substring_type::fallible_t fallible_t; + + typedef typename substring_type::char_type char_type; + typedef typename substring_type::char_traits char_traits; + typedef + typename substring_type::incompatible_char_type incompatible_char_type; + + typedef typename substring_type::substring_tuple_type substring_tuple_type; + + typedef typename substring_type::const_iterator const_iterator; + typedef typename substring_type::iterator iterator; + + typedef typename substring_type::comparator_type comparator_type; + + typedef typename substring_type::const_char_iterator const_char_iterator; + + typedef typename substring_type::string_view string_view; + + typedef typename substring_type::index_type index_type; + typedef typename substring_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename substring_type::DataFlags DataFlags; + typedef typename substring_type::ClassFlags ClassFlags; + + public: + /** + * constructors + */ + + nsTString() : substring_type(ClassFlags::NULL_TERMINATED) {} + + explicit nsTString(const char_type* aData, size_type aLength = size_type(-1)) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(aData, aLength); + } + + explicit nsTString(mozilla::Span aData) + : nsTString(aData.Elements(), aData.Length()) {} + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + explicit nsTString(char16ptr_t aStr, size_type aLength = size_type(-1)) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(static_cast(aStr), aLength); + } +#endif + + nsTString(const self_type& aStr) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(aStr); + } + + nsTString(self_type&& aStr) : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(std::move(aStr)); + } + + MOZ_IMPLICIT nsTString(const substring_tuple_type& aTuple) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(aTuple); + } + + explicit nsTString(const substring_type& aReadable) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(aReadable); + } + + explicit nsTString(substring_type&& aReadable) + : substring_type(ClassFlags::NULL_TERMINATED) { + this->Assign(std::move(aReadable)); + } + + // |operator=| does not inherit, so we must define our own + self_type& operator=(char_type aChar) { + this->Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) { + this->Assign(aData); + return *this; + } + self_type& operator=(const self_type& aStr) { + this->Assign(aStr); + return *this; + } + self_type& operator=(self_type&& aStr) { + this->Assign(std::move(aStr)); + return *this; + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + self_type& operator=(const char16ptr_t aStr) { + this->Assign(static_cast(aStr)); + return *this; + } +#endif + self_type& operator=(const substring_type& aStr) { + this->Assign(aStr); + return *this; + } + self_type& operator=(substring_type&& aStr) { + this->Assign(std::move(aStr)); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) { + this->Assign(aTuple); + return *this; + } + + /** + * returns the null-terminated string + */ + + template + struct raw_type { + typedef const U* type; + }; +#if defined(MOZ_USE_CHAR16_WRAPPER) + template + struct raw_type { + typedef char16ptr_t type; + }; +#endif + + MOZ_NO_DANGLING_ON_TEMPORARIES typename raw_type::type get() const { + return this->mData; + } + + /** + * returns character at specified index. + * + * NOTE: unlike nsTSubstring::CharAt, this function allows you to index + * the null terminator character. + */ + + char_type CharAt(index_type aIndex) const { + MOZ_ASSERT(aIndex <= this->Length(), "index exceeds allowable range"); + return this->mData[aIndex]; + } + + char_type operator[](index_type aIndex) const { return CharAt(aIndex); } + + /** + * Set a char inside this string at given index + * + * @param aChar is the char you want to write into this string + * @param anIndex is the ofs where you want to write the given char + * @return TRUE if successful + */ + bool SetCharAt(char16_t aChar, index_type aIndex); + + /** + * Allow this string to be bound to a character buffer + * until the string is rebound or mutated; the caller + * must ensure that the buffer outlives the string. + */ + void Rebind(const char_type* aData, size_type aLength); + + /** + * verify restrictions for dependent strings + */ + void AssertValidDependentString() { + MOZ_ASSERT(this->mData, "nsTDependentString must wrap a non-NULL buffer"); + MOZ_ASSERT(this->mData[substring_type::mLength] == 0, + "nsTDependentString must wrap only null-terminated strings. " + "You are probably looking for nsTDependentSubstring."); + } + + protected: + // allow subclasses to initialize fields directly + nsTString(char_type* aData, size_type aLength, DataFlags aDataFlags, + ClassFlags aClassFlags) + : substring_type(aData, aLength, aDataFlags, + aClassFlags | ClassFlags::NULL_TERMINATED) {} + + friend const nsTString& VoidCString(); + friend const nsTString& VoidString(); + + // Used by Null[C]String. + explicit nsTString(DataFlags aDataFlags) + : substring_type(char_traits::sEmptyBuffer, 0, + aDataFlags | DataFlags::TERMINATED, + ClassFlags::NULL_TERMINATED) {} +}; + +extern template class nsTString; +extern template class nsTString; + +/** + * nsTAutoStringN + * + * Subclass of nsTString that adds support for stack-based string + * allocation. It is normally not a good idea to use this class on the + * heap, because it will allocate space which may be wasted if the string + * it contains is significantly smaller or any larger than 64 characters. + * + * NAMES: + * nsAutoStringN / nsTAutoString for wide characters + * nsAutoCStringN / nsTAutoCString for narrow characters + */ +template +class MOZ_NON_MEMMOVABLE nsTAutoStringN : public nsTString { + public: + typedef nsTAutoStringN self_type; + + typedef nsTString base_string_type; + typedef typename base_string_type::string_type string_type; + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::char_traits char_traits; + typedef typename base_string_type::substring_type substring_type; + typedef typename base_string_type::size_type size_type; + typedef typename base_string_type::substring_tuple_type substring_tuple_type; + + // These are only for internal use within the string classes: + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + typedef typename base_string_type::LengthStorage LengthStorage; + + public: + /** + * constructors + */ + + nsTAutoStringN() + : string_type(mStorage, 0, DataFlags::TERMINATED | DataFlags::INLINE, + ClassFlags::INLINE), + mInlineCapacity(N - 1) { + // null-terminate + mStorage[0] = char_type(0); + } + + explicit nsTAutoStringN(char_type aChar) : self_type() { + this->Assign(aChar); + } + + explicit nsTAutoStringN(const char_type* aData, + size_type aLength = size_type(-1)) + : self_type() { + this->Assign(aData, aLength); + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + explicit nsTAutoStringN(char16ptr_t aData, size_type aLength = size_type(-1)) + : self_type(static_cast(aData), aLength) {} +#endif + + nsTAutoStringN(const self_type& aStr) : self_type() { this->Assign(aStr); } + + nsTAutoStringN(self_type&& aStr) : self_type() { + this->Assign(std::move(aStr)); + } + + explicit nsTAutoStringN(const substring_type& aStr) : self_type() { + this->Assign(aStr); + } + + explicit nsTAutoStringN(substring_type&& aStr) : self_type() { + this->Assign(std::move(aStr)); + } + + MOZ_IMPLICIT nsTAutoStringN(const substring_tuple_type& aTuple) + : self_type() { + this->Assign(aTuple); + } + + // |operator=| does not inherit, so we must define our own + self_type& operator=(char_type aChar) { + this->Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) { + this->Assign(aData); + return *this; + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + self_type& operator=(char16ptr_t aStr) { + this->Assign(aStr); + return *this; + } +#endif + self_type& operator=(const self_type& aStr) { + this->Assign(aStr); + return *this; + } + self_type& operator=(self_type&& aStr) { + this->Assign(std::move(aStr)); + return *this; + } + self_type& operator=(const substring_type& aStr) { + this->Assign(aStr); + return *this; + } + self_type& operator=(substring_type&& aStr) { + this->Assign(std::move(aStr)); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) { + this->Assign(aTuple); + return *this; + } + + static const size_t kStorageSize = N; + + protected: + friend class nsTSubstring; + + const LengthStorage mInlineCapacity; + + private: + char_type mStorage[N]; +}; + +// Externs for the most common nsTAutoStringN variations. +extern template class nsTAutoStringN; +extern template class nsTAutoStringN; + +// +// nsAutoString stores pointers into itself which are invalidated when an +// nsTArray is resized, so nsTArray must not be instantiated with nsAutoString +// elements! +// +template +class nsTArrayElementTraits; +template +class nsTArrayElementTraits> { + public: + template + struct Dont_Instantiate_nsTArray_of; + template + struct Instead_Use_nsTArray_of; + + static Dont_Instantiate_nsTArray_of>* Construct( + Instead_Use_nsTArray_of>* aE) { + return 0; + } + template + static Dont_Instantiate_nsTArray_of>* Construct( + Instead_Use_nsTArray_of>* aE, const A& aArg) { + return 0; + } + template + static Dont_Instantiate_nsTArray_of>* Construct( + Instead_Use_nsTArray_of>* aE, Args&&... aArgs) { + return 0; + } + static Dont_Instantiate_nsTArray_of>* Destruct( + Instead_Use_nsTArray_of>* aE) { + return 0; + } +}; + +/** + * getter_Copies support for adopting raw string out params that are + * heap-allocated, e.g.: + * + * char* gStr; + * void GetBlah(char** aStr) + * { + * *aStr = strdup(gStr); + * } + * + * // This works, but is clumsy. + * void Inelegant() + * { + * char* buf; + * GetBlah(&buf); + * nsCString str; + * str.Adopt(buf); + * // ... + * } + * + * // This is nicer. + * void Elegant() + * { + * nsCString str; + * GetBlah(getter_Copies(str)); + * // ... + * } + */ +template +class MOZ_STACK_CLASS nsTGetterCopies { + public: + typedef T char_type; + + explicit nsTGetterCopies(nsTSubstring& aStr) + : mString(aStr), mData(nullptr) {} + + ~nsTGetterCopies() { + mString.Adopt(mData); // OK if mData is null + } + + operator char_type**() { return &mData; } + + private: + nsTSubstring& mString; + char_type* mData; +}; + +// See the comment above nsTGetterCopies_CharT for how to use this. +template +inline nsTGetterCopies getter_Copies(nsTSubstring& aString) { + return nsTGetterCopies(aString); +} + +#endif diff --git a/xpcom/string/nsTStringComparator.cpp b/xpcom/string/nsTStringComparator.cpp new file mode 100644 index 0000000000..801a3623b9 --- /dev/null +++ b/xpcom/string/nsTStringComparator.cpp @@ -0,0 +1,91 @@ +/* -*- 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 "nsString.h" +#include "plstr.h" + +template +int NS_FASTCALL Compare(const mozilla::detail::nsTStringRepr& aLhs, + const mozilla::detail::nsTStringRepr& aRhs, + const nsTStringComparator comp) { + typedef typename nsTSubstring::size_type size_type; + typedef typename nsTSubstring::const_iterator const_iterator; + + if (&aLhs == &aRhs) { + return 0; + } + + const_iterator leftIter, rightIter; + aLhs.BeginReading(leftIter); + aRhs.BeginReading(rightIter); + + size_type lLength = aLhs.Length(); + size_type rLength = aRhs.Length(); + size_type lengthToCompare = XPCOM_MIN(lLength, rLength); + + int result; + if ((result = comp(leftIter.get(), rightIter.get(), lengthToCompare, + lengthToCompare)) == 0) { + if (lLength < rLength) { + result = -1; + } else if (rLength < lLength) { + result = 1; + } else { + result = 0; + } + } + + return result; +} + +template int NS_FASTCALL Compare( + mozilla::detail::nsTStringRepr const&, + mozilla::detail::nsTStringRepr const&, nsTStringComparator); + +template int NS_FASTCALL +Compare(mozilla::detail::nsTStringRepr const&, + mozilla::detail::nsTStringRepr const&, + nsTStringComparator); + +template +int nsTDefaultStringComparator(const T* aLhs, const T* aRhs, size_t aLLength, + size_t aRLength) { + return aLLength == aRLength ? nsCharTraits::compare(aLhs, aRhs, aLLength) + : (aLLength > aRLength) ? 1 + : -1; +} + +template int nsTDefaultStringComparator(const char*, const char*, size_t, + size_t); +template int nsTDefaultStringComparator(const char16_t*, const char16_t*, + size_t, size_t); + +int nsCaseInsensitiveCStringComparator(const char* aLhs, const char* aRhs, + size_t aLhsLength, size_t aRhsLength) { +#if defined(LIBFUZZER) && defined(LINUX) + // Make sure libFuzzer can see this string compare by calling the POSIX + // native function which is intercepted. We also call this if the lengths + // don't match so libFuzzer can at least see a partial string, but we throw + // away the result afterwards again. + int32_t result = + int32_t(strncasecmp(aLhs, aRhs, std::min(aLhsLength, aRhsLength))); + + if (aLhsLength != aRhsLength) { + return (aLhsLength > aRhsLength) ? 1 : -1; + } +#else + if (aLhsLength != aRhsLength) { + return (aLhsLength > aRhsLength) ? 1 : -1; + } + int32_t result = int32_t(PL_strncasecmp(aLhs, aRhs, aLhsLength)); +#endif + // Egads. PL_strncasecmp is returning *very* negative numbers. + // Some folks expect -1,0,1, so let's temper its enthusiasm. + if (result < 0) { + result = -1; + } + return result; +} diff --git a/xpcom/string/nsTStringHasher.h b/xpcom/string/nsTStringHasher.h new file mode 100644 index 0000000000..7b3f42ba58 --- /dev/null +++ b/xpcom/string/nsTStringHasher.h @@ -0,0 +1,30 @@ +/* -*- 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/. */ + +#ifndef nsTStringHasher_h___ +#define nsTStringHasher_h___ + +#include "mozilla/HashTable.h" // mozilla::{DefaultHasher, HashNumber, HashString} + +namespace mozilla { + +template +struct DefaultHasher> { + using Key = nsTString; + using Lookup = nsTString; + + static mozilla::HashNumber hash(const Lookup& aLookup) { + return mozilla::HashString(aLookup.get()); + } + + static bool match(const Key& aKey, const Lookup& aLookup) { + return aKey.Equals(aLookup); + } +}; + +} // namespace mozilla + +#endif // !defined(nsTStringHasher_h___) diff --git a/xpcom/string/nsTStringRepr.cpp b/xpcom/string/nsTStringRepr.cpp new file mode 100644 index 0000000000..405696fd2b --- /dev/null +++ b/xpcom/string/nsTStringRepr.cpp @@ -0,0 +1,273 @@ +/* -*- 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 "nsTStringRepr.h" + +#include "double-conversion/string-to-double.h" +#include "mozilla/FloatingPoint.h" +#include "nsError.h" +#include "nsString.h" + +namespace mozilla::detail { + +template +typename nsTStringRepr::char_type nsTStringRepr::First() const { + MOZ_RELEASE_ASSERT(this->mLength > 0, "|First()| called on an empty string"); + return this->mData[0]; +} + +template +typename nsTStringRepr::char_type nsTStringRepr::Last() const { + MOZ_RELEASE_ASSERT(this->mLength > 0, "|Last()| called on an empty string"); + return this->mData[this->mLength - 1]; +} + +template +bool nsTStringRepr::Equals(const self_type& aStr) const { + return this->mLength == aStr.mLength && + char_traits::compare(this->mData, aStr.mData, this->mLength) == 0; +} + +template +bool nsTStringRepr::Equals(const self_type& aStr, + comparator_type aComp) const { + return this->mLength == aStr.mLength && + aComp(this->mData, aStr.mData, this->mLength, aStr.mLength) == 0; +} + +template +bool nsTStringRepr::Equals(const substring_tuple_type& aTuple) const { + return Equals(substring_type(aTuple)); +} + +template +bool nsTStringRepr::Equals(const substring_tuple_type& aTuple, + comparator_type aComp) const { + return Equals(substring_type(aTuple), aComp); +} + +template +bool nsTStringRepr::Equals(const char_type* aData) const { + // unfortunately, some callers pass null :-( + if (!aData) { + MOZ_ASSERT_UNREACHABLE("null data pointer"); + return this->mLength == 0; + } + + // XXX avoid length calculation? + size_type length = char_traits::length(aData); + return this->mLength == length && + char_traits::compare(this->mData, aData, this->mLength) == 0; +} + +template +bool nsTStringRepr::Equals(const char_type* aData, + comparator_type aComp) const { + // unfortunately, some callers pass null :-( + if (!aData) { + MOZ_ASSERT_UNREACHABLE("null data pointer"); + return this->mLength == 0; + } + + // XXX avoid length calculation? + size_type length = char_traits::length(aData); + return this->mLength == length && + aComp(this->mData, aData, this->mLength, length) == 0; +} + +template +bool nsTStringRepr::EqualsASCII(const char* aData, size_type aLen) const { + return this->mLength == aLen && + char_traits::compareASCII(this->mData, aData, aLen) == 0; +} + +template +bool nsTStringRepr::EqualsASCII(const char* aData) const { + return char_traits::compareASCIINullTerminated(this->mData, this->mLength, + aData) == 0; +} + +template +bool nsTStringRepr::EqualsLatin1(const char* aData, + const size_type aLength) const { + return (this->mLength == aLength) && + char_traits::equalsLatin1(this->mData, aData, aLength); +} + +template +bool nsTStringRepr::LowerCaseEqualsASCII(const char* aData, + size_type aLen) const { + return this->mLength == aLen && + char_traits::compareLowerCaseToASCII(this->mData, aData, aLen) == 0; +} + +template +bool nsTStringRepr::LowerCaseEqualsASCII(const char* aData) const { + return char_traits::compareLowerCaseToASCIINullTerminated( + this->mData, this->mLength, aData) == 0; +} + +template +int32_t nsTStringRepr::Find(const string_view& aString, + index_type aOffset) const { + auto idx = View().find(aString, aOffset); + return idx == string_view::npos ? kNotFound : idx; +} + +template +int32_t nsTStringRepr::LowerCaseFindASCII(const std::string_view& aString, + index_type aOffset) const { + if (aOffset > Length()) { + return kNotFound; + } + auto begin = BeginReading(); + auto end = EndReading(); + auto it = + std::search(begin + aOffset, end, aString.begin(), aString.end(), + [](char_type l, char r) { + MOZ_ASSERT(!(r & ~0x7F), "Unexpected non-ASCII character"); + MOZ_ASSERT(char_traits::ASCIIToLower(r) == char_type(r), + "Search string must be ASCII lowercase"); + return char_traits::ASCIIToLower(l) == char_type(r); + }); + return it == end ? kNotFound : std::distance(begin, it); +} + +template +int32_t nsTStringRepr::RFind(const string_view& aString) const { + auto idx = View().rfind(aString); + return idx == string_view::npos ? kNotFound : idx; +} + +template +typename nsTStringRepr::size_type nsTStringRepr::CountChar( + char_type aChar) const { + return std::count(BeginReading(), EndReading(), aChar); +} + +template +int32_t nsTStringRepr::FindChar(char_type aChar, index_type aOffset) const { + auto idx = View().find(aChar, aOffset); + return idx == string_view::npos ? kNotFound : idx; +} + +template +int32_t nsTStringRepr::RFindChar(char_type aChar, int32_t aOffset) const { + auto idx = View().rfind(aChar, aOffset != -1 ? aOffset : string_view::npos); + return idx == string_view::npos ? kNotFound : idx; +} + +template +int32_t nsTStringRepr::FindCharInSet(const string_view& aSet, + index_type aOffset) const { + auto idx = View().find_first_of(aSet, aOffset); + return idx == string_view::npos ? kNotFound : idx; +} + +template +int32_t nsTStringRepr::RFindCharInSet(const string_view& aSet, + int32_t aOffset) const { + auto idx = + View().find_last_of(aSet, aOffset != -1 ? aOffset : string_view::npos); + return idx == string_view::npos ? kNotFound : idx; +} + +template +bool nsTStringRepr::EqualsIgnoreCase(const std::string_view& aString) const { + return std::equal(BeginReading(), EndReading(), aString.begin(), + aString.end(), [](char_type l, char r) { + return char_traits::ASCIIToLower(l) == + char_traits::ASCIIToLower(char_type(r)); + }); +} + +// We can't use the method `StringToDoubleConverter::ToDouble` due to linking +// issues on Windows as it's in mozglue. Instead, implement the selection logic +// using an overload set. +// +// StringToFloat is used instead of StringToDouble for floats due to differences +// in rounding behaviour. +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char* aData, size_t aLength, int* aProcessed, float* aResult) { + *aResult = aConverter.StringToFloat(aData, aLength, aProcessed); +} + +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char* aData, size_t aLength, int* aProcessed, double* aResult) { + *aResult = aConverter.StringToDouble(aData, aLength, aProcessed); +} + +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char16_t* aData, size_t aLength, int* aProcessed, float* aResult) { + *aResult = aConverter.StringToFloat(reinterpret_cast(aData), + aLength, aProcessed); +} + +static void StringToFP( + const double_conversion::StringToDoubleConverter& aConverter, + const char16_t* aData, size_t aLength, int* aProcessed, double* aResult) { + *aResult = aConverter.StringToDouble(reinterpret_cast(aData), + aLength, aProcessed); +} + +template +static FloatT ParseFloatingPoint(const nsTStringRepr& aString, + bool aAllowTrailingChars, + nsresult* aErrorCode) { + // Performs conversion to double following the "rules for parsing + // floating-point number values" from the HTML standard. + // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#rules-for-parsing-floating-point-number-values + // + // This behaviour allows for leading spaces, and will not generate infinity or + // NaN values except in error conditions. + int flags = double_conversion::StringToDoubleConverter::ALLOW_LEADING_SPACES; + if (aAllowTrailingChars) { + flags |= double_conversion::StringToDoubleConverter::ALLOW_TRAILING_JUNK; + } + double_conversion::StringToDoubleConverter converter( + flags, mozilla::UnspecifiedNaN(), + mozilla::UnspecifiedNaN(), nullptr, nullptr); + + FloatT result; + int processed; + StringToFP(converter, aString.Data(), aString.Length(), &processed, &result); + + *aErrorCode = std::isfinite(result) ? NS_OK : NS_ERROR_ILLEGAL_VALUE; + return result; +} + +template +double nsTStringRepr::ToDouble(nsresult* aErrorCode) const { + return ParseFloatingPoint(*this, /* aAllowTrailingChars */ false, + aErrorCode); +} + +template +double nsTStringRepr::ToDoubleAllowTrailingChars( + nsresult* aErrorCode) const { + return ParseFloatingPoint(*this, /* aAllowTrailingChars */ true, + aErrorCode); +} + +template +float nsTStringRepr::ToFloat(nsresult* aErrorCode) const { + return ParseFloatingPoint(*this, /* aAllowTrailingChars */ false, + aErrorCode); +} + +template +float nsTStringRepr::ToFloatAllowTrailingChars(nsresult* aErrorCode) const { + return ParseFloatingPoint(*this, /* aAllowTrailingChars */ true, + aErrorCode); +} + +} // namespace mozilla::detail + +template class mozilla::detail::nsTStringRepr; +template class mozilla::detail::nsTStringRepr; diff --git a/xpcom/string/nsTStringRepr.h b/xpcom/string/nsTStringRepr.h new file mode 100644 index 0000000000..7e7fa53384 --- /dev/null +++ b/xpcom/string/nsTStringRepr.h @@ -0,0 +1,546 @@ +/* -*- 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/. */ + +#ifndef nsTStringRepr_h +#define nsTStringRepr_h + +#include +#include +#include // std::enable_if + +#include "mozilla/Char16.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/fallible.h" +#include "nsStringBuffer.h" +#include "nsStringFlags.h" +#include "nsStringFwd.h" +#include "nsStringIterator.h" +#include "nsCharTraits.h" + +template +class nsTSubstringTuple; + +namespace mozilla { + +// This is mainly intended to be used in the context of nsTStrings where +// we want to enable a specific function only for a given character class. In +// order for this technique to work the member function needs to be templated +// on something other than `T`. We keep this in the `mozilla` namespace rather +// than `nsTStringRepr` as it's intentionally not dependent on `T`. +// +// The 'T' at the end of `Char[16]OnlyT` is refering to the `::type` portion +// which will only be defined if the character class is correct. This is similar +// to `std::enable_if_t` which is available in C++14, but not C++11. +// +// `CharType` is generally going to be a shadowed type of `T`. +// +// Example usage of a function that will only be defined if `T` == `char`: +// +// template +// class nsTSubstring : public nsTStringRepr { +// template > +// int Foo() { return 42; } +// }; +// +// Please note that we had to use a separate type `Q` for this to work. You +// will get a semi-decent compiler error if you use `T` directly. + +template +using CharOnlyT = + typename std::enable_if::value>::type; + +template +using Char16OnlyT = + typename std::enable_if::value>::type; + +namespace detail { + +// nsTStringLengthStorage is a helper class which holds the string's length and +// provides getters and setters for converting to and from `size_t`. This is +// done to allow the length to be stored in a `uint32_t` using assertions. +template +class nsTStringLengthStorage { + public: + // The maximum byte capacity for a `nsTString` must fit within an `int32_t`, + // with enough room for a trailing null, as consumers often cast `Length()` + // and `Capacity()` to smaller types like `int32_t`. + static constexpr size_t kMax = + size_t{std::numeric_limits::max()} / sizeof(T) - 1; + static_assert( + (kMax + 1) * sizeof(T) <= std::numeric_limits::max(), + "nsTString's maximum length, including the trailing null, must fit " + "within `int32_t`, as callers will cast to `int32_t` occasionally"); + static_assert(((CheckedInt{kMax} + 1) * sizeof(T) + + sizeof(nsStringBuffer)) + .isValid(), + "Math required to allocate a nsStringBuffer for a " + "maximum-capacity string must not overflow uint32_t"); + + // Implicit conversion and assignment from `size_t` which assert that the + // value is in-range. + MOZ_IMPLICIT constexpr nsTStringLengthStorage(size_t aLength) + : mLength(static_cast(aLength)) { + MOZ_RELEASE_ASSERT(aLength <= kMax, "string is too large"); + } + constexpr nsTStringLengthStorage& operator=(size_t aLength) { + MOZ_RELEASE_ASSERT(aLength <= kMax, "string is too large"); + mLength = static_cast(aLength); + return *this; + } + MOZ_IMPLICIT constexpr operator size_t() const { return mLength; } + + private: + uint32_t mLength = 0; +}; + +// nsTStringRepr defines a string's memory layout and some accessor methods. +// This class exists so that nsTLiteralString can avoid inheriting +// nsTSubstring's destructor. All methods on this class must be const because +// literal strings are not writable. +// +// This class is an implementation detail and should not be instantiated +// directly, nor used in any way outside of the string code itself. It is +// buried in a namespace to discourage its use in function parameters. +// If you need to take a parameter, use [const] ns[C]Substring&. +// If you need to instantiate a string, use ns[C]String or descendents. +// +// NAMES: +// nsStringRepr for wide characters +// nsCStringRepr for narrow characters +template +class nsTStringRepr { + public: + typedef mozilla::fallible_t fallible_t; + + typedef T char_type; + + typedef nsCharTraits char_traits; + typedef typename char_traits::incompatible_char_type incompatible_char_type; + + typedef nsTStringRepr self_type; + typedef self_type base_string_type; + + typedef nsTSubstring substring_type; + typedef nsTSubstringTuple substring_tuple_type; + + typedef nsReadingIterator const_iterator; + typedef char_type* iterator; + + typedef nsTStringComparator comparator_type; + + typedef const char_type* const_char_iterator; + + typedef std::basic_string_view string_view; + + typedef size_t index_type; + typedef size_t size_type; + + // These are only for internal use within the string classes: + typedef StringDataFlags DataFlags; + typedef StringClassFlags ClassFlags; + typedef nsTStringLengthStorage LengthStorage; + + // Reading iterators. + constexpr const_char_iterator BeginReading() const { return mData; } + constexpr const_char_iterator EndReading() const { return mData + mLength; } + + // Deprecated reading iterators. + const_iterator& BeginReading(const_iterator& aIter) const { + aIter.mStart = mData; + aIter.mEnd = mData + mLength; + aIter.mPosition = aIter.mStart; + return aIter; + } + + const_iterator& EndReading(const_iterator& aIter) const { + aIter.mStart = mData; + aIter.mEnd = mData + mLength; + aIter.mPosition = aIter.mEnd; + return aIter; + } + + const_char_iterator& BeginReading(const_char_iterator& aIter) const { + return aIter = mData; + } + + const_char_iterator& EndReading(const_char_iterator& aIter) const { + return aIter = mData + mLength; + } + + // Accessors. + template + struct raw_type { + typedef const U* type; + }; +#if defined(MOZ_USE_CHAR16_WRAPPER) + template + struct raw_type { + typedef char16ptr_t type; + }; +#endif + + // Returns pointer to string data (not necessarily null-terminated) + constexpr typename raw_type::type Data() const { return mData; } + + constexpr size_type Length() const { return static_cast(mLength); } + + constexpr string_view View() const { return string_view(Data(), Length()); } + + constexpr operator string_view() const { return View(); } + + constexpr DataFlags GetDataFlags() const { return mDataFlags; } + + constexpr bool IsEmpty() const { return mLength == 0; } + + constexpr bool IsLiteral() const { + return !!(mDataFlags & DataFlags::LITERAL); + } + + constexpr bool IsVoid() const { return !!(mDataFlags & DataFlags::VOIDED); } + + constexpr bool IsTerminated() const { + return !!(mDataFlags & DataFlags::TERMINATED); + } + + constexpr char_type CharAt(index_type aIndex) const { + NS_ASSERTION(aIndex < Length(), "index exceeds allowable range"); + return mData[aIndex]; + } + + constexpr char_type operator[](index_type aIndex) const { + return CharAt(aIndex); + } + + char_type First() const; + + char_type Last() const; + + // Equality. + bool NS_FASTCALL Equals(const self_type&) const; + bool NS_FASTCALL Equals(const self_type&, comparator_type) const; + + bool NS_FASTCALL Equals(const substring_tuple_type& aTuple) const; + bool NS_FASTCALL Equals(const substring_tuple_type& aTuple, + comparator_type) const; + + bool NS_FASTCALL Equals(const char_type* aData) const; + bool NS_FASTCALL Equals(const char_type* aData, comparator_type) const; + + /** + * Compare this string and another ASCII-case-insensitively. + * + * This method is similar to `LowerCaseEqualsASCII` however both strings are + * lowercased, meaning that `aString` need not be all lowercase. + * + * @param aString is the string to check + * @return boolean + */ + bool EqualsIgnoreCase(const std::string_view& aString) const; + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + bool NS_FASTCALL Equals(char16ptr_t aData) const { + return Equals(static_cast(aData)); + } + template > + bool NS_FASTCALL Equals(char16ptr_t aData, comparator_type aComp) const { + return Equals(static_cast(aData), aComp); + } +#endif + + // An efficient comparison with ASCII that can be used even + // for wide strings. Call this version when you know the + // length of 'data'. + bool NS_FASTCALL EqualsASCII(const char* aData, size_type aLen) const; + // An efficient comparison with ASCII that can be used even + // for wide strings. Call this version when 'data' is + // null-terminated. + bool NS_FASTCALL EqualsASCII(const char* aData) const; + + // An efficient comparison with Latin1 characters that can be used even for + // wide strings. + bool EqualsLatin1(const char* aData, size_type aLength) const; + + // EqualsLiteral must ONLY be called with an actual literal string, or + // a char array *constant* declared without an explicit size and with an + // initializer that is a string literal or is otherwise null-terminated. + // Use EqualsASCII for other char array variables. + // (Although this method may happen to produce expected results for other + // char arrays that have bound one greater than the sequence of interest, + // such use is discouraged for reasons of readability and maintainability.) + // The template trick to acquire the array bound at compile time without + // using a macro is due to Corey Kosak, with much thanks. + template + inline bool EqualsLiteral(const char (&aStr)[N]) const { + return EqualsASCII(aStr, N - 1); + } + + // EqualsLiteral must ONLY be called with an actual literal string, or + // a char array *constant* declared without an explicit size and with an + // initializer that is a string literal or is otherwise null-terminated. + // Use EqualsASCII for other char array variables. + // (Although this method may happen to produce expected results for other + // char arrays that have bound one greater than the sequence of interest, + // such use is discouraged for reasons of readability and maintainability.) + // The template trick to acquire the array bound at compile time without + // using a macro is due to Corey Kosak, with much thanks. + template >> + inline bool EqualsLiteral(const char_type (&aStr)[N]) const { + return *this == nsTLiteralString(aStr); + } + + // The LowerCaseEquals methods compare the ASCII-lowercase version of + // this string (lowercasing only ASCII uppercase characters) to some + // ASCII/Literal string. The ASCII string is *not* lowercased for + // you. If you compare to an ASCII or literal string that contains an + // uppercase character, it is guaranteed to return false. We will + // throw assertions too. + bool NS_FASTCALL LowerCaseEqualsASCII(const char* aData, + size_type aLen) const; + bool NS_FASTCALL LowerCaseEqualsASCII(const char* aData) const; + + // LowerCaseEqualsLiteral must ONLY be called with an actual literal string, + // or a char array *constant* declared without an explicit size and with an + // initializer that is a string literal or is otherwise null-terminated. + // Use LowerCaseEqualsASCII for other char array variables. + // (Although this method may happen to produce expected results for other + // char arrays that have bound one greater than the sequence of interest, + // such use is discouraged for reasons of readability and maintainability.) + template + bool LowerCaseEqualsLiteral(const char (&aStr)[N]) const { + return LowerCaseEqualsASCII(aStr, N - 1); + } + + // Returns true if this string overlaps with the given string fragment. + bool IsDependentOn(const char_type* aStart, const char_type* aEnd) const { + // If it _isn't_ the case that one fragment starts after the other ends, + // or ends before the other starts, then, they conflict: + // + // !(f2.begin >= f1.aEnd || f2.aEnd <= f1.begin) + // + // Simplified, that gives us (To avoid relying on Undefined Behavior + // from comparing pointers from different allocations (which in + // principle gives the optimizer the permission to assume elsewhere + // that the pointers are from the same allocation), the comparisons + // are done on integers, which merely relies on implementation-defined + // behavior of converting pointers to integers. std::less and + // std::greater implementations don't actually provide the guarantees + // that they should.): + return (reinterpret_cast(aStart) < + reinterpret_cast(mData + mLength) && + reinterpret_cast(aEnd) > + reinterpret_cast(mData)); + } + + /** + * Search for the given substring within this string. + * + * @param aString is substring to be sought in this + * @param aOffset tells us where in this string to start searching + * @return offset in string, or kNotFound + */ + int32_t Find(const string_view& aString, index_type aOffset = 0) const; + + // Previously there was an overload of `Find()` which took a bool second + // argument. Avoid issues by explicitly preventing that overload. + // TODO: Remove this at some point. + template && + std::is_convertible_v>> + int32_t Find(const string_view& aString, I aOffset) const { + static_assert(!std::is_same_v, "offset must not be `bool`"); + return Find(aString, static_cast(aOffset)); + } + + /** + * Search for the given ASCII substring within this string, ignoring case. + * + * @param aString is substring to be sought in this + * @param aOffset tells us where in this string to start searching + * @return offset in string, or kNotFound + */ + int32_t LowerCaseFindASCII(const std::string_view& aString, + index_type aOffset = 0) const; + + /** + * Scan the string backwards, looking for the given substring. + * + * @param aString is substring to be sought in this + * @return offset in string, or kNotFound + */ + int32_t RFind(const string_view& aString) const; + + size_type CountChar(char_type) const; + + bool Contains(char_type aChar) const { return FindChar(aChar) != kNotFound; } + + /** + * Search for the first instance of a given char within this string + * + * @param aChar is the character to search for + * @param aOffset tells us where in this string to start searching + * @return offset in string, or kNotFound + */ + int32_t FindChar(char_type aChar, index_type aOffset = 0) const; + + /** + * Search for the last instance of a given char within this string + * + * @param aChar is the character to search for + * @param aOffset tells us where in this string to start searching + * @return offset in string, or kNotFound + */ + int32_t RFindChar(char_type aChar, int32_t aOffset = -1) const; + + /** + * This method searches this string for the first character found in + * the given string. + * + * @param aSet contains set of chars to be found + * @param aOffset tells us where in this string to start searching + * (counting from left) + * @return offset in string, or kNotFound + */ + + int32_t FindCharInSet(const string_view& aSet, index_type aOffset = 0) const; + + /** + * This method searches this string for the last character found in + * the given string. + * + * @param aSet contains set of chars to be found + * @param aOffset tells us where in this string to start searching + * (counting from left) + * @return offset in string, or kNotFound + */ + + int32_t RFindCharInSet(const string_view& aSet, int32_t aOffset = -1) const; + + /** + * Perform locale-independent string to double-precision float conversion. + * + * Leading spaces in the string will be ignored. The returned value will be + * finite unless aErrorCode is set to a failed status. + * + * @param aErrorCode will contain error if one occurs + * @return double-precision float rep of string value + */ + double ToDouble(nsresult* aErrorCode) const; + + /** + * Perform locale-independent string to single-precision float conversion. + * + * Leading spaces in the string will be ignored. The returned value will be + * finite unless aErrorCode is set to a failed status. + * + * @param aErrorCode will contain error if one occurs + * @return single-precision float rep of string value + */ + float ToFloat(nsresult* aErrorCode) const; + + /** + * Similar to above ToDouble and ToFloat but allows trailing characters that + * are not converted. + */ + double ToDoubleAllowTrailingChars(nsresult* aErrorCode) const; + float ToFloatAllowTrailingChars(nsresult* aErrorCode) const; + + protected: + nsTStringRepr() = delete; // Never instantiate directly + + constexpr nsTStringRepr(char_type* aData, size_type aLength, + DataFlags aDataFlags, ClassFlags aClassFlags) + : mData(aData), + mLength(aLength), + mDataFlags(aDataFlags), + mClassFlags(aClassFlags) {} + + static constexpr size_type kMaxCapacity = LengthStorage::kMax; + + /** + * Checks if the given capacity is valid for this string type. + */ + [[nodiscard]] static constexpr bool CheckCapacity(size_type aCapacity) { + return aCapacity <= kMaxCapacity; + } + + char_type* mData; + LengthStorage mLength; + DataFlags mDataFlags; + ClassFlags const mClassFlags; +}; + +extern template class nsTStringRepr; +extern template class nsTStringRepr; + +} // namespace detail +} // namespace mozilla + +template +int NS_FASTCALL Compare(const mozilla::detail::nsTStringRepr& aLhs, + const mozilla::detail::nsTStringRepr& aRhs, + nsTStringComparator = nsTDefaultStringComparator); + +extern template int NS_FASTCALL Compare( + const mozilla::detail::nsTStringRepr&, + const mozilla::detail::nsTStringRepr&, nsTStringComparator); + +extern template int NS_FASTCALL +Compare(const mozilla::detail::nsTStringRepr&, + const mozilla::detail::nsTStringRepr&, + nsTStringComparator); + +template +inline constexpr bool operator!=( + const mozilla::detail::nsTStringRepr& aLhs, + const mozilla::detail::nsTStringRepr& aRhs) { + return !aLhs.Equals(aRhs); +} + +template +inline constexpr bool operator!=(const mozilla::detail::nsTStringRepr& aLhs, + const T* aRhs) { + return !aLhs.Equals(aRhs); +} + +template +inline bool operator<(const mozilla::detail::nsTStringRepr& aLhs, + const mozilla::detail::nsTStringRepr& aRhs) { + return Compare(aLhs, aRhs) < 0; +} + +template +inline bool operator<=(const mozilla::detail::nsTStringRepr& aLhs, + const mozilla::detail::nsTStringRepr& aRhs) { + return Compare(aLhs, aRhs) <= 0; +} + +template +inline bool operator==(const mozilla::detail::nsTStringRepr& aLhs, + const mozilla::detail::nsTStringRepr& aRhs) { + return aLhs.Equals(aRhs); +} + +template +inline bool operator==(const mozilla::detail::nsTStringRepr& aLhs, + const T* aRhs) { + return aLhs.Equals(aRhs); +} + +template +inline bool operator>=(const mozilla::detail::nsTStringRepr& aLhs, + const mozilla::detail::nsTStringRepr& aRhs) { + return Compare(aLhs, aRhs) >= 0; +} + +template +inline bool operator>(const mozilla::detail::nsTStringRepr& aLhs, + const mozilla::detail::nsTStringRepr& aRhs) { + return Compare(aLhs, aRhs) > 0; +} + +#endif diff --git a/xpcom/string/nsTSubstring.cpp b/xpcom/string/nsTSubstring.cpp new file mode 100644 index 0000000000..b81b845fee --- /dev/null +++ b/xpcom/string/nsTSubstring.cpp @@ -0,0 +1,1706 @@ +/* -*- 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 "double-conversion/double-conversion.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Printf.h" +#include "mozilla/ResultExtensions.h" + +#include "nsASCIIMask.h" +#include "nsCharTraits.h" +#include "nsISupports.h" +#include "nsString.h" +#include "nsTArray.h" + +#ifdef DEBUG +# include "nsStringStats.h" +#else +# define STRING_STAT_INCREMENT(_s) +#endif + +// It's not worthwhile to reallocate the buffer and memcpy the +// contents over when the size difference isn't large. With +// power-of-two allocation buckets and 64 as the typical inline +// capacity, considering that above 1000 there performance aspects +// of realloc and memcpy seem to be absorbed, relative to the old +// code, by the performance benefits of the new code being exact, +// we need to choose which transitions of 256 to 128, 512 to 256 +// and 1024 to 512 to allow. As a guess, let's pick the middle +// one as the the largest potential transition that we forgo. So +// we'll shrink from 1024 bucket to 512 bucket but not from 512 +// bucket to 256 bucket. We'll decide by comparing the difference +// of capacities. As bucket differences, the differences are 256 +// and 512. Since the capacities have various overheads, we +// can't compare with 256 or 512 exactly but it's easier to +// compare to some number that's between the two, so it's +// far away from either to ignore the overheads. +const uint32_t kNsStringBufferShrinkingThreshold = 384; + +using double_conversion::DoubleToStringConverter; + +// --------------------------------------------------------------------------- + +static const char16_t gNullChar = 0; + +char* const nsCharTraits::sEmptyBuffer = + (char*)const_cast(&gNullChar); +char16_t* const nsCharTraits::sEmptyBuffer = + const_cast(&gNullChar); + +// --------------------------------------------------------------------------- + +static void ReleaseData(void* aData, nsAString::DataFlags aFlags) { + if (aFlags & nsAString::DataFlags::REFCOUNTED) { + nsStringBuffer::FromData(aData)->Release(); + } else if (aFlags & nsAString::DataFlags::OWNED) { + free(aData); + STRING_STAT_INCREMENT(AdoptFree); + // Treat this as destruction of a "StringAdopt" object for leak + // tracking purposes. + MOZ_LOG_DTOR(aData, "StringAdopt", 1); + } + // otherwise, nothing to do. +} + +// --------------------------------------------------------------------------- + +#ifdef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE +template +nsTSubstring::nsTSubstring(char_type* aData, size_type aLength, + DataFlags aDataFlags, ClassFlags aClassFlags) + : ::mozilla::detail::nsTStringRepr(aData, aLength, aDataFlags, + aClassFlags) { + AssertValid(); + + if (aDataFlags & DataFlags::OWNED) { + STRING_STAT_INCREMENT(Adopt); + MOZ_LOG_CTOR(this->mData, "StringAdopt", 1); + } +} +#endif /* XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE */ + +/** + * helper function for down-casting a nsTSubstring to an nsTAutoString. + */ +template +inline const nsTAutoString* AsAutoString(const nsTSubstring* aStr) { + return static_cast*>(aStr); +} + +template +mozilla::Result, nsresult> +nsTSubstring::BulkWrite(size_type aCapacity, size_type aPrefixToPreserve, + bool aAllowShrinking) { + auto r = StartBulkWriteImpl(aCapacity, aPrefixToPreserve, aAllowShrinking); + if (MOZ_UNLIKELY(r.isErr())) { + return r.propagateErr(); + } + return mozilla::BulkWriteHandle(this, r.unwrap()); +} + +template +auto nsTSubstring::StartBulkWriteImpl(size_type aCapacity, + size_type aPrefixToPreserve, + bool aAllowShrinking, + size_type aSuffixLength, + size_type aOldSuffixStart, + size_type aNewSuffixStart) + -> mozilla::Result { + // Note! Capacity does not include room for the terminating null char. + + MOZ_ASSERT(aPrefixToPreserve <= aCapacity, + "Requested preservation of an overlong prefix."); + MOZ_ASSERT(aNewSuffixStart + aSuffixLength <= aCapacity, + "Requesed move of suffix to out-of-bounds location."); + // Can't assert aOldSuffixStart, because mLength may not be valid anymore, + // since this method allows itself to be called more than once. + + // If zero capacity is requested, set the string to the special empty + // string. + if (MOZ_UNLIKELY(!aCapacity)) { + ReleaseData(this->mData, this->mDataFlags); + SetToEmptyBuffer(); + return 0; + } + + // Note! Capacity() returns 0 when the string is immutable. + const size_type curCapacity = Capacity(); + + bool shrinking = false; + + // We've established that aCapacity > 0. + // |curCapacity == 0| means that the buffer is immutable or 0-sized, so we + // need to allocate a new buffer. We cannot use the existing buffer even + // though it might be large enough. + + if (aCapacity <= curCapacity) { + if (aAllowShrinking) { + shrinking = true; + } else { + char_traits::move(this->mData + aNewSuffixStart, + this->mData + aOldSuffixStart, aSuffixLength); + if (aSuffixLength) { + char_traits::uninitialize(this->mData + aPrefixToPreserve, + XPCOM_MIN(aNewSuffixStart - aPrefixToPreserve, + kNsStringBufferMaxPoison)); + char_traits::uninitialize( + this->mData + aNewSuffixStart + aSuffixLength, + XPCOM_MIN(curCapacity + 1 - aNewSuffixStart - aSuffixLength, + kNsStringBufferMaxPoison)); + } else { + char_traits::uninitialize(this->mData + aPrefixToPreserve, + XPCOM_MIN(curCapacity + 1 - aPrefixToPreserve, + kNsStringBufferMaxPoison)); + } + return curCapacity; + } + } + + char_type* oldData = this->mData; + DataFlags oldFlags = this->mDataFlags; + + char_type* newData; + DataFlags newDataFlags; + size_type newCapacity; + + // If this is an nsTAutoStringN, it's possible that we can use the inline + // buffer. + if ((this->mClassFlags & ClassFlags::INLINE) && + (aCapacity <= AsAutoString(this)->mInlineCapacity)) { + newCapacity = AsAutoString(this)->mInlineCapacity; + newData = (char_type*)AsAutoString(this)->mStorage; + newDataFlags = DataFlags::TERMINATED | DataFlags::INLINE; + } else { + // If |aCapacity > kMaxCapacity|, then our doubling algorithm may not be + // able to allocate it. Just bail out in cases like that. We don't want + // to be allocating 2GB+ strings anyway. + static_assert((sizeof(nsStringBuffer) & 0x1) == 0, + "bad size for nsStringBuffer"); + if (MOZ_UNLIKELY(!this->CheckCapacity(aCapacity))) { + return mozilla::Err(NS_ERROR_OUT_OF_MEMORY); + } + + // We increase our capacity so that the allocated buffer grows + // exponentially, which gives us amortized O(1) appending. Below the + // threshold, we use powers-of-two. Above the threshold, we grow by at + // least 1.125, rounding up to the nearest MiB. + const size_type slowGrowthThreshold = 8 * 1024 * 1024; + + // nsStringBuffer allocates sizeof(nsStringBuffer) + passed size, and + // storageSize below wants extra 1 * sizeof(char_type). + const size_type neededExtraSpace = + sizeof(nsStringBuffer) / sizeof(char_type) + 1; + + size_type temp; + if (aCapacity >= slowGrowthThreshold) { + size_type minNewCapacity = + curCapacity + (curCapacity >> 3); // multiply by 1.125 + temp = XPCOM_MAX(aCapacity, minNewCapacity) + neededExtraSpace; + + // Round up to the next multiple of MiB, but ensure the expected + // capacity doesn't include the extra space required by nsStringBuffer + // and null-termination. + const size_t MiB = 1 << 20; + temp = (MiB * ((temp + MiB - 1) / MiB)) - neededExtraSpace; + } else { + // Round up to the next power of two. + temp = + mozilla::RoundUpPow2(aCapacity + neededExtraSpace) - neededExtraSpace; + } + + newCapacity = XPCOM_MIN(temp, base_string_type::kMaxCapacity); + MOZ_ASSERT(newCapacity >= aCapacity, + "should have hit the early return at the top"); + // Avoid shrinking if the new buffer size is close to the old. Note that + // unsigned underflow is defined behavior. + if ((curCapacity - newCapacity) <= kNsStringBufferShrinkingThreshold && + (this->mDataFlags & DataFlags::REFCOUNTED)) { + MOZ_ASSERT(aAllowShrinking, "How come we didn't return earlier?"); + // We're already close enough to the right size. + newData = oldData; + newCapacity = curCapacity; + } else { + size_type storageSize = (newCapacity + 1) * sizeof(char_type); + // Since we allocate only by powers of 2 we always fit into a full + // mozjemalloc bucket, it's not useful to use realloc, which may spend + // time uselessly copying too much. + nsStringBuffer* newHdr = nsStringBuffer::Alloc(storageSize).take(); + if (newHdr) { + newData = (char_type*)newHdr->Data(); + } else if (shrinking) { + // We're still in a consistent state. + // + // Since shrinking is just a memory footprint optimization, we + // don't propagate OOM if we tried to shrink in order to avoid + // OOM crashes from infallible callers. If we're lucky, soon enough + // a fallible caller reaches OOM and is able to deal or we end up + // disposing of this string before reaching OOM again. + newData = oldData; + newCapacity = curCapacity; + } else { + return mozilla::Err(NS_ERROR_OUT_OF_MEMORY); + } + } + newDataFlags = DataFlags::TERMINATED | DataFlags::REFCOUNTED; + } + + this->mData = newData; + this->mDataFlags = newDataFlags; + + if (oldData == newData) { + char_traits::move(newData + aNewSuffixStart, oldData + aOldSuffixStart, + aSuffixLength); + if (aSuffixLength) { + char_traits::uninitialize(this->mData + aPrefixToPreserve, + XPCOM_MIN(aNewSuffixStart - aPrefixToPreserve, + kNsStringBufferMaxPoison)); + char_traits::uninitialize( + this->mData + aNewSuffixStart + aSuffixLength, + XPCOM_MIN(newCapacity + 1 - aNewSuffixStart - aSuffixLength, + kNsStringBufferMaxPoison)); + } else { + char_traits::uninitialize(this->mData + aPrefixToPreserve, + XPCOM_MIN(newCapacity + 1 - aPrefixToPreserve, + kNsStringBufferMaxPoison)); + } + } else { + char_traits::copy(newData, oldData, aPrefixToPreserve); + char_traits::copy(newData + aNewSuffixStart, oldData + aOldSuffixStart, + aSuffixLength); + ReleaseData(oldData, oldFlags); + } + + return newCapacity; +} + +template +void nsTSubstring::FinishBulkWriteImpl(size_type aLength) { + if (aLength) { + FinishBulkWriteImplImpl(aLength); + } else { + ReleaseData(this->mData, this->mDataFlags); + SetToEmptyBuffer(); + } + AssertValid(); +} + +template +void nsTSubstring::Finalize() { + ReleaseData(this->mData, this->mDataFlags); + // this->mData, this->mLength, and this->mDataFlags are purposefully left + // dangling +} + +template +bool nsTSubstring::ReplacePrep(index_type aCutStart, size_type aCutLength, + size_type aNewLength) { + aCutLength = XPCOM_MIN(aCutLength, this->mLength - aCutStart); + + mozilla::CheckedInt newTotalLen = this->Length(); + newTotalLen += aNewLength; + newTotalLen -= aCutLength; + if (!newTotalLen.isValid()) { + return false; + } + + if (aCutStart == this->mLength && Capacity() > newTotalLen.value()) { + this->mDataFlags &= ~DataFlags::VOIDED; + this->mData[newTotalLen.value()] = char_type(0); + this->mLength = newTotalLen.value(); + return true; + } + + return ReplacePrepInternal(aCutStart, aCutLength, aNewLength, + newTotalLen.value()); +} + +template +bool nsTSubstring::ReplacePrepInternal(index_type aCutStart, + size_type aCutLen, size_type aFragLen, + size_type aNewLen) { + size_type newSuffixStart = aCutStart + aFragLen; + size_type oldSuffixStart = aCutStart + aCutLen; + size_type suffixLength = this->mLength - oldSuffixStart; + + mozilla::Result r = StartBulkWriteImpl( + aNewLen, aCutStart, false, suffixLength, oldSuffixStart, newSuffixStart); + if (r.isErr()) { + return false; + } + FinishBulkWriteImpl(aNewLen); + return true; +} + +template +typename nsTSubstring::size_type nsTSubstring::Capacity() const { + // return 0 to indicate an immutable or 0-sized buffer + + size_type capacity; + if (this->mDataFlags & DataFlags::REFCOUNTED) { + // if the string is readonly, then we pretend that it has no capacity. + nsStringBuffer* hdr = nsStringBuffer::FromData(this->mData); + if (hdr->IsReadonly()) { + capacity = 0; + } else { + capacity = (size_t(hdr->StorageSize()) / sizeof(char_type)) - 1; + } + } else if (this->mDataFlags & DataFlags::INLINE) { + MOZ_ASSERT(this->mClassFlags & ClassFlags::INLINE); + capacity = AsAutoString(this)->mInlineCapacity; + } else if (this->mDataFlags & DataFlags::OWNED) { + // we don't store the capacity of an adopted buffer because that would + // require an additional member field. the best we can do is base the + // capacity on our length. remains to be seen if this is the right + // trade-off. + capacity = this->mLength; + } else { + capacity = 0; + } + + return capacity; +} + +template +bool nsTSubstring::EnsureMutable(size_type aNewLen) { + if (aNewLen == size_type(-1) || aNewLen == this->mLength) { + if (this->mDataFlags & (DataFlags::INLINE | DataFlags::OWNED)) { + return true; + } + if ((this->mDataFlags & DataFlags::REFCOUNTED) && + !nsStringBuffer::FromData(this->mData)->IsReadonly()) { + return true; + } + + aNewLen = this->mLength; + } + return SetLength(aNewLen, mozilla::fallible); +} + +// --------------------------------------------------------------------------- + +// This version of Assign is optimized for single-character assignment. +template +void nsTSubstring::Assign(char_type aChar) { + if (MOZ_UNLIKELY(!Assign(aChar, mozilla::fallible))) { + AllocFailed(1); + } +} + +template +bool nsTSubstring::Assign(char_type aChar, const fallible_t&) { + auto r = StartBulkWriteImpl(1, 0, true); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + *this->mData = aChar; + FinishBulkWriteImpl(1); + return true; +} + +template +void nsTSubstring::Assign(const char_type* aData, size_type aLength) { + if (MOZ_UNLIKELY(!Assign(aData, aLength, mozilla::fallible))) { + AllocFailed(aLength == size_type(-1) ? char_traits::length(aData) + : aLength); + } +} + +template +bool nsTSubstring::Assign(const char_type* aData, + const fallible_t& aFallible) { + return Assign(aData, size_type(-1), aFallible); +} + +template +bool nsTSubstring::Assign(const char_type* aData, size_type aLength, + const fallible_t& aFallible) { + if (!aData || aLength == 0) { + Truncate(); + return true; + } + + if (MOZ_UNLIKELY(aLength == size_type(-1))) { + aLength = char_traits::length(aData); + } + + if (MOZ_UNLIKELY(this->IsDependentOn(aData, aData + aLength))) { + return Assign(string_type(aData, aLength), aFallible); + } + + auto r = StartBulkWriteImpl(aLength, 0, true); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + char_traits::copy(this->mData, aData, aLength); + FinishBulkWriteImpl(aLength); + return true; +} + +template +void nsTSubstring::AssignASCII(const char* aData, size_type aLength) { + if (MOZ_UNLIKELY(!AssignASCII(aData, aLength, mozilla::fallible))) { + AllocFailed(aLength); + } +} + +template +bool nsTSubstring::AssignASCII(const char* aData, size_type aLength, + const fallible_t& aFallible) { + MOZ_ASSERT(aLength != size_type(-1)); + + // A Unicode string can't depend on an ASCII string buffer, + // so this dependence check only applies to CStrings. + if constexpr (std::is_same_v) { + if (this->IsDependentOn(aData, aData + aLength)) { + return Assign(string_type(aData, aLength), aFallible); + } + } + + auto r = StartBulkWriteImpl(aLength, 0, true); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + char_traits::copyASCII(this->mData, aData, aLength); + FinishBulkWriteImpl(aLength); + return true; +} + +template +void nsTSubstring::AssignLiteral(const char_type* aData, size_type aLength) { + ReleaseData(this->mData, this->mDataFlags); + SetData(const_cast(aData), aLength, + DataFlags::TERMINATED | DataFlags::LITERAL); +} + +template +void nsTSubstring::Assign(const self_type& aStr) { + if (!Assign(aStr, mozilla::fallible)) { + AllocFailed(aStr.Length()); + } +} + +template +bool nsTSubstring::Assign(const self_type& aStr, + const fallible_t& aFallible) { + // |aStr| could be sharable. We need to check its flags to know how to + // deal with it. + + if (&aStr == this) { + return true; + } + + if (!aStr.mLength) { + Truncate(); + this->mDataFlags |= aStr.mDataFlags & DataFlags::VOIDED; + return true; + } + + if (aStr.mDataFlags & DataFlags::REFCOUNTED) { + // nice! we can avoid a string copy :-) + + // |aStr| should be null-terminated + NS_ASSERTION(aStr.mDataFlags & DataFlags::TERMINATED, + "shared, but not terminated"); + + ReleaseData(this->mData, this->mDataFlags); + + SetData(aStr.mData, aStr.mLength, + DataFlags::TERMINATED | DataFlags::REFCOUNTED); + + // get an owning reference to the this->mData + nsStringBuffer::FromData(this->mData)->AddRef(); + return true; + } else if (aStr.mDataFlags & DataFlags::LITERAL) { + MOZ_ASSERT(aStr.mDataFlags & DataFlags::TERMINATED, "Unterminated literal"); + + AssignLiteral(aStr.mData, aStr.mLength); + return true; + } + + // else, treat this like an ordinary assignment. + return Assign(aStr.Data(), aStr.Length(), aFallible); +} + +template +void nsTSubstring::Assign(self_type&& aStr) { + if (!Assign(std::move(aStr), mozilla::fallible)) { + AllocFailed(aStr.Length()); + } +} + +template +void nsTSubstring::AssignOwned(self_type&& aStr) { + MOZ_ASSERT(aStr.mDataFlags & (DataFlags::REFCOUNTED | DataFlags::OWNED), + "neither shared nor owned"); + + // If they have a REFCOUNTED or OWNED buffer, we can avoid a copy - so steal + // their buffer and reset them to the empty string. + + // |aStr| should be null-terminated + MOZ_ASSERT(aStr.mDataFlags & DataFlags::TERMINATED, + "shared or owned, but not terminated"); + + ReleaseData(this->mData, this->mDataFlags); + + SetData(aStr.mData, aStr.mLength, aStr.mDataFlags); + aStr.SetToEmptyBuffer(); +} + +template +bool nsTSubstring::Assign(self_type&& aStr, const fallible_t& aFallible) { + // We're moving |aStr| in this method, so we need to try to steal the data, + // and in the fallback perform a copy-assignment followed by a truncation of + // the original string. + + if (&aStr == this) { + NS_WARNING("Move assigning a string to itself?"); + return true; + } + + if (aStr.mDataFlags & (DataFlags::REFCOUNTED | DataFlags::OWNED)) { + AssignOwned(std::move(aStr)); + return true; + } + + // Otherwise treat this as a normal assignment, and truncate the moved string. + // We don't truncate the source string if the allocation failed. + if (!Assign(aStr, aFallible)) { + return false; + } + aStr.Truncate(); + return true; +} + +template +void nsTSubstring::Assign(const substring_tuple_type& aTuple) { + if (!Assign(aTuple, mozilla::fallible)) { + AllocFailed(aTuple.Length()); + } +} + +template +bool nsTSubstring::AssignNonDependent(const substring_tuple_type& aTuple, + size_type aTupleLength, + const mozilla::fallible_t& aFallible) { + NS_ASSERTION(aTuple.Length() == aTupleLength, "wrong length passed"); + + auto r = StartBulkWriteImpl(aTupleLength); + if (r.isErr()) { + return false; + } + + aTuple.WriteTo(this->mData, aTupleLength); + + FinishBulkWriteImpl(aTupleLength); + return true; +} + +template +bool nsTSubstring::Assign(const substring_tuple_type& aTuple, + const fallible_t& aFallible) { + const auto [isDependentOnThis, tupleLength] = + aTuple.IsDependentOnWithLength(this->mData, this->mData + this->mLength); + if (isDependentOnThis) { + string_type temp; + self_type& tempSubstring = temp; + if (!tempSubstring.AssignNonDependent(aTuple, tupleLength, aFallible)) { + return false; + } + AssignOwned(std::move(temp)); + return true; + } + + return AssignNonDependent(aTuple, tupleLength, aFallible); +} + +template +void nsTSubstring::Adopt(char_type* aData, size_type aLength) { + if (aData) { + ReleaseData(this->mData, this->mDataFlags); + + if (aLength == size_type(-1)) { + aLength = char_traits::length(aData); + } + + SetData(aData, aLength, DataFlags::TERMINATED | DataFlags::OWNED); + + STRING_STAT_INCREMENT(Adopt); + // Treat this as construction of a "StringAdopt" object for leak + // tracking purposes. + MOZ_LOG_CTOR(this->mData, "StringAdopt", 1); + } else { + SetIsVoid(true); + } +} + +// This version of Replace is optimized for single-character replacement. +template +void nsTSubstring::Replace(index_type aCutStart, size_type aCutLength, + char_type aChar) { + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + if (ReplacePrep(aCutStart, aCutLength, 1)) { + this->mData[aCutStart] = aChar; + } +} + +template +bool nsTSubstring::Replace(index_type aCutStart, size_type aCutLength, + char_type aChar, const fallible_t&) { + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + if (!ReplacePrep(aCutStart, aCutLength, 1)) { + return false; + } + + this->mData[aCutStart] = aChar; + + return true; +} + +template +void nsTSubstring::Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength) { + if (!Replace(aCutStart, aCutLength, aData, aLength, mozilla::fallible)) { + AllocFailed(this->Length() - aCutLength + 1); + } +} + +template +bool nsTSubstring::Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength, + const fallible_t& aFallible) { + // unfortunately, some callers pass null :-( + if (!aData) { + aLength = 0; + } else { + if (aLength == size_type(-1)) { + aLength = char_traits::length(aData); + } + + if (this->IsDependentOn(aData, aData + aLength)) { + nsTAutoString temp(aData, aLength); + return Replace(aCutStart, aCutLength, temp, aFallible); + } + } + + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + bool ok = ReplacePrep(aCutStart, aCutLength, aLength); + if (!ok) { + return false; + } + + if (aLength > 0) { + char_traits::copy(this->mData + aCutStart, aData, aLength); + } + + return true; +} + +template +void nsTSubstring::Replace(index_type aCutStart, size_type aCutLength, + const substring_tuple_type& aTuple) { + const auto [isDependentOnThis, tupleLength] = + aTuple.IsDependentOnWithLength(this->mData, this->mData + this->mLength); + + if (isDependentOnThis) { + nsTAutoString temp; + if (!temp.AssignNonDependent(aTuple, tupleLength, mozilla::fallible)) { + AllocFailed(tupleLength); + } + Replace(aCutStart, aCutLength, temp); + return; + } + + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + if (ReplacePrep(aCutStart, aCutLength, tupleLength) && tupleLength > 0) { + aTuple.WriteTo(this->mData + aCutStart, tupleLength); + } +} + +template +void nsTSubstring::ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type* aData, + size_type aLength) { + aCutStart = XPCOM_MIN(aCutStart, this->Length()); + + if (!aCutStart && aCutLength == this->Length() && + !(this->mDataFlags & DataFlags::REFCOUNTED)) { + // Check for REFCOUNTED above to avoid undoing the effect of + // SetCapacity(). + AssignLiteral(aData, aLength); + } else if (ReplacePrep(aCutStart, aCutLength, aLength) && aLength > 0) { + char_traits::copy(this->mData + aCutStart, aData, aLength); + } +} + +template +void nsTSubstring::Append(char_type aChar) { + if (MOZ_UNLIKELY(!Append(aChar, mozilla::fallible))) { + AllocFailed(this->mLength + 1); + } +} + +template +bool nsTSubstring::Append(char_type aChar, const fallible_t& aFallible) { + size_type oldLen = this->mLength; + size_type newLen = oldLen + 1; // Can't overflow + auto r = StartBulkWriteImpl(newLen, oldLen, false); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + this->mData[oldLen] = aChar; + FinishBulkWriteImpl(newLen); + return true; +} + +template +void nsTSubstring::Append(const char_type* aData, size_type aLength) { + if (MOZ_UNLIKELY(!Append(aData, aLength, mozilla::fallible))) { + AllocFailed(this->mLength + (aLength == size_type(-1) + ? char_traits::length(aData) + : aLength)); + } +} + +template +bool nsTSubstring::Append(const char_type* aData, size_type aLength, + const fallible_t& aFallible) { + if (MOZ_UNLIKELY(aLength == size_type(-1))) { + aLength = char_traits::length(aData); + } + + if (MOZ_UNLIKELY(!aLength)) { + // Avoid undoing the effect of SetCapacity() if both + // mLength and aLength are zero. + return true; + } + + if (MOZ_UNLIKELY(this->IsDependentOn(aData, aData + aLength))) { + return Append(string_type(aData, aLength), mozilla::fallible); + } + size_type oldLen = this->mLength; + mozilla::CheckedInt newLen(oldLen); + newLen += aLength; + if (MOZ_UNLIKELY(!newLen.isValid())) { + return false; + } + auto r = StartBulkWriteImpl(newLen.value(), oldLen, false); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + char_traits::copy(this->mData + oldLen, aData, aLength); + FinishBulkWriteImpl(newLen.value()); + return true; +} + +template +void nsTSubstring::AppendASCII(const char* aData, size_type aLength) { + if (MOZ_UNLIKELY(!AppendASCII(aData, aLength, mozilla::fallible))) { + AllocFailed(this->mLength + + (aLength == size_type(-1) ? strlen(aData) : aLength)); + } +} + +template +bool nsTSubstring::AppendASCII(const char* aData, + const fallible_t& aFallible) { + return AppendASCII(aData, size_type(-1), aFallible); +} + +template +bool nsTSubstring::AppendASCII(const char* aData, size_type aLength, + const fallible_t& aFallible) { + if (MOZ_UNLIKELY(aLength == size_type(-1))) { + aLength = strlen(aData); + } + + if (MOZ_UNLIKELY(!aLength)) { + // Avoid undoing the effect of SetCapacity() if both + // mLength and aLength are zero. + return true; + } + + if constexpr (std::is_same_v) { + // 16-bit string can't depend on an 8-bit buffer + if (MOZ_UNLIKELY(this->IsDependentOn(aData, aData + aLength))) { + return Append(string_type(aData, aLength), mozilla::fallible); + } + } + + size_type oldLen = this->mLength; + mozilla::CheckedInt newLen(oldLen); + newLen += aLength; + if (MOZ_UNLIKELY(!newLen.isValid())) { + return false; + } + auto r = StartBulkWriteImpl(newLen.value(), oldLen, false); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + char_traits::copyASCII(this->mData + oldLen, aData, aLength); + FinishBulkWriteImpl(newLen.value()); + return true; +} + +template +void nsTSubstring::Append(const self_type& aStr) { + if (MOZ_UNLIKELY(!Append(aStr, mozilla::fallible))) { + AllocFailed(this->mLength + aStr.Length()); + } +} + +template +bool nsTSubstring::Append(const self_type& aStr, + const fallible_t& aFallible) { + // Check refcounted to avoid undoing the effects of SetCapacity(). + if (MOZ_UNLIKELY(!this->mLength && + !(this->mDataFlags & DataFlags::REFCOUNTED))) { + return Assign(aStr, mozilla::fallible); + } + return Append(aStr.BeginReading(), aStr.Length(), mozilla::fallible); +} + +template +void nsTSubstring::Append(const substring_tuple_type& aTuple) { + if (MOZ_UNLIKELY(!Append(aTuple, mozilla::fallible))) { + AllocFailed(this->mLength + aTuple.Length()); + } +} + +template +bool nsTSubstring::Append(const substring_tuple_type& aTuple, + const fallible_t& aFallible) { + const auto [isDependentOnThis, tupleLength] = + aTuple.IsDependentOnWithLength(this->mData, this->mData + this->mLength); + + if (MOZ_UNLIKELY(!tupleLength)) { + // Avoid undoing the effect of SetCapacity() if both + // mLength and tupleLength are zero. + return true; + } + + if (MOZ_UNLIKELY(isDependentOnThis)) { + return Append(string_type(aTuple), aFallible); + } + + size_type oldLen = this->mLength; + mozilla::CheckedInt newLen(oldLen); + newLen += tupleLength; + if (MOZ_UNLIKELY(!newLen.isValid())) { + return false; + } + auto r = StartBulkWriteImpl(newLen.value(), oldLen, false); + if (MOZ_UNLIKELY(r.isErr())) { + return false; + } + aTuple.WriteTo(this->mData + oldLen, tupleLength); + FinishBulkWriteImpl(newLen.value()); + return true; +} + +template +void nsTSubstring::SetCapacity(size_type aCapacity) { + if (!SetCapacity(aCapacity, mozilla::fallible)) { + AllocFailed(aCapacity); + } +} + +template +bool nsTSubstring::SetCapacity(size_type aCapacity, const fallible_t&) { + size_type length = this->mLength; + // This method can no longer be used to shorten the + // logical length. + size_type capacity = XPCOM_MAX(aCapacity, length); + + auto r = StartBulkWriteImpl(capacity, length, true); + if (r.isErr()) { + return false; + } + + if (MOZ_UNLIKELY(!capacity)) { + // Zero capacity was requested on a zero-length + // string. In this special case, we are pointing + // to the special empty buffer, which is already + // zero-terminated and not writable, so we must + // not attempt to zero-terminate it. + AssertValid(); + return true; + } + + // FinishBulkWriteImpl with argument zero releases + // the heap-allocated buffer. However, SetCapacity() + // is a special case that allows mLength to be zero + // while a heap-allocated buffer exists. + // By calling FinishBulkWriteImplImpl, we skip the + // zero case handling that's inappropriate in the + // SetCapacity() case. + FinishBulkWriteImplImpl(length); + return true; +} + +template +void nsTSubstring::SetLength(size_type aLength) { + if (!SetLength(aLength, mozilla::fallible)) { + AllocFailed(aLength); + } +} + +template +bool nsTSubstring::SetLength(size_type aLength, + const fallible_t& aFallible) { + size_type preserve = XPCOM_MIN(aLength, this->Length()); + auto r = StartBulkWriteImpl(aLength, preserve, true); + if (r.isErr()) { + return false; + } + + FinishBulkWriteImpl(aLength); + + return true; +} + +template +void nsTSubstring::Truncate() { + ReleaseData(this->mData, this->mDataFlags); + SetToEmptyBuffer(); + AssertValid(); +} + +template +void nsTSubstring::SetIsVoid(bool aVal) { + if (aVal) { + Truncate(); + this->mDataFlags |= DataFlags::VOIDED; + } else { + this->mDataFlags &= ~DataFlags::VOIDED; + } +} + +template +void nsTSubstring::StripChar(char_type aChar) { + if (this->mLength == 0) { + return; + } + + if (!EnsureMutable()) { // XXX do this lazily? + AllocFailed(this->mLength); + } + + // XXX(darin): this code should defer writing until necessary. + + char_type* to = this->mData; + char_type* from = this->mData; + char_type* end = this->mData + this->mLength; + + while (from < end) { + char_type theChar = *from++; + if (aChar != theChar) { + *to++ = theChar; + } + } + *to = char_type(0); // add the null + this->mLength = to - this->mData; +} + +template +void nsTSubstring::StripChars(const char_type* aChars) { + if (this->mLength == 0) { + return; + } + + if (!EnsureMutable()) { // XXX do this lazily? + AllocFailed(this->mLength); + } + + // XXX(darin): this code should defer writing until necessary. + + char_type* to = this->mData; + char_type* from = this->mData; + char_type* end = this->mData + this->mLength; + + while (from < end) { + char_type theChar = *from++; + const char_type* test = aChars; + + for (; *test && *test != theChar; ++test) + ; + + if (!*test) { + // Not stripped, copy this char. + *to++ = theChar; + } + } + *to = char_type(0); // add the null + this->mLength = to - this->mData; +} + +template +void nsTSubstring::StripTaggedASCII(const ASCIIMaskArray& aToStrip) { + if (this->mLength == 0) { + return; + } + + size_t untaggedPrefixLength = 0; + for (; untaggedPrefixLength < this->mLength; ++untaggedPrefixLength) { + uint32_t theChar = (uint32_t)this->mData[untaggedPrefixLength]; + if (mozilla::ASCIIMask::IsMasked(aToStrip, theChar)) { + break; + } + } + + if (untaggedPrefixLength == this->mLength) { + return; + } + + if (!EnsureMutable()) { + AllocFailed(this->mLength); + } + + char_type* to = this->mData + untaggedPrefixLength; + char_type* from = to; + char_type* end = this->mData + this->mLength; + + while (from < end) { + uint32_t theChar = (uint32_t)*from++; + // Replacing this with a call to ASCIIMask::IsMasked + // regresses performance somewhat, so leaving it inlined. + if (!mozilla::ASCIIMask::IsMasked(aToStrip, theChar)) { + // Not stripped, copy this char. + *to++ = (char_type)theChar; + } + } + *to = char_type(0); // add the null + this->mLength = to - this->mData; +} + +template +void nsTSubstring::StripCRLF() { + // Expanding this call to copy the code from StripTaggedASCII + // instead of just calling it does somewhat help with performance + // but it is not worth it given the duplicated code. + StripTaggedASCII(mozilla::ASCIIMask::MaskCRLF()); +} + +template +struct MOZ_STACK_CLASS PrintfAppend : public mozilla::PrintfTarget { + explicit PrintfAppend(nsTSubstring* aString) : mString(aString) {} + + bool append(const char* aStr, size_t aLen) override { + if (aLen == 0) { + return true; + } + + mString->AppendASCII(aStr, aLen); + return true; + } + + private: + nsTSubstring* mString; +}; + +template +void nsTSubstring::AppendPrintf(const char* aFormat, ...) { + PrintfAppend appender(this); + va_list ap; + va_start(ap, aFormat); + bool r = appender.vprint(aFormat, ap); + if (!r) { + MOZ_CRASH("Allocation or other failure in PrintfTarget::print"); + } + va_end(ap); +} + +template +void nsTSubstring::AppendVprintf(const char* aFormat, va_list aAp) { + PrintfAppend appender(this); + bool r = appender.vprint(aFormat, aAp); + if (!r) { + MOZ_CRASH("Allocation or other failure in PrintfTarget::print"); + } +} + +template +void nsTSubstring::AppendIntDec(int32_t aInteger) { + PrintfAppend appender(this); + bool r = appender.appendIntDec(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template +void nsTSubstring::AppendIntDec(uint32_t aInteger) { + PrintfAppend appender(this); + bool r = appender.appendIntDec(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template +void nsTSubstring::AppendIntOct(uint32_t aInteger) { + PrintfAppend appender(this); + bool r = appender.appendIntOct(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template +void nsTSubstring::AppendIntHex(uint32_t aInteger) { + PrintfAppend appender(this); + bool r = appender.appendIntHex(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template +void nsTSubstring::AppendIntDec(int64_t aInteger) { + PrintfAppend appender(this); + bool r = appender.appendIntDec(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template +void nsTSubstring::AppendIntDec(uint64_t aInteger) { + PrintfAppend appender(this); + bool r = appender.appendIntDec(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template +void nsTSubstring::AppendIntOct(uint64_t aInteger) { + PrintfAppend appender(this); + bool r = appender.appendIntOct(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +template +void nsTSubstring::AppendIntHex(uint64_t aInteger) { + PrintfAppend appender(this); + bool r = appender.appendIntHex(aInteger); + if (MOZ_UNLIKELY(!r)) { + MOZ_CRASH("Allocation or other failure while appending integers"); + } +} + +// Returns the length of the formatted aDouble in aBuf. +static int FormatWithoutTrailingZeros(char (&aBuf)[40], double aDouble, + int aPrecision) { + static const DoubleToStringConverter converter( + DoubleToStringConverter::UNIQUE_ZERO | + DoubleToStringConverter::NO_TRAILING_ZERO | + DoubleToStringConverter::EMIT_POSITIVE_EXPONENT_SIGN, + "Infinity", "NaN", 'e', -6, 21, 6, 1); + double_conversion::StringBuilder builder(aBuf, sizeof(aBuf)); + converter.ToPrecision(aDouble, aPrecision, &builder); + int length = builder.position(); + builder.Finalize(); + return length; +} + +template +void nsTSubstring::AppendFloat(float aFloat) { + char buf[40]; + int length = FormatWithoutTrailingZeros(buf, aFloat, 6); + AppendASCII(buf, length); +} + +template +void nsTSubstring::AppendFloat(double aFloat) { + char buf[40]; + int length = FormatWithoutTrailingZeros(buf, aFloat, 15); + AppendASCII(buf, length); +} + +template +size_t nsTSubstring::SizeOfExcludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const { + if (this->mDataFlags & DataFlags::REFCOUNTED) { + return nsStringBuffer::FromData(this->mData) + ->SizeOfIncludingThisIfUnshared(aMallocSizeOf); + } + if (this->mDataFlags & DataFlags::OWNED) { + return aMallocSizeOf(this->mData); + } + + // If we reach here, exactly one of the following must be true: + // - DataFlags::VOIDED is set, and this->mData points to sEmptyBuffer; + // - DataFlags::INLINE is set, and this->mData points to a buffer within a + // string object (e.g. nsAutoString); + // - None of DataFlags::REFCOUNTED, DataFlags::OWNED, DataFlags::INLINE is + // set, and this->mData points to a buffer owned by something else. + // + // In all three cases, we don't measure it. + return 0; +} + +template +size_t nsTSubstring::SizeOfExcludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const { + // This is identical to SizeOfExcludingThisIfUnshared except for the + // DataFlags::REFCOUNTED case. + if (this->mDataFlags & DataFlags::REFCOUNTED) { + return nsStringBuffer::FromData(this->mData) + ->SizeOfIncludingThisEvenIfShared(aMallocSizeOf); + } + if (this->mDataFlags & DataFlags::OWNED) { + return aMallocSizeOf(this->mData); + } + return 0; +} + +template +size_t nsTSubstring::SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThisIfUnshared(aMallocSizeOf); +} + +template +size_t nsTSubstring::SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThisEvenIfShared(aMallocSizeOf); +} + +template +nsTSubstringSplitter nsTSubstring::Split(const char_type aChar) const { + return nsTSubstringSplitter( + nsTCharSeparatedTokenizerTemplate< + NS_TokenizerIgnoreNothing, T, + nsTokenizerFlags::IncludeEmptyTokenAtEnd>(*this, aChar)); +} + +// Common logic for nsTSubstring::ToInteger and nsTSubstring::ToInteger64. +template +int_type ToIntegerCommon(const nsTSubstring& aSrc, nsresult* aErrorCode, + uint32_t aRadix) { + MOZ_ASSERT(aRadix == 10 || aRadix == 16); + + // Initial value, override if we find an integer. + *aErrorCode = NS_ERROR_ILLEGAL_VALUE; + + // Begin by skipping over leading chars that shouldn't be part of the number. + auto cp = aSrc.BeginReading(); + auto endcp = aSrc.EndReading(); + bool negate = false; + bool done = false; + + // NB: For backwards compatibility I'm not going to change this logic but + // it seems really odd. Previously there was logic to auto-detect the + // radix if kAutoDetect was passed in. In practice this value was never + // used, so it pretended to auto detect and skipped some preceding + // letters (excluding valid hex digits) but never used the result. + // + // For example if you pass in "Get the number: 10", aRadix = 10 we'd + // skip the 'G', and then fail to parse "et the number: 10". If aRadix = + // 16 we'd skip the 'G', and parse just 'e' returning 14. + while ((cp < endcp) && (!done)) { + switch (*cp++) { + // clang-format off + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + done = true; + break; + // clang-format on + case '-': + negate = true; + break; + default: + break; + } + } + + if (!done) { + // No base 16 or base 10 digits were found. + return 0; + } + + // Step back. + cp--; + + mozilla::CheckedInt result; + + // Now iterate the numeric chars and build our result. + while (cp < endcp) { + auto theChar = *cp++; + if (('0' <= theChar) && (theChar <= '9')) { + result = (aRadix * result) + (theChar - '0'); + } else if ((theChar >= 'A') && (theChar <= 'F')) { + if (10 == aRadix) { + // Invalid base 10 digit, error out. + return 0; + } else { + result = (aRadix * result) + ((theChar - 'A') + 10); + } + } else if ((theChar >= 'a') && (theChar <= 'f')) { + if (10 == aRadix) { + // Invalid base 10 digit, error out. + return 0; + } else { + result = (aRadix * result) + ((theChar - 'a') + 10); + } + } else if ((('X' == theChar) || ('x' == theChar)) && result == 0) { + // For some reason we support a leading 'x' regardless of radix. For + // example: "000000x500", aRadix = 10 would be parsed as 500 rather + // than 0. + continue; + } else { + // We've encountered a char that's not a legal number or sign and we can + // terminate processing. + break; + } + + if (!result.isValid()) { + // Overflow! + return 0; + } + } + + // Integer found. + *aErrorCode = NS_OK; + + if (negate) { + result = -result; + } + + return result.value(); +} + +template +int32_t nsTSubstring::ToInteger(nsresult* aErrorCode, + uint32_t aRadix) const { + return ToIntegerCommon(*this, aErrorCode, aRadix); +} + +/** + * nsTSubstring::ToInteger64 + */ +template +int64_t nsTSubstring::ToInteger64(nsresult* aErrorCode, + uint32_t aRadix) const { + return ToIntegerCommon(*this, aErrorCode, aRadix); +} + +/** + * nsTSubstring::Mid + */ +template +typename nsTSubstring::size_type nsTSubstring::Mid( + self_type& aResult, index_type aStartPos, size_type aLengthToCopy) const { + if (aStartPos == 0 && aLengthToCopy >= this->mLength) { + aResult = *this; + } else { + aResult = Substring(*this, aStartPos, aLengthToCopy); + } + + return aResult.mLength; +} + +/** + * nsTSubstring::StripWhitespace + */ + +template +void nsTSubstring::StripWhitespace() { + if (!StripWhitespace(mozilla::fallible)) { + this->AllocFailed(this->mLength); + } +} + +template +bool nsTSubstring::StripWhitespace(const fallible_t&) { + if (!this->EnsureMutable()) { + return false; + } + + this->StripTaggedASCII(mozilla::ASCIIMask::MaskWhitespace()); + return true; +} + +/** + * nsTSubstring::ReplaceChar,ReplaceSubstring + */ + +template +void nsTSubstring::ReplaceChar(char_type aOldChar, char_type aNewChar) { + int32_t i = this->FindChar(aOldChar); + if (i == kNotFound) { + return; + } + + if (!this->EnsureMutable()) { + this->AllocFailed(this->mLength); + } + for (; i != kNotFound; i = this->FindChar(aOldChar, i + 1)) { + this->mData[i] = aNewChar; + } +} + +template +void nsTSubstring::ReplaceChar(const string_view& aSet, char_type aNewChar) { + int32_t i = this->FindCharInSet(aSet); + if (i == kNotFound) { + return; + } + + if (!this->EnsureMutable()) { + this->AllocFailed(this->mLength); + } + for (; i != kNotFound; i = this->FindCharInSet(aSet, i + 1)) { + this->mData[i] = aNewChar; + } +} + +template +void nsTSubstring::ReplaceSubstring(const char_type* aTarget, + const char_type* aNewValue) { + ReplaceSubstring(nsTDependentString(aTarget), + nsTDependentString(aNewValue)); +} + +template +bool nsTSubstring::ReplaceSubstring(const char_type* aTarget, + const char_type* aNewValue, + const fallible_t& aFallible) { + return ReplaceSubstring(nsTDependentString(aTarget), + nsTDependentString(aNewValue), aFallible); +} + +template +void nsTSubstring::ReplaceSubstring(const self_type& aTarget, + const self_type& aNewValue) { + if (!ReplaceSubstring(aTarget, aNewValue, mozilla::fallible)) { + // Note that this may wildly underestimate the allocation that failed, as + // we could have been replacing multiple copies of aTarget. + this->AllocFailed(this->mLength + (aNewValue.Length() - aTarget.Length())); + } +} + +template +bool nsTSubstring::ReplaceSubstring(const self_type& aTarget, + const self_type& aNewValue, + const fallible_t&) { + struct Segment { + uint32_t mBegin, mLength; + Segment(uint32_t aBegin, uint32_t aLength) + : mBegin(aBegin), mLength(aLength) {} + }; + + if (aTarget.Length() == 0) { + return true; + } + + // Remember all of the non-matching parts. + AutoTArray nonMatching; + uint32_t i = 0; + mozilla::CheckedUint32 newLength; + while (true) { + int32_t r = this->Find(aTarget, i); + int32_t until = (r == kNotFound) ? this->Length() - i : r - i; + nonMatching.AppendElement(Segment(i, until)); + newLength += until; + if (r == kNotFound) { + break; + } + + newLength += aNewValue.Length(); + i = r + aTarget.Length(); + if (i >= this->Length()) { + // Add an auxiliary entry at the end of the list to help as an edge case + // for the algorithms below. + nonMatching.AppendElement(Segment(this->Length(), 0)); + break; + } + } + + if (!newLength.isValid()) { + return false; + } + + // If there's only one non-matching segment, then the target string was not + // found, and there's nothing to do. + if (nonMatching.Length() == 1) { + MOZ_ASSERT( + nonMatching[0].mBegin == 0 && nonMatching[0].mLength == this->Length(), + "We should have the correct non-matching segment."); + return true; + } + + // Make sure that we can mutate our buffer. + // Note that we always allocate at least an this->mLength sized buffer, + // because the rest of the algorithm relies on having access to all of the + // original string. In other words, we over-allocate in the shrinking case. + uint32_t oldLen = this->Length(); + auto r = + this->StartBulkWriteImpl(XPCOM_MAX(oldLen, newLength.value()), oldLen); + if (r.isErr()) { + return false; + } + + if (aTarget.Length() >= aNewValue.Length()) { + // In the shrinking case, start filling the buffer from the beginning. + const uint32_t delta = (aTarget.Length() - aNewValue.Length()); + for (i = 1; i < nonMatching.Length(); ++i) { + // When we move the i'th non-matching segment into position, we need to + // account for the characters deleted by the previous |i| replacements by + // subtracting |i * delta|. + const char_type* sourceSegmentPtr = this->mData + nonMatching[i].mBegin; + char_type* destinationSegmentPtr = + this->mData + nonMatching[i].mBegin - i * delta; + // Write the i'th replacement immediately before the new i'th non-matching + // segment. + char_traits::copy(destinationSegmentPtr - aNewValue.Length(), + aNewValue.Data(), aNewValue.Length()); + char_traits::move(destinationSegmentPtr, sourceSegmentPtr, + nonMatching[i].mLength); + } + } else { + // In the growing case, start filling the buffer from the end. + const uint32_t delta = (aNewValue.Length() - aTarget.Length()); + for (i = nonMatching.Length() - 1; i > 0; --i) { + // When we move the i'th non-matching segment into position, we need to + // account for the characters added by the previous |i| replacements by + // adding |i * delta|. + const char_type* sourceSegmentPtr = this->mData + nonMatching[i].mBegin; + char_type* destinationSegmentPtr = + this->mData + nonMatching[i].mBegin + i * delta; + char_traits::move(destinationSegmentPtr, sourceSegmentPtr, + nonMatching[i].mLength); + // Write the i'th replacement immediately before the new i'th non-matching + // segment. + char_traits::copy(destinationSegmentPtr - aNewValue.Length(), + aNewValue.Data(), aNewValue.Length()); + } + } + + // Adjust the length and make sure the string is null terminated. + this->FinishBulkWriteImpl(newLength.value()); + + return true; +} + +/** + * nsTSubstring::Trim + */ + +template +void nsTSubstring::Trim(const std::string_view& aSet, bool aTrimLeading, + bool aTrimTrailing, bool aIgnoreQuotes) { + char_type* start = this->mData; + char_type* end = this->mData + this->mLength; + + // skip over quotes if requested + if (aIgnoreQuotes && this->mLength > 2 && + this->mData[0] == this->mData[this->mLength - 1] && + (this->mData[0] == '\'' || this->mData[0] == '"')) { + ++start; + --end; + } + + if (aTrimLeading) { + uint32_t cutStart = start - this->mData; + uint32_t cutLength = 0; + + // walk forward from start to end + for (; start != end; ++start, ++cutLength) { + if ((*start & ~0x7F) || // non-ascii + aSet.find(char(*start)) == std::string_view::npos) { + break; + } + } + + if (cutLength) { + this->Cut(cutStart, cutLength); + + // reset iterators + start = this->mData + cutStart; + end = this->mData + this->mLength - cutStart; + } + } + + if (aTrimTrailing) { + uint32_t cutEnd = end - this->mData; + uint32_t cutLength = 0; + + // walk backward from end to start + --end; + for (; end >= start; --end, ++cutLength) { + if ((*end & ~0x7F) || // non-ascii + aSet.find(char(*end)) == std::string_view::npos) { + break; + } + } + + if (cutLength) { + this->Cut(cutEnd - cutLength, cutLength); + } + } +} + +/** + * nsTSubstring::CompressWhitespace. + */ + +template +void nsTSubstring::CompressWhitespace(bool aTrimLeading, + bool aTrimTrailing) { + // Quick exit + if (this->mLength == 0) { + return; + } + + if (!this->EnsureMutable()) { + this->AllocFailed(this->mLength); + } + + const ASCIIMaskArray& mask = mozilla::ASCIIMask::MaskWhitespace(); + + char_type* to = this->mData; + char_type* from = this->mData; + char_type* end = this->mData + this->mLength; + + // Compresses runs of whitespace down to a normal space ' ' and convert + // any whitespace to a normal space. This assumes that whitespace is + // all standard 7-bit ASCII. + bool skipWS = aTrimLeading; + while (from < end) { + uint32_t theChar = *from++; + if (mozilla::ASCIIMask::IsMasked(mask, theChar)) { + if (!skipWS) { + *to++ = ' '; + skipWS = true; + } + } else { + *to++ = theChar; + skipWS = false; + } + } + + // If we need to trim the trailing whitespace, back up one character. + if (aTrimTrailing && skipWS && to > this->mData) { + to--; + } + + *to = char_type(0); // add the null + this->mLength = to - this->mData; +} + +template class nsTSubstring; +template class nsTSubstring; diff --git a/xpcom/string/nsTSubstring.h b/xpcom/string/nsTSubstring.h new file mode 100644 index 0000000000..d54dcafd7b --- /dev/null +++ b/xpcom/string/nsTSubstring.h @@ -0,0 +1,1454 @@ +/* -*- 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/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsTSubstring_h +#define nsTSubstring_h + +#include +#include + +#include "mozilla/Casting.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/IntegerTypeTraits.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Span.h" +#include "mozilla/Unused.h" + +#include "nsTStringRepr.h" + +#ifndef MOZILLA_INTERNAL_API +# error "Using XPCOM strings is limited to code linked into libxul." +#endif + +// The max number of logically uninitialized code units to +// fill with a marker byte or to mark as unintialized for +// memory checking. (Limited to avoid quadratic behavior.) +const size_t kNsStringBufferMaxPoison = 16; + +class nsStringBuffer; +template +class nsTSubstringSplitter; +template +class nsTString; +template +class nsTSubstring; + +namespace mozilla { + +/** + * This handle represents permission to perform low-level writes + * the storage buffer of a string in a manner that's aware of the + * actual capacity of the storage buffer allocation and that's + * cache-friendly in the sense that the writing of zero terminator + * for C compatibility can happen in linear memory access order + * (i.e. the zero terminator write takes place after writing + * new content to the string as opposed to the zero terminator + * write happening first causing a non-linear memory write for + * cache purposes). + * + * If you requested a prefix to be preserved when starting + * or restarting the bulk write, the prefix is present at the + * start of the buffer exposed by this handle as Span or + * as a raw pointer, and it's your responsibility to start + * writing after after the preserved prefix (which you + * presumably wanted not to overwrite since you asked for + * it to be preserved). + * + * In a success case, you must call Finish() with the new + * length of the string. In failure cases, it's OK to return + * early from the function whose local variable this handle is. + * The destructor of this class takes care of putting the + * string in a valid and mostly harmless state in that case + * by setting the value of a non-empty string to a single + * REPLACEMENT CHARACTER or in the case of nsACString that's + * too short for a REPLACEMENT CHARACTER to fit, an ASCII + * SUBSTITUTE. + * + * You must not allow this handle to outlive the string you + * obtained it from. + * + * You must not access the string you obtained this handle + * from in any way other than through this handle until + * you call Finish() on the handle or the handle goes out + * of scope. + * + * Once you've called Finish(), you must not call any + * methods on this handle and must not use values previously + * obtained. + * + * Once you call RestartBulkWrite(), you must not use + * values previously obtained from this handle and must + * reobtain the new corresponding values. + */ +template +class BulkWriteHandle final { + friend class nsTSubstring; + + public: + typedef typename mozilla::detail::nsTStringRepr base_string_type; + typedef typename base_string_type::size_type size_type; + + /** + * Pointer to the start of the writable buffer. Never nullptr. + * + * This pointer is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + T* Elements() const { + MOZ_ASSERT(mString); + return mString->mData; + } + + /** + * How many code units can be written to the buffer. + * (Note: This is not the same as the string's Length().) + * + * This value is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + size_type Length() const { + MOZ_ASSERT(mString); + return mCapacity; + } + + /** + * Pointer past the end of the buffer. + * + * This pointer is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + T* End() const { return Elements() + Length(); } + + /** + * The writable buffer as Span. + * + * This Span is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + auto AsSpan() const { return mozilla::Span{Elements(), Length()}; } + + /** + * Autoconvert to the buffer as writable Span. + * + * This Span is valid until whichever of these happens first: + * 1) Finish() is called + * 2) RestartBulkWrite() is called + * 3) BulkWriteHandle goes out of scope + */ + operator mozilla::Span() const { return AsSpan(); } + + /** + * Restart the bulk write with a different capacity. + * + * This method invalidates previous return values + * of the other methods above. + * + * Can fail if out of memory leaving the buffer + * in the state before this call. + * + * @param aCapacity the new requested capacity + * @param aPrefixToPreserve the number of code units at + * the start of the string to + * copy over to the new buffer + * @param aAllowShrinking whether the string is + * allowed to attempt to + * allocate a smaller buffer + * for its content and copy + * the data over. + */ + mozilla::Result RestartBulkWrite( + size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking) { + MOZ_ASSERT(mString); + MOZ_TRY_VAR(mCapacity, mString->StartBulkWriteImpl( + aCapacity, aPrefixToPreserve, aAllowShrinking)); + return mozilla::Ok(); + } + + /** + * Indicate that the bulk write finished successfully. + * + * @param aLength the number of code units written; + * must not exceed Length() + * @param aAllowShrinking whether the string is + * allowed to attempt to + * allocate a smaller buffer + * for its content and copy + * the data over. + */ + void Finish(size_type aLength, bool aAllowShrinking) { + MOZ_ASSERT(mString); + MOZ_ASSERT(aLength <= mCapacity); + if (!aLength) { + // Truncate is safe even when the string is in an invalid state + mString->Truncate(); + mString = nullptr; + return; + } + if (aAllowShrinking) { + mozilla::Unused << mString->StartBulkWriteImpl(aLength, aLength, true); + } + mString->FinishBulkWriteImpl(aLength); + mString = nullptr; + } + + BulkWriteHandle(BulkWriteHandle&& aOther) + : mString(aOther.Forget()), mCapacity(aOther.mCapacity) {} + + ~BulkWriteHandle() { + if (!mString || !mCapacity) { + return; + } + // The old zero terminator may be gone by now, so we need + // to write a new one somewhere and make length match. + // We can use a length between 1 and self.capacity. + // The contents of the string can be partially uninitialized + // or partially initialized in a way that would be dangerous + // if parsed by some recipient. It's prudent to write something + // same as the contents of the string. U+FFFD is the safest + // placeholder, but when it doesn't fit, let's use ASCII + // substitute. Merely truncating the string to a zero-length + // string might be dangerous in some scenarios. See + // https://www.unicode.org/reports/tr36/#Substituting_for_Ill_Formed_Subsequences + // for closely related scenario. + auto ptr = Elements(); + // Cast the pointer below to silence warnings + if (sizeof(T) == 1) { + unsigned char* charPtr = reinterpret_cast(ptr); + if (mCapacity >= 3) { + *charPtr++ = 0xEF; + *charPtr++ = 0xBF; + *charPtr++ = 0xBD; + mString->mLength = 3; + } else { + *charPtr++ = 0x1A; + mString->mLength = 1; + } + *charPtr = 0; + } else if (sizeof(T) == 2) { + char16_t* charPtr = reinterpret_cast(ptr); + *charPtr++ = 0xFFFD; + *charPtr = 0; + mString->mLength = 1; + } else { + MOZ_ASSERT_UNREACHABLE("Only 8-bit and 16-bit code units supported."); + } + } + + BulkWriteHandle() = delete; + BulkWriteHandle(const BulkWriteHandle&) = delete; + BulkWriteHandle& operator=(const BulkWriteHandle&) = delete; + + private: + BulkWriteHandle(nsTSubstring* aString, size_type aCapacity) + : mString(aString), mCapacity(aCapacity) {} + + nsTSubstring* Forget() { + auto string = mString; + mString = nullptr; + return string; + } + + nsTSubstring* mString; // nullptr upon finish + size_type mCapacity; +}; + +} // namespace mozilla + +/** + * nsTSubstring is an abstract string class. From an API perspective, this + * class is the root of the string class hierarchy. It represents a single + * contiguous array of characters, which may or may not be null-terminated. + * This type is not instantiated directly. A sub-class is instantiated + * instead. For example, see nsTString. + * + * NAMES: + * nsAString for wide characters + * nsACString for narrow characters + * + */ +template +class nsTSubstring : public mozilla::detail::nsTStringRepr { + friend class mozilla::BulkWriteHandle; + friend class nsStringBuffer; + + public: + typedef nsTSubstring self_type; + + typedef nsTString string_type; + + typedef typename mozilla::detail::nsTStringRepr base_string_type; + typedef typename base_string_type::substring_type substring_type; + + typedef typename base_string_type::fallible_t fallible_t; + + typedef typename base_string_type::char_type char_type; + typedef typename base_string_type::char_traits char_traits; + typedef + typename base_string_type::incompatible_char_type incompatible_char_type; + + typedef typename base_string_type::substring_tuple_type substring_tuple_type; + + typedef typename base_string_type::const_iterator const_iterator; + typedef typename base_string_type::iterator iterator; + + typedef typename base_string_type::comparator_type comparator_type; + + typedef typename base_string_type::const_char_iterator const_char_iterator; + + typedef typename base_string_type::string_view string_view; + + typedef typename base_string_type::index_type index_type; + typedef typename base_string_type::size_type size_type; + + // These are only for internal use within the string classes: + typedef typename base_string_type::DataFlags DataFlags; + typedef typename base_string_type::ClassFlags ClassFlags; + typedef typename base_string_type::LengthStorage LengthStorage; + + // this acts like a virtual destructor + ~nsTSubstring() { Finalize(); } + + /** + * writing iterators + * + * BeginWriting() makes the string mutable (if it isn't + * already) and returns (or writes into an outparam) a + * pointer that provides write access to the string's buffer. + * + * Note: Consider if BulkWrite() suits your use case better + * than BeginWriting() combined with SetLength(). + * + * Note: Strings autoconvert into writable mozilla::Span, + * which may suit your use case better than calling + * BeginWriting() directly. + * + * When writing via the pointer obtained from BeginWriting(), + * you are allowed to write at most the number of code units + * indicated by Length() or, alternatively, write up to, but + * not including, the position indicated by EndWriting(). + * + * In particular, calling SetCapacity() does not affect what + * the above paragraph says. + */ + + iterator BeginWriting() { + if (!EnsureMutable()) { + AllocFailed(base_string_type::mLength); + } + + return base_string_type::mData; + } + + iterator BeginWriting(const fallible_t&) { + return EnsureMutable() ? base_string_type::mData : iterator(0); + } + + iterator EndWriting() { + if (!EnsureMutable()) { + AllocFailed(base_string_type::mLength); + } + + return base_string_type::mData + base_string_type::mLength; + } + + iterator EndWriting(const fallible_t&) { + return EnsureMutable() + ? (base_string_type::mData + base_string_type::mLength) + : iterator(0); + } + + /** + * Perform string to int conversion. + * @param aErrorCode will contain error if one occurs + * @param aRadix is the radix to use. Only 10 and 16 are supported. + * @return int rep of string value, and possible (out) error code + */ + int32_t ToInteger(nsresult* aErrorCode, uint32_t aRadix = 10) const; + + /** + * Perform string to 64-bit int conversion. + * @param aErrorCode will contain error if one occurs + * @param aRadix is the radix to use. Only 10 and 16 are supported. + * @return 64-bit int rep of string value, and possible (out) error code + */ + int64_t ToInteger64(nsresult* aErrorCode, uint32_t aRadix = 10) const; + + /** + * assignment + */ + + void NS_FASTCALL Assign(char_type aChar); + [[nodiscard]] bool NS_FASTCALL Assign(char_type aChar, const fallible_t&); + + void NS_FASTCALL Assign(const char_type* aData, + size_type aLength = size_type(-1)); + [[nodiscard]] bool NS_FASTCALL Assign(const char_type* aData, + const fallible_t&); + [[nodiscard]] bool NS_FASTCALL Assign(const char_type* aData, + size_type aLength, const fallible_t&); + + void NS_FASTCALL Assign(const self_type&); + [[nodiscard]] bool NS_FASTCALL Assign(const self_type&, const fallible_t&); + + void NS_FASTCALL Assign(self_type&&); + [[nodiscard]] bool NS_FASTCALL Assign(self_type&&, const fallible_t&); + + void NS_FASTCALL Assign(const substring_tuple_type&); + [[nodiscard]] bool NS_FASTCALL Assign(const substring_tuple_type&, + const fallible_t&); + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + void Assign(char16ptr_t aData) { + Assign(static_cast(aData)); + } + + template > + void Assign(char16ptr_t aData, size_type aLength) { + Assign(static_cast(aData), aLength); + } + + template > + [[nodiscard]] bool Assign(char16ptr_t aData, size_type aLength, + const fallible_t& aFallible) { + return Assign(static_cast(aData), aLength, aFallible); + } +#endif + + void NS_FASTCALL AssignASCII(const char* aData, size_type aLength); + [[nodiscard]] bool NS_FASTCALL AssignASCII(const char* aData, + size_type aLength, + const fallible_t&); + + void NS_FASTCALL AssignASCII(const char* aData) { + AssignASCII(aData, strlen(aData)); + } + [[nodiscard]] bool NS_FASTCALL AssignASCII(const char* aData, + const fallible_t& aFallible) { + return AssignASCII(aData, strlen(aData), aFallible); + } + + // AssignLiteral must ONLY be called with an actual literal string, or + // a character array *constant* of static storage duration declared + // without an explicit size and with an initializer that is a string + // literal or is otherwise null-terminated. + // Use Assign or AssignASCII for other character array variables. + // + // This method does not need a fallible version, because it uses the + // POD buffer of the literal as the string's buffer without allocating. + // The literal does not need to be ASCII. If this a 16-bit string, this + // method takes a u"" literal. (The overload on 16-bit strings that takes + // a "" literal takes only ASCII.) + template + void AssignLiteral(const char_type (&aStr)[N]) { + AssignLiteral(aStr, N - 1); + } + + // AssignLiteral must ONLY be called with an actual literal string, or + // a char array *constant* declared without an explicit size and with an + // initializer that is a string literal or is otherwise null-terminated. + // Use AssignASCII for other char array variables. + // + // This method takes an 8-bit (ASCII-only!) string that is expanded + // into a 16-bit string at run time causing a run-time allocation. + // To avoid the run-time allocation (at the cost of the literal + // taking twice the size in the binary), use the above overload that + // takes a u"" string instead. Using the overload that takes a u"" + // literal is generally preferred when working with 16-bit strings. + // + // There is not a fallible version of this method because it only really + // applies to small allocations that we wouldn't want to check anyway. + template > + void AssignLiteral(const incompatible_char_type (&aStr)[N]) { + AssignASCII(aStr, N - 1); + } + + self_type& operator=(char_type aChar) { + Assign(aChar); + return *this; + } + self_type& operator=(const char_type* aData) { + Assign(aData); + return *this; + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + self_type& operator=(char16ptr_t aData) { + Assign(aData); + return *this; + } +#endif + self_type& operator=(const self_type& aStr) { + Assign(aStr); + return *this; + } + self_type& operator=(self_type&& aStr) { + Assign(std::move(aStr)); + return *this; + } + self_type& operator=(const substring_tuple_type& aTuple) { + Assign(aTuple); + return *this; + } + + // Adopt a heap-allocated char sequence for this string; is Voided if aData + // is null. Useful for e.g. converting an strdup'd C string into an + // nsCString. See also getter_Copies(), which is a useful wrapper. + void NS_FASTCALL Adopt(char_type* aData, size_type aLength = size_type(-1)); + + /** + * buffer manipulation + */ + + void NS_FASTCALL Replace(index_type aCutStart, size_type aCutLength, + char_type aChar); + [[nodiscard]] bool NS_FASTCALL Replace(index_type aCutStart, + size_type aCutLength, char_type aChar, + const fallible_t&); + void NS_FASTCALL Replace(index_type aCutStart, size_type aCutLength, + const char_type* aData, + size_type aLength = size_type(-1)); + [[nodiscard]] bool NS_FASTCALL Replace(index_type aCutStart, + size_type aCutLength, + const char_type* aData, + size_type aLength, const fallible_t&); + void Replace(index_type aCutStart, size_type aCutLength, + const self_type& aStr) { + Replace(aCutStart, aCutLength, aStr.Data(), aStr.Length()); + } + [[nodiscard]] bool Replace(index_type aCutStart, size_type aCutLength, + const self_type& aStr, + const fallible_t& aFallible) { + return Replace(aCutStart, aCutLength, aStr.Data(), aStr.Length(), + aFallible); + } + void NS_FASTCALL Replace(index_type aCutStart, size_type aCutLength, + const substring_tuple_type& aTuple); + + // ReplaceLiteral must ONLY be called with an actual literal string, or + // a character array *constant* of static storage duration declared + // without an explicit size and with an initializer that is a string + // literal or is otherwise null-terminated. + // Use Replace for other character array variables. + template + void ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type (&aStr)[N]) { + ReplaceLiteral(aCutStart, aCutLength, aStr, N - 1); + } + + /** + * |Left|, |Mid|, and |Right| are annoying signatures that seem better almost + * any _other_ way than they are now. Consider these alternatives + * + * // ...a member function that returns a |Substring| + * aWritable = aReadable.Left(17); + * // ...a global function that returns a |Substring| + * aWritable = Left(aReadable, 17); + * // ...a global function that does the assignment + * Left(aReadable, 17, aWritable); + * + * as opposed to the current signature + * + * // ...a member function that does the assignment + * aReadable.Left(aWritable, 17); + * + * or maybe just stamping them out in favor of |Substring|, they are just + * duplicate functionality + * + * aWritable = Substring(aReadable, 0, 17); + */ + size_type Mid(self_type& aResult, index_type aStartPos, + size_type aCount) const; + + size_type Left(self_type& aResult, size_type aCount) const { + return Mid(aResult, 0, aCount); + } + + size_type Right(self_type& aResult, size_type aCount) const { + aCount = XPCOM_MIN(this->Length(), aCount); + return Mid(aResult, this->mLength - aCount, aCount); + } + + /** + * This method strips whitespace throughout the string. + */ + void StripWhitespace(); + bool StripWhitespace(const fallible_t&); + + /** + * This method is used to remove all occurrences of aChar from this + * string. + * + * @param aChar -- char to be stripped + */ + void StripChar(char_type aChar); + + /** + * This method is used to remove all occurrences of aChars from this + * string. + * + * @param aChars -- chars to be stripped + */ + void StripChars(const char_type* aChars); + + /** + * This method is used to remove all occurrences of some characters this + * from this string. The characters removed have the corresponding + * entries in the bool array set to true; we retain all characters + * with code beyond 127. + * THE CALLER IS RESPONSIBLE for making sure the complete boolean + * array, 128 entries, is properly initialized. + * + * See also: ASCIIMask class. + * + * @param aToStrip -- Array where each entry is true if the + * corresponding ASCII character is to be stripped. All + * characters beyond code 127 are retained. Note that this + * parameter is of ASCIIMaskArray type, but we expand the typedef + * to avoid having to include nsASCIIMask.h in this include file + * as it brings other includes. + */ + void StripTaggedASCII(const std::array& aToStrip); + + /** + * A shortcut to strip \r and \n. + */ + void StripCRLF(); + + /** + * swaps occurence of 1 string for another + */ + void ReplaceChar(char_type aOldChar, char_type aNewChar); + void ReplaceChar(const string_view& aSet, char_type aNewChar); + + /** + * Replace all occurrences of aTarget with aNewValue. + * The complexity of this function is O(n+m), n being the length of the string + * and m being the length of aNewValue. + */ + void ReplaceSubstring(const self_type& aTarget, const self_type& aNewValue); + void ReplaceSubstring(const char_type* aTarget, const char_type* aNewValue); + [[nodiscard]] bool ReplaceSubstring(const self_type& aTarget, + const self_type& aNewValue, + const fallible_t&); + [[nodiscard]] bool ReplaceSubstring(const char_type* aTarget, + const char_type* aNewValue, + const fallible_t&); + + /** + * This method trims characters found in aSet from either end of the + * underlying string. + * + * @param aSet -- contains chars to be trimmed from both ends + * @param aTrimLeading + * @param aTrimTrailing + * @param aIgnoreQuotes -- if true, causes surrounding quotes to be ignored + * @return this + */ + void Trim(const std::string_view& aSet, bool aTrimLeading = true, + bool aTrimTrailing = true, bool aIgnoreQuotes = false); + + /** + * This method strips whitespace from string. + * You can control whether whitespace is yanked from start and end of + * string as well. + * + * @param aTrimLeading controls stripping of leading ws + * @param aTrimTrailing controls stripping of trailing ws + */ + void CompressWhitespace(bool aTrimLeading = true, bool aTrimTrailing = true); + + void Append(char_type aChar); + + [[nodiscard]] bool Append(char_type aChar, const fallible_t& aFallible); + + void Append(const char_type* aData, size_type aLength = size_type(-1)); + + [[nodiscard]] bool Append(const char_type* aData, size_type aLength, + const fallible_t& aFallible); + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + void Append(char16ptr_t aData, size_type aLength = size_type(-1)) { + Append(static_cast(aData), aLength); + } +#endif + + void Append(const self_type& aStr); + + [[nodiscard]] bool Append(const self_type& aStr, const fallible_t& aFallible); + + void Append(const substring_tuple_type& aTuple); + + [[nodiscard]] bool Append(const substring_tuple_type& aTuple, + const fallible_t& aFallible); + + void AppendASCII(const char* aData, size_type aLength = size_type(-1)); + + [[nodiscard]] bool AppendASCII(const char* aData, + const fallible_t& aFallible); + + [[nodiscard]] bool AppendASCII(const char* aData, size_type aLength, + const fallible_t& aFallible); + + // Appends a literal string ("" literal in the 8-bit case and u"" literal + // in the 16-bit case) to the string. + // + // AppendLiteral must ONLY be called with an actual literal string, or + // a character array *constant* of static storage duration declared + // without an explicit size and with an initializer that is a string + // literal or is otherwise null-terminated. + // Use Append or AppendASCII for other character array variables. + template + void AppendLiteral(const char_type (&aStr)[N]) { + // The case where base_string_type::mLength is zero is intentionally + // left unoptimized (could be optimized as call to AssignLiteral), + // because it's rare/nonexistent. If you add that optimization, + // please be sure to also check that + // !(base_string_type::mDataFlags & DataFlags::REFCOUNTED) + // to avoid undoing the effects of SetCapacity(). + Append(aStr, N - 1); + } + + template + void AppendLiteral(const char_type (&aStr)[N], const fallible_t& aFallible) { + // The case where base_string_type::mLength is zero is intentionally + // left unoptimized (could be optimized as call to AssignLiteral), + // because it's rare/nonexistent. If you add that optimization, + // please be sure to also check that + // !(base_string_type::mDataFlags & DataFlags::REFCOUNTED) + // to avoid undoing the effects of SetCapacity(). + return Append(aStr, N - 1, aFallible); + } + + // Only enable for T = char16_t + // + // Appends an 8-bit literal string ("" literal) to a 16-bit string by + // expanding it. The literal must only contain ASCII. + // + // Using u"" literals with 16-bit strings is generally preferred. + template > + void AppendLiteral(const incompatible_char_type (&aStr)[N]) { + AppendASCII(aStr, N - 1); + } + + // Only enable for T = char16_t + template > + [[nodiscard]] bool AppendLiteral(const incompatible_char_type (&aStr)[N], + const fallible_t& aFallible) { + return AppendASCII(aStr, N - 1, aFallible); + } + + /** + * Append a formatted string to the current string. Uses the + * standard printf format codes. This uses NSPR formatting, which will be + * locale-aware for floating-point values. You probably don't want to use + * this with floating-point values as a result. + */ + void AppendPrintf(const char* aFormat, ...) MOZ_FORMAT_PRINTF(2, 3); + void AppendVprintf(const char* aFormat, va_list aAp) MOZ_FORMAT_PRINTF(2, 0); + void AppendInt(int32_t aInteger) { AppendIntDec(aInteger); } + void AppendInt(int32_t aInteger, int aRadix) { + if (aRadix == 10) { + AppendIntDec(aInteger); + } else if (aRadix == 8) { + AppendIntOct(static_cast(aInteger)); + } else { + AppendIntHex(static_cast(aInteger)); + } + } + void AppendInt(uint32_t aInteger) { AppendIntDec(aInteger); } + void AppendInt(uint32_t aInteger, int aRadix) { + if (aRadix == 10) { + AppendIntDec(aInteger); + } else if (aRadix == 8) { + AppendIntOct(aInteger); + } else { + AppendIntHex(aInteger); + } + } + void AppendInt(int64_t aInteger) { AppendIntDec(aInteger); } + void AppendInt(int64_t aInteger, int aRadix) { + if (aRadix == 10) { + AppendIntDec(aInteger); + } else if (aRadix == 8) { + AppendIntOct(static_cast(aInteger)); + } else { + AppendIntHex(static_cast(aInteger)); + } + } + void AppendInt(uint64_t aInteger) { AppendIntDec(aInteger); } + void AppendInt(uint64_t aInteger, int aRadix) { + if (aRadix == 10) { + AppendIntDec(aInteger); + } else if (aRadix == 8) { + AppendIntOct(aInteger); + } else { + AppendIntHex(aInteger); + } + } + + private: + void AppendIntDec(int32_t); + void AppendIntDec(uint32_t); + void AppendIntOct(uint32_t); + void AppendIntHex(uint32_t); + void AppendIntDec(int64_t); + void AppendIntDec(uint64_t); + void AppendIntOct(uint64_t); + void AppendIntHex(uint64_t); + + public: + /** + * Append the given float to this string + */ + void NS_FASTCALL AppendFloat(float aFloat); + void NS_FASTCALL AppendFloat(double aFloat); + + self_type& operator+=(char_type aChar) { + Append(aChar); + return *this; + } + self_type& operator+=(const char_type* aData) { + Append(aData); + return *this; + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + self_type& operator+=(char16ptr_t aData) { + Append(aData); + return *this; + } +#endif + self_type& operator+=(const self_type& aStr) { + Append(aStr); + return *this; + } + self_type& operator+=(const substring_tuple_type& aTuple) { + Append(aTuple); + return *this; + } + + void Insert(char_type aChar, index_type aPos) { Replace(aPos, 0, aChar); } + void Insert(const char_type* aData, index_type aPos, + size_type aLength = size_type(-1)) { + Replace(aPos, 0, aData, aLength); + } +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + void Insert(char16ptr_t aData, index_type aPos, + size_type aLength = size_type(-1)) { + Insert(static_cast(aData), aPos, aLength); + } +#endif + void Insert(const self_type& aStr, index_type aPos) { + Replace(aPos, 0, aStr); + } + void Insert(const substring_tuple_type& aTuple, index_type aPos) { + Replace(aPos, 0, aTuple); + } + + // InsertLiteral must ONLY be called with an actual literal string, or + // a character array *constant* of static storage duration declared + // without an explicit size and with an initializer that is a string + // literal or is otherwise null-terminated. + // Use Insert for other character array variables. + template + void InsertLiteral(const char_type (&aStr)[N], index_type aPos) { + ReplaceLiteral(aPos, 0, aStr, N - 1); + } + + void Cut(index_type aCutStart, size_type aCutLength) { + Replace(aCutStart, aCutLength, char_traits::sEmptyBuffer, 0); + } + + nsTSubstringSplitter Split(const char_type aChar) const; + + /** + * buffer sizing + */ + + /** + * Attempts to set the capacity to the given size in number of + * code units without affecting the length of the string in + * order to avoid reallocation during a subsequent sequence of + * appends. + * + * This method is appropriate to use before a sequence of multiple + * operations from the following list (without operations that are + * not on the list between the SetCapacity() call and operations + * from the list): + * + * Append() + * AppendASCII() + * AppendLiteral() (except if the string is empty: bug 1487606) + * AppendPrintf() + * AppendInt() + * AppendFloat() + * LossyAppendUTF16toASCII() + * AppendASCIItoUTF16() + * + * DO NOT call SetCapacity() if the subsequent operations on the + * string do not meet the criteria above. Operations that undo + * the benefits of SetCapacity() include but are not limited to: + * + * SetLength() + * Truncate() + * Assign() + * AssignLiteral() + * Adopt() + * CopyASCIItoUTF16() + * LossyCopyUTF16toASCII() + * AppendUTF16toUTF8() + * AppendUTF8toUTF16() + * CopyUTF16toUTF8() + * CopyUTF8toUTF16() + * + * If your string is an nsAuto[C]String and you are calling + * SetCapacity() with a constant N, please instead declare the + * string as nsAuto[C]StringN without calling SetCapacity(). + * + * There is no need to include room for the null terminator: it is + * the job of the string class. + * + * Note: Calling SetCapacity() does not give you permission to + * use the pointer obtained from BeginWriting() to write + * past the current length (as returned by Length()) of the + * string. Please use either BulkWrite() or SetLength() + * instead. + * + * Note: SetCapacity() won't make the string shorter if + * called with an argument smaller than the length of the + * string. + * + * Note: You must not use previously obtained iterators + * or spans after calling SetCapacity(). + */ + void NS_FASTCALL SetCapacity(size_type aNewCapacity); + [[nodiscard]] bool NS_FASTCALL SetCapacity(size_type aNewCapacity, + const fallible_t&); + + /** + * Changes the logical length of the string, potentially + * allocating a differently-sized buffer for the string. + * + * When making the string shorter, this method never + * reports allocation failure. + * + * Exposes uninitialized memory if the string got longer. + * + * If called with the argument 0, releases the + * heap-allocated buffer, if any. (But the no-argument + * overload of Truncate() is a more idiomatic and efficient + * option than SetLength(0).) + * + * Note: You must not use previously obtained iterators + * or spans after calling SetLength(). + */ + void NS_FASTCALL SetLength(size_type aNewLength); + [[nodiscard]] bool NS_FASTCALL SetLength(size_type aNewLength, + const fallible_t&); + + /** + * Like SetLength() but asserts in that the string + * doesn't become longer. Never fails, so doesn't need a + * fallible variant. + * + * Note: You must not use previously obtained iterators + * or spans after calling Truncate(). + */ + void Truncate(size_type aNewLength) { + MOZ_RELEASE_ASSERT(aNewLength <= base_string_type::mLength, + "Truncate cannot make string longer"); + mozilla::DebugOnly success = SetLength(aNewLength, mozilla::fallible); + MOZ_ASSERT(success); + } + + /** + * A more efficient overload for Truncate(0). Releases the + * heap-allocated buffer if any. + */ + void Truncate(); + + /** + * buffer access + */ + + /** + * Get a const pointer to the string's internal buffer. The caller + * MUST NOT modify the characters at the returned address. + * + * @returns The length of the buffer in characters. + */ + inline size_type GetData(const char_type** aData) const { + *aData = base_string_type::mData; + return base_string_type::mLength; + } + + /** + * Get a pointer to the string's internal buffer, optionally resizing + * the buffer first. If size_type(-1) is passed for newLen, then the + * current length of the string is used. The caller MAY modify the + * characters at the returned address (up to but not exceeding the + * length of the string). + * + * @returns The length of the buffer in characters or 0 if unable to + * satisfy the request due to low-memory conditions. + */ + size_type GetMutableData(char_type** aData, + size_type aNewLen = size_type(-1)) { + if (!EnsureMutable(aNewLen)) { + AllocFailed(aNewLen == size_type(-1) ? base_string_type::Length() + : aNewLen); + } + + *aData = base_string_type::mData; + return base_string_type::Length(); + } + + size_type GetMutableData(char_type** aData, size_type aNewLen, + const fallible_t&) { + if (!EnsureMutable(aNewLen)) { + *aData = nullptr; + return 0; + } + + *aData = base_string_type::mData; + return base_string_type::mLength; + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + template > + size_type GetMutableData(wchar_t** aData, size_type aNewLen = size_type(-1)) { + return GetMutableData(reinterpret_cast(aData), aNewLen); + } + + template > + size_type GetMutableData(wchar_t** aData, size_type aNewLen, + const fallible_t& aFallible) { + return GetMutableData(reinterpret_cast(aData), aNewLen, + aFallible); + } +#endif + + mozilla::Span GetMutableData(size_type aNewLen = size_type(-1)) { + if (!EnsureMutable(aNewLen)) { + AllocFailed(aNewLen == size_type(-1) ? base_string_type::Length() + : aNewLen); + } + + return mozilla::Span{base_string_type::mData, base_string_type::Length()}; + } + + mozilla::Maybe> GetMutableData(size_type aNewLen, + const fallible_t&) { + if (!EnsureMutable(aNewLen)) { + return mozilla::Nothing(); + } + return Some( + mozilla::Span{base_string_type::mData, base_string_type::Length()}); + } + + /** + * Span integration + */ + + operator mozilla::Span() const { + return mozilla::Span{base_string_type::BeginReading(), + base_string_type::Length()}; + } + + void Append(mozilla::Span aSpan) { + Append(aSpan.Elements(), aSpan.Length()); + } + + [[nodiscard]] bool Append(mozilla::Span aSpan, + const fallible_t& aFallible) { + return Append(aSpan.Elements(), aSpan.Length(), aFallible); + } + + void NS_FASTCALL AssignASCII(mozilla::Span aData) { + AssignASCII(aData.Elements(), aData.Length()); + } + [[nodiscard]] bool NS_FASTCALL AssignASCII(mozilla::Span aData, + const fallible_t& aFallible) { + return AssignASCII(aData.Elements(), aData.Length(), aFallible); + } + + void AppendASCII(mozilla::Span aData) { + AppendASCII(aData.Elements(), aData.Length()); + } + + template > + operator mozilla::Span() const { + return mozilla::Span{ + reinterpret_cast(base_string_type::BeginReading()), + base_string_type::Length()}; + } + + template > + void Append(mozilla::Span aSpan) { + Append(reinterpret_cast(aSpan.Elements()), aSpan.Length()); + } + + template > + [[nodiscard]] bool Append(mozilla::Span aSpan, + const fallible_t& aFallible) { + return Append(reinterpret_cast(aSpan.Elements()), + aSpan.Length(), aFallible); + } + + void Insert(mozilla::Span aSpan, index_type aPos) { + Insert(aSpan.Elements(), aPos, aSpan.Length()); + } + + /** + * string data is never null, but can be marked void. if true, the + * string will be truncated. @see nsTSubstring::IsVoid + */ + + void NS_FASTCALL SetIsVoid(bool); + + /** + * If the string uses a shared buffer, this method + * clears the pointer without releasing the buffer. + */ + void ForgetSharedBuffer() { + if (base_string_type::mDataFlags & DataFlags::REFCOUNTED) { + SetToEmptyBuffer(); + } + } + + protected: + void AssertValid() { + MOZ_DIAGNOSTIC_ASSERT(!(this->mClassFlags & ClassFlags::INVALID_MASK)); + MOZ_DIAGNOSTIC_ASSERT(!(this->mDataFlags & DataFlags::INVALID_MASK)); + MOZ_ASSERT(!(this->mClassFlags & ClassFlags::NULL_TERMINATED) || + (this->mDataFlags & DataFlags::TERMINATED), + "String classes whose static type guarantees a null-terminated " + "buffer must not be assigned a non-null-terminated buffer."); + } + + public: + /** + * this is public to support automatic conversion of tuple to string + * base type, which helps avoid converting to nsTAString. + */ + MOZ_IMPLICIT nsTSubstring(const substring_tuple_type& aTuple) + : base_string_type(nullptr, 0, DataFlags(0), ClassFlags(0)) { + AssertValid(); + Assign(aTuple); + } + + size_t SizeOfExcludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThisIfUnshared( + mozilla::MallocSizeOf aMallocSizeOf) const; + + /** + * WARNING: Only use these functions if you really know what you are + * doing, because they can easily lead to double-counting strings. If + * you do use them, please explain clearly in a comment why it's safe + * and won't lead to double-counting. + */ + size_t SizeOfExcludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const; + size_t SizeOfIncludingThisEvenIfShared( + mozilla::MallocSizeOf aMallocSizeOf) const; + + template + void NS_ABORT_OOM(T) { + struct never {}; // a compiler-friendly way to do static_assert(false) + static_assert( + std::is_same_v, + "In string classes, use AllocFailed to account for sizeof(char_type). " + "Use the global ::NS_ABORT_OOM if you really have a count of bytes."); + } + + MOZ_ALWAYS_INLINE void AllocFailed(size_t aLength) { + ::NS_ABORT_OOM(aLength * sizeof(char_type)); + } + + protected: + // default initialization + nsTSubstring() + : base_string_type(char_traits::sEmptyBuffer, 0, DataFlags::TERMINATED, + ClassFlags(0)) { + AssertValid(); + } + + // copy-constructor, constructs as dependent on given object + // (NOTE: this is for internal use only) + nsTSubstring(const self_type& aStr) + : base_string_type(aStr.base_string_type::mData, + aStr.base_string_type::mLength, + aStr.base_string_type::mDataFlags & + (DataFlags::TERMINATED | DataFlags::VOIDED), + ClassFlags(0)) { + AssertValid(); + } + + // initialization with ClassFlags + explicit nsTSubstring(ClassFlags aClassFlags) + : base_string_type(char_traits::sEmptyBuffer, 0, DataFlags::TERMINATED, + aClassFlags) { + AssertValid(); + } + + /** + * allows for direct initialization of a nsTSubstring object. + */ + nsTSubstring(char_type* aData, size_type aLength, DataFlags aDataFlags, + ClassFlags aClassFlags) +#if defined(NS_BUILD_REFCNT_LOGGING) +# define XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE + ; +#else +# undef XPCOM_STRING_CONSTRUCTOR_OUT_OF_LINE + : base_string_type(aData, aLength, aDataFlags, aClassFlags) { + AssertValid(); + } +#endif /* NS_BUILD_REFCNT_LOGGING */ + + void SetToEmptyBuffer() { + base_string_type::mData = char_traits::sEmptyBuffer; + base_string_type::mLength = 0; + base_string_type::mDataFlags = DataFlags::TERMINATED; + AssertValid(); + } + + void SetData(char_type* aData, LengthStorage aLength, DataFlags aDataFlags) { + base_string_type::mData = aData; + base_string_type::mLength = aLength; + base_string_type::mDataFlags = aDataFlags; + AssertValid(); + } + + /** + * this function releases mData and does not change the value of + * any of its member variables. in other words, this function acts + * like a destructor. + */ + void NS_FASTCALL Finalize(); + + public: + /** + * Starts a low-level write transaction to the string. + * + * Prepares the string for mutation such that the capacity + * of the string is at least aCapacity. The returned handle + * exposes the actual, potentially larger, capacity. + * + * If meeting the capacity or mutability requirement requires + * reallocation, aPrefixToPreserve code units are copied from the + * start of the old buffer to the start of the new buffer. + * aPrefixToPreserve must not be greater than the string's current + * length or greater than aCapacity. + * + * aAllowShrinking indicates whether an allocation may be + * performed when the string is already mutable and the requested + * capacity is smaller than the current capacity. + * + * If this method returns successfully, you must not access + * the string except through the returned BulkWriteHandle + * until either the BulkWriteHandle goes out of scope or + * you call Finish() on the BulkWriteHandle. + * + * Compared to SetLength() and BeginWriting(), this more + * complex API accomplishes two things: + * 1) It exposes the actual capacity which may be larger + * than the requested capacity, which is useful in some + * multi-step write operations that don't allocate for + * the worst case up front. + * 2) It writes the zero terminator after the string + * content has been written, which results in a + * cache-friendly linear write pattern. + */ + mozilla::Result, nsresult> NS_FASTCALL BulkWrite( + size_type aCapacity, size_type aPrefixToPreserve, bool aAllowShrinking); + + /** + * THIS IS NOT REALLY A PUBLIC METHOD! DO NOT CALL FROM OUTSIDE + * THE STRING IMPLEMENTATION. (It's public only because friend + * declarations don't allow extern or static and this needs to + * be called from Rust FFI glue.) + * + * Prepares mData to be mutated such that the capacity of the string + * (not counting the zero-terminator) is at least aCapacity. + * Returns the actual capacity, which may be larger than what was + * requested or Err(NS_ERROR_OUT_OF_MEMORY) on allocation failure. + * + * mLength is ignored by this method. If the buffer is reallocated, + * aUnitsToPreserve specifies how many code units to copy over to + * the new buffer. The old buffer is freed if applicable. + * + * Unless the return value is Err(NS_ERROR_OUT_OF_MEMORY) to signal + * failure or 0 to signal that the string has been set to + * the special empty state, this method leaves the string in an + * invalid state! The caller is responsible for calling + * FinishBulkWrite() (or in Rust calling + * nsA[C]StringBulkWriteHandle::finish()), which put the string + * into a valid state by setting mLength and zero-terminating. + * This method sets the flag to claim that the string is + * zero-terminated before it actually is. + * + * Once this method has been called and before FinishBulkWrite() + * has been called, only accessing mData or calling this method + * again are valid operations. Do not call any other methods or + * access other fields between calling this method and + * FinishBulkWrite(). + * + * @param aCapacity The requested capacity. The return value + * will be greater than or equal to this value. + * @param aPrefixToPreserve The number of code units at the start + * of the old buffer to copy into the + * new buffer. + * @parem aAllowShrinking If true, an allocation may be performed + * if the requested capacity is smaller + * than the current capacity. + * @param aSuffixLength The length, in code units, of a suffix + * to move. + * @param aOldSuffixStart The old start index of the suffix to + * move. + * @param aNewSuffixStart The new start index of the suffix to + * move. + * + */ + mozilla::Result NS_FASTCALL StartBulkWriteImpl( + size_type aCapacity, size_type aPrefixToPreserve = 0, + bool aAllowShrinking = true, size_type aSuffixLength = 0, + size_type aOldSuffixStart = 0, size_type aNewSuffixStart = 0); + + private: + void AssignOwned(self_type&& aStr); + bool AssignNonDependent(const substring_tuple_type& aTuple, + size_type aTupleLength, + const mozilla::fallible_t& aFallible); + + /** + * Do not call this except from within FinishBulkWriteImpl() and + * SetCapacity(). + */ + MOZ_ALWAYS_INLINE void NS_FASTCALL + FinishBulkWriteImplImpl(LengthStorage aLength) { + base_string_type::mData[aLength] = char_type(0); + base_string_type::mLength = aLength; +#ifdef DEBUG + // ifdefed in order to avoid the call to Capacity() in non-debug + // builds. + // + // Our string is mutable, so Capacity() doesn't return zero. + // Capacity() doesn't include the space for the zero terminator, + // but we want to unitialize that slot, too. Since we start + // counting after the zero terminator the we just wrote above, + // we end up overwriting the space for terminator not reflected + // in the capacity number. + char_traits::uninitialize( + base_string_type::mData + aLength + 1, + XPCOM_MIN(size_t(Capacity() - aLength), kNsStringBufferMaxPoison)); +#endif + } + + protected: + /** + * Restores the string to a valid state after a call to StartBulkWrite() + * that returned a non-error result. The argument to this method + * must be less than or equal to the value returned by the most recent + * StartBulkWrite() call. + */ + void NS_FASTCALL FinishBulkWriteImpl(size_type aLength); + + /** + * this function prepares a section of mData to be modified. if + * necessary, this function will reallocate mData and possibly move + * existing data to open up the specified section. + * + * @param aCutStart specifies the starting offset of the section + * @param aCutLength specifies the length of the section to be replaced + * @param aNewLength specifies the length of the new section + * + * for example, suppose mData contains the string "abcdef" then + * + * ReplacePrep(2, 3, 4); + * + * would cause mData to look like "ab____f" where the characters + * indicated by '_' have an unspecified value and can be freely + * modified. this function will null-terminate mData upon return. + * + * this function returns false if is unable to allocate sufficient + * memory. + */ + [[nodiscard]] bool ReplacePrep(index_type aCutStart, size_type aCutLength, + size_type aNewLength); + + [[nodiscard]] bool NS_FASTCALL ReplacePrepInternal(index_type aCutStart, + size_type aCutLength, + size_type aNewFragLength, + size_type aNewTotalLength); + + /** + * returns the number of writable storage units starting at mData. + * the value does not include space for the null-terminator character. + * + * NOTE: this function returns 0 if mData is immutable (or the buffer + * is 0-sized). + */ + size_type NS_FASTCALL Capacity() const; + + /** + * this helper function can be called prior to directly manipulating + * the contents of mData. see, for example, BeginWriting. + */ + [[nodiscard]] bool NS_FASTCALL + EnsureMutable(size_type aNewLen = size_type(-1)); + + void NS_FASTCALL ReplaceLiteral(index_type aCutStart, size_type aCutLength, + const char_type* aData, size_type aLength); + + public: + // NOTE: this method is declared public _only_ for convenience for + // callers who don't have access to the original nsLiteralString_CharT. + void NS_FASTCALL AssignLiteral(const char_type* aData, size_type aLength); +}; + +extern template class nsTSubstring; +extern template class nsTSubstring; + +static_assert(sizeof(nsTSubstring) == + sizeof(mozilla::detail::nsTStringRepr), + "Don't add new data fields to nsTSubstring_CharT. " + "Add to nsTStringRepr instead."); + +#include "nsCharSeparatedTokenizer.h" +#include "nsTDependentSubstring.h" + +/** + * Span integration + */ +namespace mozilla { +Span(const nsTSubstring&)->Span; +Span(const nsTSubstring&)->Span; + +} // namespace mozilla + +#endif diff --git a/xpcom/string/nsTSubstringTuple.cpp b/xpcom/string/nsTSubstringTuple.cpp new file mode 100644 index 0000000000..3219cb19fa --- /dev/null +++ b/xpcom/string/nsTSubstringTuple.cpp @@ -0,0 +1,92 @@ +/* -*- 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 "nsTSubstringTuple.h" +#include "mozilla/CheckedInt.h" + +/** + * computes the aggregate string length + */ + +template +typename nsTSubstringTuple::size_type nsTSubstringTuple::Length() const { + mozilla::CheckedInt len; + if (mHead) { + len = mHead->Length(); + } else { + len = mFragA->Length(); + } + + len += mFragB->Length(); + MOZ_RELEASE_ASSERT(len.isValid(), "Substring tuple length is invalid"); + return len.value(); +} + +/** + * writes the aggregate string to the given buffer. aBufLen is assumed + * to be equal to or greater than the value returned by the Length() + * method. the string written to |aBuf| is not null-terminated. + */ + +template +void nsTSubstringTuple::WriteTo(char_type* aBuf, size_type aBufLen) const { + MOZ_RELEASE_ASSERT(aBufLen >= mFragB->Length(), "buffer too small"); + size_type headLen = aBufLen - mFragB->Length(); + if (mHead) { + mHead->WriteTo(aBuf, headLen); + } else { + MOZ_RELEASE_ASSERT(mFragA->Length() == headLen, "buffer incorrectly sized"); + char_traits::copy(aBuf, mFragA->Data(), mFragA->Length()); + } + + char_traits::copy(aBuf + headLen, mFragB->Data(), mFragB->Length()); +} + +/** + * returns true if this tuple is dependent on (i.e., overlapping with) + * the given char sequence. + */ + +template +bool nsTSubstringTuple::IsDependentOn(const char_type* aStart, + const char_type* aEnd) const { + // we start with the right-most fragment since it is faster to check. + + if (mFragB->IsDependentOn(aStart, aEnd)) { + return true; + } + + if (mHead) { + return mHead->IsDependentOn(aStart, aEnd); + } + + return mFragA->IsDependentOn(aStart, aEnd); +} + +template +auto nsTSubstringTuple::IsDependentOnWithLength(const char_type* aStart, + const char_type* aEnd) const + -> std::pair { + // we start with the right-most fragment since it is faster to check for + // dependency. + const bool rightDependentOn = mFragB->IsDependentOn(aStart, aEnd); + + if (rightDependentOn) { + return {true, Length()}; + } + + const auto [leftDependentOn, leftLen] = + mHead ? mHead->IsDependentOnWithLength(aStart, aEnd) + : std::pair{mFragA->IsDependentOn(aStart, aEnd), mFragA->Length()}; + + const auto checkedLen = + mozilla::CheckedInt{leftLen} + mFragB->Length(); + MOZ_RELEASE_ASSERT(checkedLen.isValid(), "Substring tuple length is invalid"); + return {leftDependentOn, checkedLen.value()}; +} + +template class nsTSubstringTuple; +template class nsTSubstringTuple; diff --git a/xpcom/string/nsTSubstringTuple.h b/xpcom/string/nsTSubstringTuple.h new file mode 100644 index 0000000000..dff8dedca9 --- /dev/null +++ b/xpcom/string/nsTSubstringTuple.h @@ -0,0 +1,89 @@ +/* -*- 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/. */ +// IWYU pragma: private, include "nsString.h" + +#ifndef nsTSubstringTuple_h +#define nsTSubstringTuple_h + +#include "mozilla/Attributes.h" +#include "nsTStringRepr.h" + +/** + * nsTSubstringTuple + * + * Represents a tuple of string fragments. Built as a recursive binary tree. + * It is used to implement the concatenation of two or more string objects. + * + * NOTE: This class is a private implementation detail and should never be + * referenced outside the string code. + */ +template +class MOZ_TEMPORARY_CLASS nsTSubstringTuple { + public: + typedef T char_type; + typedef nsCharTraits char_traits; + + typedef nsTSubstringTuple self_type; + typedef mozilla::detail::nsTStringRepr base_string_type; + typedef size_t size_type; + + public: + nsTSubstringTuple(const base_string_type* aStrA, + const base_string_type* aStrB) + : mHead(nullptr), mFragA(aStrA), mFragB(aStrB) {} + + nsTSubstringTuple(const self_type& aHead, const base_string_type* aStrB) + : mHead(&aHead), + mFragA(nullptr), // this fragment is ignored when aHead != nullptr + mFragB(aStrB) {} + + /** + * computes the aggregate string length + */ + size_type Length() const; + + /** + * writes the aggregate string to the given buffer. bufLen is assumed + * to be equal to or greater than the value returned by the Length() + * method. the string written to |buf| is not null-terminated. + */ + void WriteTo(char_type* aBuf, size_type aBufLen) const; + + /** + * returns true if this tuple is dependent on (i.e., overlapping with) + * the given char sequence. + */ + bool IsDependentOn(const char_type* aStart, const char_type* aEnd) const; + + /** + * returns a pair of the results of IsDependentOn() and Length(). This is more + * efficient than calling both functions subsequently, as this traverses the + * tree only once. + */ + std::pair IsDependentOnWithLength( + const char_type* aStart, const char_type* aEnd) const; + + private: + const self_type* const mHead; + const base_string_type* const mFragA; + const base_string_type* const mFragB; +}; + +template +inline const nsTSubstringTuple operator+( + const mozilla::detail::nsTStringRepr& aStrA, + const mozilla::detail::nsTStringRepr& aStrB) { + return nsTSubstringTuple(&aStrA, &aStrB); +} + +template +inline const nsTSubstringTuple operator+( + const nsTSubstringTuple& aHead, + const mozilla::detail::nsTStringRepr& aStrB) { + return nsTSubstringTuple(aHead, &aStrB); +} + +#endif diff --git a/xpcom/string/nsTextFormatter.cpp b/xpcom/string/nsTextFormatter.cpp new file mode 100644 index 0000000000..4db5338e2b --- /dev/null +++ b/xpcom/string/nsTextFormatter.cpp @@ -0,0 +1,895 @@ +/* -*- 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/. */ + +/* + * Portable safe sprintf code. + * + * Code based on mozilla/nsprpub/src/io/prprf.c rev 3.7 + * + * Contributor(s): + * Kipp E.B. Hickman (original author) + * Frank Yung-Fong Tang + * Daniele Nicolodi + */ + +#include +#include +#include +#include "prdtoa.h" +#include "mozilla/Logging.h" +#include "mozilla/Sprintf.h" +#include "nsCRTGlue.h" +#include "nsTextFormatter.h" + +struct nsTextFormatter::SprintfStateStr { + int (*stuff)(SprintfStateStr* aState, const char16_t* aStr, uint32_t aLen); + + char16_t* base; + char16_t* cur; + uint32_t maxlen; + + void* stuffclosure; +}; + +#define _LEFT 0x1 +#define _SIGNED 0x2 +#define _SPACED 0x4 +#define _ZEROS 0x8 +#define _NEG 0x10 +#define _UNSIGNED 0x20 + +#define ELEMENTS_OF(array_) (sizeof(array_) / sizeof(array_[0])) + +/* +** Fill into the buffer using the data in src +*/ +int nsTextFormatter::fill2(SprintfStateStr* aState, const char16_t* aSrc, + int aSrcLen, int aWidth, int aFlags) { + char16_t space = ' '; + int rv; + + aWidth -= aSrcLen; + /* Right adjusting */ + if ((aWidth > 0) && ((aFlags & _LEFT) == 0)) { + if (aFlags & _ZEROS) { + space = '0'; + } + while (--aWidth >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + + /* Copy out the source data */ + rv = (*aState->stuff)(aState, aSrc, aSrcLen); + if (rv < 0) { + return rv; + } + + /* Left adjusting */ + if ((aWidth > 0) && ((aFlags & _LEFT) != 0)) { + while (--aWidth >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + return 0; +} + +/* +** Fill a number. The order is: optional-sign zero-filling conversion-digits +*/ +int nsTextFormatter::fill_n(nsTextFormatter::SprintfStateStr* aState, + const char16_t* aSrc, int aSrcLen, int aWidth, + int aPrec, int aFlags) { + int zerowidth = 0; + int precwidth = 0; + int signwidth = 0; + int leftspaces = 0; + int rightspaces = 0; + int cvtwidth; + int rv; + char16_t sign; + char16_t space = ' '; + char16_t zero = '0'; + + if ((aFlags & _UNSIGNED) == 0) { + if (aFlags & _NEG) { + sign = '-'; + signwidth = 1; + } else if (aFlags & _SIGNED) { + sign = '+'; + signwidth = 1; + } else if (aFlags & _SPACED) { + sign = ' '; + signwidth = 1; + } + } + cvtwidth = signwidth + aSrcLen; + + if (aPrec > 0) { + if (aPrec > aSrcLen) { + /* Need zero filling */ + precwidth = aPrec - aSrcLen; + cvtwidth += precwidth; + } + } + + if ((aFlags & _ZEROS) && (aPrec < 0)) { + if (aWidth > cvtwidth) { + /* Zero filling */ + zerowidth = aWidth - cvtwidth; + cvtwidth += zerowidth; + } + } + + if (aFlags & _LEFT) { + if (aWidth > cvtwidth) { + /* Space filling on the right (i.e. left adjusting) */ + rightspaces = aWidth - cvtwidth; + } + } else { + if (aWidth > cvtwidth) { + /* Space filling on the left (i.e. right adjusting) */ + leftspaces = aWidth - cvtwidth; + } + } + while (--leftspaces >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + if (signwidth) { + rv = (*aState->stuff)(aState, &sign, 1); + if (rv < 0) { + return rv; + } + } + while (--precwidth >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + while (--zerowidth >= 0) { + rv = (*aState->stuff)(aState, &zero, 1); + if (rv < 0) { + return rv; + } + } + rv = (*aState->stuff)(aState, aSrc, aSrcLen); + if (rv < 0) { + return rv; + } + while (--rightspaces >= 0) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + return 0; +} + +/* +** Convert a 64-bit integer into its printable form +*/ +int nsTextFormatter::cvt_ll(SprintfStateStr* aState, uint64_t aNum, int aWidth, + int aPrec, int aRadix, int aFlags, + const char16_t* aHexStr) { + char16_t cvtbuf[100]; + char16_t* cvt; + int digits; + + /* according to the man page this needs to happen */ + if (aPrec == 0 && aNum == 0) { + return 0; + } + + /* + ** Converting decimal is a little tricky. In the unsigned case we + ** need to stop when we hit 10 digits. In the signed case, we can + ** stop when the number is zero. + */ + cvt = &cvtbuf[0] + ELEMENTS_OF(cvtbuf); + digits = 0; + while (aNum != 0) { + uint64_t quot = aNum / aRadix; + uint64_t rem = aNum % aRadix; + *--cvt = aHexStr[rem & 0xf]; + digits++; + aNum = quot; + } + if (digits == 0) { + *--cvt = '0'; + digits++; + } + + /* + ** Now that we have the number converted without its sign, deal with + ** the sign and zero padding. + */ + return fill_n(aState, cvt, digits, aWidth, aPrec, aFlags); +} + +/* +** Convert a double precision floating point number into its printable +** form. +*/ +int nsTextFormatter::cvt_f(SprintfStateStr* aState, double aDouble, int aWidth, + int aPrec, const char16_t aType, int aFlags) { + int mode = 2; + int decpt; + int sign; + char buf[256]; + char* bufp = buf; + int bufsz = 256; + char num[256]; + char* nump; + char* endnum; + int numdigits = 0; + char exp = 'e'; + + if (aPrec == -1) { + aPrec = 6; + } else if (aPrec > 50) { + // limit precision to avoid PR_dtoa bug 108335 + // and to prevent buffers overflows + aPrec = 50; + } + + switch (aType) { + case 'f': + numdigits = aPrec; + mode = 3; + break; + case 'E': + exp = 'E'; + [[fallthrough]]; + case 'e': + numdigits = aPrec + 1; + mode = 2; + break; + case 'G': + exp = 'E'; + [[fallthrough]]; + case 'g': + if (aPrec == 0) { + aPrec = 1; + } + numdigits = aPrec; + mode = 2; + break; + default: + NS_ERROR("invalid aType passed to cvt_f"); + } + + if (PR_dtoa(aDouble, mode, numdigits, &decpt, &sign, &endnum, num, bufsz) == + PR_FAILURE) { + buf[0] = '\0'; + return -1; + } + numdigits = endnum - num; + nump = num; + + if (sign) { + *bufp++ = '-'; + } else if (aFlags & _SIGNED) { + *bufp++ = '+'; + } + + if (decpt == 9999) { + while ((*bufp++ = *nump++)) { + } + } else { + switch (aType) { + case 'E': + case 'e': + + *bufp++ = *nump++; + if (aPrec > 0) { + *bufp++ = '.'; + while (*nump) { + *bufp++ = *nump++; + aPrec--; + } + while (aPrec-- > 0) { + *bufp++ = '0'; + } + } + *bufp++ = exp; + + ::snprintf(bufp, bufsz - (bufp - buf), "%+03d", decpt - 1); + break; + + case 'f': + + if (decpt < 1) { + *bufp++ = '0'; + if (aPrec > 0) { + *bufp++ = '.'; + while (decpt++ && aPrec-- > 0) { + *bufp++ = '0'; + } + while (*nump && aPrec-- > 0) { + *bufp++ = *nump++; + } + while (aPrec-- > 0) { + *bufp++ = '0'; + } + } + } else { + while (*nump && decpt-- > 0) { + *bufp++ = *nump++; + } + while (decpt-- > 0) { + *bufp++ = '0'; + } + if (aPrec > 0) { + *bufp++ = '.'; + while (*nump && aPrec-- > 0) { + *bufp++ = *nump++; + } + while (aPrec-- > 0) { + *bufp++ = '0'; + } + } + } + *bufp = '\0'; + break; + + case 'G': + case 'g': + + if ((decpt < -3) || ((decpt - 1) >= aPrec)) { + *bufp++ = *nump++; + numdigits--; + if (numdigits > 0) { + *bufp++ = '.'; + while (*nump) { + *bufp++ = *nump++; + } + } + *bufp++ = exp; + ::snprintf(bufp, bufsz - (bufp - buf), "%+03d", decpt - 1); + } else { + if (decpt < 1) { + *bufp++ = '0'; + if (aPrec > 0) { + *bufp++ = '.'; + while (decpt++) { + *bufp++ = '0'; + } + while (*nump) { + *bufp++ = *nump++; + } + } + } else { + while (*nump && decpt-- > 0) { + *bufp++ = *nump++; + numdigits--; + } + while (decpt-- > 0) { + *bufp++ = '0'; + } + if (numdigits > 0) { + *bufp++ = '.'; + while (*nump) { + *bufp++ = *nump++; + } + } + } + *bufp = '\0'; + } + } + } + + char16_t rbuf[256]; + char16_t* rbufp = rbuf; + bufp = buf; + // cast to char16_t + while ((*rbufp++ = *bufp++)) { + } + *rbufp = '\0'; + + return fill2(aState, rbuf, NS_strlen(rbuf), aWidth, aFlags); +} + +/* +** Convert a string into its printable form. |aWidth| is the output +** width. |aPrec| is the maximum number of characters of |aStr| to output, +** where -1 means until NUL. +*/ +int nsTextFormatter::cvt_S(SprintfStateStr* aState, const char16_t* aStr, + int aWidth, int aPrec, int aFlags) { + int slen; + + if (aPrec == 0) { + return 0; + } + + /* Limit string length by precision value */ + slen = aStr ? NS_strlen(aStr) : 6; + if (aPrec > 0) { + if (aPrec < slen) { + slen = aPrec; + } + } + + /* and away we go */ + return fill2(aState, aStr ? aStr : u"(null)", slen, aWidth, aFlags); +} + +/* +** Convert a string into its printable form. |aWidth| is the output +** width. |aPrec| is the maximum number of characters of |aStr| to output, +** where -1 means until NUL. +*/ +int nsTextFormatter::cvt_s(nsTextFormatter::SprintfStateStr* aState, + const char* aStr, int aWidth, int aPrec, + int aFlags) { + // Be sure to handle null the same way as %S. + if (aStr == nullptr) { + return cvt_S(aState, nullptr, aWidth, aPrec, aFlags); + } + NS_ConvertUTF8toUTF16 utf16Val(aStr); + return cvt_S(aState, utf16Val.get(), aWidth, aPrec, aFlags); +} + +/* +** The workhorse sprintf code. +*/ +int nsTextFormatter::dosprintf(SprintfStateStr* aState, const char16_t* aFmt, + mozilla::Span aValues) { + static const char16_t space = ' '; + static const char16_t hex[] = u"0123456789abcdef"; + static const char16_t HEX[] = u"0123456789ABCDEF"; + static const BoxedValue emptyString(u""); + + char16_t c; + int flags, width, prec, radix; + + const char16_t* hexp; + + // Next argument for non-numbered arguments. + size_t nextNaturalArg = 0; + // True if we ever saw a numbered argument. + bool sawNumberedArg = false; + + while ((c = *aFmt++) != 0) { + int rv; + + if (c != '%') { + rv = (*aState->stuff)(aState, aFmt - 1, 1); + if (rv < 0) { + return rv; + } + continue; + } + + // Save the location of the "%" in case we decide it isn't a + // format and want to just emit the text from the format string. + const char16_t* percentPointer = aFmt - 1; + + /* + ** Gobble up the % format string. Hopefully we have handled all + ** of the strange cases! + */ + flags = 0; + c = *aFmt++; + if (c == '%') { + /* quoting a % with %% */ + rv = (*aState->stuff)(aState, aFmt - 1, 1); + if (rv < 0) { + return rv; + } + continue; + } + + // Check for a numbered argument. + bool sawWidth = false; + const BoxedValue* thisArg = nullptr; + if (c >= '0' && c <= '9') { + size_t argNumber = 0; + while (c && c >= '0' && c <= '9') { + argNumber = (argNumber * 10) + (c - '0'); + c = *aFmt++; + } + + if (c == '$') { + // Mixing numbered arguments and implicit arguments is + // disallowed. + if (nextNaturalArg > 0) { + return -1; + } + + c = *aFmt++; + + // Numbered arguments start at 1. + --argNumber; + if (argNumber >= aValues.Length()) { + // A correctness issue but not a safety issue. + MOZ_ASSERT(false); + thisArg = &emptyString; + } else { + thisArg = &aValues[argNumber]; + } + sawNumberedArg = true; + } else { + width = argNumber; + sawWidth = true; + } + } + + if (!sawWidth) { + /* + * Examine optional flags. Note that we do not implement the + * '#' flag of sprintf(). The ANSI C spec. of the '#' flag is + * somewhat ambiguous and not ideal, which is perhaps why + * the various sprintf() implementations are inconsistent + * on this feature. + */ + while ((c == '-') || (c == '+') || (c == ' ') || (c == '0')) { + if (c == '-') { + flags |= _LEFT; + } + if (c == '+') { + flags |= _SIGNED; + } + if (c == ' ') { + flags |= _SPACED; + } + if (c == '0') { + flags |= _ZEROS; + } + c = *aFmt++; + } + if (flags & _SIGNED) { + flags &= ~_SPACED; + } + if (flags & _LEFT) { + flags &= ~_ZEROS; + } + + /* width */ + if (c == '*') { + // Not supported with numbered arguments. + if (sawNumberedArg) { + return -1; + } + + if (nextNaturalArg >= aValues.Length() || + !aValues[nextNaturalArg].IntCompatible()) { + // A correctness issue but not a safety issue. + MOZ_ASSERT(false); + width = 0; + } else { + width = aValues[nextNaturalArg++].mValue.mInt; + } + c = *aFmt++; + } else { + width = 0; + while ((c >= '0') && (c <= '9')) { + width = (width * 10) + (c - '0'); + c = *aFmt++; + } + } + } + + /* precision */ + prec = -1; + if (c == '.') { + c = *aFmt++; + if (c == '*') { + // Not supported with numbered arguments. + if (sawNumberedArg) { + return -1; + } + + if (nextNaturalArg >= aValues.Length() || + !aValues[nextNaturalArg].IntCompatible()) { + // A correctness issue but not a safety issue. + MOZ_ASSERT(false); + } else { + prec = aValues[nextNaturalArg++].mValue.mInt; + } + c = *aFmt++; + } else { + prec = 0; + while ((c >= '0') && (c <= '9')) { + prec = (prec * 10) + (c - '0'); + c = *aFmt++; + } + } + } + + // If the argument isn't known yet, find it now. This is done + // after the width and precision code, in case '*' was used. + if (thisArg == nullptr) { + // Mixing numbered arguments and implicit arguments is + // disallowed. + if (sawNumberedArg) { + return -1; + } + + if (nextNaturalArg >= aValues.Length()) { + // A correctness issue but not a safety issue. + MOZ_ASSERT(false); + thisArg = &emptyString; + } else { + thisArg = &aValues[nextNaturalArg++]; + } + } + + /* Size. Defaults to 32 bits. */ + uint64_t mask = UINT32_MAX; + if (c == 'h') { + c = *aFmt++; + mask = UINT16_MAX; + } else if (c == 'L') { + c = *aFmt++; + mask = UINT64_MAX; + } else if (c == 'l') { + c = *aFmt++; + if (c == 'l') { + c = *aFmt++; + mask = UINT64_MAX; + } else { + mask = UINT32_MAX; + } + } + + /* format */ + hexp = hex; + radix = 10; + // Several `MOZ_ASSERT`s below check for argument compatibility + // with the format specifier. These are only debug assertions, + // not release assertions, and exist to catch problems in C++ + // callers of `nsTextFormatter`, as we do not have compile-time + // checking of format strings. In release mode, these assertions + // will be no-ops, and we will fall through to printing the + // argument based on the known type of the argument. + switch (c) { + case 'd': + case 'i': /* decimal/integer */ + MOZ_ASSERT(thisArg->IntCompatible()); + break; + + case 'o': /* octal */ + MOZ_ASSERT(thisArg->IntCompatible()); + radix = 8; + flags |= _UNSIGNED; + break; + + case 'u': /* unsigned decimal */ + MOZ_ASSERT(thisArg->IntCompatible()); + radix = 10; + flags |= _UNSIGNED; + break; + + case 'x': /* unsigned hex */ + MOZ_ASSERT(thisArg->IntCompatible()); + radix = 16; + flags |= _UNSIGNED; + break; + + case 'X': /* unsigned HEX */ + MOZ_ASSERT(thisArg->IntCompatible()); + radix = 16; + hexp = HEX; + flags |= _UNSIGNED; + break; + + case 'e': + case 'E': + case 'f': + case 'g': + case 'G': + MOZ_ASSERT(thisArg->mKind == DOUBLE); + // Type-based printing below. + break; + + case 'S': + case 's': + MOZ_ASSERT(thisArg->mKind == STRING || thisArg->mKind == STRING16); + // Type-based printing below. + break; + + case 'c': { + if (!thisArg->IntCompatible()) { + MOZ_ASSERT(false); + // Type-based printing below. + break; + } + + if ((flags & _LEFT) == 0) { + while (width-- > 1) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + char16_t ch = thisArg->mValue.mInt; + rv = (*aState->stuff)(aState, &ch, 1); + if (rv < 0) { + return rv; + } + if (flags & _LEFT) { + while (width-- > 1) { + rv = (*aState->stuff)(aState, &space, 1); + if (rv < 0) { + return rv; + } + } + } + } + continue; + + case 'p': + if (!thisArg->PointerCompatible()) { + MOZ_ASSERT(false); + break; + } + static_assert(sizeof(uint64_t) >= sizeof(void*), + "pointers are larger than 64 bits"); + rv = cvt_ll(aState, uintptr_t(thisArg->mValue.mPtr), width, prec, 16, + flags | _UNSIGNED, hexp); + if (rv < 0) { + return rv; + } + continue; + + case 'n': + if (thisArg->mKind != INTPOINTER) { + return -1; + } + + if (thisArg->mValue.mIntPtr != nullptr) { + *thisArg->mValue.mIntPtr = aState->cur - aState->base; + } + continue; + + default: + /* Not a % token after all... skip it */ + rv = (*aState->stuff)(aState, percentPointer, aFmt - percentPointer); + if (rv < 0) { + return rv; + } + continue; + } + + // If we get here, we want to handle the argument according to its + // actual type; modified by the flags as appropriate. + switch (thisArg->mKind) { + case INT: + case UINT: { + int64_t val = thisArg->mValue.mInt; + if ((flags & _UNSIGNED) == 0 && val < 0) { + val = -val; + flags |= _NEG; + } + rv = cvt_ll(aState, uint64_t(val) & mask, width, prec, radix, flags, + hexp); + } break; + case INTPOINTER: + case POINTER: + // Always treat these as unsigned hex, no matter the format. + static_assert(sizeof(uint64_t) >= sizeof(void*), + "pointers are larger than 64 bits"); + rv = cvt_ll(aState, uintptr_t(thisArg->mValue.mPtr), width, prec, 16, + flags | _UNSIGNED, hexp); + break; + case DOUBLE: + if (c != 'f' && c != 'E' && c != 'e' && c != 'G' && c != 'g') { + // Pick some default. + c = 'g'; + } + rv = cvt_f(aState, thisArg->mValue.mDouble, width, prec, c, flags); + break; + case STRING: + rv = cvt_s(aState, thisArg->mValue.mString, width, prec, flags); + break; + case STRING16: + rv = cvt_S(aState, thisArg->mValue.mString16, width, prec, flags); + break; + default: + // Can't happen. + MOZ_ASSERT(0); + } + + if (rv < 0) { + return rv; + } + } + + return 0; +} + +/************************************************************************/ + +int nsTextFormatter::StringStuff(nsTextFormatter::SprintfStateStr* aState, + const char16_t* aStr, uint32_t aLen) { + ptrdiff_t off = aState->cur - aState->base; + + nsAString* str = static_cast(aState->stuffclosure); + str->Append(aStr, aLen); + + aState->base = str->BeginWriting(); + aState->cur = aState->base + off; + + return 0; +} + +void nsTextFormatter::vssprintf(nsAString& aOut, const char16_t* aFmt, + mozilla::Span aValues) { + SprintfStateStr ss; + ss.stuff = StringStuff; + ss.base = 0; + ss.cur = 0; + ss.maxlen = 0; + ss.stuffclosure = &aOut; + + aOut.Truncate(); + dosprintf(&ss, aFmt, aValues); +} + +/* +** Stuff routine that discards overflow data +*/ +int nsTextFormatter::LimitStuff(SprintfStateStr* aState, const char16_t* aStr, + uint32_t aLen) { + uint32_t limit = aState->maxlen - (aState->cur - aState->base); + + if (aLen > limit) { + aLen = limit; + } + while (aLen) { + --aLen; + *aState->cur++ = *aStr++; + } + return 0; +} + +uint32_t nsTextFormatter::vsnprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, + mozilla::Span aValues) { + SprintfStateStr ss; + + MOZ_ASSERT((int32_t)aOutLen > 0); + if ((int32_t)aOutLen <= 0) { + return 0; + } + + ss.stuff = LimitStuff; + ss.base = aOut; + ss.cur = aOut; + ss.maxlen = aOutLen; + int result = dosprintf(&ss, aFmt, aValues); + + if (ss.cur == ss.base) { + return 0; + } + + // Append a NUL. However, be sure not to count it in the returned + // length. + if (ss.cur - ss.base >= ptrdiff_t(ss.maxlen)) { + --ss.cur; + } + *ss.cur = '\0'; + + // Check the result now, so that an unterminated string can't + // possibly escape. + if (result < 0) { + return -1; + } + + return ss.cur - ss.base; +} diff --git a/xpcom/string/nsTextFormatter.h b/xpcom/string/nsTextFormatter.h new file mode 100644 index 0000000000..f571043da2 --- /dev/null +++ b/xpcom/string/nsTextFormatter.h @@ -0,0 +1,172 @@ +/* -*- 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/. */ + +/* + * This code was copied from xpcom/ds/nsTextFormatter r1.3 + * Memory model and Frozen linkage changes only. + * -- Prasad + */ + +#ifndef nsTextFormatter_h___ +#define nsTextFormatter_h___ + +/* + ** API for PR printf like routines. Supports the following formats + ** %d - decimal + ** %u - unsigned decimal + ** %x - unsigned hex + ** %X - unsigned uppercase hex + ** %o - unsigned octal + ** %hd, %hu, %hx, %hX, %ho - 16-bit versions of above + ** %ld, %lu, %lx, %lX, %lo - 32-bit versions of above + ** %lld, %llu, %llx, %llX, %llo - 64 bit versions of above + ** %s - utf8 string + ** %S - char16_t string + ** %c - character + ** %p - pointer (deals with machine dependent pointer size) + ** %f - float + ** %g - float + */ +#include +#include +#include "nscore.h" +#include "nsString.h" +#include "mozilla/Span.h" + +#ifdef XPCOM_GLUE +# error \ + "nsTextFormatter is not available in the standalone glue due to NSPR dependencies." +#endif + +class nsTextFormatter { + public: + /* + * sprintf into a fixed size buffer. Guarantees that the buffer is null + * terminated. Returns the length of the written output, NOT including the + * null terminator, or (uint32_t)-1 if an error occurs. + */ + template + static uint32_t snprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, T... aArgs) { + BoxedValue values[] = {BoxedValue(aArgs)...}; + return vsnprintf(aOut, aOutLen, aFmt, + mozilla::Span(values, sizeof...(aArgs))); + } + + /* + * sprintf into an existing nsAString, overwriting any contents it already + * has. Infallible. + */ + template + static void ssprintf(nsAString& aOut, const char16_t* aFmt, T... aArgs) { + BoxedValue values[] = {BoxedValue(aArgs)...}; + vssprintf(aOut, aFmt, mozilla::Span(values, sizeof...(aArgs))); + } + + private: + enum ArgumentKind { + INT, + UINT, + POINTER, + DOUBLE, + STRING, + STRING16, + INTPOINTER, + }; + + union ValueUnion { + int64_t mInt; + uint64_t mUInt; + void const* mPtr; + double mDouble; + char const* mString; + char16_t const* mString16; + int* mIntPtr; + }; + + struct BoxedValue { + ArgumentKind mKind; + ValueUnion mValue; + + explicit BoxedValue(int aArg) : mKind(INT) { mValue.mInt = aArg; } + + explicit BoxedValue(unsigned int aArg) : mKind(UINT) { + mValue.mUInt = aArg; + } + + explicit BoxedValue(long aArg) : mKind(INT) { mValue.mInt = aArg; } + + explicit BoxedValue(unsigned long aArg) : mKind(UINT) { + mValue.mUInt = aArg; + } + + explicit BoxedValue(long long aArg) : mKind(INT) { mValue.mInt = aArg; } + + explicit BoxedValue(unsigned long long aArg) : mKind(UINT) { + mValue.mUInt = aArg; + } + + explicit BoxedValue(const void* aArg) : mKind(POINTER) { + mValue.mPtr = aArg; + } + + explicit BoxedValue(double aArg) : mKind(DOUBLE) { mValue.mDouble = aArg; } + + explicit BoxedValue(const char* aArg) : mKind(STRING) { + mValue.mString = aArg; + } + + explicit BoxedValue(const char16_t* aArg) : mKind(STRING16) { + mValue.mString16 = aArg; + } + +#if defined(MOZ_USE_CHAR16_WRAPPER) + explicit BoxedValue(const char16ptr_t aArg) : mKind(STRING16) { + mValue.mString16 = aArg; + } + +#endif + + explicit BoxedValue(int* aArg) : mKind(INTPOINTER) { + mValue.mIntPtr = aArg; + } + + bool IntCompatible() const { return mKind == INT || mKind == UINT; } + + bool PointerCompatible() const { + return mKind == POINTER || mKind == STRING || mKind == STRING16 || + mKind == INTPOINTER; + } + }; + + struct SprintfStateStr; + + static int fill2(SprintfStateStr* aState, const char16_t* aSrc, int aSrcLen, + int aWidth, int aFlags); + static int fill_n(SprintfStateStr* aState, const char16_t* aSrc, int aSrcLen, + int aWidth, int aPrec, int aFlags); + static int cvt_ll(SprintfStateStr* aState, uint64_t aNum, int aWidth, + int aPrec, int aRadix, int aFlags, const char16_t* aHexStr); + static int cvt_f(SprintfStateStr* aState, double aDouble, int aWidth, + int aPrec, const char16_t aType, int aFlags); + static int cvt_S(SprintfStateStr* aState, const char16_t* aStr, int aWidth, + int aPrec, int aFlags); + static int cvt_s(SprintfStateStr* aState, const char* aStr, int aWidth, + int aPrec, int aFlags); + static int dosprintf(SprintfStateStr* aState, const char16_t* aFmt, + mozilla::Span aValues); + static int StringStuff(SprintfStateStr* aState, const char16_t* aStr, + uint32_t aLen); + static int LimitStuff(SprintfStateStr* aState, const char16_t* aStr, + uint32_t aLen); + static uint32_t vsnprintf(char16_t* aOut, uint32_t aOutLen, + const char16_t* aFmt, + mozilla::Span aValues); + static void vssprintf(nsAString& aOut, const char16_t* aFmt, + mozilla::Span aValues); +}; + +#endif /* nsTextFormatter_h___ */ diff --git a/xpcom/string/nsUTF8Utils.h b/xpcom/string/nsUTF8Utils.h new file mode 100644 index 0000000000..0145011ec1 --- /dev/null +++ b/xpcom/string/nsUTF8Utils.h @@ -0,0 +1,247 @@ +/* -*- 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/. */ +#ifndef nsUTF8Utils_h_ +#define nsUTF8Utils_h_ + +// NB: This code may be used from non-XPCOM code, in particular, the +// standalone updater executable. That is, this file may be used in +// two ways: if MOZILLA_INTERNAL_API is defined, this file will +// provide signatures for the Mozilla abstract string types. It will +// use XPCOM assertion/debugging macros, etc. + +#include + +#include "mozilla/Assertions.h" +#include "mozilla/EndianUtils.h" + +#include "nsCharTraits.h" + +#ifdef MOZILLA_INTERNAL_API +# define UTF8UTILS_WARNING(msg) NS_WARNING(msg) +#else +# define UTF8UTILS_WARNING(msg) +#endif + +class UTF8traits { + public: + static bool isASCII(char aChar) { return (aChar & 0x80) == 0x00; } + static bool isInSeq(char aChar) { return (aChar & 0xC0) == 0x80; } + static bool is2byte(char aChar) { return (aChar & 0xE0) == 0xC0; } + static bool is3byte(char aChar) { return (aChar & 0xF0) == 0xE0; } + static bool is4byte(char aChar) { return (aChar & 0xF8) == 0xF0; } + static bool is5byte(char aChar) { return (aChar & 0xFC) == 0xF8; } + static bool is6byte(char aChar) { return (aChar & 0xFE) == 0xFC; } + // return the number of bytes in a sequence beginning with aChar + static int bytes(char aChar) { + if (isASCII(aChar)) { + return 1; + } + if (is2byte(aChar)) { + return 2; + } + if (is3byte(aChar)) { + return 3; + } + if (is4byte(aChar)) { + return 4; + } + MOZ_ASSERT_UNREACHABLE("should not be used for in-sequence characters"); + return 1; + } +}; + +/** + * Extract the next Unicode scalar value from the buffer and return it. The + * pointer passed in is advanced to the start of the next character in the + * buffer. Upon error, the return value is 0xFFFD, *aBuffer is advanced + * over the maximal valid prefix and *aErr is set to true (if aErr is not + * null). + * + * Note: This method never sets *aErr to false to allow error accumulation + * across multiple calls. + * + * Precondition: *aBuffer < aEnd + */ +class UTF8CharEnumerator { + public: + static inline char32_t NextChar(const char** aBuffer, const char* aEnd, + bool* aErr = nullptr) { + MOZ_ASSERT(aBuffer, "null buffer pointer pointer"); + MOZ_ASSERT(aEnd, "null end pointer"); + + const unsigned char* p = reinterpret_cast(*aBuffer); + const unsigned char* end = reinterpret_cast(aEnd); + + MOZ_ASSERT(p, "null buffer"); + MOZ_ASSERT(p < end, "Bogus range"); + + unsigned char first = *p; + ++p; + + if (MOZ_LIKELY(first < 0x80U)) { + *aBuffer = reinterpret_cast(p); + return first; + } + + // Unsigned underflow is defined behavior + if (MOZ_UNLIKELY((p == end) || ((first - 0xC2U) >= (0xF5U - 0xC2U)))) { + *aBuffer = reinterpret_cast(p); + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } + + unsigned char second = *p; + + if (first < 0xE0U) { + // Two-byte + if (MOZ_LIKELY((second & 0xC0U) == 0x80U)) { + ++p; + *aBuffer = reinterpret_cast(p); + return ((uint32_t(first) & 0x1FU) << 6) | (uint32_t(second) & 0x3FU); + } + *aBuffer = reinterpret_cast(p); + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } + + if (MOZ_LIKELY(first < 0xF0U)) { + // Three-byte + unsigned char lower = 0x80U; + unsigned char upper = 0xBFU; + if (first == 0xE0U) { + lower = 0xA0U; + } else if (first == 0xEDU) { + upper = 0x9FU; + } + if (MOZ_LIKELY(second >= lower && second <= upper)) { + ++p; + if (MOZ_LIKELY(p != end)) { + unsigned char third = *p; + if (MOZ_LIKELY((third & 0xC0U) == 0x80U)) { + ++p; + *aBuffer = reinterpret_cast(p); + return ((uint32_t(first) & 0xFU) << 12) | + ((uint32_t(second) & 0x3FU) << 6) | + (uint32_t(third) & 0x3FU); + } + } + } + *aBuffer = reinterpret_cast(p); + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } + + // Four-byte + unsigned char lower = 0x80U; + unsigned char upper = 0xBFU; + if (first == 0xF0U) { + lower = 0x90U; + } else if (first == 0xF4U) { + upper = 0x8FU; + } + if (MOZ_LIKELY(second >= lower && second <= upper)) { + ++p; + if (MOZ_LIKELY(p != end)) { + unsigned char third = *p; + if (MOZ_LIKELY((third & 0xC0U) == 0x80U)) { + ++p; + if (MOZ_LIKELY(p != end)) { + unsigned char fourth = *p; + if (MOZ_LIKELY((fourth & 0xC0U) == 0x80U)) { + ++p; + *aBuffer = reinterpret_cast(p); + return ((uint32_t(first) & 0x7U) << 18) | + ((uint32_t(second) & 0x3FU) << 12) | + ((uint32_t(third) & 0x3FU) << 6) | + (uint32_t(fourth) & 0x3FU); + } + } + } + } + } + *aBuffer = reinterpret_cast(p); + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } +}; + +/** + * Extract the next Unicode scalar value from the buffer and return it. The + * pointer passed in is advanced to the start of the next character in the + * buffer. Upon error, the return value is 0xFFFD, *aBuffer is advanced over + * the unpaired surrogate and *aErr is set to true (if aErr is not null). + * + * Note: This method never sets *aErr to false to allow error accumulation + * across multiple calls. + * + * Precondition: *aBuffer < aEnd + */ +class UTF16CharEnumerator { + public: + static inline char32_t NextChar(const char16_t** aBuffer, + const char16_t* aEnd, bool* aErr = nullptr) { + MOZ_ASSERT(aBuffer, "null buffer pointer pointer"); + MOZ_ASSERT(aEnd, "null end pointer"); + + const char16_t* p = *aBuffer; + + MOZ_ASSERT(p, "null buffer"); + MOZ_ASSERT(p < aEnd, "Bogus range"); + + char16_t c = *p++; + + // Let's use encoding_rs-style code golf here. + // Unsigned underflow is defined behavior + char16_t cMinusSurrogateStart = c - 0xD800U; + if (MOZ_LIKELY(cMinusSurrogateStart > (0xDFFFU - 0xD800U))) { + *aBuffer = p; + return c; + } + if (MOZ_LIKELY(cMinusSurrogateStart <= (0xDBFFU - 0xD800U))) { + // High surrogate + if (MOZ_LIKELY(p != aEnd)) { + char16_t second = *p; + // Unsigned underflow is defined behavior + if (MOZ_LIKELY((second - 0xDC00U) <= (0xDFFFU - 0xDC00U))) { + *aBuffer = ++p; + return (uint32_t(c) << 10) + uint32_t(second) - + (((0xD800U << 10) - 0x10000U) + 0xDC00U); + } + } + } + // Unpaired surrogate + *aBuffer = p; + if (aErr) { + *aErr = true; + } + return 0xFFFDU; + } +}; + +template +inline UnsignedT RewindToPriorUTF8Codepoint(const Char* utf8Chars, + UnsignedT index) { + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v, + "UTF-8 data must be in 8-bit units"); + static_assert(std::is_unsigned_v, "index type must be unsigned"); + while (index > 0 && (utf8Chars[index] & 0xC0) == 0x80) --index; + + return index; +} + +#undef UTF8UTILS_WARNING + +#endif /* !defined(nsUTF8Utils_h_) */ diff --git a/xpcom/system/moz.build b/xpcom/system/moz.build new file mode 100644 index 0000000000..ced4fa16d2 --- /dev/null +++ b/xpcom/system/moz.build @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + "nsIBlocklistService.idl", + "nsICrashReporter.idl", + "nsIDeviceSensors.idl", + "nsIGeolocationProvider.idl", + "nsIGIOService.idl", + "nsIGSettingsService.idl", + "nsIHapticFeedback.idl", + "nsIPlatformInfo.idl", + "nsISystemInfo.idl", + "nsIXULAppInfo.idl", + "nsIXULRuntime.idl", +] + +XPIDL_MODULE = "xpcom_system" + +with Files("nsIBlocklistService.idl"): + BUG_COMPONENT = ("Toolkit", "Blocklist Implementation") diff --git a/xpcom/system/nsIBlocklistService.idl b/xpcom/system/nsIBlocklistService.idl new file mode 100644 index 0000000000..37fb864111 --- /dev/null +++ b/xpcom/system/nsIBlocklistService.idl @@ -0,0 +1,24 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +[scriptable, uuid(a6dcc76e-9f62-4cc1-a470-b483a1a6f096)] +interface nsIBlocklistService : nsISupports +{ + // Indicates that the item does not appear in the blocklist. + const unsigned long STATE_NOT_BLOCKED = 0; + // Indicates that the item is in the blocklist but the problem is not severe + // enough to warant forcibly blocking. + const unsigned long STATE_SOFTBLOCKED = 1; + // Indicates that the item should be blocked and never used. + const unsigned long STATE_BLOCKED = 2; + + // Unused; Please increment if we add more blocklist states. + const unsigned long STATE_MAX = 3; + + readonly attribute boolean isLoaded; +}; diff --git a/xpcom/system/nsICrashReporter.idl b/xpcom/system/nsICrashReporter.idl new file mode 100644 index 0000000000..0bcb6dcb49 --- /dev/null +++ b/xpcom/system/nsICrashReporter.idl @@ -0,0 +1,175 @@ +/* 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 "nsISupports.idl" + +interface nsIFile; +interface nsIURL; + +/** + * Provides access to crash reporting functionality. + * + * @status UNSTABLE - This interface is not frozen and will probably change in + * future releases. + */ + +[scriptable, uuid(4b74c39a-cf69-4a8a-8e63-169d81ad1ecf)] +interface nsICrashReporter : nsISupports +{ + /** + * Get the enabled status of the crash reporter. + */ + readonly attribute boolean crashReporterEnabled; + + /** + * Enable or disable crash reporting at runtime. Not available to script + * because the JS engine relies on proper exception handler chaining. + */ + [noscript] + void setEnabled(in bool enabled); + + /** + * Get or set the URL to which crash reports will be submitted. + * Only https and http URLs are allowed, as the submission is handled + * by OS-native networking libraries. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting is not initialized + * @throw NS_ERROR_INVALID_ARG on set if a non-http(s) URL is assigned + * @throw NS_ERROR_FAILURE on get if no URL is set + */ + attribute nsIURL serverURL; + + /** + * Get or set the path on the local system to which minidumps will be + * written when a crash happens. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting is not initialized + */ + attribute nsIFile minidumpPath; + + /** + * Get the minidump file corresponding to the specified ID. + * + * @param id + * ID of the crash. Likely a UUID. + * + * @return The minidump file associated with the ID. + * + * @throw NS_ERROR_FILE_NOT_FOUND if the minidump could not be found + */ + nsIFile getMinidumpForID(in AString id); + + /** + * Get the extra file corresponding to the specified ID. + * + * @param id + * ID of the crash. Likely a UUID. + * + * @return The extra file associated with the ID. + * + * @throw NS_ERROR_FILE_NOT_FOUND if the extra file could not be found + */ + nsIFile getExtraFileForID(in AString id); + + /** + * Add some extra data to be submitted with a crash report. + * + * @param key + * Name of a known crash annotation constant. + * @param data + * Data to be added. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized + * @throw NS_ERROR_INVALID_ARG if key contains an invalid value or data + * contains invalid characters. Invalid + * character for data is '\0'. + */ + void annotateCrashReport(in AUTF8String key, in AUTF8String data); + + /** + * Remove a crash report annotation. + * + * @param key + * Name of a known crash annotation constant. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized + * @throw NS_ERROR_INVALID_ARG if key contains an invalid value. + */ + void removeCrashReportAnnotation(in AUTF8String key); + + /** + * Checks if an annotation is allowlisted for inclusion in the crash ping. + * + * @param key + * Name of a known crash annotation constant. + * + * @return True if the specified value is a valid annotation and can be + included in the crash ping, false otherwise. + * @throw NS_ERROR_INVALID_ARG if key contains an invalid value. + */ + boolean isAnnotationAllowlistedForPing(in ACString value); + + /** + * Append some data to the "Notes" field, to be submitted with a crash report. + * Unlike annotateCrashReport, this method will append to existing data. + * + * @param data + * Data to be added. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized + * @throw NS_ERROR_INVALID_ARG if data contains invalid characters. + * The only invalid character is '\0'. + */ + void appendAppNotesToCrashReport(in ACString data); + + /** + * Register a given memory range to be included in the crash report. + * + * @param ptr + * The starting address for the bytes. + * @param size + * The number of bytes to include. + * + * @throw NS_ERROR_NOT_INITIALIZED if crash reporting not initialized + * @throw NS_ERROR_NOT_IMPLEMENTED if unavailable on the current OS + */ + void registerAppMemory(in unsigned long long ptr, in unsigned long long size); + + /** + * Write a minidump immediately, with the user-supplied exception + * information. This is implemented on Windows only, because + * SEH (structured exception handling) exists on Windows only. + * + * @param aExceptionInfo EXCEPTION_INFO* provided by Window's SEH + */ + [noscript] void writeMinidumpForException(in voidPtr aExceptionInfo); + + /** + * Append note containing an Obj-C exception's info. + * + * @param aException NSException object to append note for + */ + [noscript] void appendObjCExceptionInfoToAppNotes(in voidPtr aException); + + /** + * User preference for submitting crash reports. + */ + attribute boolean submitReports; + + /** + * Cause the crash reporter to re-evaluate where crash events should go. + * + * This should be called during application startup and whenever profiles + * change. + */ + void UpdateCrashEventsDir(); + + /** + * Save an anonymized memory report file for inclusion in a future crash + * report in this session. + * + * @throws NS_ERROR_NOT_INITIALIZED if crash reporting is disabled. + */ + void saveMemoryReport(); +}; diff --git a/xpcom/system/nsIDeviceSensors.idl b/xpcom/system/nsIDeviceSensors.idl new file mode 100644 index 0000000000..dcb67cb925 --- /dev/null +++ b/xpcom/system/nsIDeviceSensors.idl @@ -0,0 +1,60 @@ +/* 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 "nsISupports.idl" + +interface nsIDOMWindow; + +[scriptable, uuid(0462247e-fe8c-4aa5-b675-3752547e485f)] +interface nsIDeviceSensorData : nsISupports +{ + // Keep in sync with hal/HalSensor.h + + // 1. TYPE_ORIENTATION: absolute device orientation, not spec-conform and + // deprecated on Android. + // 2. TYPE_ROTATION_VECTOR: absolute device orientation affected by drift. + // 3. TYPE_GAME_ROTATION_VECTOR: relative device orientation less affected by drift. + // Preferred fallback priorities on Android are [3, 2, 1] for the general case + // and [2, 1] if absolute orientation (compass heading) is required. + const unsigned long TYPE_ORIENTATION = 0; + const unsigned long TYPE_ACCELERATION = 1; + const unsigned long TYPE_PROXIMITY = 2; + const unsigned long TYPE_LINEAR_ACCELERATION = 3; + const unsigned long TYPE_GYROSCOPE = 4; + const unsigned long TYPE_LIGHT = 5; + const unsigned long TYPE_ROTATION_VECTOR = 6; + const unsigned long TYPE_GAME_ROTATION_VECTOR = 7; + + readonly attribute unsigned long type; + + readonly attribute double x; + readonly attribute double y; + readonly attribute double z; +}; + +[scriptable, uuid(e46e47c7-55ff-44c4-abce-21b14ba07f86)] +interface nsIDeviceSensors : nsISupports +{ + /** + * Returns true if the given window has any listeners of the given type + */ + bool hasWindowListener(in unsigned long aType, in nsIDOMWindow aWindow); + + // Holds pointers, not AddRef objects -- it is up to the caller + // to call RemoveWindowListener before the window is deleted. + + [noscript] void addWindowListener(in unsigned long aType, in nsIDOMWindow aWindow); + [noscript] void removeWindowListener(in unsigned long aType, in nsIDOMWindow aWindow); + [noscript] void removeWindowAsListener(in nsIDOMWindow aWindow); +}; + +%{C++ + +#define NS_DEVICE_SENSORS_CID \ +{ 0xecba5203, 0x77da, 0x465a, \ +{ 0x86, 0x5e, 0x78, 0xb7, 0xaf, 0x10, 0xd8, 0xf7 } } + +#define NS_DEVICE_SENSORS_CONTRACTID "@mozilla.org/devicesensors;1" + +%} diff --git a/xpcom/system/nsIGIOService.idl b/xpcom/system/nsIGIOService.idl new file mode 100644 index 0000000000..36f19d28e3 --- /dev/null +++ b/xpcom/system/nsIGIOService.idl @@ -0,0 +1,88 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" +#include "nsIMIMEInfo.idl" + +interface nsIUTF8StringEnumerator; +interface nsIURI; +interface nsIFile; +interface nsIMutableArray; + +/* nsIGIOMimeApp holds information about an application that is looked up + with nsIGIOService::GetAppForMimeType. */ +// 66009894-9877-405b-9321-bf30420e34e6 prev uuid + +[scriptable, uuid(ca6bad0c-8a48-48ac-82c7-27bb8f510fbe)] +interface nsIGIOMimeApp : nsIHandlerApp +{ + const long EXPECTS_URIS = 0; + const long EXPECTS_PATHS = 1; + const long EXPECTS_URIS_FOR_NON_FILES = 2; + + readonly attribute AUTF8String id; + readonly attribute AUTF8String command; + readonly attribute long expectsURIs; // see constants above + readonly attribute nsIUTF8StringEnumerator supportedURISchemes; + + void setAsDefaultForMimeType(in AUTF8String mimeType); + void setAsDefaultForFileExtensions(in AUTF8String extensions); + void setAsDefaultForURIScheme(in AUTF8String uriScheme); +}; + +/* + * The VFS service makes use of two distinct registries. + * + * The application registry holds information about applications (uniquely + * identified by id), such as which MIME types and URI schemes they are + * capable of handling, whether they run in a terminal, etc. + * + * The MIME registry holds information about MIME types, such as which + * extensions map to a given MIME type. The MIME registry also stores the + * id of the application selected to handle each MIME type. + */ + +// prev id dea20bf0-4e4d-48c5-b932-dc3e116dc64b +[scriptable, builtinclass, uuid(eda22a30-84e1-4e16-9ca0-cd1553c2b34a)] +interface nsIGIOService : nsISupports +{ + + /*** MIME registry methods ***/ + + /* Obtain the MIME type registered for an extension. The extension + should not include a leading dot. */ + AUTF8String getMimeTypeFromExtension(in AUTF8String extension); + + /* Obtain the preferred application for opening a given URI scheme */ + nsIHandlerApp getAppForURIScheme(in AUTF8String aURIScheme); + + /* Obtain list of application capable of opening given URI scheme */ + nsIMutableArray getAppsForURIScheme(in AUTF8String aURIScheme); + + /* Obtain the preferred application for opening a given MIME type */ + nsIHandlerApp getAppForMimeType(in AUTF8String mimeType); + + /* Create application info for given command and name */ + nsIGIOMimeApp createAppFromCommand(in AUTF8String cmd, + in AUTF8String appName); + + /* Find the application info by given command */ + nsIGIOMimeApp findAppFromCommand(in AUTF8String cmd); + + /* Obtain a description for the given MIME type */ + AUTF8String getDescriptionForMimeType(in AUTF8String mimeType); + + /*** Misc. methods ***/ + [infallible] readonly attribute boolean isRunningUnderFlatpak; + + /* Open the given URI in the default application */ + [noscript] void showURI(in nsIURI uri); + [noscript] void revealFile(in nsIFile file); + [noscript] void launchFile(in ACString path); +}; + +%{C++ +#define NS_GIOSERVICE_CONTRACTID "@mozilla.org/gio-service;1" +%} diff --git a/xpcom/system/nsIGSettingsService.idl b/xpcom/system/nsIGSettingsService.idl new file mode 100644 index 0000000000..26d86a77e0 --- /dev/null +++ b/xpcom/system/nsIGSettingsService.idl @@ -0,0 +1,30 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +interface nsIArray; + +[scriptable, uuid(16d5b0ed-e756-4f1b-a8ce-9132e869acd8)] +interface nsIGSettingsCollection : nsISupports +{ + void setString(in AUTF8String key, in AUTF8String value); + void setBoolean(in AUTF8String key, in boolean value); + void setInt(in AUTF8String key, in long value); + AUTF8String getString(in AUTF8String key); + boolean getBoolean(in AUTF8String key); + long getInt(in AUTF8String key); + nsIArray getStringList(in AUTF8String key); +}; + +[scriptable, uuid(849c088b-57d1-4f24-b7b2-3dc4acb04c0a)] +interface nsIGSettingsService : nsISupports +{ + nsIGSettingsCollection getCollectionForSchema(in AUTF8String schema); +}; + +%{C++ +#define NS_GSETTINGSSERVICE_CONTRACTID "@mozilla.org/gsettings-service;1" +%} diff --git a/xpcom/system/nsIGeolocationProvider.idl b/xpcom/system/nsIGeolocationProvider.idl new file mode 100644 index 0000000000..ff2f5a5004 --- /dev/null +++ b/xpcom/system/nsIGeolocationProvider.idl @@ -0,0 +1,82 @@ +/* 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 "nsISupports.idl" + +interface nsIURI; +interface nsIDOMGeoPosition; +interface nsIGeolocationPrompt; + +/** + + * Interface provides a way for a geolocation provider to + * notify the system that a new location is available. + */ +[scriptable, uuid(643dc5e9-b911-4b2c-8d44-603162696baf)] +interface nsIGeolocationUpdate : nsISupports { + + /** + * Notify the geolocation service that a new geolocation + * has been discovered. + * This must be called on the main thread + */ + void update(in nsIDOMGeoPosition position); + /** + * Notify the geolocation service of an error. + * This must be called on the main thread. + * The parameter refers to one of the constants in the + * nsIDOMGeoPositionError interface. + * Use this to report spurious errors coming from the + * provider; for errors occurring inside the methods in + * the nsIGeolocationProvider interface, just use the return + * value. + */ + [can_run_script] + void notifyError(in unsigned short error); +}; + + +/** + * Interface provides location information to the nsGeolocator + * via the nsIDOMGeolocationCallback interface. After + * startup is called, any geo location change should call + * callback.update(). + */ +[scriptable, uuid(AC4A133B-9F92-4F7C-B369-D40CB6B17650)] +interface nsIGeolocationProvider : nsISupports { + + /** + * Start up the provider. This is called before any other + * method. may be called multiple times. + */ + void startup(); + + /** + * watch + * When a location change is observed, notify the callback. + */ + void watch(in nsIGeolocationUpdate callback); + + /** + * shutdown + * Shuts down the location device. + */ + void shutdown(); + + /** + * hint to provide to use any amount of power to provide a better result + */ + void setHighAccuracy(in boolean enable); + +}; + +%{C++ +/* + This must be implemented by geolocation providers. It + must support nsIGeolocationProvider. +*/ +#define NS_GEOLOCATION_PROVIDER_CONTRACTID "@mozilla.org/geolocation/provider;1" +%} diff --git a/xpcom/system/nsIHapticFeedback.idl b/xpcom/system/nsIHapticFeedback.idl new file mode 100644 index 0000000000..25d0d8e7bb --- /dev/null +++ b/xpcom/system/nsIHapticFeedback.idl @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + + +[scriptable, uuid(91917c98-a8f3-4c98-8f10-4afb872f54c7)] +interface nsIHapticFeedback : nsISupports { + + const long ShortPress = 0; + const long LongPress = 1; + + /** + * Perform haptic feedback + * + * @param isLongPress + * indicate whether feedback is for a long press (vs. short press) + */ + void performSimpleAction(in long isLongPress); +}; diff --git a/xpcom/system/nsIPlatformInfo.idl b/xpcom/system/nsIPlatformInfo.idl new file mode 100644 index 0000000000..fba78b6ecc --- /dev/null +++ b/xpcom/system/nsIPlatformInfo.idl @@ -0,0 +1,19 @@ +/* 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 "nsISupports.idl" + +[scriptable, uuid(ab6650cf-0806-4aea-b8f2-40fdae74f1cc)] +interface nsIPlatformInfo : nsISupports +{ + /** + * The version of the XULRunner platform. + */ + readonly attribute ACString platformVersion; + + /** + * The build ID/date of gecko and the XULRunner platform. + */ + readonly attribute ACString platformBuildID; +}; diff --git a/xpcom/system/nsISystemInfo.idl b/xpcom/system/nsISystemInfo.idl new file mode 100644 index 0000000000..0cadb7e498 --- /dev/null +++ b/xpcom/system/nsISystemInfo.idl @@ -0,0 +1,40 @@ +/* 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 "nsISupports.idl" + + +[scriptable, uuid(09a0502b-cedc-4cae-bf7c-35662dbd1249)] +interface nsISystemInfo : nsISupports +{ + /** + * Asynchronously get info about what types of disks we're using for the + * profile and binary. + * Note: only implemented on Windows, will return null elsewhere. + */ + [implicit_jscontext] + readonly attribute Promise diskInfo; + + /** + * Asynchronously get CountryCode info. + * Note: only implemented on macOS and Windows, will return null elsewhere. + */ + [implicit_jscontext] + readonly attribute Promise countryCode; + + /** + * Asynchronously gets OS info on the system's install year. + * Note: only implemented on Windows, will return null elsewhere. + */ + [implicit_jscontext] + readonly attribute Promise osInfo; + + /** + * Asynchronously gets process info that indicates if the process is running + * under Wow64 and WowARM64. + * Note: only implemented on Windows, will return null elsewhere. + */ + [implicit_jscontext] + readonly attribute Promise processInfo; +}; diff --git a/xpcom/system/nsIXULAppInfo.idl b/xpcom/system/nsIXULAppInfo.idl new file mode 100644 index 0000000000..502052eb79 --- /dev/null +++ b/xpcom/system/nsIXULAppInfo.idl @@ -0,0 +1,64 @@ +/* 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 "nsIPlatformInfo.idl" + +/** + * A scriptable interface to the nsXULAppAPI structure. See nsXULAppAPI.h for + * a detailed description of each attribute. + */ + +[scriptable, uuid(ddea4f31-3c5e-4769-ac68-21ab4b3d7845)] +interface nsIXULAppInfo : nsIPlatformInfo +{ + /** + * @see XREAppData.vendor + * @returns an empty string if XREAppData.vendor is not set. + */ + readonly attribute ACString vendor; + + /** + * @see XREAppData.name + */ + readonly attribute ACString name; + + /** + * @see XREAppData.ID + * @returns an empty string if XREAppData.ID is not set. + */ + readonly attribute ACString ID; + + /** + * The version of the XUL application. It is different than the + * version of the XULRunner platform. Be careful about which one you want. + * + * @see XREAppData.version + * @returns an empty string if XREAppData.version is not set. + */ + readonly attribute ACString version; + + /** + * The build ID/date of the application. For xulrunner applications, + * this will be different than the build ID of the platform. Be careful + * about which one you want. + */ + readonly attribute ACString appBuildID; + + /** + * @see XREAppData.UAName + * @returns an empty string if XREAppData.UAName is not set. + */ + readonly attribute ACString UAName; + + /** + * @see XREAppData.sourceURL + * @returns an empty string if XREAppData.sourceURL is not set. + */ + readonly attribute ACString sourceURL; + + /** + * @see XREAppData.updateURL + */ + readonly attribute ACString updateURL; +}; diff --git a/xpcom/system/nsIXULRuntime.idl b/xpcom/system/nsIXULRuntime.idl new file mode 100644 index 0000000000..1bd0ca8cb8 --- /dev/null +++ b/xpcom/system/nsIXULRuntime.idl @@ -0,0 +1,396 @@ +/* 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 "nsISupports.idl" + +%{C++ + +namespace mozilla { +// Simple C++ getter for nsIXULRuntime::browserTabsRemoteAutostart +// This getter is a temporary function that checks for special +// conditions in which e10s support is not great yet, and should +// therefore be disabled. Bug 1065561 tracks its removal. +bool BrowserTabsRemoteAutostart(); +uint32_t GetMaxWebProcessCount(); + +// Returns the value of the fission.autostart pref. Since fission can be +// disabled on a per-window basis, this should only be used when you need the +// global value of the pref. For other use cases, you should use +// nsILoadContext::UseRemoteSubframes instead. This will also check for special +// conditions, like safe mode, which may require fission to be disabled, or +// environment variables MOZ_FORCE_ENABLE_FISSION and MOZ_FORCE_DISABLE_FISSION, +// used by mach run to enable/disable fission regardless of pref settings. +bool FissionAutostart(); + +// Returns whether or not we are currently enrolled in the fission experiment. +bool FissionExperimentEnrolled(); + +// Returns true if FissionAutostart() is true or +// fission.disableSessionHistoryInParent is false. +bool SessionHistoryInParent(); + +// Returns true if SessionHistoryInParent() returns true and +// fission.bfcacheInParent is true. +bool BFCacheInParent(); +} + +%} + +/** + * Provides information about the XUL runtime. + * @status UNSTABLE - This interface is not frozen and will probably change in + * future releases. If you need this functionality to be + * stable/frozen, please contact Benjamin Smedberg. + */ + +[scriptable, uuid(03602fac-fa3f-4a50-9baa-b88456fb4a0f)] +interface nsIXULRuntime : nsISupports +{ + /** + * Whether the application was launched in safe mode. + */ + readonly attribute boolean inSafeMode; + + /** + * The status of a given normandy experiment. + */ + cenum ExperimentStatus : 8 { + // The user is not actively enrolled in the experiment. + eExperimentStatusUnenrolled = 0, + // The user is enrolled in the control group, and should see the default + // behavior. + eExperimentStatusControl = 1, + // The user is enrolled in the treatment group, and should see the + // experimental behavior which is being tested. + eExperimentStatusTreatment = 2, + // The user was enrolled in the experiment, but became ineligible due to + // manually modifying a relevant preference. + eExperimentStatusDisqualified = 3, + // The user was selected for the phased Fission rollout. + eExperimentStatusRollout = 4, + + eExperimentStatusCount, + }; + + // If you update this enum, don't forget to raise the limit in + // TelemetryEnvironmentTesting.sys.mjs and record the new value in + // environment.rst + cenum ContentWin32kLockdownState : 8 { + LockdownEnabled = 1, // no longer used + MissingWebRender = 2, + OperatingSystemNotSupported = 3, + PrefNotSet = 4, // no longer used + MissingRemoteWebGL = 5, + MissingNonNativeTheming = 6, + DisabledByEnvVar = 7, // - MOZ_ENABLE_WIN32K is set + DisabledBySafeMode = 8, + DisabledByE10S = 9, // - E10S is disabled for whatever reason + DisabledByUserPref = 10, // - The user manually set + // security.sandbox.content.win32k-disable to false + EnabledByUserPref = 11, // The user manually set + // security.sandbox.content.win32k-disable to true + DisabledByControlGroup = + 12, // The user is in the Control Group, so it is disabled + EnabledByTreatmentGroup = + 13, // The user is in the Treatment Group, so it is enabled + DisabledByDefault = 14, // The default value of the pref is false + EnabledByDefault = 15, // The default value of the pref is true + DecodersArentRemote = 16, + IncompatibleMitigationPolicy = 17, // Some incompatible Windows Exploit Mitigation policies are enabled + }; + + // This is the current value of the experiment for the session + readonly attribute nsIXULRuntime_ExperimentStatus win32kExperimentStatus; + // This will return what the browser thinks is the _current_ status of win32k lockdown + // but this should only be used for testing + readonly attribute nsIXULRuntime_ContentWin32kLockdownState win32kLiveStatusTestingOnly; + // This is the current value of win32k lockdown for the session. It is set at startup, + // and never changed. + readonly attribute nsIXULRuntime_ContentWin32kLockdownState win32kSessionStatus; + + // NOTE: Please do not add new values to this enum without also updating the + // mapping in aboutSupport.js + cenum FissionDecisionStatus : 8 { + eFissionStatusUnknown = 0, + // Fission is disabled because the user is in the control group of a + // Normandy experiment. + eFissionExperimentControl = 1, + // Fission is enabled because the user is in the treatment group of a + // Normandy experiment. + eFissionExperimentTreatment = 2, + // Fission is disabled because the `MOZ_FORCE_DISABLE_E10S` environment + // variable is set. + eFissionDisabledByE10sEnv = 3, + // Fission is enabled because the `MOZ_FORCE_ENABLE_FISSION` environment + // variable is set. + eFissionEnabledByEnv = 4, + // Fission is disabled because the `MOZ_FORCE_DISABLE_FISSION` environment + // variable is set. + eFissionDisabledByEnv = 5, + // Fission is enabled because the "fission.autostart" preference is true + // by default. + eFissionEnabledByDefault = 7, + // Fission is disabled because the "fission.autostart" preference is false + // by default. + eFissionDisabledByDefault = 8, + // Fission is enabled because the "fission.autostart" preference was + // turned on by the user. + eFissionEnabledByUserPref = 9, + // Fission is enabled because the "fission.autostart" preference was + // turned on by the user. + eFissionDisabledByUserPref = 10, + // Fission is disabled because e10s is disabled for some other reason. + eFissionDisabledByE10sOther = 11, + // Fission is enabled by a Normandy phased rollout. + eFissionEnabledByRollout = 12, + }; + + /** + * Whether Fission should be automatically enabled for new browser windows. + * This may not match the value of the 'fission.autostart' pref. + * + * This value is guaranteed to remain constant for the length of a browser + * session. + */ + readonly attribute boolean fissionAutostart; + + /** + * The deciding factor which caused Fission to be enabled or disabled in + * this session. The string version is the same of the name of the constant, + * without the leading `eFission`, and with an initial lower-case letter. + */ + readonly attribute nsIXULRuntime_FissionDecisionStatus fissionDecisionStatus; + readonly attribute ACString fissionDecisionStatusString; + + /** + * Whether session history is stored in the parent process. + */ + readonly attribute boolean sessionHistoryInParent; + + /** + * Whether to write console errors to a log file. If a component + * encounters startup errors that might prevent the app from showing + * proper UI, it should set this flag to "true". + */ + attribute boolean logConsoleErrors; + + /** + * A string tag identifying the current operating system. This is taken + * from the OS_TARGET configure variable. It will always be available. + */ + readonly attribute AUTF8String OS; + + /** + * A string tag identifying the binary ABI of the current processor and + * compiler vtable. This is taken from the TARGET_XPCOM_ABI configure + * variable. It may not be available on all platforms, especially + * unusual processor or compiler combinations. + * + * The result takes the form -, for example: + * x86-msvc + * ppc-gcc3 + * + * This value should almost always be used in combination with "OS". + * + * @throw NS_ERROR_NOT_AVAILABLE if not available. + */ + readonly attribute AUTF8String XPCOMABI; + + /** + * A string tag identifying the target widget toolkit in use. + * This is taken from the MOZ_WIDGET_TOOLKIT configure variable. + */ + readonly attribute AUTF8String widgetToolkit; + + /** + * The legal values of processType. + */ + const unsigned long PROCESS_TYPE_DEFAULT = 0; + const unsigned long PROCESS_TYPE_CONTENT = 2; + const unsigned long PROCESS_TYPE_IPDLUNITTEST = 3; + const unsigned long PROCESS_TYPE_GMPLUGIN = 4; + const unsigned long PROCESS_TYPE_GPU = 5; + const unsigned long PROCESS_TYPE_VR = 6; + const unsigned long PROCESS_TYPE_RDD = 7; + const unsigned long PROCESS_TYPE_SOCKET = 8; + const unsigned long PROCESS_TYPE_REMOTESANDBOXBROKER = 9; + const unsigned long PROCESS_TYPE_FORKSERVER = 10; + const unsigned long PROCESS_TYPE_UTILITY = 11; + + /** + * The type of the caller's process. Returns one of the values above. + */ + readonly attribute unsigned long processType; + + /** + * The system process ID of the caller's process. + */ + readonly attribute unsigned long processID; + + /** + * A globally unique and non-recycled ID of the caller's process. + */ + readonly attribute uint64_t uniqueProcessID; + + /** + * The type of remote content process we're running in. + * null if we're in the parent/chrome process. This can contain + * a URI if Fission is enabled, so don't use it for any kind of + * telemetry. + */ + readonly attribute AUTF8String remoteType; + + /** + * If true, browser tabs may be opened by default in a different process + * from the main browser UI. + */ + readonly attribute boolean browserTabsRemoteAutostart; + + /** + * Returns the number of content processes to use for normal web pages. If + * this value is > 1, then e10s-multi should be considered to be "on". + * + * NB: If browserTabsRemoteAutostart is false, then this value has no + * meaning and e10s should be considered to be "off"! + */ + readonly attribute uint32_t maxWebProcessCount; + + /** + * The current e10s-multi experiment number. Set dom.ipc.multiOptOut to (at + * least) this to disable it until the next experiment. + */ + const uint32_t E10S_MULTI_EXPERIMENT = 1; + + /** + * If true, the accessibility service is running. + */ + readonly attribute boolean accessibilityEnabled; + + /** + * Executable of Windows service that activated accessibility. + */ + readonly attribute AString accessibilityInstantiator; + + /** + * Temporary, do not use. Indicates if an incompat version of JAWS + * screen reader software is loaded in our process space. + */ + readonly attribute boolean shouldBlockIncompatJaws; + + /** + * Indicates whether the current Firefox build is 64-bit. + */ + readonly attribute boolean is64Bit; + + /** + * Indicates whether or not text recognition of images supported by the OS. + */ + readonly attribute boolean isTextRecognitionSupported; + + /** + * Signal the apprunner to invalidate caches on the next restart. + * This will cause components to be autoregistered and all + * fastload data to be re-created. + */ + void invalidateCachesOnRestart(); + + /** + * Starts a child process. This method is intented to pre-start a + * content child process so that when it is actually needed, it is + * ready to go. + * + * @throw NS_ERROR_NOT_AVAILABLE if not available. + */ + void ensureContentProcess(); + + /** + * Modification time of the profile lock before the profile was locked on + * this startup. Used to know the last time the profile was used and not + * closed cleanly. This is set to 0 if there was no existing profile lock. + */ + readonly attribute PRTime replacedLockTime; + + /** + * The default update channel (MOZ_UPDATE_CHANNEL). + */ + readonly attribute AUTF8String defaultUpdateChannel; + + /** + * The distribution ID for this build (MOZ_DISTRIBUTION_ID). + */ + readonly attribute AUTF8String distributionID; + + /** + * True if Windows DLL blocklist initialized correctly. This is + * primarily for automated testing purposes. + */ + readonly attribute boolean windowsDLLBlocklistStatus; + + /** + * True if this application was started by the OS as part of an automatic + * restart mechanism (such as RegisterApplicationRestart on Windows). + */ + readonly attribute boolean restartedByOS; + + /** Whether the chrome color-scheme is dark */ + readonly attribute boolean chromeColorSchemeIsDark; + + /** Whether the content color-scheme derived from the app theme is dark */ + readonly attribute boolean contentThemeDerivedColorSchemeIsDark; + + /** Whether the user prefers reduced motion */ + readonly attribute boolean prefersReducedMotion; + + /** Whether we should draw over the titlebar */ + readonly attribute boolean drawInTitlebar; + + /** Returns the desktop environment identifier. Only meaningful on GTK */ + readonly attribute ACString desktopEnvironment; + + /** + * The path of the shortcut used to start the current process, or "" if none. + * + * Windows Main process only, otherwise throws NS_ERROR_NOT_AVAILABLE + * + * May be mising in some cases where the user did launch from a shortcut: + * - If the updater ran on startup + * - If the AUMID was set before the shortcut could be saved + * + * @throw NS_ERROR_NOT_AVAILABLE if not available. + */ + readonly attribute AString processStartupShortcut; + + /** + * Returns a value corresponding to one of the + * |mozilla::LauncherRegistryInfo::EnabledState| values. + */ + readonly attribute uint32_t launcherProcessState; + + /** + * Returns the last application version that used the current profile or null + * if the last version could not be found (compatibility.ini was either + * missing or invalid). Throws NS_ERROR_UNAVAILABLE if called from a content + * process. + */ + readonly attribute ACString lastAppVersion; + + /** + * Returns the last application build ID that used the current profile or null + * if the last build ID could not be found (compatibility.ini was either + * missing or invalid). Throws NS_ERROR_UNAVAILABLE if called from a content + * process. + */ + readonly attribute ACString lastAppBuildID; +}; + + +%{C++ + +namespace mozilla { + +nsIXULRuntime::ContentWin32kLockdownState GetWin32kLockdownState(); + +} + +%} diff --git a/xpcom/tests/NotXPCOMTest.idl b/xpcom/tests/NotXPCOMTest.idl new file mode 100644 index 0000000000..acc1574e79 --- /dev/null +++ b/xpcom/tests/NotXPCOMTest.idl @@ -0,0 +1,17 @@ +/* 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 "nsISupports.idl" + +[scriptable, uuid(93142a4f-e4cf-424a-b833-e638f87d2607)] +interface nsIScriptableOK : nsISupports +{ + void method1(); +}; + +[scriptable, builtinclass, uuid(237d01a3-771e-4c6e-adf9-c97f9aab2950)] +interface nsIScriptableWithNotXPCOM : nsISupports +{ + [notxpcom] void method2(); +}; diff --git a/xpcom/tests/RegFactory.cpp b/xpcom/tests/RegFactory.cpp new file mode 100644 index 0000000000..7130ee5e1c --- /dev/null +++ b/xpcom/tests/RegFactory.cpp @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 +#include "prlink.h" +#include "nsIComponentRegistrar.h" +#include "nsIServiceManager.h" +#include "nsIFile.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +static bool gUnreg = false; + +void print_err(nsresult err) { + switch (err) { + case NS_ERROR_FACTORY_NOT_LOADED: + cerr << "Factory not loaded"; + break; + case NS_NOINTERFACE: + cerr << "No Interface"; + break; + case NS_ERROR_NULL_POINTER: + cerr << "Null pointer"; + break; + case NS_ERROR_OUT_OF_MEMORY: + cerr << "Out of memory"; + break; + default: + cerr << hex << err << dec; + } +} + +nsresult Register(nsIComponentRegistrar* registrar, const char* path) { + nsCOMPtr file; + nsresult rv = + NS_NewLocalFile(NS_ConvertUTF8toUTF16(path), true, getter_AddRefs(file)); + if (NS_FAILED(rv)) return rv; + rv = registrar->AutoRegister(file); + return rv; +} + +nsresult Unregister(const char* path) { + /* NEEDS IMPLEMENTATION */ +#if 0 + nsresult res = nsComponentManager::AutoUnregisterComponent(path); + return res; +#else + return NS_ERROR_FAILURE; +#endif +} + +int ProcessArgs(nsIComponentRegistrar* registrar, int argc, char* argv[]) { + int i = 1; + nsresult res; + + while (i < argc) { + if (argv[i][0] == '-') { + int j; + for (j = 1; argv[i][j] != '\0'; j++) { + switch (argv[i][j]) { + case 'u': + gUnreg = true; + break; + default: + cerr << "Unknown option '" << argv[i][j] << "'\n"; + } + } + i++; + } else { + if (gUnreg) { + res = Unregister(argv[i]); + if (NS_SUCCEEDED(res)) { + cout << "Successfully unregistered: " << argv[i] << "\n"; + } else { + cerr << "Unregister failed ("; + print_err(res); + cerr << "): " << argv[i] << "\n"; + } + } else { + res = Register(registrar, argv[i]); + if (NS_SUCCEEDED(res)) { + cout << "Successfully registered: " << argv[i] << "\n"; + } else { + cerr << "Register failed ("; + print_err(res); + cerr << "): " << argv[i] << "\n"; + } + } + i++; + } + } + return 0; +} + +int main(int argc, char* argv[]) { + int ret = 0; + nsresult rv; + { + nsCOMPtr servMan; + rv = NS_InitXPCOM(getter_AddRefs(servMan), nullptr, nullptr); + if (NS_FAILED(rv)) return -1; + nsCOMPtr registrar = do_QueryInterface(servMan); + NS_ASSERTION(registrar, "Null nsIComponentRegistrar"); + + /* With no arguments, RegFactory will autoregister */ + if (argc <= 1) { + rv = registrar->AutoRegister(nullptr); + ret = (NS_FAILED(rv)) ? -1 : 0; + } else + ret = ProcessArgs(registrar, argc, argv); + } // this scopes the nsCOMPtrs + // no nsCOMPtrs are allowed to be alive when you call NS_ShutdownXPCOM + rv = NS_ShutdownXPCOM(nullptr); + NS_ASSERTION(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); + return ret; +} diff --git a/xpcom/tests/SizeTest01.cpp b/xpcom/tests/SizeTest01.cpp new file mode 100644 index 0000000000..790b0fa032 --- /dev/null +++ b/xpcom/tests/SizeTest01.cpp @@ -0,0 +1,107 @@ +// Test01.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsINode); + +/* + This test file compares the generated code size of similar functions + between raw COM interface pointers (|AddRef|ing and |Release|ing by hand) and + |nsCOMPtr|s. + + Function size results were determined by examining dissassembly of the + generated code. mXXX is the size of the generated code on the Macintosh. wXXX + is the size on Windows. For these tests, all reasonable optimizations were + enabled and exceptions were disabled (just as we build for release). + + The tests in this file explore only the simplest functionality: + assigning a pointer to be reference counted into a [raw, nsCOMPtr] object; + ensuring that it is |AddRef|ed and |Release|d appropriately; calling through + the pointer to a function supplied by the underlying COM interface. + + Windows: + raw_optimized + 31 bytes raw, nsCOMPtr* + 34 nsCOMPtr_optimized* + 38 nsCOMPtr_optimized + 42 nsCOMPtr + 46 + + Macintosh: + raw_optimized, nsCOMPtr_optimized + 112 bytes (1.0000) nsCOMPtr + 120 (1.0714) i.e., 7.14% bigger than + raw_optimized et al + raw + 140 (1.2500) + + The overall difference in size between Windows and Macintosh is caused + by the the PowerPC RISC architecture where every instruction is 4 bytes. + + On Macintosh, nsCOMPtr generates out-of-line destructors which are + not referenced, and which can be stripped by the linker. +*/ + +void Test01_raw(nsINode* aDOMNode, nsString* aResult) +// m140, w34 +{ + /* + This test is designed to be more like a typical large function where, + because you are working with several resources, you don't just return + when one of them is |nullptr|. Similarly: |Test01_nsCOMPtr00|, and + |Test01_nsIPtr00|. + */ + + nsINode* node = aDOMNode; + NS_IF_ADDREF(node); + + if (node) node->GetNodeName(*aResult); + + NS_IF_RELEASE(node); +} + +void Test01_raw_optimized(nsINode* aDOMNode, nsString* aResult) +// m112, w31 +{ + /* + This test simulates smaller functions where you _do_ just return + |nullptr| at the first sign of trouble. Similarly: + |Test01_nsCOMPtr01|, and |Test01_nsIPtr01|. + */ + + /* + This test produces smaller code that |Test01_raw| because it avoids + the three tests: |NS_IF_...|, and |if ( node )|. + */ + + // -- the following code is assumed, but is commented out so we compare only + // the relevent generated code + + // if ( !aDOMNode ) + // return; + + nsINode* node = aDOMNode; + NS_ADDREF(node); + node->GetNodeName(*aResult); + NS_RELEASE(node); +} + +void Test01_nsCOMPtr(nsINode* aDOMNode, nsString* aResult) +// m120, w46/34 +{ + nsCOMPtr node = aDOMNode; + + if (node) node->GetNodeName(*aResult); +} + +void Test01_nsCOMPtr_optimized(nsINode* aDOMNode, nsString* aResult) +// m112, w42/38 +{ + // if ( !aDOMNode ) + // return; + + nsCOMPtr node = aDOMNode; + node->GetNodeName(*aResult); +} diff --git a/xpcom/tests/SizeTest02.cpp b/xpcom/tests/SizeTest02.cpp new file mode 100644 index 0000000000..2673bbe70d --- /dev/null +++ b/xpcom/tests/SizeTest02.cpp @@ -0,0 +1,87 @@ +// Test02.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsINode); + +/* + This test file compares the generated code size of similar functions + between raw COM interface pointers (|AddRef|ing and |Release|ing by hand) and + |nsCOMPtr|s. + + Function size results were determined by examining dissassembly of the + generated code. mXXX is the size of the generated code on the Macintosh. wXXX + is the size on Windows. For these tests, all reasonable optimizations were + enabled and exceptions were disabled (just as we build for release). + + The tests in this file explore more complicated functionality: assigning + a pointer to be reference counted into a [raw, nsCOMPtr] object using + |QueryInterface|; ensuring that it is |AddRef|ed and |Release|d + appropriately; calling through the pointer to a function supplied by the + underlying COM interface. The tests in this file expand on the tests in + "Test01.cpp" by adding |QueryInterface|. + + Windows: + raw01 + 52 nsCOMPtr 63 raw + 66 nsCOMPtr* 68 + + Macintosh: + nsCOMPtr 120 (1.0000) Raw01 + 128 (1.1429) i.e., 14.29% bigger than nsCOMPtr Raw00 + 144 (1.2000) +*/ + +void // nsresult +Test02_Raw00(nsISupports* aDOMNode, nsString* aResult) +// m144, w66 +{ + // -- the following code is assumed, but is commented out so we compare only + // the relevent generated code + + // if ( !aDOMNode ) + // return NS_ERROR_NULL_POINTER; + + nsINode* node = 0; + nsresult status = + aDOMNode->QueryInterface(NS_GET_IID(nsINode), (void**)&node); + if (NS_SUCCEEDED(status)) { + node->GetNodeName(*aResult); + } + + NS_IF_RELEASE(node); + + // return status; +} + +void // nsresult +Test02_Raw01(nsISupports* aDOMNode, nsString* aResult) +// m128, w52 +{ + // if ( !aDOMNode ) + // return NS_ERROR_NULL_POINTER; + + nsINode* node; + nsresult status = + aDOMNode->QueryInterface(NS_GET_IID(nsINode), (void**)&node); + if (NS_SUCCEEDED(status)) { + node->GetNodeName(*aResult); + NS_RELEASE(node); + } + + // return status; +} + +void // nsresult +Test02_nsCOMPtr(nsISupports* aDOMNode, nsString* aResult) +// m120, w63/68 +{ + nsresult status; + nsCOMPtr node = do_QueryInterface(aDOMNode, &status); + + if (node) node->GetNodeName(*aResult); + + // return status; +} diff --git a/xpcom/tests/SizeTest03.cpp b/xpcom/tests/SizeTest03.cpp new file mode 100644 index 0000000000..055db264cd --- /dev/null +++ b/xpcom/tests/SizeTest03.cpp @@ -0,0 +1,94 @@ +// Test03.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +NS_DEF_PTR(nsINode); + +/* + Windows: + nsCOMPtr_optimized* + 45 raw_optimized + 48 nsCOMPtr_optimized + 50 nsCOMPtr + 54 nsCOMPtr* + 59 raw + 62 + + Macintosh: + nsCOMPtr_optimized 112 + (1.0000) + raw_optimized 124 bytes + (1.1071) i.e., 10.71% bigger than nsCOMPtr_optimized nsCOMPtr + 144 (1.2857) +*/ + +void // nsresult +Test03_raw(nsINode* aDOMNode, nsString* aResult) +// m140, w62 +{ + // -- the following code is assumed, but is commented out so we compare only + // the relevent generated code + + // if ( !aDOMNode || !aResult ) + // return NS_ERROR_NULL_POINTER; + + nsINode* parent = 0; + nsresult status = aDOMNode->GetParentNode(&parent); + + if (NS_SUCCEEDED(status)) { + parent->GetNodeName(*aResult); + } + + NS_IF_RELEASE(parent); + + // return status; +} + +void // nsresult +Test03_raw_optimized(nsINode* aDOMNode, nsString* aResult) +// m124, w48 +{ + // if ( !aDOMNode || !aResult ) + // return NS_ERROR_NULL_POINTER; + + nsINode* parent; + nsresult status = aDOMNode->GetParentNode(&parent); + + if (NS_SUCCEEDED(status)) { + parent->GetNodeName(*aResult); + NS_RELEASE(parent); + } + + // return status; +} + +void // nsresult +Test03_nsCOMPtr(nsINode* aDOMNode, nsString* aResult) +// m144, w54/59 +{ + // if ( !aDOMNode || !aResult ) + // return NS_ERROR_NULL_POINTER; + + nsCOMPtr parent; + nsresult status = aDOMNode->GetParentNode(getter_AddRefs(parent)); + if (parent) parent->GetNodeName(*aResult); + + // return status; +} + +void // nsresult +Test03_nsCOMPtr_optimized(nsINode* aDOMNode, nsString* aResult) +// m112, w50/45 +{ + // if ( !aDOMNode || !aResult ) + // return NS_ERROR_NULL_POINTER; + + nsINode* temp; + nsresult status = aDOMNode->GetParentNode(&temp); + nsCOMPtr parent(dont_AddRef(temp)); + if (parent) parent->GetNodeName(*aResult); + + // return status; +} diff --git a/xpcom/tests/SizeTest04.cpp b/xpcom/tests/SizeTest04.cpp new file mode 100644 index 0000000000..c026a1cabf --- /dev/null +++ b/xpcom/tests/SizeTest04.cpp @@ -0,0 +1,61 @@ +// Test04.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsINode); + +/* + Windows: + nsCOMPtr 13 raw + 36 + + Macintosh: + nsCOMPtr + 36 bytes (1.0000) raw + 120 (3.3333) i.e., 333.33% bigger + than nsCOMPtr +*/ + +class Test04_Raw { + public: + Test04_Raw(); + ~Test04_Raw(); + + void /*nsresult*/ SetNode(nsINode* newNode); + + private: + nsINode* mNode; +}; + +Test04_Raw::Test04_Raw() : mNode(0) { + // nothing else to do here +} + +Test04_Raw::~Test04_Raw() { NS_IF_RELEASE(mNode); } + +void // nsresult +Test04_Raw::SetNode(nsINode* newNode) +// m120, w36 +{ + NS_IF_ADDREF(newNode); + NS_IF_RELEASE(mNode); + mNode = newNode; + + // return NS_OK; +} + +class Test04_nsCOMPtr { + public: + void /*nsresult*/ SetNode(nsINode* newNode); + + private: + nsCOMPtr mNode; +}; + +void // nsresult +Test04_nsCOMPtr::SetNode(nsINode* newNode) +// m36, w13/13 +{ + mNode = newNode; +} diff --git a/xpcom/tests/SizeTest05.cpp b/xpcom/tests/SizeTest05.cpp new file mode 100644 index 0000000000..4c813a0dc3 --- /dev/null +++ b/xpcom/tests/SizeTest05.cpp @@ -0,0 +1,65 @@ +// Test05.cpp + +#include "nsINode.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsINode); + +/* + Windows: + raw, nsCOMPtr 21 bytes + + Macintosh: + Raw, nsCOMPtr 64 bytes +*/ + +class Test05_Raw { + public: + Test05_Raw(); + ~Test05_Raw(); + + void /*nsresult*/ GetNode(nsINode** aNode); + + private: + nsINode* mNode; +}; + +Test05_Raw::Test05_Raw() : mNode(0) { + // nothing else to do here +} + +Test05_Raw::~Test05_Raw() { NS_IF_RELEASE(mNode); } + +void // nsresult +Test05_Raw::GetNode(nsINode** aNode) +// m64, w21 +{ + // if ( !aNode ) + // return NS_ERROR_NULL_POINTER; + + *aNode = mNode; + NS_IF_ADDREF(*aNode); + + // return NS_OK; +} + +class Test05_nsCOMPtr { + public: + void /*nsresult*/ GetNode(nsINode** aNode); + + private: + nsCOMPtr mNode; +}; + +void // nsresult +Test05_nsCOMPtr::GetNode(nsINode** aNode) +// m64, w21 +{ + // if ( !aNode ) + // return NS_ERROR_NULL_POINTER; + + *aNode = mNode; + NS_IF_ADDREF(*aNode); + + // return NS_OK; +} diff --git a/xpcom/tests/SizeTest06.cpp b/xpcom/tests/SizeTest06.cpp new file mode 100644 index 0000000000..11fb352320 --- /dev/null +++ b/xpcom/tests/SizeTest06.cpp @@ -0,0 +1,148 @@ +// Test06.cpp + +#include "nsPIDOMWindow.h" +#include "nsIDocShell.h" +#include "nsIBaseWindow.h" +#include "nsCOMPtr.h" + +NS_DEF_PTR(nsPIDOMWindow); +NS_DEF_PTR(nsIBaseWindow); + +/* + Windows: + nsCOMPtr_optimized 176 + nsCOMPtr_as_found 181 + nsCOMPtr_optimized* 182 + nsCOMPtr02* 184 + nsCOMPtr02 187 + nsCOMPtr02* 188 + nsCOMPtr03 189 + raw_optimized, nsCOMPtr00 191 + nsCOMPtr00* 199 + nsCOMPtr_as_found* 201 + raw 214 + + Macintosh: + nsCOMPtr_optimized 300 (1.0000) + nsCOMPtr02 320 (1.0667) i.e., 6.67% bigger than + nsCOMPtr_optimized nsCOMPtr00 328 (1.0933) raw_optimized, + nsCOMPtr03 332 (1.1067) nsCOMPtr_as_found 344 (1.1467) raw + 388 (1.2933) + +*/ + +void // nsresult +Test06_raw(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) +// m388, w214 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + nsPIDOMWindow* window = 0; + nsresult status = + aDOMWindow->QueryInterface(NS_GET_IID(nsPIDOMWindow), (void**)&window); + nsIDocShell* docShell = 0; + if (window) window->GetDocShell(&docShell); + nsIWebShell* rootWebShell = 0; + NS_IF_RELEASE(rootWebShell); + NS_IF_RELEASE(docShell); + NS_IF_RELEASE(window); + // return status; +} + +void // nsresult +Test06_raw_optimized(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) +// m332, w191 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + (*aBaseWindow) = 0; + nsPIDOMWindow* window; + nsresult status = + aDOMWindow->QueryInterface(NS_GET_IID(nsPIDOMWindow), (void**)&window); + if (NS_SUCCEEDED(status)) { + nsIDocShell* docShell = 0; + window->GetDocShell(&docShell); + if (docShell) { + NS_RELEASE(docShell); + } + NS_RELEASE(window); + } + // return status; +} + +void Test06_nsCOMPtr_as_found(nsIDOMWindow* aDOMWindow, + nsCOMPtr* aBaseWindow) +// m344, w181/201 +{ + // if (!aDOMWindow) + // return; + nsCOMPtr window = do_QueryInterface(aDOMWindow); + nsCOMPtr docShell; + if (window) window->GetDocShell(getter_AddRefs(docShell)); +} + +void // nsresult +Test06_nsCOMPtr00(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) +// m328, w191/199 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + nsresult status; + nsCOMPtr window = do_QueryInterface(aDOMWindow, &status); + nsIDocShell* temp0 = 0; + if (window) window->GetDocShell(&temp0); + nsCOMPtr docShell = dont_AddRef(temp0); + (*aBaseWindow) = 0; + // return status; +} + +void // nsresult +Test06_nsCOMPtr_optimized(nsIDOMWindow* aDOMWindow, + nsCOMPtr* aBaseWindow) +// m300, w176/182 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + nsresult status; + nsCOMPtr window = do_QueryInterface(aDOMWindow, &status); + nsIDocShell* temp0 = 0; + if (window) window->GetDocShell(&temp0); + (*aBaseWindow) = do_QueryInterface(nullptr, &status); + // return status; +} + +void // nsresult +Test06_nsCOMPtr02(nsIDOMWindow* aDOMWindow, nsIBaseWindow** aBaseWindow) +// m320, w187/184 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + (*aBaseWindow) = 0; + nsresult status; + nsCOMPtr window = do_QueryInterface(aDOMWindow, &status); + if (window) { + nsIDocShell* temp0; + window->GetDocShell(&temp0); + } + // return status; +} + +void // nsresult +Test06_nsCOMPtr03(nsIDOMWindow* aDOMWindow, + nsCOMPtr* aBaseWindow) +// m332, w189/188 +{ + // if (!aDOMWindow) + // return NS_ERROR_NULL_POINTER; + (*aBaseWindow) = 0; + nsresult status; + nsCOMPtr window = do_QueryInterface(aDOMWindow, &status); + if (window) { + nsIDocShell* temp0; + window->GetDocShell(&temp0); + nsCOMPtr docShell = dont_AddRef(temp0); + if (docShell) { + } + } + // return status; +} diff --git a/xpcom/tests/TestArguments.cpp b/xpcom/tests/TestArguments.cpp new file mode 100644 index 0000000000..ab162d31fb --- /dev/null +++ b/xpcom/tests/TestArguments.cpp @@ -0,0 +1,16 @@ +#include + +int main(int argc, char* argv[]) { + if (argc != 9) return -1; + + if (strcmp("mozilla", argv[1]) != 0) return 1; + if (strcmp("firefox", argv[2]) != 0) return 2; + if (strcmp("thunderbird", argv[3]) != 0) return 3; + if (strcmp("seamonkey", argv[4]) != 0) return 4; + if (strcmp("foo", argv[5]) != 0) return 5; + if (strcmp("bar", argv[6]) != 0) return 6; + if (strcmp("argument with spaces", argv[7]) != 0) return 7; + if (strcmp(R"("argument with quotes")", argv[8]) != 0) return 8; + + return 0; +} diff --git a/xpcom/tests/TestBlockingProcess.cpp b/xpcom/tests/TestBlockingProcess.cpp new file mode 100644 index 0000000000..a1996aefdb --- /dev/null +++ b/xpcom/tests/TestBlockingProcess.cpp @@ -0,0 +1,8 @@ +#include +#include "mozilla/Unused.h" + +int main() { + char tmp; + mozilla::Unused << fread(&tmp, sizeof(tmp), 1, stdin); + return 0; +} diff --git a/xpcom/tests/TestHarness.h b/xpcom/tests/TestHarness.h new file mode 100644 index 0000000000..e575497746 --- /dev/null +++ b/xpcom/tests/TestHarness.h @@ -0,0 +1,268 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +/* + * Test harness for XPCOM objects, providing a scoped XPCOM initializer, + * nsCOMPtr, nsRefPtr, do_CreateInstance, do_GetService, ns(Auto|C|)String, + * and stdio.h/stdlib.h. + */ + +#ifndef TestHarness_h__ +#define TestHarness_h__ + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" + +#include "prenv.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIDirectoryService.h" +#include "nsIFile.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" +#include +#include +#include +#include "mozilla/AppShutdown.h" + +static uint32_t gFailCount = 0; + +/** + * Prints the given failure message and arguments using printf, prepending + * "TEST-UNEXPECTED-FAIL " for the benefit of the test harness and + * appending "\n" to eliminate having to type it at each call site. + */ +MOZ_FORMAT_PRINTF(1, 2) void fail(const char* msg, ...) { + va_list ap; + + printf("TEST-UNEXPECTED-FAIL | "); + + va_start(ap, msg); + vprintf(msg, ap); + va_end(ap); + + putchar('\n'); + ++gFailCount; +} + +/** + * Prints the given success message and arguments using printf, prepending + * "TEST-PASS " for the benefit of the test harness and + * appending "\n" to eliminate having to type it at each call site. + */ +MOZ_FORMAT_PRINTF(1, 2) void passed(const char* msg, ...) { + va_list ap; + + printf("TEST-PASS | "); + + va_start(ap, msg); + vprintf(msg, ap); + va_end(ap); + + putchar('\n'); +} + +//----------------------------------------------------------------------------- + +class ScopedXPCOM : public nsIDirectoryServiceProvider2 { + public: + NS_DECL_ISUPPORTS + + explicit ScopedXPCOM(const char* testName, + nsIDirectoryServiceProvider* dirSvcProvider = nullptr) + : mDirSvcProvider(dirSvcProvider) { + mTestName = testName; + printf("Running %s tests...\n", mTestName); + + mInitRv = NS_InitXPCOM(nullptr, nullptr, this); + if (NS_FAILED(mInitRv)) { + fail("NS_InitXPCOM returned failure code 0x%" PRIx32, + static_cast(mInitRv)); + return; + } + } + + ~ScopedXPCOM() { + // If we created a profile directory, we need to remove it. + if (mProfD) { + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdownNetTeardown); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdownTeardown); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdown); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdownQM); + mozilla::AppShutdown::AdvanceShutdownPhase( + mozilla::ShutdownPhase::AppShutdownTelemetry); + + if (NS_FAILED(mProfD->Remove(true))) { + NS_WARNING("Problem removing profile directory"); + } + + mProfD = nullptr; + } + + if (NS_SUCCEEDED(mInitRv)) { + nsresult rv = NS_ShutdownXPCOM(nullptr); + if (NS_FAILED(rv)) { + fail("XPCOM shutdown failed with code 0x%" PRIx32, + static_cast(rv)); + exit(1); + } + } + + printf("Finished running %s tests.\n", mTestName); + } + + bool failed() { return NS_FAILED(mInitRv); } + + already_AddRefed GetProfileDirectory() { + if (mProfD) { + nsCOMPtr copy = mProfD; + return copy.forget(); + } + + // Create a unique temporary folder to use for this test. + // Note that runcppunittests.py will run tests with a temp + // directory as the cwd, so just put something under that. + nsCOMPtr profD; + nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_PROCESS_DIR, + getter_AddRefs(profD)); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = profD->Append(u"cpp-unit-profd"_ns); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = profD->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755); + NS_ENSURE_SUCCESS(rv, nullptr); + + mProfD = profD; + return profD.forget(); + } + + already_AddRefed GetGREDirectory() { + if (mGRED) { + nsCOMPtr copy = mGRED; + return copy.forget(); + } + + char* env = PR_GetEnv("MOZ_XRE_DIR"); + nsCOMPtr greD; + if (env) { + NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false, getter_AddRefs(greD)); + } + + mGRED = greD; + return greD.forget(); + } + + already_AddRefed GetGREBinDirectory() { + if (mGREBinD) { + nsCOMPtr copy = mGREBinD; + return copy.forget(); + } + + nsCOMPtr greD = GetGREDirectory(); + if (!greD) { + return greD.forget(); + } + greD->Clone(getter_AddRefs(mGREBinD)); + +#ifdef XP_MACOSX + nsAutoCString leafName; + mGREBinD->GetNativeLeafName(leafName); + if (leafName.EqualsLiteral("Resources")) { + mGREBinD->SetNativeLeafName("MacOS"_ns); + } +#endif + + nsCOMPtr copy = mGREBinD; + return copy.forget(); + } + + //////////////////////////////////////////////////////////////////////////// + //// nsIDirectoryServiceProvider + + NS_IMETHOD GetFile(const char* aProperty, bool* _persistent, + nsIFile** _result) override { + // If we were supplied a directory service provider, ask it first. + if (mDirSvcProvider && NS_SUCCEEDED(mDirSvcProvider->GetFile( + aProperty, _persistent, _result))) { + return NS_OK; + } + + // Otherwise, the test harness provides some directories automatically. + if (0 == strcmp(aProperty, NS_APP_USER_PROFILE_50_DIR) || + 0 == strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR) || + 0 == strcmp(aProperty, NS_APP_PROFILE_LOCAL_DIR_STARTUP)) { + nsCOMPtr profD = GetProfileDirectory(); + NS_ENSURE_TRUE(profD, NS_ERROR_FAILURE); + + nsCOMPtr clone; + nsresult rv = profD->Clone(getter_AddRefs(clone)); + NS_ENSURE_SUCCESS(rv, rv); + + *_persistent = true; + clone.forget(_result); + return NS_OK; + } else if (0 == strcmp(aProperty, NS_GRE_DIR)) { + nsCOMPtr greD = GetGREDirectory(); + NS_ENSURE_TRUE(greD, NS_ERROR_FAILURE); + + *_persistent = true; + greD.forget(_result); + return NS_OK; + } else if (0 == strcmp(aProperty, NS_GRE_BIN_DIR)) { + nsCOMPtr greBinD = GetGREBinDirectory(); + NS_ENSURE_TRUE(greBinD, NS_ERROR_FAILURE); + + *_persistent = true; + greBinD.forget(_result); + return NS_OK; + } + + return NS_ERROR_FAILURE; + } + + //////////////////////////////////////////////////////////////////////////// + //// nsIDirectoryServiceProvider2 + + NS_IMETHOD GetFiles(const char* aProperty, + nsISimpleEnumerator** _enum) override { + // If we were supplied a directory service provider, ask it first. + nsCOMPtr provider = + do_QueryInterface(mDirSvcProvider); + if (provider && NS_SUCCEEDED(provider->GetFiles(aProperty, _enum))) { + return NS_OK; + } + + return NS_ERROR_FAILURE; + } + + private: + const char* mTestName; + nsresult mInitRv = NS_ERROR_NOT_INITIALIZED; + nsCOMPtr mDirSvcProvider; + nsCOMPtr mProfD; + nsCOMPtr mGRED; + nsCOMPtr mGREBinD; +}; + +NS_IMPL_QUERY_INTERFACE(ScopedXPCOM, nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2) + +NS_IMETHODIMP_(MozExternalRefCountType) +ScopedXPCOM::AddRef() { return 2; } + +NS_IMETHODIMP_(MozExternalRefCountType) +ScopedXPCOM::Release() { return 1; } + +#endif // TestHarness_h__ diff --git a/xpcom/tests/TestMemoryPressureWatcherLinux.cpp b/xpcom/tests/TestMemoryPressureWatcherLinux.cpp new file mode 100644 index 0000000000..8adc9f1092 --- /dev/null +++ b/xpcom/tests/TestMemoryPressureWatcherLinux.cpp @@ -0,0 +1,65 @@ +/* -*- 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/AvailableMemoryWatcherUtils.h" + +#include + +using namespace mozilla; + +const char* kMemInfoPath = "/proc/meminfo"; +const char* kTestfilePath = "testdata"; + +// Test that we are reading some value from /proc/meminfo. +// If the values are nonzero, the test is a success. +void TestFromProc() { + MemoryInfo memInfo{0, 0}; + ReadMemoryFile(kMemInfoPath, memInfo); + MOZ_RELEASE_ASSERT(memInfo.memTotal != 0); + MOZ_RELEASE_ASSERT(memInfo.memAvailable != 0); +} + +// Test a file using expected syntax. +void TestFromFile() { + MemoryInfo memInfo{0, 0}; + std::ofstream aFile(kTestfilePath); + aFile << "MemTotal: 12345 kB\n"; + aFile << "MemFree: 99999 kB\n"; + aFile << "MemAvailable: 54321 kB\n"; + aFile.close(); + + ReadMemoryFile(kTestfilePath, memInfo); + + MOZ_RELEASE_ASSERT(memInfo.memTotal == 12345); + MOZ_RELEASE_ASSERT(memInfo.memAvailable == 54321); + + // remove our dummy file + remove(kTestfilePath); +} + +// Test a file with useless data. Results should be +// the starting struct with {0,0}. +void TestInvalidFile() { + MemoryInfo memInfo{0, 0}; + std::ofstream aFile(kTestfilePath); + aFile << "foo: 12345 kB\n"; + aFile << "bar"; + aFile.close(); + + ReadMemoryFile(kTestfilePath, memInfo); + + MOZ_RELEASE_ASSERT(memInfo.memTotal == 0); + MOZ_RELEASE_ASSERT(memInfo.memAvailable == 0); + + // remove our dummy file + remove(kTestfilePath); +} + +int main() { + TestFromProc(); + TestFromFile(); + TestInvalidFile(); + return 0; +} diff --git a/xpcom/tests/TestPRIntN.cpp b/xpcom/tests/TestPRIntN.cpp new file mode 100644 index 0000000000..0873d16ef8 --- /dev/null +++ b/xpcom/tests/TestPRIntN.cpp @@ -0,0 +1,39 @@ +/* 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 +#include "prtypes.h" + +// This test is NOT intended to be run. It's a test to make sure +// PRInt{N} matches int{N}_t. If they don't match, we should get a +// compiler warning or error in main(). + +static void ClearNSPRIntTypes(PRInt8* a, PRInt16* b, PRInt32* c, PRInt64* d) { + *a = 0; + *b = 0; + *c = 0; + *d = 0; +} + +static void ClearStdIntTypes(int8_t* w, int16_t* x, int32_t* y, int64_t* z) { + *w = 0; + *x = 0; + *y = 0; + *z = 0; +} + +int main() { + PRInt8 a; + PRInt16 b; + PRInt32 c; + PRInt64 d; + int8_t w; + int16_t x; + int32_t y; + int64_t z; + + ClearNSPRIntTypes(&w, &x, &y, &z); + ClearStdIntTypes(&a, &b, &c, &d); + return 0; +} diff --git a/xpcom/tests/TestQuickReturn.cpp b/xpcom/tests/TestQuickReturn.cpp new file mode 100644 index 0000000000..07500bde9c --- /dev/null +++ b/xpcom/tests/TestQuickReturn.cpp @@ -0,0 +1,5 @@ +int main(int argc, char* argv[]) { + if (argc != 1) return -1; + + return 42; +} diff --git a/xpcom/tests/TestShutdown.cpp b/xpcom/tests/TestShutdown.cpp new file mode 100644 index 0000000000..4b277d423c --- /dev/null +++ b/xpcom/tests/TestShutdown.cpp @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIServiceManager.h" + +// Gee this seems simple! It's for testing for memory leaks with Purify. + +void main(int argc, char* argv[]) { + nsIServiceManager* servMgr; + nsresult rv = NS_InitXPCOM(&servMgr, nullptr, nullptr); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "NS_InitXPCOM failed"); + + // try loading a component and releasing it to see if it leaks + if (argc > 1 && argv[1] != nullptr) { + char* cidStr = argv[1]; + nsISupports* obj = nullptr; + if (cidStr[0] == '{') { + nsCID cid; + cid.Parse(cidStr); + rv = CallCreateInstance(cid, &obj); + } else { + // contractID case: + rv = CallCreateInstance(cidStr, &obj); + } + if (NS_SUCCEEDED(rv)) { + printf("Successfully created %s\n", cidStr); + NS_RELEASE(obj); + } else { + printf("Failed to create %s (%x)\n", cidStr, rv); + } + } + + rv = NS_ShutdownXPCOM(servMgr); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "NS_ShutdownXPCOM failed"); +} diff --git a/xpcom/tests/TestStreamUtils.cpp b/xpcom/tests/TestStreamUtils.cpp new file mode 100644 index 0000000000..9ba6d36aa3 --- /dev/null +++ b/xpcom/tests/TestStreamUtils.cpp @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 +#include +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsCOMPtr.h" + +//---- + +static bool test_consume_stream() { + const char kData[] = + "Get your facts first, and then you can distort them as much as you " + "please."; + + nsCOMPtr input; + nsCOMPtr output; + NS_NewPipe(getter_AddRefs(input), getter_AddRefs(output), 10, UINT32_MAX); + if (!input || !output) return false; + + uint32_t n = 0; + output->Write(kData, sizeof(kData) - 1, &n); + if (n != (sizeof(kData) - 1)) return false; + output = nullptr; // close output + + nsCString buf; + if (NS_FAILED(NS_ConsumeStream(input, UINT32_MAX, buf))) return false; + + if (!buf.Equals(kData)) return false; + + return true; +} + +//---- + +typedef bool (*TestFunc)(); +#define DECL_TEST(name) \ + { \ +# name, name \ + } + +static const struct Test { + const char* name; + TestFunc func; +} tests[] = {DECL_TEST(test_consume_stream), {nullptr, nullptr}}; + +int main(int argc, char** argv) { + int count = 1; + if (argc > 1) count = atoi(argv[1]); + + if (NS_FAILED(NS_InitXPCOM(nullptr, nullptr, nullptr))) return -1; + + while (count--) { + for (const Test* t = tests; t->name != nullptr; ++t) { + printf("%25s : %s\n", t->name, t->func() ? "SUCCESS" : "FAILURE"); + } + } + + NS_ShutdownXPCOM(nullptr); + return 0; +} diff --git a/xpcom/tests/TestUnicodeArguments.cpp b/xpcom/tests/TestUnicodeArguments.cpp new file mode 100644 index 0000000000..a44f8a2f2e --- /dev/null +++ b/xpcom/tests/TestUnicodeArguments.cpp @@ -0,0 +1,73 @@ +/** + * On Windows, a Unicode argument is passed as UTF-16 using ShellExecuteExW. + * On other platforms, it is passed as UTF-8 + */ + +static const int args_length = 4; +#if defined(XP_WIN) && defined(_MSC_VER) +# define _UNICODE +# include +# include + +static const _TCHAR* expected_utf16[args_length] = { + // Latin-1 + L"M\xF8z\xEEll\xE5", + // Cyrillic + L"\x41C\x43E\x437\x438\x43B\x43B\x430", + // Bengali + L"\x9AE\x9CB\x99C\x9BF\x9B2\x9BE", + // Cuneiform + L"\xD808\xDE2C\xD808\xDF63\xD808\xDDB7"}; + +int wmain(int argc, _TCHAR* argv[]) { + printf("argc = %d\n", argc); + + if (argc != args_length + 1) return -1; + + for (int i = 1; i < argc; ++i) { + printf("expected[%d]: ", i - 1); + for (size_t j = 0; j < _tcslen(expected_utf16[i - 1]); ++j) { + printf("%x ", *(expected_utf16[i - 1] + j)); + } + printf("\n"); + + printf("argv[%d]: ", i); + for (size_t j = 0; j < _tcslen(argv[i]); ++j) { + printf("%x ", *(argv[i] + j)); + } + printf("\n"); + + if (_tcscmp(expected_utf16[i - 1], argv[i])) { + return i; + } + } + + return 0; +} +#else +# include +# include + +static const char* expected_utf8[args_length] = { + // Latin-1 + "M\xC3\xB8z\xC3\xAEll\xC3\xA5", + // Cyrillic + "\xD0\x9C\xD0\xBE\xD0\xB7\xD0\xB8\xD0\xBB\xD0\xBB\xD0\xB0", + // Bengali + "\xE0\xA6\xAE\xE0\xA7\x8B\xE0\xA6\x9C\xE0\xA6\xBF\xE0\xA6\xB2\xE0\xA6\xBE", + // Cuneiform + "\xF0\x92\x88\xAC\xF0\x92\x8D\xA3\xF0\x92\x86\xB7"}; + +int main(int argc, char* argv[]) { + if (argc != args_length + 1) return -1; + + for (int i = 1; i < argc; ++i) { + printf("argv[%d] = %s; expected = %s\n", i, argv[i], expected_utf8[i - 1]); + if (strcmp(expected_utf8[i - 1], argv[i])) { + return i; + } + } + + return 0; +} +#endif diff --git a/xpcom/tests/TestWinReg.js b/xpcom/tests/TestWinReg.js new file mode 100644 index 0000000000..8462f3ff04 --- /dev/null +++ b/xpcom/tests/TestWinReg.js @@ -0,0 +1,64 @@ +/* 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/. */ + +/* + * This script is intended to be run using xpcshell + */ + +const nsIWindowsRegKey = Ci.nsIWindowsRegKey; +const BASE_PATH = "SOFTWARE\\Mozilla\\Firefox"; + +function idump(indent, str) { + for (var j = 0; j < indent; ++j) { + dump(" "); + } + dump(str); +} + +function list_values(indent, key) { + idump(indent, "{\n"); + var count = key.valueCount; + for (var i = 0; i < count; ++i) { + var vn = key.getValueName(i); + var val = ""; + if (key.getValueType(vn) == nsIWindowsRegKey.TYPE_STRING) { + val = key.readStringValue(vn); + } + if (vn == "") { + idump(indent + 1, '(Default): "' + val + '"\n'); + } else { + idump(indent + 1, vn + ': "' + val + '"\n'); + } + } + idump(indent, "}\n"); +} + +function list_children(indent, key) { + list_values(indent, key); + + var count = key.childCount; + for (var i = 0; i < count; ++i) { + var cn = key.getChildName(i); + idump(indent, "[" + cn + "]\n"); + list_children(indent + 1, key.openChild(cn, nsIWindowsRegKey.ACCESS_READ)); + } +} + +// enumerate everything under BASE_PATH +var key = + Cc["@mozilla.org/windows-registry-key;1"].createInstance(nsIWindowsRegKey); +key.open( + nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE, + BASE_PATH, + nsIWindowsRegKey.ACCESS_READ +); +list_children(1, key); + +key.close(); +key.open( + nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + BASE_PATH, + nsIWindowsRegKey.ACCESS_READ +); +list_children(1, key); diff --git a/xpcom/tests/TestingAtomList.h b/xpcom/tests/TestingAtomList.h new file mode 100644 index 0000000000..ffeada6057 --- /dev/null +++ b/xpcom/tests/TestingAtomList.h @@ -0,0 +1,6 @@ +/* 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/. */ + +TESTING_ATOM(foo, "foo") +TESTING_ATOM(bar, "bar") diff --git a/xpcom/tests/crashtests/bug-1714685.html b/xpcom/tests/crashtests/bug-1714685.html new file mode 100644 index 0000000000..a06e9d44cf --- /dev/null +++ b/xpcom/tests/crashtests/bug-1714685.html @@ -0,0 +1,13 @@ + diff --git a/xpcom/tests/crashtests/crashtests.list b/xpcom/tests/crashtests/crashtests.list new file mode 100644 index 0000000000..7c1435dedd --- /dev/null +++ b/xpcom/tests/crashtests/crashtests.list @@ -0,0 +1 @@ +load bug-1714685.html diff --git a/xpcom/tests/gtest/Helpers.cpp b/xpcom/tests/gtest/Helpers.cpp new file mode 100644 index 0000000000..84053cbeb3 --- /dev/null +++ b/xpcom/tests/gtest/Helpers.cpp @@ -0,0 +1,201 @@ +/* -*- 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/. */ + +/* Helper routines for xpcom gtests. */ + +#include "Helpers.h" + +#include +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsIOutputStream.h" +#include "nsStreamUtils.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" + +namespace testing { + +// Populate an array with the given number of bytes. Data is lorem ipsum +// random text, but deterministic across multiple calls. +void CreateData(uint32_t aNumBytes, nsTArray& aDataOut) { + static const char data[] = + "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec egestas " + "purus eu condimentum iaculis. In accumsan leo eget odio porttitor, non " + "rhoncus nulla vestibulum. Etiam lacinia consectetur nisl nec " + "sollicitudin. Sed fringilla accumsan diam, pulvinar varius massa. Duis " + "mollis dignissim felis, eget tempus nisi tristique ut. Fusce euismod, " + "lectus non lacinia tempor, tellus diam suscipit quam, eget hendrerit " + "lacus nunc fringilla ante. Sed ultrices massa vitae risus molestie, ut " + "finibus quam laoreet nullam."; + static const uint32_t dataLength = sizeof(data) - 1; + + aDataOut.SetCapacity(aNumBytes); + + while (aNumBytes > 0) { + uint32_t amount = std::min(dataLength, aNumBytes); + aDataOut.AppendElements(data, amount); + aNumBytes -= amount; + } +} + +// Write the given number of bytes out to the stream. Loop until expected +// bytes count is reached or an error occurs. +void Write(nsIOutputStream* aStream, const nsTArray& aData, + uint32_t aOffset, uint32_t aNumBytes) { + uint32_t remaining = + std::min(aNumBytes, static_cast(aData.Length() - aOffset)); + + while (remaining > 0) { + uint32_t numWritten; + nsresult rv = + aStream->Write(aData.Elements() + aOffset, remaining, &numWritten); + ASSERT_NS_SUCCEEDED(rv); + if (numWritten < 1) { + break; + } + aOffset += numWritten; + remaining -= numWritten; + } +} + +// Write the given number of bytes and then close the stream. +void WriteAllAndClose(nsIOutputStream* aStream, const nsTArray& aData) { + Write(aStream, aData, 0, aData.Length()); + aStream->Close(); +} + +// Synchronously consume the given input stream and validate the resulting data +// against the given array of expected values. +void ConsumeAndValidateStream(nsIInputStream* aStream, + const nsTArray& aExpectedData) { + nsDependentCSubstring data(aExpectedData.Elements(), aExpectedData.Length()); + ConsumeAndValidateStream(aStream, data); +} + +// Synchronously consume the given input stream and validate the resulting data +// against the given string of expected values. +void ConsumeAndValidateStream(nsIInputStream* aStream, + const nsACString& aExpectedData) { + nsAutoCString outputData; + nsresult rv = NS_ConsumeStream(aStream, UINT32_MAX, outputData); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(aExpectedData.Length(), outputData.Length()); + ASSERT_TRUE(aExpectedData.Equals(outputData)); +} + +NS_IMPL_ISUPPORTS(OutputStreamCallback, nsIOutputStreamCallback); + +OutputStreamCallback::OutputStreamCallback() : mCalled(false) {} + +OutputStreamCallback::~OutputStreamCallback() = default; + +NS_IMETHODIMP +OutputStreamCallback::OnOutputStreamReady(nsIAsyncOutputStream* aStream) { + mCalled = true; + return NS_OK; +} + +NS_IMPL_ISUPPORTS(InputStreamCallback, nsIInputStreamCallback); + +InputStreamCallback::InputStreamCallback() : mCalled(false) {} + +InputStreamCallback::~InputStreamCallback() = default; + +NS_IMETHODIMP +InputStreamCallback::OnInputStreamReady(nsIAsyncInputStream* aStream) { + mCalled = true; + return NS_OK; +} + +AsyncStringStream::AsyncStringStream(const nsACString& aBuffer) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); +} + +NS_IMETHODIMP +AsyncStringStream::Available(uint64_t* aLength) { + return mStream->Available(aLength); +} + +NS_IMETHODIMP +AsyncStringStream::StreamStatus() { return mStream->StreamStatus(); } + +NS_IMETHODIMP +AsyncStringStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) { + return mStream->Read(aBuffer, aCount, aReadCount); +} + +NS_IMETHODIMP +AsyncStringStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aResult) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +AsyncStringStream::Close() { + nsresult rv = mStream->Close(); + if (NS_SUCCEEDED(rv)) { + MaybeExecCallback(mCallback, mCallbackEventTarget); + } + return rv; +} + +NS_IMETHODIMP +AsyncStringStream::IsNonBlocking(bool* aNonBlocking) { + return mStream->IsNonBlocking(aNonBlocking); +} + +NS_IMETHODIMP +AsyncStringStream::CloseWithStatus(nsresult aStatus) { return Close(); } + +NS_IMETHODIMP +AsyncStringStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + if (aFlags & nsIAsyncInputStream::WAIT_CLOSURE_ONLY) { + mCallback = aCallback; + mCallbackEventTarget = aEventTarget; + return NS_OK; + } + + MaybeExecCallback(aCallback, aEventTarget); + return NS_OK; +} + +void AsyncStringStream::MaybeExecCallback(nsIInputStreamCallback* aCallback, + nsIEventTarget* aEventTarget) { + if (!aCallback) { + return; + } + + nsCOMPtr callback = aCallback; + nsCOMPtr self = this; + + nsCOMPtr r = NS_NewRunnableFunction( + "AsyncWait", [callback, self]() { callback->OnInputStreamReady(self); }); + + if (aEventTarget) { + aEventTarget->Dispatch(r.forget()); + } else { + r->Run(); + } +} + +NS_IMPL_ISUPPORTS(AsyncStringStream, nsIAsyncInputStream, nsIInputStream) + +NS_IMPL_ADDREF(LengthInputStream); +NS_IMPL_RELEASE(LengthInputStream); + +NS_INTERFACE_MAP_BEGIN(LengthInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, mIsInputStreamLength) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, + mIsAsyncInputStreamLength) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +NS_IMPL_ISUPPORTS(LengthCallback, nsIInputStreamLengthCallback) + +} // namespace testing diff --git a/xpcom/tests/gtest/Helpers.h b/xpcom/tests/gtest/Helpers.h new file mode 100644 index 0000000000..cfd7d6fc2e --- /dev/null +++ b/xpcom/tests/gtest/Helpers.h @@ -0,0 +1,195 @@ +/* -*- 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/. */ + +#ifndef __Helpers_h +#define __Helpers_h + +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIInputStreamLength.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "nsTArrayForwardDeclare.h" +#include "nsThreadUtils.h" +#include + +class nsIInputStream; +class nsIOutputStream; + +namespace testing { + +void CreateData(uint32_t aNumBytes, nsTArray& aDataOut); + +void Write(nsIOutputStream* aStream, const nsTArray& aData, + uint32_t aOffset, uint32_t aNumBytes); + +void WriteAllAndClose(nsIOutputStream* aStream, const nsTArray& aData); + +void ConsumeAndValidateStream(nsIInputStream* aStream, + const nsTArray& aExpectedData); + +void ConsumeAndValidateStream(nsIInputStream* aStream, + const nsACString& aExpectedData); + +class OutputStreamCallback final : public nsIOutputStreamCallback { + public: + OutputStreamCallback(); + + bool Called() const { return mCalled; } + + private: + ~OutputStreamCallback(); + + bool mCalled; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAMCALLBACK +}; + +class InputStreamCallback final : public nsIInputStreamCallback { + public: + InputStreamCallback(); + + bool Called() const { return mCalled; } + + private: + ~InputStreamCallback(); + + bool mCalled; + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAMCALLBACK +}; + +class AsyncStringStream final : public nsIAsyncInputStream { + nsCOMPtr mStream; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + explicit AsyncStringStream(const nsACString& aBuffer); + + private: + ~AsyncStringStream() = default; + + void MaybeExecCallback(nsIInputStreamCallback* aCallback, + nsIEventTarget* aEventTarget); + + nsCOMPtr mCallback; + nsCOMPtr mCallbackEventTarget; +}; + +// This class implements a simple nsIInputStreamLength stream. +class LengthInputStream : public nsIInputStream, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength { + nsCOMPtr mStream; + bool mIsInputStreamLength; + bool mIsAsyncInputStreamLength; + nsresult mLengthRv; + bool mNegativeValue; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + LengthInputStream(const nsACString& aBuffer, bool aIsInputStreamLength, + bool aIsAsyncInputStreamLength, nsresult aLengthRv = NS_OK, + bool aNegativeValue = false) + : mIsInputStreamLength(aIsInputStreamLength), + mIsAsyncInputStreamLength(aIsAsyncInputStreamLength), + mLengthRv(aLengthRv), + mNegativeValue(aNegativeValue) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + return mStream->IsNonBlocking(aNonBlocking); + } + + NS_IMETHOD + Length(int64_t* aLength) override { + if (mNegativeValue) { + *aLength = -1; + } else { + mStream->Available((uint64_t*)aLength); + } + return mLengthRv; + } + + NS_IMETHOD + AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) override { + RefPtr self = this; + nsCOMPtr callback = aCallback; + + nsCOMPtr r = + NS_NewRunnableFunction("AsyncLengthWait", [self, callback]() { + int64_t length; + self->Length(&length); + callback->OnInputStreamLengthReady(self, length); + }); + + return aEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + } + + protected: + virtual ~LengthInputStream() = default; +}; + +class LengthCallback final : public nsIInputStreamLengthCallback { + bool mCalled; + int64_t mSize; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + LengthCallback() : mCalled(false), mSize(0) {} + + NS_IMETHOD + OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream, + int64_t aLength) override { + mCalled = true; + mSize = aLength; + return NS_OK; + } + + bool Called() const { return mCalled; } + + int64_t Size() const { return mSize; } + + private: + ~LengthCallback() = default; +}; + +} // namespace testing + +#endif // __Helpers_h diff --git a/xpcom/tests/gtest/TestAllocReplacement.cpp b/xpcom/tests/gtest/TestAllocReplacement.cpp new file mode 100644 index 0000000000..4b2c41b0f3 --- /dev/null +++ b/xpcom/tests/gtest/TestAllocReplacement.cpp @@ -0,0 +1,106 @@ +/* -*- 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 "mozmemory.h" +#include "gtest/gtest.h" + +// We want to ensure that various functions are hooked properly and that +// allocations are getting routed through jemalloc. The strategy +// pursued below relies on jemalloc_info_ptr knowing about the pointers +// returned by the allocator. If the function has been hooked correctly, +// then jemalloc_info_ptr returns a TagLiveAlloc tag, or TagUnknown +// otherwise. +// We could also check the hooking of |free| and similar functions: once +// we free() the returned pointer, jemalloc_info_ptr would return a tag +// that is not TagLiveAlloc. However, in the GTests environment, with +// other threads running in the background, it is possible for some of +// them to get a new allocation at the same location we just freed, and +// jemalloc_info_ptr would return a TagLiveAlloc tag. + +#define ASSERT_ALLOCATION_HAPPENED(lambda) \ + ASSERT_TRUE(ValidateHookedAllocation(lambda, free)); + +// We do run the risk of OOM'ing when we allocate something...all we can +// do is try to allocate something so small that OOM'ing is unlikely. +const size_t kAllocAmount = 16; + +static bool ValidateHookedAllocation(void* (*aAllocator)(void), + void (*aFreeFunction)(void*)) { + void* p = aAllocator(); + + if (!p) { + return false; + } + + jemalloc_ptr_info_t info; + jemalloc_ptr_info(p, &info); + + // Regardless of whether that call succeeded or failed, we are done with + // the allocated buffer now. + aFreeFunction(p); + + return (info.tag == PtrInfoTag::TagLiveAlloc); +} + +TEST(AllocReplacement, malloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { return malloc(kAllocAmount); }); +} + +TEST(AllocReplacement, calloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { return calloc(1, kAllocAmount); }); +} + +TEST(AllocReplacement, realloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { return realloc(nullptr, kAllocAmount); }); +} + +#if defined(HAVE_POSIX_MEMALIGN) +TEST(AllocReplacement, posix_memalign_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { + void* p = nullptr; + int result = posix_memalign(&p, sizeof(void*), kAllocAmount); + if (result != 0) { + return static_cast(nullptr); + } + return p; + }); +} +#endif + +#if defined(XP_WIN) +# include + +# undef ASSERT_ALLOCATION_HAPPENED +# define ASSERT_ALLOCATION_HAPPENED(lambda) \ + ASSERT_TRUE(ValidateHookedAllocation( \ + lambda, [](void* p) { HeapFree(GetProcessHeap(), 0, p); })); + +TEST(AllocReplacement, HeapAlloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { + HANDLE h = GetProcessHeap(); + return HeapAlloc(h, 0, kAllocAmount); + }); +} + +TEST(AllocReplacement, HeapReAlloc_check) +{ + ASSERT_ALLOCATION_HAPPENED([] { + HANDLE h = GetProcessHeap(); + void* p = HeapAlloc(h, 0, kAllocAmount / 2); + + if (!p) { + return static_cast(nullptr); + } + + return HeapReAlloc(h, 0, p, kAllocAmount); + }); +} +#endif diff --git a/xpcom/tests/gtest/TestArenaAllocator.cpp b/xpcom/tests/gtest/TestArenaAllocator.cpp new file mode 100644 index 0000000000..fb11952927 --- /dev/null +++ b/xpcom/tests/gtest/TestArenaAllocator.cpp @@ -0,0 +1,310 @@ +/* -*- 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/ArenaAllocator.h" +#include "mozilla/ArenaAllocatorExtensions.h" +#include "nsIMemoryReporter.h" // MOZ_MALLOC_SIZE_OF + +#include "gtest/gtest.h" + +using mozilla::ArenaAllocator; + +TEST(ArenaAllocator, Constructor) +{ ArenaAllocator<4096, 4> a; } + +TEST(ArenaAllocator, DefaultAllocate) +{ + // Test default 1-byte alignment. + ArenaAllocator<1024> a; + void* x = a.Allocate(101); + void* y = a.Allocate(101); + + // Given 1-byte aligment, we expect the allocations to follow + // each other exactly. + EXPECT_EQ(uintptr_t(x) + 101, uintptr_t(y)); +} + +TEST(ArenaAllocator, AllocateAlignment) +{ + // Test non-default 8-byte alignment. + static const size_t kAlignment = 8; + ArenaAllocator<1024, kAlignment> a; + + // Make sure aligment is correct for 1-8. + for (size_t i = 1; i <= kAlignment; i++) { + // All of these should be 8 bytes + void* x = a.Allocate(i); + void* y = a.Allocate(i); + EXPECT_EQ(uintptr_t(x) + kAlignment, uintptr_t(y)); + } + + // Test with slightly larger than specified alignment. + void* x = a.Allocate(kAlignment + 1); + void* y = a.Allocate(kAlignment + 1); + + // Given 8-byte aligment, and a non-8-byte aligned request we expect the + // allocations to be padded. + EXPECT_NE(uintptr_t(x) + kAlignment, uintptr_t(y)); + + // We expect 7 bytes of padding to have been added. + EXPECT_EQ(uintptr_t(x) + kAlignment * 2, uintptr_t(y)); +} + +#if 0 +TEST(ArenaAllocator, AllocateZeroBytes) +{ + // This would have to be a death test. Since we chose to provide an + // infallible allocator we can't just return nullptr in the 0 case as + // there's no way to differentiate that from the OOM case. + ArenaAllocator<1024> a; + void* x = a.Allocate(0); + EXPECT_FALSE(x); +} + +TEST(ArenaAllocator, BadAlignment) +{ + // This test causes build failures by triggering the static assert enforcing + // a power-of-two alignment. + ArenaAllocator<256, 3> a; + ArenaAllocator<256, 7> b; + ArenaAllocator<256, 17> c; +} +#endif + +TEST(ArenaAllocator, AllocateMultipleSizes) +{ + // Test non-default 4-byte alignment. + ArenaAllocator<4096, 4> a; + + for (int i = 1; i < 50; i++) { + void* x = a.Allocate(i); + // All the allocations should be aligned properly. + EXPECT_EQ(uintptr_t(x) % 4, uintptr_t(0)); + } + + // Test a large 64-byte alignment + ArenaAllocator<8192, 64> b; + for (int i = 1; i < 100; i++) { + void* x = b.Allocate(i); + // All the allocations should be aligned properly. + EXPECT_EQ(uintptr_t(x) % 64, uintptr_t(0)); + } +} + +TEST(ArenaAllocator, AllocateInDifferentChunks) +{ + // Test default 1-byte alignment. + ArenaAllocator<4096> a; + void* x = a.Allocate(4000); + void* y = a.Allocate(4000); + EXPECT_NE(uintptr_t(x) + 4000, uintptr_t(y)); +} + +TEST(ArenaAllocator, AllocateLargerThanArenaSize) +{ + // Test default 1-byte alignment. + ArenaAllocator<256> a; + void* x = a.Allocate(4000); + void* y = a.Allocate(4000); + EXPECT_TRUE(x); + EXPECT_TRUE(y); + + // Now try a normal allocation, it should behave as expected. + x = a.Allocate(8); + y = a.Allocate(8); + EXPECT_EQ(uintptr_t(x) + 8, uintptr_t(y)); +} + +TEST(ArenaAllocator, AllocationsPerChunk) +{ + // Test that expected number of allocations fit in one chunk. + // We use an alignment of 64-bytes to avoid worrying about differences in + // the header size on 32 and 64-bit platforms. + const size_t kArenaSize = 1024; + const size_t kAlignment = 64; + ArenaAllocator a; + + // With an alignment of 64 bytes we expect the header to take up the first + // alignment sized slot leaving bytes leaving the rest available for + // allocation. + const size_t kAllocationsPerChunk = (kArenaSize / kAlignment) - 1; + void* x = nullptr; + void* y = a.Allocate(kAlignment); + EXPECT_TRUE(y); + for (size_t i = 1; i < kAllocationsPerChunk; i++) { + x = y; + y = a.Allocate(kAlignment); + EXPECT_EQ(uintptr_t(x) + kAlignment, uintptr_t(y)); + } + + // The next allocation should be in a different chunk. + x = y; + y = a.Allocate(kAlignment); + EXPECT_NE(uintptr_t(x) + kAlignment, uintptr_t(y)); +} + +TEST(ArenaAllocator, MemoryIsValid) +{ + // Make multiple allocations and actually access the memory. This is + // expected to trip up ASAN or valgrind if out of bounds memory is + // accessed. + static const size_t kArenaSize = 1024; + static const size_t kAlignment = 64; + static const char kMark = char(0xBC); + ArenaAllocator a; + + // Single allocation that should fill the arena. + size_t sz = kArenaSize - kAlignment; + char* x = (char*)a.Allocate(sz); + EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); + memset(x, kMark, sz); + for (size_t i = 0; i < sz; i++) { + EXPECT_EQ(x[i], kMark); + } + + // Allocation over arena size. + sz = kArenaSize * 2; + x = (char*)a.Allocate(sz); + EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); + memset(x, kMark, sz); + for (size_t i = 0; i < sz; i++) { + EXPECT_EQ(x[i], kMark); + } + + // Allocation half the arena size. + sz = kArenaSize / 2; + x = (char*)a.Allocate(sz); + EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); + memset(x, kMark, sz); + for (size_t i = 0; i < sz; i++) { + EXPECT_EQ(x[i], kMark); + } + + // Repeat, this should actually end up in a new chunk. + x = (char*)a.Allocate(sz); + EXPECT_EQ(uintptr_t(x) % kAlignment, uintptr_t(0)); + memset(x, kMark, sz); + for (size_t i = 0; i < sz; i++) { + EXPECT_EQ(x[i], kMark); + } +} + +MOZ_DEFINE_MALLOC_SIZE_OF(TestSizeOf); + +TEST(ArenaAllocator, SizeOf) +{ + // This tests the sizeof functionality. We can't test for equality as we + // can't reliably guarantee what sizes the underlying allocator is going to + // choose, so we just test that things grow (or not) as expected. + static const size_t kArenaSize = 4096; + ArenaAllocator a; + + // Excluding *this we expect an empty arena allocator to have no overhead. + size_t sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_EQ(sz, size_t(0)); + + // Cause one chunk to be allocated. + (void)a.Allocate(kArenaSize / 2); + size_t prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); + + // Allocate within the current chunk. + (void)a.Allocate(kArenaSize / 4); + prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_EQ(sz, prev_sz); + + // Overflow to a new chunk. + (void)a.Allocate(kArenaSize / 2); + prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); + + // Allocate an oversized chunk with enough room for a header to fit in page + // size. We expect the underlying allocator to round up to page alignment. + (void)a.Allocate((kArenaSize * 2) - 64); + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); +} + +TEST(ArenaAllocator, Clear) +{ + // Tests that the Clear function works as expected. The best proxy for + // checking if a clear is successful is to measure the size. If it's empty we + // expect the size to be 0. + static const size_t kArenaSize = 128; + ArenaAllocator a; + + // Clearing an empty arena should work. + a.Clear(); + + size_t sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_EQ(sz, size_t(0)); + + // Allocating should work after clearing an empty arena. + void* x = a.Allocate(10); + EXPECT_TRUE(x); + + size_t prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); + + // Allocate enough for a few arena chunks to be necessary. + for (size_t i = 0; i < kArenaSize * 2; i++) { + x = a.Allocate(1); + EXPECT_TRUE(x); + } + + prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); + + // Clearing should reduce the size back to zero. + a.Clear(); + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_EQ(sz, size_t(0)); + + // Allocating should work after clearing an arena with allocations. + x = a.Allocate(10); + EXPECT_TRUE(x); + + prev_sz = sz; + sz = a.SizeOfExcludingThis(TestSizeOf); + EXPECT_GT(sz, prev_sz); +} + +TEST(ArenaAllocator, Extensions) +{ + ArenaAllocator<4096, 8> a; + + // Test with raw strings. + const char* const kTestCStr = "This is a test string."; + char* c_dup = mozilla::ArenaStrdup(kTestCStr, a); + EXPECT_STREQ(c_dup, kTestCStr); + + const char16_t* const kTestStr = u"This is a wide test string."; + char16_t* dup = mozilla::ArenaStrdup(kTestStr, a); + EXPECT_TRUE(nsString(dup).Equals(kTestStr)); + + // Make sure it works with literal strings. + constexpr auto wideStr = u"A wide string."_ns; + nsLiteralString::char_type* wide = mozilla::ArenaStrdup(wideStr, a); + EXPECT_TRUE(wideStr.Equals(wide)); + + constexpr auto cStr = "A c-string."_ns; + nsLiteralCString::char_type* cstr = mozilla::ArenaStrdup(cStr, a); + EXPECT_TRUE(cStr.Equals(cstr)); + + // Make sure it works with normal strings. + nsAutoString x(u"testing wide"); + nsAutoString::char_type* x_copy = mozilla::ArenaStrdup(x, a); + EXPECT_TRUE(x.Equals(x_copy)); + + nsAutoCString y("testing c-string"); + nsAutoCString::char_type* y_copy = mozilla::ArenaStrdup(y, a); + EXPECT_TRUE(y.Equals(y_copy)); +} diff --git a/xpcom/tests/gtest/TestArrayAlgorithm.cpp b/xpcom/tests/gtest/TestArrayAlgorithm.cpp new file mode 100644 index 0000000000..fbaf9a6a24 --- /dev/null +++ b/xpcom/tests/gtest/TestArrayAlgorithm.cpp @@ -0,0 +1,108 @@ +/* -*- 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 "gtest/gtest.h" + +#include "mozilla/ArrayAlgorithm.h" +#include "mozilla/Result.h" +#include "mozilla/ResultExtensions.h" +#include "nsTArray.h" + +using namespace mozilla; +using std::begin; +using std::end; + +namespace { +constexpr static int32_t arr1[3] = {1, 2, 3}; +} + +TEST(nsAlgorithm_TransformIntoNewArrayAbortOnErr, NoError_Fallible) +{ + auto res = TransformIntoNewArrayAbortOnErr( + begin(arr1), end(arr1), + [](const int32_t value) -> Result { + return value * 10; + }, + fallible); + ASSERT_TRUE(res.isOk()); + const nsTArray& out = res.inspect(); + + const nsTArray expected = {10, 20, 30}; + ASSERT_EQ(expected, out); +} + +TEST(nsAlgorithm_TransformIntoNewArrayAbortOnErr, NoError_Fallible_Range) +{ + auto res = TransformIntoNewArrayAbortOnErr( + arr1, + [](const int32_t value) -> Result { + return value * 10; + }, + fallible); + ASSERT_TRUE(res.isOk()); + const nsTArray& out = res.inspect(); + + const nsTArray expected = {10, 20, 30}; + ASSERT_EQ(expected, out); +} + +TEST(nsAlgorithm_TransformIntoNewArrayAbortOnErr, ErrorOnOther_Fallible) +{ + auto res = TransformIntoNewArrayAbortOnErr( + begin(arr1), end(arr1), + [](const int32_t value) -> Result { + if (value > 1) { + return Err(NS_ERROR_FAILURE); + } + return value * 10; + }, + fallible); + ASSERT_TRUE(res.isErr()); + ASSERT_EQ(NS_ERROR_FAILURE, res.inspectErr()); +} + +TEST(nsAlgorithm_TransformIntoNewArray, NoError) +{ + auto res = TransformIntoNewArray( + begin(arr1), end(arr1), + [](const int32_t value) -> int64_t { return value * 10; }); + + const nsTArray expected = {10, 20, 30}; + ASSERT_EQ(expected, res); +} + +TEST(nsAlgorithm_TransformIntoNewArray, NoError_Range) +{ + auto res = TransformIntoNewArray( + arr1, [](const int32_t value) -> int64_t { return value * 10; }); + + const nsTArray expected = {10, 20, 30}; + ASSERT_EQ(expected, res); +} + +TEST(nsAlgorithm_TransformIntoNewArray, NoError_Fallible) +{ + auto res = TransformIntoNewArray( + begin(arr1), end(arr1), + [](const int32_t value) -> int64_t { return value * 10; }, fallible); + ASSERT_TRUE(res.isOk()); + const nsTArray& out = res.inspect(); + + const nsTArray expected = {10, 20, 30}; + ASSERT_EQ(expected, out); +} + +TEST(nsAlgorithm_TransformIntoNewArray, NoError_Fallible_Range) +{ + auto res = TransformIntoNewArray( + arr1, [](const int32_t value) -> int64_t { return value * 10; }, + fallible); + ASSERT_TRUE(res.isOk()); + const nsTArray& out = res.inspect(); + + const nsTArray expected = {10, 20, 30}; + ASSERT_EQ(expected, out); +} diff --git a/xpcom/tests/gtest/TestAtoms.cpp b/xpcom/tests/gtest/TestAtoms.cpp new file mode 100644 index 0000000000..53148895d2 --- /dev/null +++ b/xpcom/tests/gtest/TestAtoms.cpp @@ -0,0 +1,177 @@ +/* -*- 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/ArrayUtils.h" + +#include "nsAtom.h" +#include "nsString.h" +#include "UTFStrings.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +using namespace mozilla; + +int32_t NS_GetUnusedAtomCount(void); + +namespace TestAtoms { + +TEST(Atoms, Basic) +{ + for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) { + nsDependentString str16(ValidStrings[i].m16); + nsDependentCString str8(ValidStrings[i].m8); + + RefPtr atom = NS_Atomize(str16); + + EXPECT_TRUE(atom->Equals(str16)); + + nsString tmp16; + nsCString tmp8; + atom->ToString(tmp16); + atom->ToUTF8String(tmp8); + EXPECT_TRUE(str16.Equals(tmp16)); + EXPECT_TRUE(str8.Equals(tmp8)); + + EXPECT_TRUE(nsDependentString(atom->GetUTF16String()).Equals(str16)); + + EXPECT_TRUE(nsAtomString(atom).Equals(str16)); + EXPECT_TRUE(nsDependentAtomString(atom).Equals(str16)); + EXPECT_TRUE(nsAtomCString(atom).Equals(str8)); + } +} + +TEST(Atoms, 16vs8) +{ + for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) { + RefPtr atom16 = NS_Atomize(ValidStrings[i].m16); + RefPtr atom8 = NS_Atomize(ValidStrings[i].m8); + EXPECT_EQ(atom16, atom8); + } +} + +TEST(Atoms, Null) +{ + nsAutoString str(u"string with a \0 char"_ns); + nsDependentString strCut(str.get()); + + EXPECT_FALSE(str.Equals(strCut)); + + RefPtr atomCut = NS_Atomize(strCut); + RefPtr atom = NS_Atomize(str); + + EXPECT_EQ(atom->GetLength(), str.Length()); + EXPECT_TRUE(atom->Equals(str)); + EXPECT_NE(atom, atomCut); + EXPECT_TRUE(atomCut->Equals(strCut)); +} + +TEST(Atoms, Invalid) +{ + for (unsigned int i = 0; i < ArrayLength(Invalid16Strings); ++i) { + nsrefcnt count = NS_GetNumberOfAtoms(); + + { + RefPtr atom16 = NS_Atomize(Invalid16Strings[i].m16); + EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid16Strings[i].m16))); + } + + EXPECT_EQ(count, NS_GetNumberOfAtoms()); + } +#ifndef DEBUG + // Don't run this test in debug builds as that intentionally asserts. + for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) { + nsrefcnt count = NS_GetNumberOfAtoms(); + + { + RefPtr atom8 = NS_Atomize(Invalid8Strings[i].m8); + RefPtr atom16 = NS_Atomize(Invalid8Strings[i].m16); + EXPECT_EQ(atom16, atom8); + EXPECT_TRUE(atom16->Equals(nsDependentString(Invalid8Strings[i].m16))); + } + + EXPECT_EQ(count, NS_GetNumberOfAtoms()); + } + + for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) { + nsrefcnt count = NS_GetNumberOfAtoms(); + + { + RefPtr atom8 = NS_Atomize(Malformed8Strings[i].m8); + RefPtr atom16 = NS_Atomize(Malformed8Strings[i].m16); + EXPECT_EQ(atom8, atom16); + } + + EXPECT_EQ(count, NS_GetNumberOfAtoms()); + } +#endif +} + +#define FIRST_ATOM_STR "first static atom. Hello!" +#define SECOND_ATOM_STR "second static atom. @World!" +#define THIRD_ATOM_STR "third static atom?!" + +static bool isStaticAtom(nsAtom* atom) { + // Don't use logic && in order to ensure that all addrefs/releases are always + // run, even if one of the tests fail. This allows us to run this code on a + // non-static atom without affecting its refcount. + bool rv = (atom->AddRef() == 2); + rv &= (atom->AddRef() == 2); + rv &= (atom->AddRef() == 2); + + rv &= (atom->Release() == 1); + rv &= (atom->Release() == 1); + rv &= (atom->Release() == 1); + return rv; +} + +TEST(Atoms, Table) +{ + nsrefcnt count = NS_GetNumberOfAtoms(); + + RefPtr thirdDynamic = NS_Atomize(THIRD_ATOM_STR); + + EXPECT_FALSE(isStaticAtom(thirdDynamic)); + + EXPECT_TRUE(thirdDynamic); + EXPECT_EQ(NS_GetNumberOfAtoms(), count + 1); +} + +static void AccessAtoms(void*) { + for (int i = 0; i < 10000; i++) { + RefPtr atom = NS_Atomize(u"A Testing Atom"); + } +} + +TEST(Atoms, ConcurrentAccessing) +{ + static const size_t kThreadCount = 4; + // Force a GC before so that we don't have any unused atom. + NS_GetNumberOfAtoms(); + EXPECT_EQ(NS_GetUnusedAtomCount(), int32_t(0)); + + // Spawn PRThreads to do the concurrent atom access, to make sure we don't + // spin the main thread event loop. Spinning the event loop may run a task + // that uses an atom, leading to a false positive test failure. + PRThread* threads[kThreadCount]; + for (size_t i = 0; i < kThreadCount; i++) { + threads[i] = PR_CreateThread(PR_USER_THREAD, AccessAtoms, nullptr, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 0); + EXPECT_TRUE(threads[i]); + } + + for (size_t i = 0; i < kThreadCount; i++) { + EXPECT_EQ(PR_SUCCESS, PR_JoinThread(threads[i])); + } + + // We should have one unused atom from this test. + EXPECT_EQ(NS_GetUnusedAtomCount(), int32_t(1)); +} + +} // namespace TestAtoms diff --git a/xpcom/tests/gtest/TestAutoRefCnt.cpp b/xpcom/tests/gtest/TestAutoRefCnt.cpp new file mode 100644 index 0000000000..92cc0d2538 --- /dev/null +++ b/xpcom/tests/gtest/TestAutoRefCnt.cpp @@ -0,0 +1,66 @@ +/* -*- 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 "nsISupportsImpl.h" + +#include "mozilla/Atomics.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +using namespace mozilla; + +class nsThreadSafeAutoRefCntRunner final : public Runnable { + public: + NS_IMETHOD Run() final { + for (int i = 0; i < 10000; i++) { + if (++sRefCnt == 1) { + sIncToOne++; + } + if (--sRefCnt == 0) { + sDecToZero++; + } + } + return NS_OK; + } + + static ThreadSafeAutoRefCnt sRefCnt; + static Atomic sIncToOne; + static Atomic sDecToZero; + + nsThreadSafeAutoRefCntRunner() : Runnable("nsThreadSafeAutoRefCntRunner") {} + + private: + ~nsThreadSafeAutoRefCntRunner() = default; +}; + +ThreadSafeAutoRefCnt nsThreadSafeAutoRefCntRunner::sRefCnt; +Atomic nsThreadSafeAutoRefCntRunner::sIncToOne(0); +Atomic nsThreadSafeAutoRefCntRunner::sDecToZero(0); + +// When a refcounted object is actually owned by a cache, we may not +// want to release the object after last reference gets released. In +// this pattern, the cache may rely on the balance of increment to one +// and decrement to zero, so that it can maintain a counter for GC. +TEST(AutoRefCnt, ThreadSafeAutoRefCntBalance) +{ + static const size_t kThreadCount = 4; + nsCOMPtr threads[kThreadCount]; + for (size_t i = 0; i < kThreadCount; i++) { + nsresult rv = + NS_NewNamedThread("AutoRefCnt Test", getter_AddRefs(threads[i]), + new nsThreadSafeAutoRefCntRunner); + EXPECT_NS_SUCCEEDED(rv); + } + for (size_t i = 0; i < kThreadCount; i++) { + threads[i]->Shutdown(); + } + EXPECT_EQ(nsThreadSafeAutoRefCntRunner::sRefCnt, nsrefcnt(0)); + EXPECT_EQ(nsThreadSafeAutoRefCntRunner::sIncToOne, + nsThreadSafeAutoRefCntRunner::sDecToZero); +} diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp new file mode 100644 index 0000000000..56d6f03ff8 --- /dev/null +++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherLinux.cpp @@ -0,0 +1,227 @@ +/* -*- 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 // For memory-locking. + +#include "gtest/gtest.h" + +#include "AvailableMemoryWatcher.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPrefs_browser.h" +#include "nsIObserverService.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" + +using namespace mozilla; + +namespace { + +// Dummy tab unloader whose one job is to dispatch a low memory event. +class MockTabUnloader final : public nsITabUnloader { + NS_DECL_THREADSAFE_ISUPPORTS + public: + MockTabUnloader() = default; + + NS_IMETHOD UnloadTabAsync() override { + // We want to issue a memory pressure event for + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + return NS_OK; + } + + private: + ~MockTabUnloader() = default; +}; + +NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader) + +// Class that gradually increases the percent memory threshold +// until it reaches 100%, which should guarantee a memory pressure +// notification. +class AvailableMemoryChecker final : public nsITimerCallback, public nsINamed { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERCALLBACK + NS_DECL_NSINAMED + + AvailableMemoryChecker(); + void Init(); + void Shutdown(); + + private: + ~AvailableMemoryChecker() = default; + + bool mResolved; + nsCOMPtr mTimer; + RefPtr mWatcher; + RefPtr mTabUnloader; + + const uint32_t kPollingInterval = 50; + const uint32_t kPrefIncrement = 5; +}; + +AvailableMemoryChecker::AvailableMemoryChecker() : mResolved(false) {} + +NS_IMPL_ISUPPORTS(AvailableMemoryChecker, nsITimerCallback, nsINamed); + +void AvailableMemoryChecker::Init() { + mTabUnloader = new MockTabUnloader; + + mWatcher = nsAvailableMemoryWatcherBase::GetSingleton(); + mWatcher->RegisterTabUnloader(mTabUnloader); + + mTimer = NS_NewTimer(); + mTimer->InitWithCallback(this, kPollingInterval, + nsITimer::TYPE_REPEATING_SLACK); +} + +void AvailableMemoryChecker::Shutdown() { + if (mTimer) { + mTimer->Cancel(); + } + Preferences::ClearUser("browser.low_commit_space_threshold_percent"); +} + +// Timer callback to increase the pref threshold. +NS_IMETHODIMP +AvailableMemoryChecker::Notify(nsITimer* aTimer) { + uint32_t threshold = + StaticPrefs::browser_low_commit_space_threshold_percent(); + if (threshold >= 100) { + mResolved = true; + return NS_OK; + } + threshold += kPrefIncrement; + Preferences::SetUint("browser.low_commit_space_threshold_percent", threshold); + return NS_OK; +} + +NS_IMETHODIMP AvailableMemoryChecker::GetName(nsACString& aName) { + aName.AssignLiteral("AvailableMemoryChecker"); + return NS_OK; +} + +// Class that listens for a given notification, then records +// if it was received. +class Spinner final : public nsIObserver { + nsCOMPtr mObserverSvc; + nsDependentCString mTopic; + bool mTopicObserved; + + ~Spinner() = default; + + public: + NS_DECL_ISUPPORTS + + Spinner(nsIObserverService* aObserverSvc, const char* aTopic) + : mObserverSvc(aObserverSvc), mTopic(aTopic), mTopicObserved(false) {} + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (mTopic == aTopic) { + mTopicObserved = true; + mObserverSvc->RemoveObserver(this, aTopic); + + // Force the loop to move in case there is no event in the queue. + nsCOMPtr dummyEvent = new Runnable(__func__); + NS_DispatchToMainThread(dummyEvent); + } + return NS_OK; + } + void StartListening() { + mObserverSvc->AddObserver(this, mTopic.get(), false); + } + bool TopicObserved() { return mTopicObserved; } + bool WaitForNotification(); +}; +NS_IMPL_ISUPPORTS(Spinner, nsIObserver); + +bool Spinner::WaitForNotification() { + bool isTimeout = false; + + nsCOMPtr timer; + + // This timer should time us out if we never observe our notification. + // Set to 5000 since the memory checker should finish incrementing the + // pref by then, and if it hasn't then it is probably stuck somehow. + NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), + [](nsITimer*, void* isTimeout) { + *reinterpret_cast(isTimeout) = true; + }, + &isTimeout, 5000, nsITimer::TYPE_ONE_SHOT, __func__); + + SpinEventLoopUntil("Spinner:WaitForNotification"_ns, [&]() -> bool { + if (isTimeout) { + return true; + } + return mTopicObserved; + }); + return !isTimeout; +} + +void StartUserInteraction(const nsCOMPtr& aObserverSvc) { + aObserverSvc->NotifyObservers(nullptr, "user-interaction-active", nullptr); +} + +TEST(AvailableMemoryWatcher, BasicTest) +{ + nsCOMPtr observerSvc = services::GetObserverService(); + RefPtr aSpinner = new Spinner(observerSvc, "memory-pressure"); + aSpinner->StartListening(); + + // Start polling for low memory. + StartUserInteraction(observerSvc); + + RefPtr checker = new AvailableMemoryChecker(); + checker->Init(); + + aSpinner->WaitForNotification(); + + // The checker should have dispatched a low memory event before reaching 100% + // memory pressure threshold, so the topic should be observed by the spinner. + EXPECT_TRUE(aSpinner->TopicObserved()); + checker->Shutdown(); +} + +TEST(AvailableMemoryWatcher, MemoryLowToHigh) +{ + // Setting this pref to 100 ensures we start in a low memory scenario. + Preferences::SetUint("browser.low_commit_space_threshold_percent", 100); + + nsCOMPtr observerSvc = services::GetObserverService(); + RefPtr lowMemorySpinner = + new Spinner(observerSvc, "memory-pressure"); + lowMemorySpinner->StartListening(); + + StartUserInteraction(observerSvc); + + // Start polling for low memory. We should start with low memory when we start + // the checker. + RefPtr checker = new AvailableMemoryChecker(); + checker->Init(); + + lowMemorySpinner->WaitForNotification(); + + EXPECT_TRUE(lowMemorySpinner->TopicObserved()); + + RefPtr highMemorySpinner = + new Spinner(observerSvc, "memory-pressure-stop"); + highMemorySpinner->StartListening(); + + // Now that we are definitely low on memory, let's reset the pref to 0 to + // exit low memory. + Preferences::SetUint("browser.low_commit_space_threshold_percent", 0); + + highMemorySpinner->WaitForNotification(); + + EXPECT_TRUE(highMemorySpinner->TopicObserved()); + + checker->Shutdown(); +} +} // namespace diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp new file mode 100644 index 0000000000..cbd564b7db --- /dev/null +++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherMac.cpp @@ -0,0 +1,226 @@ +/* -*- 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 "gtest/gtest.h" + +#include "AvailableMemoryWatcher.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/Unused.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" + +using namespace mozilla; + +namespace { + +template +bool WaitUntil(const ConditionT& aCondition, uint32_t aTimeoutMs) { + bool isTimeout = false; + + // The message queue can be empty and the loop stops + // waiting for a new event before detecting timeout. + // Creating a timer to fire a timeout event. + nsCOMPtr timer; + NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), + [](nsITimer*, void* isTimeout) { + *reinterpret_cast(isTimeout) = true; + }, + &isTimeout, aTimeoutMs, nsITimer::TYPE_ONE_SHOT, __func__); + + SpinEventLoopUntil("TestAvailableMemoryWatcherMac"_ns, [&]() -> bool { + if (isTimeout) { + return true; + } + return aCondition(); + }); + + return !isTimeout; +} + +class Spinner final : public nsIObserver { + nsCOMPtr mObserverSvc; + nsDependentCString mTopicToWatch; + bool mTopicObserved; + + ~Spinner() = default; + + public: + NS_DECL_ISUPPORTS + + Spinner(nsIObserverService* aObserverSvc, const char* const aTopic, + const char16_t* const aSubTopic) + : mObserverSvc(aObserverSvc), + mTopicToWatch(aTopic), + mTopicObserved(false) {} + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (mTopicToWatch == aTopic) { + mTopicObserved = true; + mObserverSvc->RemoveObserver(this, aTopic); + + // Force the loop to move in case that there is no event in the queue. + nsCOMPtr dummyEvent = new Runnable(__func__); + NS_DispatchToMainThread(dummyEvent); + } + return NS_OK; + } + + void StartListening() { + mTopicObserved = false; + mObserverSvc->AddObserver(this, mTopicToWatch.get(), false); + } + + bool Wait(uint32_t aTimeoutMs) { + return WaitUntil([this]() { return this->mTopicObserved; }, aTimeoutMs); + } +}; + +NS_IMPL_ISUPPORTS(Spinner, nsIObserver) + +class MockTabUnloader final : public nsITabUnloader { + ~MockTabUnloader() = default; + + uint32_t mCounter; + + public: + MockTabUnloader() : mCounter(0) {} + + NS_DECL_THREADSAFE_ISUPPORTS + + void ResetCounter() { mCounter = 0; } + uint32_t GetCounter() const { return mCounter; } + + NS_IMETHOD UnloadTabAsync() override { + ++mCounter; + // Issue a memory-pressure to verify OnHighMemory issues + // a memory-pressure-stop event. + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader) + +} // namespace + +class AvailableMemoryWatcherFixture : public TelemetryTestFixture { + nsCOMPtr mObserverSvc; + + protected: + RefPtr mWatcher; + RefPtr mHighMemoryObserver; + RefPtr mLowMemoryObserver; + RefPtr mTabUnloader; + + static constexpr uint32_t kStateChangeTimeoutMs = 20000; + static constexpr uint32_t kNotificationTimeoutMs = 20000; + + void SetUp() override { + TelemetryTestFixture::SetUp(); + + mObserverSvc = do_GetService(NS_OBSERVERSERVICE_CONTRACTID); + ASSERT_TRUE(mObserverSvc); + + mHighMemoryObserver = + new Spinner(mObserverSvc, "memory-pressure-stop", nullptr); + mLowMemoryObserver = new Spinner(mObserverSvc, "memory-pressure", nullptr); + + mTabUnloader = new MockTabUnloader; + + mWatcher = nsAvailableMemoryWatcherBase::GetSingleton(); + mWatcher->RegisterTabUnloader(mTabUnloader); + } +}; + +class MemoryWatcherTelemetryEvent { + static nsLiteralString sEventCategory; + static nsLiteralString sEventMethod; + static nsLiteralString sEventObject; + uint32_t mLastCountOfEvents; + + public: + explicit MemoryWatcherTelemetryEvent(JSContext* aCx) : mLastCountOfEvents(0) { + JS::RootedValue snapshot(aCx); + TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot); + nsTArray eventValues = TelemetryTestHelpers::EventValuesToArray( + aCx, snapshot, sEventCategory, sEventMethod, sEventObject); + mLastCountOfEvents = eventValues.Length(); + } + + void ValidateLastEvent(JSContext* aCx) { + JS::RootedValue snapshot(aCx); + TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot); + nsTArray eventValues = TelemetryTestHelpers::EventValuesToArray( + aCx, snapshot, sEventCategory, sEventMethod, sEventObject); + + // A new event was generated. + EXPECT_EQ(eventValues.Length(), mLastCountOfEvents + 1); + if (eventValues.IsEmpty()) { + return; + } + + // Update mLastCountOfEvents for a subsequent call to ValidateLastEvent + ++mLastCountOfEvents; + + nsTArray tokens; + for (const nsAString& token : eventValues.LastElement().Split(',')) { + tokens.AppendElement(token); + } + EXPECT_EQ(tokens.Length(), 3U); + + // The third token should be a valid floating number. + nsresult rv; + tokens[2].ToDouble(&rv); + EXPECT_NS_SUCCEEDED(rv); + } +}; + +nsLiteralString MemoryWatcherTelemetryEvent::sEventCategory = + u"memory_watcher"_ns; +nsLiteralString MemoryWatcherTelemetryEvent::sEventMethod = + u"on_high_memory"_ns; +nsLiteralString MemoryWatcherTelemetryEvent::sEventObject = u"stats"_ns; + +/* + * Test the browser memory pressure reponse by artificially putting the system + * into the "critical" level and ensuring 1) a tab unload attempt occurs and + * 2) the Gecko memory-pressure notitificiation start and stop events occur. + */ +TEST_F(AvailableMemoryWatcherFixture, MemoryPressureResponse) { + // Set the memory pressure state to normal in case we are already + // running in a low memory pressure state. + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eNormal); + + // Reset state + mTabUnloader->ResetCounter(); + AutoJSContextWithGlobal cx(mCleanGlobal); + MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext()); + + // Simulate a low memory OS callback and make sure we observe + // a memory-pressure event and a tab unload. + mLowMemoryObserver->StartListening(); + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eWarning); + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eCritical); + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + EXPECT_TRUE(mLowMemoryObserver->Wait(kStateChangeTimeoutMs)); + + // Simulate the normal memory pressure OS callback and make + // sure we observe a memory-pressure-stop event. + mHighMemoryObserver->StartListening(); + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eWarning); + mWatcher->OnMemoryPressureChanged(MacMemoryPressureLevel::Value::eNormal); + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); + + telemetryEvent.ValidateLastEvent(cx.GetJSContext()); +} diff --git a/xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp b/xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp new file mode 100644 index 0000000000..409d547aaa --- /dev/null +++ b/xpcom/tests/gtest/TestAvailableMemoryWatcherWin.cpp @@ -0,0 +1,663 @@ +/* -*- 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 +#include +#include +#include "gtest/gtest.h" + +#include "AvailableMemoryWatcher.h" +#include "mozilla/Atomics.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/Unused.h" +#include "mozilla/Vector.h" +#include "nsComponentManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsITimer.h" +#include "nsMemoryPressure.h" +#include "nsWindowsHelpers.h" +#include "nsIWindowsRegKey.h" +#include "nsXULAppAPI.h" +#include "TelemetryFixture.h" +#include "TelemetryTestHelpers.h" + +using namespace mozilla; + +namespace { + +static constexpr size_t kBytesInMB = 1024 * 1024; + +template +bool WaitUntil(const ConditionT& aCondition, uint32_t aTimeoutMs) { + const uint64_t t0 = ::GetTickCount64(); + bool isTimeout = false; + + // The message queue can be empty and the loop stops + // waiting for a new event before detecting timeout. + // Creating a timer to fire a timeout event. + nsCOMPtr timer; + NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), + [](nsITimer*, void* isTimeout) { + *reinterpret_cast(isTimeout) = true; + }, + &isTimeout, aTimeoutMs, nsITimer::TYPE_ONE_SHOT, __func__); + + SpinEventLoopUntil("xpcom-tests:WaitUntil"_ns, [&]() -> bool { + if (isTimeout) { + return true; + } + + bool done = aCondition(); + if (done) { + fprintf(stderr, "Done in %llu msec\n", ::GetTickCount64() - t0); + } + return done; + }); + + return !isTimeout; +} + +class Spinner final : public nsIObserver { + nsCOMPtr mObserverSvc; + nsDependentCString mTopicToWatch; + Maybe mSubTopicToWatch; + bool mTopicObserved; + + ~Spinner() = default; + + public: + NS_DECL_ISUPPORTS + + Spinner(nsIObserverService* aObserverSvc, const char* const aTopic, + const char16_t* const aSubTopic) + : mObserverSvc(aObserverSvc), + mTopicToWatch(aTopic), + mSubTopicToWatch(aSubTopic ? Some(nsDependentString(aSubTopic)) + : Nothing()), + mTopicObserved(false) {} + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + if (mTopicToWatch == aTopic) { + if ((mSubTopicToWatch.isNothing() && !aData) || + mSubTopicToWatch.ref() == aData) { + mTopicObserved = true; + mObserverSvc->RemoveObserver(this, aTopic); + + // Force the loop to move in case that there is no event in the queue. + nsCOMPtr dummyEvent = new Runnable(__func__); + NS_DispatchToMainThread(dummyEvent); + } + } else { + fprintf(stderr, "Unexpected topic: %s\n", aTopic); + } + + return NS_OK; + } + + void StartListening() { + mTopicObserved = false; + mObserverSvc->AddObserver(this, mTopicToWatch.get(), false); + } + + bool Wait(uint32_t aTimeoutMs) { + return WaitUntil([this]() { return this->mTopicObserved; }, aTimeoutMs); + } +}; + +NS_IMPL_ISUPPORTS(Spinner, nsIObserver) + +/** + * Starts a new thread with a message queue to process + * memory allocation/free requests + */ +class MemoryEater { + using PageT = UniquePtr; + + static DWORD WINAPI ThreadStart(LPVOID aParam) { + return reinterpret_cast(aParam)->ThreadProc(); + } + + static void TouchMemory(void* aAddr, size_t aSize) { + constexpr uint32_t kPageSize = 4096; + volatile uint8_t x = 0; + auto base = reinterpret_cast(aAddr); + for (int64_t i = 0, pages = aSize / kPageSize; i < pages; ++i) { + // Pick a random place in every allocated page + // and dereference it. + x ^= *(base + i * kPageSize + rand() % kPageSize); + } + (void)x; + } + + static uint32_t GetAvailablePhysicalMemoryInMb() { + MEMORYSTATUSEX statex = {sizeof(statex)}; + if (!::GlobalMemoryStatusEx(&statex)) { + return 0; + } + + return static_cast(statex.ullAvailPhys / kBytesInMB); + } + + static bool AddWorkingSet(size_t aSize, Vector& aOutput) { + constexpr size_t kMinGranularity = 64 * 1024; + + size_t currentSize = aSize; + while (aSize >= kMinGranularity) { + if (!GetAvailablePhysicalMemoryInMb()) { + // If the available physical memory is less than 1MB, we finish + // allocation though there may be still the available commit space. + fprintf(stderr, "No enough physical memory.\n"); + return false; + } + + PageT page(::VirtualAlloc(nullptr, currentSize, MEM_RESERVE | MEM_COMMIT, + PAGE_READWRITE)); + if (!page) { + DWORD gle = ::GetLastError(); + if (gle != ERROR_COMMITMENT_LIMIT) { + return false; + } + + // Try again with a smaller allocation size. + currentSize /= 2; + continue; + } + + aSize -= currentSize; + + // VirtualAlloc consumes the commit space, but we need to *touch* memory + // to consume physical memory + TouchMemory(page.get(), currentSize); + Unused << aOutput.emplaceBack(std::move(page)); + } + return true; + } + + DWORD mThreadId; + nsAutoHandle mThread; + nsAutoHandle mMessageQueueReady; + Atomic mTaskStatus; + + enum class TaskType : UINT { + Alloc = WM_USER, // WPARAM = Allocation size + Free, + + Last, + }; + + DWORD ThreadProc() { + Vector stock; + MSG msg; + + // Force the system to create a message queue + ::PeekMessage(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE); + + // Ready to get a message. Unblock the main thread. + ::SetEvent(mMessageQueueReady.get()); + + for (;;) { + BOOL result = ::GetMessage(&msg, reinterpret_cast(-1), WM_QUIT, + static_cast(TaskType::Last)); + if (result == -1) { + return ::GetLastError(); + } + if (!result) { + // Got WM_QUIT + break; + } + + switch (static_cast(msg.message)) { + case TaskType::Alloc: + mTaskStatus = AddWorkingSet(msg.wParam, stock); + break; + case TaskType::Free: + stock = Vector(); + mTaskStatus = true; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected message in the queue"); + break; + } + } + + return static_cast(msg.wParam); + } + + bool PostTask(TaskType aTask, WPARAM aW = 0, LPARAM aL = 0) const { + return !!::PostThreadMessageW(mThreadId, static_cast(aTask), aW, aL); + } + + public: + MemoryEater() + : mThread(::CreateThread(nullptr, 0, ThreadStart, this, 0, &mThreadId)), + mMessageQueueReady(::CreateEventW(nullptr, /*bManualReset*/ TRUE, + /*bInitialState*/ FALSE, nullptr)) { + ::WaitForSingleObject(mMessageQueueReady.get(), INFINITE); + } + + ~MemoryEater() { + ::PostThreadMessageW(mThreadId, WM_QUIT, 0, 0); + if (::WaitForSingleObject(mThread.get(), 30000) != WAIT_OBJECT_0) { + ::TerminateThread(mThread.get(), 0); + } + } + + bool GetTaskStatus() const { return mTaskStatus; } + void RequestAlloc(size_t aSize) { PostTask(TaskType::Alloc, aSize); } + void RequestFree() { PostTask(TaskType::Free); } +}; + +class MockTabUnloader final : public nsITabUnloader { + ~MockTabUnloader() = default; + + uint32_t mCounter; + + public: + MockTabUnloader() : mCounter(0) {} + + NS_DECL_THREADSAFE_ISUPPORTS + + void ResetCounter() { mCounter = 0; } + uint32_t GetCounter() const { return mCounter; } + + NS_IMETHOD UnloadTabAsync() override { + ++mCounter; + // Issue a memory-pressure to verify OnHighMemory issues + // a memory-pressure-stop event. + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(MockTabUnloader, nsITabUnloader) + +} // namespace + +class AvailableMemoryWatcherFixture : public TelemetryTestFixture { + static const char kPrefLowCommitSpaceThreshold[]; + + RefPtr mWatcher; + nsCOMPtr mObserverSvc; + + protected: + static bool IsPageFileExpandable() { + const auto kMemMgmtKey = + u"SYSTEM\\CurrentControlSet\\Control\\" + u"Session Manager\\Memory Management"_ns; + + nsresult rv; + nsCOMPtr regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + if (NS_FAILED(rv)) { + return false; + } + + rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, kMemMgmtKey, + nsIWindowsRegKey::ACCESS_READ); + if (NS_FAILED(rv)) { + return false; + } + + nsAutoString pagingFiles; + rv = regKey->ReadStringValue(u"PagingFiles"_ns, pagingFiles); + if (NS_FAILED(rv)) { + return false; + } + + // The value data is REG_MULTI_SZ and each element is " ". + // If the page file size is automatically managed for all drives, the + // is set to "?:\pagefile.sys". + // If the page file size is configured per drive, for a drive whose page + // file is set to "system managed size", both and are set to 0. + return !pagingFiles.IsEmpty() && + (pagingFiles[0] == u'?' || FindInReadable(u" 0 0"_ns, pagingFiles)); + } + + static size_t GetAllocationSizeToTriggerMemoryNotification() { + // The percentage of the used physical memory to the total physical memory + // size which is big enough to trigger a memory resource notification. + constexpr uint32_t kThresholdPercentage = 98; + // If the page file is not expandable, leave a little commit space. + const uint32_t kMinimumSafeCommitSpaceMb = + IsPageFileExpandable() ? 0 : 1024; + + MEMORYSTATUSEX statex = {sizeof(statex)}; + EXPECT_TRUE(::GlobalMemoryStatusEx(&statex)); + + // How much memory needs to be used to trigger the notification + const size_t targetUsedTotalMb = + (statex.ullTotalPhys / kBytesInMB) * kThresholdPercentage / 100; + + // How much memory is currently consumed + const size_t currentConsumedMb = + (statex.ullTotalPhys - statex.ullAvailPhys) / kBytesInMB; + + if (currentConsumedMb >= targetUsedTotalMb) { + fprintf(stderr, "The available physical memory is already low.\n"); + return 0; + } + + // How much memory we need to allocate to trigger the notification + const uint32_t allocMb = targetUsedTotalMb - currentConsumedMb; + + // If we allocate the target amount, how much commit space will be + // left available. + const uint32_t estimtedAvailCommitSpace = std::max( + 0, + static_cast((statex.ullAvailPageFile / kBytesInMB) - allocMb)); + + // If the available commit space will be too low, we should not continue + if (estimtedAvailCommitSpace < kMinimumSafeCommitSpaceMb) { + fprintf(stderr, "The available commit space will be short - %d\n", + estimtedAvailCommitSpace); + return 0; + } + + fprintf(stderr, + "Total physical memory = %ul\n" + "Available commit space = %ul\n" + "Amount to allocate = %ul\n" + "Future available commit space after allocation = %d\n", + static_cast(statex.ullTotalPhys / kBytesInMB), + static_cast(statex.ullAvailPageFile / kBytesInMB), + allocMb, estimtedAvailCommitSpace); + return allocMb * kBytesInMB; + } + + static void SetThresholdAsPercentageOfCommitSpace(uint32_t aPercentage) { + aPercentage = std::min(100u, aPercentage); + + MEMORYSTATUSEX statex = {sizeof(statex)}; + EXPECT_TRUE(::GlobalMemoryStatusEx(&statex)); + + const uint32_t newVal = static_cast( + (statex.ullAvailPageFile / kBytesInMB) * aPercentage / 100); + fprintf(stderr, "Setting %s to %u\n", kPrefLowCommitSpaceThreshold, newVal); + + Preferences::SetUint(kPrefLowCommitSpaceThreshold, newVal); + } + + static constexpr uint32_t kStateChangeTimeoutMs = 20000; + static constexpr uint32_t kNotificationTimeoutMs = 20000; + + RefPtr mHighMemoryObserver; + RefPtr mTabUnloader; + MemoryEater mMemEater; + nsAutoHandle mLowMemoryHandle; + + void SetUp() override { + TelemetryTestFixture::SetUp(); + + mObserverSvc = do_GetService(NS_OBSERVERSERVICE_CONTRACTID); + ASSERT_TRUE(mObserverSvc); + + mHighMemoryObserver = + new Spinner(mObserverSvc, "memory-pressure-stop", nullptr); + mTabUnloader = new MockTabUnloader; + + mWatcher = nsAvailableMemoryWatcherBase::GetSingleton(); + mWatcher->RegisterTabUnloader(mTabUnloader); + + mLowMemoryHandle.own( + ::CreateMemoryResourceNotification(LowMemoryResourceNotification)); + ASSERT_TRUE(mLowMemoryHandle); + + // We set the threshold to 50% of the current available commit space. + // This means we declare low-memory when the available commit space + // gets lower than this threshold, otherwise we declare high-memory. + SetThresholdAsPercentageOfCommitSpace(50); + } + + void TearDown() override { + StopUserInteraction(); + Preferences::ClearUser(kPrefLowCommitSpaceThreshold); + } + + bool WaitForMemoryResourceNotification() { + uint64_t t0 = ::GetTickCount64(); + if (::WaitForSingleObject(mLowMemoryHandle, kNotificationTimeoutMs) != + WAIT_OBJECT_0) { + fprintf(stderr, "The memory notification was not triggered.\n"); + return false; + } + fprintf(stderr, "Notified in %llu msec\n", ::GetTickCount64() - t0); + return true; + } + + void StartUserInteraction() { + mObserverSvc->NotifyObservers(nullptr, "user-interaction-active", nullptr); + } + + void StopUserInteraction() { + mObserverSvc->NotifyObservers(nullptr, "user-interaction-inactive", + nullptr); + } +}; + +const char AvailableMemoryWatcherFixture::kPrefLowCommitSpaceThreshold[] = + "browser.low_commit_space_threshold_mb"; + +class MemoryWatcherTelemetryEvent { + static nsLiteralString sEventCategory; + static nsLiteralString sEventMethod; + static nsLiteralString sEventObject; + + uint32_t mLastCountOfEvents; + + public: + explicit MemoryWatcherTelemetryEvent(JSContext* aCx) : mLastCountOfEvents(0) { + JS::RootedValue snapshot(aCx); + TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot); + nsTArray eventValues = TelemetryTestHelpers::EventValuesToArray( + aCx, snapshot, sEventCategory, sEventMethod, sEventObject); + mLastCountOfEvents = eventValues.Length(); + } + + void ValidateLastEvent(JSContext* aCx) { + JS::RootedValue snapshot(aCx); + TelemetryTestHelpers::GetEventSnapshot(aCx, &snapshot); + nsTArray eventValues = TelemetryTestHelpers::EventValuesToArray( + aCx, snapshot, sEventCategory, sEventMethod, sEventObject); + + // A new event was generated. + EXPECT_EQ(eventValues.Length(), mLastCountOfEvents + 1); + if (eventValues.IsEmpty()) { + return; + } + + // Update mLastCountOfEvents for a subsequent call to ValidateLastEvent + ++mLastCountOfEvents; + + nsTArray tokens; + for (const nsAString& token : eventValues.LastElement().Split(',')) { + tokens.AppendElement(token); + } + EXPECT_EQ(tokens.Length(), 3U); + if (tokens.Length() != 3U) { + const wchar_t* valueStr = eventValues.LastElement().get(); + fprintf(stderr, "Unexpected event value: %S\n", valueStr); + return; + } + + // Since this test does not involve TabUnloader, the first two numbers + // are always expected to be zero. + EXPECT_STREQ(tokens[0].get(), L"0"); + EXPECT_STREQ(tokens[1].get(), L"0"); + + // The third token should be a valid floating number. + nsresult rv; + tokens[2].ToDouble(&rv); + EXPECT_NS_SUCCEEDED(rv); + } +}; + +nsLiteralString MemoryWatcherTelemetryEvent::sEventCategory = + u"memory_watcher"_ns; +nsLiteralString MemoryWatcherTelemetryEvent::sEventMethod = + u"on_high_memory"_ns; +nsLiteralString MemoryWatcherTelemetryEvent::sEventObject = u"stats"_ns; + +TEST_F(AvailableMemoryWatcherFixture, AlwaysActive) { + AutoJSContextWithGlobal cx(mCleanGlobal); + MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext()); + StartUserInteraction(); + + const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification(); + if (!allocSize) { + // Not enough memory to safely create a low-memory situation. + // Aborting the test without failure. + return; + } + + mTabUnloader->ResetCounter(); + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + // If the notification was not triggered, abort the test without failure + // because it's not a fault in nsAvailableMemoryWatcher. + return; + } + + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + + mHighMemoryObserver->StartListening(); + mMemEater.RequestFree(); + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); + + telemetryEvent.ValidateLastEvent(cx.GetJSContext()); +} + +TEST_F(AvailableMemoryWatcherFixture, InactiveToActive) { + AutoJSContextWithGlobal cx(mCleanGlobal); + MemoryWatcherTelemetryEvent telemetryEvent(cx.GetJSContext()); + const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification(); + if (!allocSize) { + // Not enough memory to safely create a low-memory situation. + // Aborting the test without failure. + return; + } + + mTabUnloader->ResetCounter(); + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + // If the notification was not triggered, abort the test without failure + // because it's not a fault in nsAvailableMemoryWatcher. + return; + } + + mHighMemoryObserver->StartListening(); + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + + mMemEater.RequestFree(); + + // OnHighMemory should not be triggered during no user interaction + // eve after all memory was freed. Expecting false. + EXPECT_FALSE(mHighMemoryObserver->Wait(3000)); + + StartUserInteraction(); + + // After user is active, we expect true. + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); + + telemetryEvent.ValidateLastEvent(cx.GetJSContext()); +} + +TEST_F(AvailableMemoryWatcherFixture, HighCommitSpace_AlwaysActive) { + // Setting a low threshold simulates a high commit space. + SetThresholdAsPercentageOfCommitSpace(1); + StartUserInteraction(); + + const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification(); + if (!allocSize) { + // Not enough memory to safely create a low-memory situation. + // Aborting the test without failure. + return; + } + + mTabUnloader->ResetCounter(); + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + // If the notification was not triggered, abort the test without failure + // because it's not a fault in nsAvailableMemoryWatcher. + return; + } + + // Tab unload will not be triggered because the commit space is not low. + EXPECT_FALSE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs / 2)); + + mMemEater.RequestFree(); + ::Sleep(kStateChangeTimeoutMs / 2); + + // Set a high threshold and make sure the watcher will trigger the tab + // unloader next time. + SetThresholdAsPercentageOfCommitSpace(50); + + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + return; + } + + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + + mHighMemoryObserver->StartListening(); + mMemEater.RequestFree(); + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); +} + +TEST_F(AvailableMemoryWatcherFixture, HighCommitSpace_InactiveToActive) { + // Setting a low threshold simulates a high commit space. + SetThresholdAsPercentageOfCommitSpace(1); + + const size_t allocSize = GetAllocationSizeToTriggerMemoryNotification(); + if (!allocSize) { + // Not enough memory to safely create a low-memory situation. + // Aborting the test without failure. + return; + } + + mTabUnloader->ResetCounter(); + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + // If the notification was not triggered, abort the test without failure + // because it's not a fault in nsAvailableMemoryWatcher. + return; + } + + // Tab unload will not be triggered because the commit space is not low. + EXPECT_FALSE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs / 2)); + + mMemEater.RequestFree(); + ::Sleep(kStateChangeTimeoutMs / 2); + + // Set a high threshold and make sure the watcher will trigger the tab + // unloader next time. + SetThresholdAsPercentageOfCommitSpace(50); + + // When the user becomes active, the watcher will resume the timer. + StartUserInteraction(); + + mMemEater.RequestAlloc(allocSize); + if (!WaitForMemoryResourceNotification()) { + return; + } + + EXPECT_TRUE(WaitUntil([this]() { return mTabUnloader->GetCounter() >= 1; }, + kStateChangeTimeoutMs)); + + mHighMemoryObserver->StartListening(); + mMemEater.RequestFree(); + EXPECT_TRUE(mHighMemoryObserver->Wait(kStateChangeTimeoutMs)); +} diff --git a/xpcom/tests/gtest/TestBase64.cpp b/xpcom/tests/gtest/TestBase64.cpp new file mode 100644 index 0000000000..51c5b95aa9 --- /dev/null +++ b/xpcom/tests/gtest/TestBase64.cpp @@ -0,0 +1,454 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/Attributes.h" +#include "mozilla/Base64.h" +#include "nsComponentManagerUtils.h" +#include "nsIScriptableBase64Encoder.h" +#include "nsIInputStream.h" +#include "nsString.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +struct Chunk { + Chunk(uint32_t l, const char* c) : mLength(l), mData(c) {} + + uint32_t mLength; + const char* mData; +}; + +struct Test { + Test(Chunk* c, const char* r) : mChunks(c), mResult(r) {} + + Chunk* mChunks; + const char* mResult; +}; + +static Chunk kTest1Chunks[] = {Chunk(9, "Hello sir"), Chunk(0, nullptr)}; + +static Chunk kTest2Chunks[] = {Chunk(3, "Hel"), Chunk(3, "lo "), + Chunk(3, "sir"), Chunk(0, nullptr)}; + +static Chunk kTest3Chunks[] = {Chunk(1, "I"), Chunk(0, nullptr)}; + +static Chunk kTest4Chunks[] = {Chunk(2, "Hi"), Chunk(0, nullptr)}; + +static Chunk kTest5Chunks[] = {Chunk(1, "B"), Chunk(2, "ob"), + Chunk(0, nullptr)}; + +static Chunk kTest6Chunks[] = {Chunk(2, "Bo"), Chunk(1, "b"), + Chunk(0, nullptr)}; + +static Chunk kTest7Chunks[] = {Chunk(1, "F"), // Carry over 1 + Chunk(4, "iref"), // Carry over 2 + Chunk(2, "ox"), // 1 + Chunk(4, " is "), // 2 + Chunk(2, "aw"), // 1 + Chunk(4, "esom"), // 2 + Chunk(2, "e!"), Chunk(0, nullptr)}; + +static Chunk kTest8Chunks[] = {Chunk(5, "ALL T"), + Chunk(1, "H"), + Chunk(4, "ESE "), + Chunk(2, "WO"), + Chunk(21, "RLDS ARE YOURS EXCEPT"), + Chunk(9, " EUROPA. "), + Chunk(25, "ATTEMPT NO LANDING THERE."), + Chunk(0, nullptr)}; + +static Test kTests[] = { + // Test 1, test a simple round string in one chunk + Test(kTest1Chunks, "SGVsbG8gc2ly"), + // Test 2, test a simple round string split into round chunks + Test(kTest2Chunks, "SGVsbG8gc2ly"), + // Test 3, test a single chunk that's 2 short + Test(kTest3Chunks, "SQ=="), + // Test 4, test a single chunk that's 1 short + Test(kTest4Chunks, "SGk="), + // Test 5, test a single chunk that's 2 short, followed by a chunk of 2 + Test(kTest5Chunks, "Qm9i"), + // Test 6, test a single chunk that's 1 short, followed by a chunk of 1 + Test(kTest6Chunks, "Qm9i"), + // Test 7, test alternating carryovers + Test(kTest7Chunks, "RmlyZWZveCBpcyBhd2Vzb21lIQ=="), + // Test 8, test a longish string + Test(kTest8Chunks, + "QUxMIFRIRVNFIFdPUkxEUyBBUkUgWU9VUlMgRVhDRVBUIEVVUk9QQS4gQVRURU1QVCBOT" + "yBMQU5ESU5HIFRIRVJFLg=="), + // Terminator + Test(nullptr, nullptr)}; + +class FakeInputStream final : public nsIInputStream { + ~FakeInputStream() = default; + + public: + FakeInputStream() + : mTestNumber(0), + mTest(&kTests[0]), + mChunk(&mTest->mChunks[0]), + mClosed(false) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + + void Reset(); + bool NextTest(); + void CheckTest(nsACString& aResult); + void CheckTest(nsAString& aResult); + + private: + uint32_t mTestNumber; + const Test* mTest; + const Chunk* mChunk; + bool mClosed; +}; + +NS_IMPL_ISUPPORTS(FakeInputStream, nsIInputStream) + +NS_IMETHODIMP +FakeInputStream::Close() { + mClosed = true; + return NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::Available(uint64_t* aAvailable) { + *aAvailable = 0; + + if (mClosed) return NS_BASE_STREAM_CLOSED; + + const Chunk* chunk = mChunk; + while (chunk->mLength) { + *aAvailable += chunk->mLength; + chunk++; + } + + return NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::StreamStatus() { + return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aOut) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FakeInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aRead) { + *aRead = 0; + + if (mClosed) return NS_BASE_STREAM_CLOSED; + + while (mChunk->mLength) { + uint32_t written = 0; + + nsresult rv = (*aWriter)(this, aClosure, mChunk->mData, *aRead, + mChunk->mLength, &written); + + *aRead += written; + NS_ENSURE_SUCCESS(rv, rv); + + mChunk++; + } + + return NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::IsNonBlocking(bool* aIsBlocking) { + *aIsBlocking = false; + return NS_OK; +} + +void FakeInputStream::Reset() { + mClosed = false; + mChunk = &mTest->mChunks[0]; +} + +bool FakeInputStream::NextTest() { + mTestNumber++; + mTest = &kTests[mTestNumber]; + mChunk = &mTest->mChunks[0]; + mClosed = false; + + return mTest->mChunks ? true : false; +} + +void FakeInputStream::CheckTest(nsACString& aResult) { + ASSERT_STREQ(aResult.BeginReading(), mTest->mResult); +} + +void FakeInputStream::CheckTest(nsAString& aResult) { + ASSERT_TRUE(aResult.EqualsASCII(mTest->mResult)) + << "Actual: " << aResult.BeginReading() << std::endl + << "Expected: " << mTest->mResult; +} + +TEST(Base64, StreamEncoder) +{ + nsCOMPtr encoder = + do_CreateInstance("@mozilla.org/scriptablebase64encoder;1"); + ASSERT_TRUE(encoder); + + RefPtr stream = new FakeInputStream(); + do { + nsString wideString; + nsCString string; + + nsresult rv; + rv = encoder->EncodeToString(stream, 0, wideString); + ASSERT_NS_SUCCEEDED(rv); + + stream->Reset(); + + rv = encoder->EncodeToCString(stream, 0, string); + ASSERT_NS_SUCCEEDED(rv); + + stream->CheckTest(wideString); + stream->CheckTest(string); + } while (stream->NextTest()); +} + +struct EncodeDecodeTestCase { + const char* mInput; + const char* mOutput; +}; + +static EncodeDecodeTestCase sRFC4648TestCases[] = { + {"", ""}, + {"f", "Zg=="}, + {"fo", "Zm8="}, + {"foo", "Zm9v"}, + {"foob", "Zm9vYg=="}, + {"fooba", "Zm9vYmE="}, + {"foobar", "Zm9vYmFy"}, +}; + +TEST(Base64, RFC4648Encoding) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString in(testcase.mInput); + nsAutoCString out; + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } + + for (auto& testcase : sRFC4648TestCases) { + NS_ConvertUTF8toUTF16 in(testcase.mInput); + nsAutoString out; + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, RFC4648Encoding_TransformAndAppend_EmptyPrefix) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString in(testcase.mInput); + nsAutoString out; + nsresult rv = + mozilla::Base64EncodeAppend(in.BeginReading(), in.Length(), out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, RFC4648Encoding_TransformAndAppend_NonEmptyPrefix) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString in(testcase.mInput); + nsAutoString out{u"foo"_ns}; + nsresult rv = + mozilla::Base64EncodeAppend(in.BeginReading(), in.Length(), out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(StringBeginsWith(out, u"foo"_ns)); + ASSERT_TRUE(Substring(out, 3).EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, RFC4648Decoding) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString out(testcase.mOutput); + nsAutoCString in; + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(in.EqualsASCII(testcase.mInput)); + } + + for (auto& testcase : sRFC4648TestCases) { + NS_ConvertUTF8toUTF16 out(testcase.mOutput); + nsAutoString in; + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(in.EqualsASCII(testcase.mInput)); + } +} + +TEST(Base64, RFC4648DecodingRawPointers) +{ + for (auto& testcase : sRFC4648TestCases) { + size_t outputLength = strlen(testcase.mOutput); + size_t inputLength = strlen(testcase.mInput); + + // This will be allocated by Base64Decode. + char* buffer = nullptr; + + uint32_t binaryLength = 0; + nsresult rv = mozilla::Base64Decode(testcase.mOutput, outputLength, &buffer, + &binaryLength); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(binaryLength, inputLength); + ASSERT_STREQ(testcase.mInput, buffer); + free(buffer); + } +} + +static EncodeDecodeTestCase sNonASCIITestCases[] = { + {"\x80", "gA=="}, + {"\xff", "/w=="}, + {"\x80\x80", "gIA="}, + {"\x80\x81", "gIE="}, + {"\xff\xff", "//8="}, + {"\x80\x80\x80", "gICA"}, + {"\xff\xff\xff", "////"}, + {"\x80\x80\x80\x80", "gICAgA=="}, + {"\xff\xff\xff\xff", "/////w=="}, +}; + +TEST(Base64, NonASCIIEncoding) +{ + for (auto& testcase : sNonASCIITestCases) { + nsDependentCString in(testcase.mInput); + nsAutoCString out; + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, NonASCIIEncodingWideString) +{ + for (auto& testcase : sNonASCIITestCases) { + nsAutoString in, out; + // XXX Handles Latin1 despite the name + AppendASCIItoUTF16(nsDependentCString(testcase.mInput), in); + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, NonASCIIDecoding) +{ + for (auto& testcase : sNonASCIITestCases) { + nsDependentCString out(testcase.mOutput); + nsAutoCString in; + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(in.Equals(testcase.mInput)); + } +} + +TEST(Base64, NonASCIIDecodingWideString) +{ + for (auto& testcase : sNonASCIITestCases) { + nsAutoString in, out; + // XXX Handles Latin1 despite the name + AppendASCIItoUTF16(nsDependentCString(testcase.mOutput), out); + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + // Can't use EqualsASCII, because our comparison string isn't ASCII. + for (size_t i = 0; i < in.Length(); ++i) { + ASSERT_TRUE(((unsigned int)in[i] & 0xff00) == 0); + ASSERT_EQ((unsigned char)in[i], (unsigned char)testcase.mInput[i]); + } + ASSERT_TRUE(strlen(testcase.mInput) == in.Length()); + } +} + +// For historical reasons, our wide string base64 encode routines mask off +// the high bits of non-latin1 wide strings. +TEST(Base64, EncodeNon8BitWideString) +{ + { + const nsAutoString non8Bit(u"\x1ff"); + nsAutoString out; + nsresult rv = mozilla::Base64Encode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsLiteral("/w==")); + } + { + const nsAutoString non8Bit(u"\xfff"); + nsAutoString out; + nsresult rv = mozilla::Base64Encode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsLiteral("/w==")); + } +} + +// For historical reasons, our wide string base64 decode routines mask off +// the high bits of non-latin1 wide strings. +TEST(Base64, DecodeNon8BitWideString) +{ + { + // This would be "/w==" in a nsCString + const nsAutoString non8Bit(u"\x12f\x177=="); + const nsAutoString expectedOutput(u"\xff"); + ASSERT_EQ(non8Bit.Length(), 4u); + nsAutoString out; + nsresult rv = mozilla::Base64Decode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.Equals(expectedOutput)); + } + { + const nsAutoString non8Bit(u"\xf2f\xf77=="); + const nsAutoString expectedOutput(u"\xff"); + nsAutoString out; + nsresult rv = mozilla::Base64Decode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.Equals(expectedOutput)); + } +} + +TEST(Base64, DecodeWideTo8Bit) +{ + for (auto& testCase : sRFC4648TestCases) { + const nsAutoCString in8bit(testCase.mOutput); + const NS_ConvertUTF8toUTF16 inWide(testCase.mOutput); + nsAutoCString out2; + nsAutoCString out1; + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(mozilla::Base64Decode(inWide, out1))); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(mozilla::Base64Decode(in8bit, out2))); + ASSERT_EQ(out1, out2); + } +} + +TEST(Base64, TruncateOnInvalidDecodeCString) +{ + constexpr auto invalid = "@@=="_ns; + nsAutoCString out("I should be truncated!"); + nsresult rv = mozilla::Base64Decode(invalid, out); + ASSERT_NS_FAILED(rv); + ASSERT_EQ(out.Length(), 0u); +} + +TEST(Base64, TruncateOnInvalidDecodeWideString) +{ + constexpr auto invalid = u"@@=="_ns; + nsAutoString out(u"I should be truncated!"); + nsresult rv = mozilla::Base64Decode(invalid, out); + ASSERT_NS_FAILED(rv); + ASSERT_EQ(out.Length(), 0u); +} + +// TODO: Add tests for OOM handling. diff --git a/xpcom/tests/gtest/TestCOMArray.cpp b/xpcom/tests/gtest/TestCOMArray.cpp new file mode 100644 index 0000000000..9e29e15230 --- /dev/null +++ b/xpcom/tests/gtest/TestCOMArray.cpp @@ -0,0 +1,295 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:ts=4:et:sw=4: +/* 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 "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "gtest/gtest.h" + +// {9e70a320-be02-11d1-8031-006008159b5a} +#define NS_IFOO_IID \ + { \ + 0x9e70a320, 0xbe02, 0x11d1, { \ + 0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a \ + } \ + } + +class IFoo : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + NS_IMETHOD_(MozExternalRefCountType) RefCnt() = 0; + NS_IMETHOD_(int32_t) ID() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID) + +class Foo final : public IFoo { + ~Foo(); + + public: + explicit Foo(int32_t aID); + + // nsISupports implementation + NS_DECL_ISUPPORTS + + // IFoo implementation + NS_IMETHOD_(MozExternalRefCountType) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return mID; } + + static int32_t gCount; + + int32_t mID; +}; + +int32_t Foo::gCount = 0; + +Foo::Foo(int32_t aID) { + mID = aID; + ++gCount; +} + +Foo::~Foo() { --gCount; } + +NS_IMPL_ISUPPORTS(Foo, IFoo) + +// {0e70a320-be02-11d1-8031-006008159b5a} +#define NS_IBAR_IID \ + { \ + 0x0e70a320, 0xbe02, 0x11d1, { \ + 0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a \ + } \ + } + +class IBar : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IBAR_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IBar, NS_IBAR_IID) + +class Bar final : public IBar { + public: + explicit Bar(nsCOMArray& aArray); + + // nsISupports implementation + NS_DECL_ISUPPORTS + + static int32_t sReleaseCalled; + + private: + ~Bar(); + + nsCOMArray& mArray; +}; + +int32_t Bar::sReleaseCalled = 0; + +typedef nsCOMArray Array2; + +Bar::Bar(Array2& aArray) : mArray(aArray) {} + +Bar::~Bar() { EXPECT_FALSE(mArray.RemoveObject(this)); } + +NS_IMPL_ADDREF(Bar) +NS_IMPL_QUERY_INTERFACE(Bar, IBar) + +NS_IMETHODIMP_(MozExternalRefCountType) +Bar::Release(void) { + ++Bar::sReleaseCalled; + EXPECT_GT(int(mRefCnt), 0); + NS_ASSERT_OWNINGTHREAD(_class); + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "Bar"); + if (mRefCnt == 0) { + mRefCnt = 1; /* stabilize */ + delete this; + return 0; + } + return mRefCnt; +} + +TEST(COMArray, Sizing) +{ + nsCOMArray arr; + + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr foo = new Foo(i); + arr.AppendObject(foo); + } + + ASSERT_EQ(arr.Count(), int32_t(20)); + ASSERT_EQ(Foo::gCount, int32_t(20)); + + arr.TruncateLength(10); + + ASSERT_EQ(arr.Count(), int32_t(10)); + ASSERT_EQ(Foo::gCount, int32_t(10)); + + arr.SetCount(30); + + ASSERT_EQ(arr.Count(), int32_t(30)); + ASSERT_EQ(Foo::gCount, int32_t(10)); + + for (int32_t i = 0; i < 10; ++i) { + ASSERT_NE(arr[i], nullptr); + } + + for (int32_t i = 10; i < 30; ++i) { + ASSERT_EQ(arr[i], nullptr); + } +} + +TEST(COMArray, ObjectFunctions) +{ + int32_t base; + { + nsCOMArray arr2; + + IBar *thirdObject = nullptr, *fourthObject = nullptr, + *fifthObject = nullptr, *ninthObject = nullptr; + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr bar = new Bar(arr2); + switch (i) { + case 2: + thirdObject = bar; + break; + case 3: + fourthObject = bar; + break; + case 4: + fifthObject = bar; + break; + case 8: + ninthObject = bar; + break; + } + arr2.AppendObject(bar); + } + + base = Bar::sReleaseCalled; + + arr2.SetCount(10); + ASSERT_EQ(Bar::sReleaseCalled, base + 10); + ASSERT_EQ(arr2.Count(), int32_t(10)); + + arr2.RemoveObjectAt(9); + ASSERT_EQ(Bar::sReleaseCalled, base + 11); + ASSERT_EQ(arr2.Count(), int32_t(9)); + + arr2.RemoveObject(ninthObject); + ASSERT_EQ(Bar::sReleaseCalled, base + 12); + ASSERT_EQ(arr2.Count(), int32_t(8)); + + arr2.RemoveObjectsAt(2, 3); + ASSERT_EQ(Bar::sReleaseCalled, base + 15); + ASSERT_EQ(arr2.Count(), int32_t(5)); + for (int32_t j = 0; j < arr2.Count(); ++j) { + ASSERT_NE(arr2.ObjectAt(j), thirdObject); + ASSERT_NE(arr2.ObjectAt(j), fourthObject); + ASSERT_NE(arr2.ObjectAt(j), fifthObject); + } + + arr2.RemoveObjectsAt(4, 1); + ASSERT_EQ(Bar::sReleaseCalled, base + 16); + ASSERT_EQ(arr2.Count(), int32_t(4)); + + arr2.Clear(); + ASSERT_EQ(Bar::sReleaseCalled, base + 20); + } +} + +TEST(COMArray, ElementFunctions) +{ + int32_t base; + { + nsCOMArray arr2; + + IBar *thirdElement = nullptr, *fourthElement = nullptr, + *fifthElement = nullptr, *ninthElement = nullptr; + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr bar = new Bar(arr2); + switch (i) { + case 2: + thirdElement = bar; + break; + case 3: + fourthElement = bar; + break; + case 4: + fifthElement = bar; + break; + case 8: + ninthElement = bar; + break; + } + arr2.AppendElement(bar); + } + + base = Bar::sReleaseCalled; + + arr2.TruncateLength(10); + ASSERT_EQ(Bar::sReleaseCalled, base + 10); + ASSERT_EQ(arr2.Length(), uint32_t(10)); + + arr2.RemoveElementAt(9); + ASSERT_EQ(Bar::sReleaseCalled, base + 11); + ASSERT_EQ(arr2.Length(), uint32_t(9)); + + arr2.RemoveElement(ninthElement); + ASSERT_EQ(Bar::sReleaseCalled, base + 12); + ASSERT_EQ(arr2.Length(), uint32_t(8)); + + arr2.RemoveElementsAt(2, 3); + ASSERT_EQ(Bar::sReleaseCalled, base + 15); + ASSERT_EQ(arr2.Length(), uint32_t(5)); + for (uint32_t j = 0; j < arr2.Length(); ++j) { + ASSERT_NE(arr2.ElementAt(j), thirdElement); + ASSERT_NE(arr2.ElementAt(j), fourthElement); + ASSERT_NE(arr2.ElementAt(j), fifthElement); + } + + arr2.RemoveElementsAt(4, 1); + ASSERT_EQ(Bar::sReleaseCalled, base + 16); + ASSERT_EQ(arr2.Length(), uint32_t(4)); + + arr2.Clear(); + ASSERT_EQ(Bar::sReleaseCalled, base + 20); + } +} + +TEST(COMArray, Destructor) +{ + int32_t base; + Bar::sReleaseCalled = 0; + + { + nsCOMArray arr2; + + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr bar = new Bar(arr2); + arr2.AppendObject(bar); + } + + base = Bar::sReleaseCalled; + + // Let arr2 be destroyed + } + ASSERT_EQ(Bar::sReleaseCalled, base + 20); +} + +TEST(COMArray, ConvertIteratorToConstIterator) +{ + nsCOMArray array; + + for (int32_t i = 0; i < 20; ++i) { + nsCOMPtr foo = new Foo(i); + array.AppendObject(foo); + } + + nsCOMArray::const_iterator it = array.begin(); + ASSERT_EQ(array.cbegin(), it); +} diff --git a/xpcom/tests/gtest/TestCOMPtr.cpp b/xpcom/tests/gtest/TestCOMPtr.cpp new file mode 100644 index 0000000000..01fde5632a --- /dev/null +++ b/xpcom/tests/gtest/TestCOMPtr.cpp @@ -0,0 +1,436 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsCOMPtr.h" +#include "gtest/gtest.h" + +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Unused.h" + +#define NS_IFOO_IID \ + { \ + 0x6f7652e0, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +namespace TestCOMPtr { + +class IFoo : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + public: + IFoo(); + // virtual dtor because IBar uses our Release() + virtual ~IFoo(); + + NS_IMETHOD_(MozExternalRefCountType) AddRef() override; + NS_IMETHOD_(MozExternalRefCountType) Release() override; + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + + unsigned int refcount_; + + static int total_constructions_; + static int total_destructions_; + static int total_queries_; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID) + +int IFoo::total_constructions_; +int IFoo::total_destructions_; +int IFoo::total_queries_; + +IFoo::IFoo() : refcount_(0) { ++total_constructions_; } + +IFoo::~IFoo() { ++total_destructions_; } + +MozExternalRefCountType IFoo::AddRef() { + ++refcount_; + return refcount_; +} + +MozExternalRefCountType IFoo::Release() { + int newcount = --refcount_; + + if (newcount == 0) { + delete this; + } + + return newcount; +} + +nsresult IFoo::QueryInterface(const nsIID& aIID, void** aResult) { + total_queries_++; + + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(IFoo))) + rawPtr = this; + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +static nsresult CreateIFoo(void** result) +// a typical factory function (that calls AddRef) +{ + auto* foop = new IFoo; + + foop->AddRef(); + *result = foop; + + return NS_OK; +} + +static void set_a_IFoo(nsCOMPtr* result) { + // Various places in this file do a static_cast to nsISupports* in order to + // make the QI non-trivial, to avoid hitting a static assert. + nsCOMPtr foop(do_QueryInterface(static_cast(new IFoo))); + *result = foop; +} + +static nsCOMPtr return_a_IFoo() { + nsCOMPtr foop(do_QueryInterface(static_cast(new IFoo))); + return foop; +} + +#define NS_IBAR_IID \ + { \ + 0x6f7652e1, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +class IBar : public IFoo { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IBAR_IID) + + public: + IBar(); + ~IBar() override; + + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + + static int total_destructions_; + static int total_queries_; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IBar, NS_IBAR_IID) + +int IBar::total_destructions_; +int IBar::total_queries_; + +IBar::IBar() = default; + +IBar::~IBar() { total_destructions_++; } + +nsresult IBar::QueryInterface(const nsID& aIID, void** aResult) { + total_queries_++; + + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(IBar))) + rawPtr = this; + else if (aIID.Equals(NS_GET_IID(IFoo))) + rawPtr = static_cast(this); + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +static nsresult CreateIBar(void** result) +// a typical factory function (that calls AddRef) +{ + auto* barp = new IBar; + + barp->AddRef(); + *result = barp; + + return NS_OK; +} + +static void AnIFooPtrPtrContext(IFoo**) {} + +static void AVoidPtrPtrContext(void**) {} + +static void AnISupportsPtrPtrContext(nsISupports**) {} + +} // namespace TestCOMPtr + +using namespace TestCOMPtr; + +TEST(COMPtr, Bloat_Raw_Unsafe) +{ + // ER: I'm not sure what this is testing... + IBar* barP = 0; + nsresult rv = CreateIBar(reinterpret_cast(&barP)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(barP); + + IFoo* fooP = 0; + rv = barP->QueryInterface(NS_GET_IID(IFoo), reinterpret_cast(&fooP)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(fooP); + + NS_RELEASE(fooP); + NS_RELEASE(barP); +} + +TEST(COMPtr, Bloat_Smart) +{ + // ER: I'm not sure what this is testing... + nsCOMPtr barP; + nsresult rv = CreateIBar(getter_AddRefs(barP)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(barP); + + nsCOMPtr fooP(do_QueryInterface(static_cast(barP), &rv)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(fooP); +} + +TEST(COMPtr, AddRefAndRelease) +{ + IFoo::total_constructions_ = 0; + IFoo::total_destructions_ = 0; + IBar::total_destructions_ = 0; + + { + nsCOMPtr foop(do_QueryInterface(static_cast(new IFoo))); + ASSERT_EQ(foop->refcount_, (unsigned int)1); + ASSERT_EQ(IFoo::total_constructions_, 1); + ASSERT_EQ(IFoo::total_destructions_, 0); + + foop = do_QueryInterface(static_cast(new IFoo)); + ASSERT_EQ(foop->refcount_, (unsigned int)1); + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 1); + + // [Shouldn't compile] Is it a compile time error to try to |AddRef| by + // hand? + // foop->AddRef(); + + // [Shouldn't compile] Is it a compile time error to try to |Release| be + // hand? + // foop->Release(); + + // [Shouldn't compile] Is it a compile time error to try to |delete| an + // |nsCOMPtr|? + // delete foop; + + static_cast(foop)->AddRef(); + ASSERT_EQ(foop->refcount_, (unsigned int)2); + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 1); + + static_cast(foop)->Release(); + ASSERT_EQ(foop->refcount_, (unsigned int)1); + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 1); + } + + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 2); + + { + nsCOMPtr foop(do_QueryInterface(static_cast(new IBar))); + mozilla::Unused << foop; + } + + ASSERT_EQ(IBar::total_destructions_, 1); +} + +TEST(COMPtr, Comparison) +{ + IFoo::total_constructions_ = 0; + IFoo::total_destructions_ = 0; + + { + nsCOMPtr foo1p( + do_QueryInterface(static_cast(new IFoo))); + nsCOMPtr foo2p( + do_QueryInterface(static_cast(new IFoo))); + + ASSERT_EQ(IFoo::total_constructions_, 2); + + // Test != operator + ASSERT_NE(foo1p, foo2p); + ASSERT_NE(foo1p, foo2p.get()); + + // Test == operator + foo1p = foo2p; + + ASSERT_EQ(IFoo::total_destructions_, 1); + + ASSERT_EQ(foo1p, foo2p); + ASSERT_EQ(foo2p, foo2p.get()); + ASSERT_EQ(foo2p.get(), foo2p); + + // Test () operator + ASSERT_TRUE(foo1p); + + ASSERT_EQ(foo1p->refcount_, (unsigned int)2); + ASSERT_EQ(foo2p->refcount_, (unsigned int)2); + } + + ASSERT_EQ(IFoo::total_destructions_, 2); +} + +TEST(COMPtr, DontAddRef) +{ + { + auto* raw_foo1p = new IFoo; + raw_foo1p->AddRef(); + + auto* raw_foo2p = new IFoo; + raw_foo2p->AddRef(); + + nsCOMPtr foo1p(dont_AddRef(raw_foo1p)); + ASSERT_EQ(raw_foo1p, foo1p); + ASSERT_EQ(foo1p->refcount_, (unsigned int)1); + + nsCOMPtr foo2p; + foo2p = dont_AddRef(raw_foo2p); + ASSERT_EQ(raw_foo2p, foo2p); + ASSERT_EQ(foo2p->refcount_, (unsigned int)1); + } +} + +TEST(COMPtr, AssignmentHelpers) +{ + IFoo::total_constructions_ = 0; + IFoo::total_destructions_ = 0; + + { + nsCOMPtr foop; + ASSERT_FALSE(foop); + CreateIFoo(nsGetterAddRefs(foop)); + ASSERT_TRUE(foop); + } + + ASSERT_EQ(IFoo::total_constructions_, 1); + ASSERT_EQ(IFoo::total_destructions_, 1); + + { + nsCOMPtr foop; + ASSERT_FALSE(foop); + CreateIFoo(getter_AddRefs(foop)); + ASSERT_TRUE(foop); + } + + ASSERT_EQ(IFoo::total_constructions_, 2); + ASSERT_EQ(IFoo::total_destructions_, 2); + + { + nsCOMPtr foop; + ASSERT_FALSE(foop); + set_a_IFoo(address_of(foop)); + ASSERT_TRUE(foop); + + ASSERT_EQ(IFoo::total_constructions_, 3); + ASSERT_EQ(IFoo::total_destructions_, 2); + + foop = return_a_IFoo(); + ASSERT_TRUE(foop); + + ASSERT_EQ(IFoo::total_constructions_, 4); + ASSERT_EQ(IFoo::total_destructions_, 3); + } + + ASSERT_EQ(IFoo::total_constructions_, 4); + ASSERT_EQ(IFoo::total_destructions_, 4); + + { + nsCOMPtr fooP(do_QueryInterface(static_cast(new IFoo))); + ASSERT_TRUE(fooP); + + ASSERT_EQ(IFoo::total_constructions_, 5); + ASSERT_EQ(IFoo::total_destructions_, 4); + + nsCOMPtr fooP2(std::move(fooP)); + ASSERT_TRUE(fooP2); + + ASSERT_EQ(IFoo::total_constructions_, 5); + ASSERT_EQ(IFoo::total_destructions_, 4); + } + + ASSERT_EQ(IFoo::total_constructions_, 5); + ASSERT_EQ(IFoo::total_destructions_, 5); +} + +TEST(COMPtr, QueryInterface) +{ + IFoo::total_queries_ = 0; + IBar::total_queries_ = 0; + + { + nsCOMPtr fooP; + ASSERT_FALSE(fooP); + fooP = do_QueryInterface(static_cast(new IFoo)); + ASSERT_TRUE(fooP); + ASSERT_EQ(IFoo::total_queries_, 1); + + nsCOMPtr foo2P; + + // Test that |QueryInterface| _not_ called when assigning a smart-pointer + // of the same type.); + foo2P = fooP; + ASSERT_EQ(IFoo::total_queries_, 1); + } + + { + nsCOMPtr barP(do_QueryInterface(static_cast(new IBar))); + ASSERT_EQ(IBar::total_queries_, 1); + + // Test that |QueryInterface| is called when assigning a smart-pointer of + // a different type. + nsCOMPtr fooP(do_QueryInterface(static_cast(barP))); + ASSERT_EQ(IBar::total_queries_, 2); + ASSERT_EQ(IFoo::total_queries_, 1); + ASSERT_TRUE(fooP); + } +} + +TEST(COMPtr, GetterConversions) +{ + // This is just a compilation test. We add a few asserts to keep gtest happy. + { + nsCOMPtr fooP; + ASSERT_FALSE(fooP); + + AnIFooPtrPtrContext(getter_AddRefs(fooP)); + AVoidPtrPtrContext(getter_AddRefs(fooP)); + } + + { + nsCOMPtr supportsP; + ASSERT_FALSE(supportsP); + + AVoidPtrPtrContext(getter_AddRefs(supportsP)); + AnISupportsPtrPtrContext(getter_AddRefs(supportsP)); + } +} diff --git a/xpcom/tests/gtest/TestCOMPtrEq.cpp b/xpcom/tests/gtest/TestCOMPtrEq.cpp new file mode 100644 index 0000000000..2056ce1368 --- /dev/null +++ b/xpcom/tests/gtest/TestCOMPtrEq.cpp @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * This attempts to test all the possible variations of |operator==| + * used with |nsCOMPtr|s. + */ + +#include "nsCOMPtr.h" +#include "gtest/gtest.h" + +#define NS_ICOMPTREQTESTFOO_IID \ + { \ + 0x8eb5bbef, 0xd1a3, 0x4659, { \ + 0x9c, 0xf6, 0xfd, 0xf3, 0xe4, 0xd2, 0x00, 0x0e \ + } \ + } + +class nsICOMPtrEqTestFoo : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICOMPTREQTESTFOO_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsICOMPtrEqTestFoo, NS_ICOMPTREQTESTFOO_IID) + +TEST(COMPtrEq, NullEquality) +{ + nsCOMPtr s; + nsICOMPtrEqTestFoo* r = nullptr; + const nsCOMPtr sc; + const nsICOMPtrEqTestFoo* rc = nullptr; + nsICOMPtrEqTestFoo* const rk = nullptr; + const nsICOMPtrEqTestFoo* const rkc = nullptr; + nsICOMPtrEqTestFoo* d = s; + + ASSERT_EQ(s, s); + ASSERT_EQ(s, r); + ASSERT_EQ(s, sc); + ASSERT_EQ(s, rc); + ASSERT_EQ(s, rk); + ASSERT_EQ(s, rkc); + ASSERT_EQ(s, d); + ASSERT_EQ(r, s); + ASSERT_EQ(r, sc); + ASSERT_EQ(r, rc); + ASSERT_EQ(r, rk); + ASSERT_EQ(r, rkc); + ASSERT_EQ(r, d); + ASSERT_EQ(sc, s); + ASSERT_EQ(sc, r); + ASSERT_EQ(sc, sc); + ASSERT_EQ(sc, rc); + ASSERT_EQ(sc, rk); + ASSERT_EQ(sc, rkc); + ASSERT_EQ(sc, d); + ASSERT_EQ(rc, s); + ASSERT_EQ(rc, r); + ASSERT_EQ(rc, sc); + ASSERT_EQ(rc, rk); + ASSERT_EQ(rc, rkc); + ASSERT_EQ(rc, d); + ASSERT_EQ(rk, s); + ASSERT_EQ(rk, r); + ASSERT_EQ(rk, sc); + ASSERT_EQ(rk, rc); + ASSERT_EQ(rk, rkc); + ASSERT_EQ(rk, d); + ASSERT_EQ(rkc, s); + ASSERT_EQ(rkc, r); + ASSERT_EQ(rkc, sc); + ASSERT_EQ(rkc, rc); + ASSERT_EQ(rkc, rk); + ASSERT_EQ(rkc, d); + ASSERT_EQ(d, s); + ASSERT_EQ(d, r); + ASSERT_EQ(d, sc); + ASSERT_EQ(d, rc); + ASSERT_EQ(d, rk); + ASSERT_EQ(d, rkc); +} diff --git a/xpcom/tests/gtest/TestCRT.cpp b/xpcom/tests/gtest/TestCRT.cpp new file mode 100644 index 0000000000..22e161fcdd --- /dev/null +++ b/xpcom/tests/gtest/TestCRT.cpp @@ -0,0 +1,90 @@ +/* -*- 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 "nsCRT.h" +#include "nsString.h" +#include "plstr.h" +#include +#include "gtest/gtest.h" + +namespace TestCRT { + +// The return from strcmp etc is only defined to be postive, zero or +// negative. The magnitude of a non-zero return is irrelevant. +static int sign(int val) { + if (val == 0) { + return 0; + } else { + if (val > 0) { + return 1; + } else { + return -1; + } + } +} + +// Verify that nsCRT versions of string comparison routines get the +// same answers as the native non-unicode versions. We only pass in +// iso-latin-1 strings, so the comparison must be valid. +static void Check(const char* s1, const char* s2, size_t n) { + bool longerThanN = strlen(s1) > n || strlen(s2) > n; + + int clib = PL_strcmp(s1, s2); + int clib_n = PL_strncmp(s1, s2, n); + + if (!longerThanN) { + EXPECT_EQ(sign(clib), sign(clib_n)); + } + + nsAutoString t1, t2; + CopyASCIItoUTF16(mozilla::MakeStringSpan(s1), t1); + CopyASCIItoUTF16(mozilla::MakeStringSpan(s2), t2); + const char16_t* us1 = t1.get(); + const char16_t* us2 = t2.get(); + + int u2, u2_n; + u2 = nsCRT::strcmp(us1, us2); + + EXPECT_EQ(sign(clib), sign(u2)); + + u2 = NS_strcmp(us1, us2); + u2_n = NS_strncmp(us1, us2, n); + + EXPECT_EQ(sign(clib), sign(u2)); + EXPECT_EQ(sign(clib_n), sign(u2_n)); +} + +struct Test { + const char* s1; + const char* s2; + size_t n; +}; + +static Test tests[] = { + {"foo", "foo", 3}, {"foo", "fo", 3}, + + {"foo", "bar", 3}, {"foo", "ba", 3}, + + {"foo", "zap", 3}, {"foo", "za", 3}, + + {"bar", "foo", 3}, {"bar", "fo", 3}, + + {"bar", "foo", 3}, {"bar", "fo", 3}, + + {"foo", "foobar", 3}, {"foobar", "foo", 3}, + {"foobar", "foozap", 3}, {"foozap", "foobar", 3}, +}; +#define NUM_TESTS int((sizeof(tests) / sizeof(tests[0]))) + +TEST(CRT, main) +{ + TestCRT::Test* tp = tests; + for (int i = 0; i < NUM_TESTS; i++, tp++) { + Check(tp->s1, tp->s2, tp->n); + } +} + +} // namespace TestCRT diff --git a/xpcom/tests/gtest/TestCallTemplates.cpp b/xpcom/tests/gtest/TestCallTemplates.cpp new file mode 100644 index 0000000000..90a03a8a8f --- /dev/null +++ b/xpcom/tests/gtest/TestCallTemplates.cpp @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim:cindent:ts=8:et:sw=4: + * + * 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/. */ + +/* + * This test is NOT intended to be run. It's a test to make sure + * a group of functions BUILD correctly. + */ + +#include "nsISupportsUtils.h" +#include "nsIWeakReference.h" +#include "nsWeakReference.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Attributes.h" + +#define NS_ITESTSERVICE_IID \ + { \ + 0x127b5253, 0x37b1, 0x43c7, { \ + 0x96, 0x2b, 0xab, 0xf1, 0x2d, 0x22, 0x56, 0xae \ + } \ + } + +class NS_NO_VTABLE nsITestService : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITESTSERVICE_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITestService, NS_ITESTSERVICE_IID) + +#define NS_ITESTSERVICE2_IID \ + { \ + 0x137b5253, 0x37b1, 0x43c7, { \ + 0x96, 0x2b, 0xab, 0xf1, 0x2d, 0x22, 0x56, 0xaf \ + } \ + } + +class NS_NO_VTABLE nsITestService2 : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITESTSERVICE2_IID) +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITestService2, NS_ITESTSERVICE2_IID) + +class nsTestService final : public nsITestService, + public nsSupportsWeakReference { + ~nsTestService() = default; + + public: + NS_DECL_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(nsTestService, nsITestService, nsISupportsWeakReference) + +#define NS_TEST_SERVICE_CONTRACTID "@mozilla.org/test/testservice;1" +#define NS_TEST_SERVICE_CID \ + { \ + 0xa00c1406, 0x283a, 0x45c9, { \ + 0xae, 0xd2, 0x1a, 0xb6, 0xdd, 0xba, 0xfe, 0x53 \ + } \ + } +static NS_DEFINE_CID(kTestServiceCID, NS_TEST_SERVICE_CID); + +inline void JustTestingCompilation() { + /* + * NOTE: This does NOT demonstrate how these functions are + * intended to be used. They are intended for filling in out + * parameters that need to be |AddRef|ed. I'm just too lazy + * to write lots of little getter functions for a test program + * when I don't need to. + */ + + MOZ_ASSERT_UNREACHABLE("This test is not intended to run, only to compile!"); + + /* Test CallQueryInterface */ + + nsISupports* mySupportsPtr = reinterpret_cast(0x1000); + + nsITestService* myITestService = nullptr; + CallQueryInterface(mySupportsPtr, &myITestService); + + nsTestService* myTestService = + reinterpret_cast(mySupportsPtr); + nsITestService2* myTestService2; + CallQueryInterface(myTestService, &myTestService2); + + nsCOMPtr mySupportsCOMPtr = mySupportsPtr; + CallQueryInterface(mySupportsCOMPtr, &myITestService); + + RefPtr myTestServiceRefPtr = myTestService; + CallQueryInterface(myTestServiceRefPtr, &myTestService2); + + /* Test CallQueryReferent */ + + nsIWeakReference* myWeakRef = static_cast(mySupportsPtr); + CallQueryReferent(myWeakRef, &myITestService); + + /* Test CallCreateInstance */ + CallCreateInstance(kTestServiceCID, &myITestService); + CallCreateInstance(NS_TEST_SERVICE_CONTRACTID, &myITestService); + + /* Test CallGetService */ + CallGetService(kTestServiceCID, &myITestService); + CallGetService(NS_TEST_SERVICE_CONTRACTID, &myITestService); + + /* Test CallGetInterface */ + nsIInterfaceRequestor* myInterfaceRequestor = + static_cast(mySupportsPtr); + CallGetInterface(myInterfaceRequestor, &myITestService); +} diff --git a/xpcom/tests/gtest/TestCloneInputStream.cpp b/xpcom/tests/gtest/TestCloneInputStream.cpp new file mode 100644 index 0000000000..9a3b400854 --- /dev/null +++ b/xpcom/tests/gtest/TestCloneInputStream.cpp @@ -0,0 +1,236 @@ +/* -*- 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 "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Unused.h" +#include "nsICloneableInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsNetUtil.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" +#include "nsComponentManagerUtils.h" + +TEST(CloneInputStream, InvalidInput) +{ + nsCOMPtr clone; + nsresult rv = NS_CloneInputStream(nullptr, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); + ASSERT_FALSE(clone); +} + +TEST(CloneInputStream, CloneableInput) +{ + nsTArray inputData; + testing::CreateData(4 * 1024, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr stream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr clone; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + + testing::ConsumeAndValidateStream(stream, inputString); + testing::ConsumeAndValidateStream(clone, inputString); +} + +class NonCloneableInputStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit NonCloneableInputStream( + already_AddRefed aInputStream) + : mStream(aInputStream) {} + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + return mStream->IsNonBlocking(aNonBlocking); + } + + private: + ~NonCloneableInputStream() = default; + + nsCOMPtr mStream; +}; + +NS_IMPL_ISUPPORTS(NonCloneableInputStream, nsIInputStream) + +TEST(CloneInputStream, NonCloneableInput_NoFallback) +{ + nsTArray inputData; + testing::CreateData(4 * 1024, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr base; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr stream = new NonCloneableInputStream(base.forget()); + + nsCOMPtr cloneable = do_QueryInterface(stream); + ASSERT_TRUE(cloneable == nullptr); + + nsCOMPtr clone; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); + ASSERT_TRUE(clone == nullptr); + + testing::ConsumeAndValidateStream(stream, inputString); +} + +TEST(CloneInputStream, NonCloneableInput_Fallback) +{ + nsTArray inputData; + testing::CreateData(4 * 1024, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr base; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr stream = new NonCloneableInputStream(base.forget()); + + nsCOMPtr cloneable = do_QueryInterface(stream); + ASSERT_TRUE(cloneable == nullptr); + + nsCOMPtr clone; + nsCOMPtr replacement; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone), + getter_AddRefs(replacement)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(clone != nullptr); + ASSERT_TRUE(replacement != nullptr); + ASSERT_TRUE(stream.get() != replacement.get()); + ASSERT_TRUE(clone.get() != replacement.get()); + + stream = std::move(replacement); + + // The stream is being copied asynchronously on the STS event target. Spin + // a yield loop here until the data is available. Yes, this is a bit hacky, + // but AFAICT, gtest does not support async test completion. + uint64_t available; + do { + mozilla::Unused << PR_Sleep(PR_INTERVAL_NO_WAIT); + rv = stream->Available(&available); + ASSERT_NS_SUCCEEDED(rv); + } while (available < inputString.Length()); + + testing::ConsumeAndValidateStream(stream, inputString); + testing::ConsumeAndValidateStream(clone, inputString); +} + +TEST(CloneInputStream, CloneMultiplexStream) +{ + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + nsTArray inputData; + testing::CreateData(1024, inputData); + for (uint32_t i = 0; i < 2; ++i) { + nsCString inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr base; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(base); + ASSERT_NS_SUCCEEDED(rv); + } + + // Unread stream should clone successfully. + nsTArray doubled; + doubled.AppendElements(inputData); + doubled.AppendElements(inputData); + + nsCOMPtr clone; + nsresult rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + testing::ConsumeAndValidateStream(clone, doubled); + + // Stream that has been read should fail. + char buffer[512]; + uint32_t read; + rv = stream->Read(buffer, 512, &read); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr clone2; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone2)); + ASSERT_NS_FAILED(rv); +} + +TEST(CloneInputStream, CloneMultiplexStreamPartial) +{ + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + nsTArray inputData; + testing::CreateData(1024, inputData); + for (uint32_t i = 0; i < 2; ++i) { + nsCString inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr base; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(base), inputString); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(base); + ASSERT_NS_SUCCEEDED(rv); + } + + // Fail when first stream read, but second hasn't been started. + char buffer[1024]; + uint32_t read; + nsresult rv = stream->Read(buffer, 1024, &read); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr clone; + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); + + // Fail after beginning read of second stream. + rv = stream->Read(buffer, 512, &read); + ASSERT_TRUE(NS_SUCCEEDED(rv) && read == 512); + + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); + + // Fail at the end. + nsAutoCString consumed; + rv = NS_ConsumeStream(stream, UINT32_MAX, consumed); + ASSERT_NS_SUCCEEDED(rv); + + rv = NS_CloneInputStream(stream, getter_AddRefs(clone)); + ASSERT_NS_FAILED(rv); +} diff --git a/xpcom/tests/gtest/TestDafsa.cpp b/xpcom/tests/gtest/TestDafsa.cpp new file mode 100644 index 0000000000..f52bf74256 --- /dev/null +++ b/xpcom/tests/gtest/TestDafsa.cpp @@ -0,0 +1,82 @@ +/* -*- 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/Dafsa.h" +#include "gtest/gtest.h" + +#include "nsString.h" + +using mozilla::Dafsa; + +namespace dafsa_test_1 { +#include "dafsa_test_1.inc" // kDafsa +} + +TEST(Dafsa, Constructor) +{ Dafsa d(dafsa_test_1::kDafsa); } + +TEST(Dafsa, StringsFound) +{ + Dafsa d(dafsa_test_1::kDafsa); + + int tag = d.Lookup("foo.bar.baz"_ns); + EXPECT_EQ(tag, 1); + + tag = d.Lookup("a.test.string"_ns); + EXPECT_EQ(tag, 0); + + tag = d.Lookup("a.test.string2"_ns); + EXPECT_EQ(tag, 2); + + tag = d.Lookup("aaaa"_ns); + EXPECT_EQ(tag, 4); +} + +TEST(Dafsa, StringsNotFound) +{ + Dafsa d(dafsa_test_1::kDafsa); + + // Matches all but last letter. + int tag = d.Lookup("foo.bar.ba"_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); + + // Matches prefix with extra letter. + tag = d.Lookup("a.test.strings"_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); + + // Matches small portion. + tag = d.Lookup("a.test"_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); + + // Matches repeating pattern with extra letters. + tag = d.Lookup("aaaaa"_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); + + // Empty string. + tag = d.Lookup(""_ns); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); +} + +TEST(Dafsa, HugeString) +{ + Dafsa d(dafsa_test_1::kDafsa); + + int tag = d.Lookup(nsLiteralCString( + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. " + "This is a very long string that is larger than the dafsa itself. ")); + EXPECT_EQ(tag, Dafsa::kKeyNotFound); +} diff --git a/xpcom/tests/gtest/TestDeadlockDetector.cpp b/xpcom/tests/gtest/TestDeadlockDetector.cpp new file mode 100644 index 0000000000..c02ba13da2 --- /dev/null +++ b/xpcom/tests/gtest/TestDeadlockDetector.cpp @@ -0,0 +1,314 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=2 ts=4 et : + * 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/ArrayUtils.h" + +#include "prthread.h" + +#include "nsCOMPtr.h" +#include "nsTArray.h" + +#include "mozilla/CondVar.h" +#include "mozilla/RecursiveMutex.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Mutex.h" + +#include "mozilla/gtest/MozHelpers.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +// The code in this file is also used by +// storage/test/gtest/test_deadlock_detector.cpp. The following two macros are +// used to provide the necessary differentiation between this file and that +// file. +#ifndef MUTEX +# define MUTEX mozilla::Mutex +#endif +#ifndef TESTNAME +# define TESTNAME(name) XPCOM##name +#endif + +static PRThread* spawn(void (*run)(void*), void* arg) { + return PR_CreateThread(PR_SYSTEM_THREAD, run, arg, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); +} + +/** + * Simple test fixture that makes sure the gdb sleep setup in the + * ah crap handler is bypassed during the death tests. + */ +class TESTNAME(DeadlockDetectorTest) : public ::testing::Test { + protected: + void SetUp() final { SAVE_GDB_SLEEP_GLOBAL(mOldSleepDuration); } + + void TearDown() final { RESTORE_GDB_SLEEP_GLOBAL(mOldSleepDuration); } + + private: +#if defined(HAS_GDB_SLEEP_DURATION) + unsigned int mOldSleepDuration; +#endif // defined(HAS_GDB_SLEEP_DURATION) +}; + +//----------------------------------------------------------------------------- +// Single-threaded sanity tests + +// Stupidest possible deadlock. +static int Sanity_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + mozilla::gtest::DisableCrashReporter(); + + MUTEX m1("dd.sanity.m1"); + m1.Lock(); + m1.Lock(); + return 0; // not reached +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(SanityDeathTest)) { + const char* const regex = + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- Mutex : dd.sanity.m1.*" + "=== Cycle completed at.*--- Mutex : dd.sanity.m1.*" + "###!!! Deadlock may happen NOW!.*" // better catch these easy cases... + "###!!! ASSERTION: Potential deadlock detected.*"; + + ASSERT_DEATH_IF_SUPPORTED(Sanity_Child(), regex); +} + +// Slightly less stupid deadlock. +static int Sanity2_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + mozilla::gtest::DisableCrashReporter(); + + MUTEX m1("dd.sanity2.m1"); + MUTEX m2("dd.sanity2.m2"); + m1.Lock(); + m2.Lock(); + m1.Lock(); + return 0; // not reached +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity2DeathTest)) { + const char* const regex = + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- Mutex : dd.sanity2.m1.*" + "--- Next dependency:.*--- Mutex : dd.sanity2.m2.*" + "=== Cycle completed at.*--- Mutex : dd.sanity2.m1.*" + "###!!! Deadlock may happen NOW!.*" // better catch these easy cases... + "###!!! ASSERTION: Potential deadlock detected.*"; + + ASSERT_DEATH_IF_SUPPORTED(Sanity2_Child(), regex); +} + +#if 0 +// Temporarily disabled, see bug 1370644. +int +Sanity3_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS +{ + mozilla::gtest::DisableCrashReporter(); + + MUTEX m1("dd.sanity3.m1"); + MUTEX m2("dd.sanity3.m2"); + MUTEX m3("dd.sanity3.m3"); + MUTEX m4("dd.sanity3.m4"); + + m1.Lock(); + m2.Lock(); + m3.Lock(); + m4.Lock(); + m4.Unlock(); + m3.Unlock(); + m2.Unlock(); + m1.Unlock(); + + m4.Lock(); + m1.Lock(); + return 0; +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity3DeathTest)) +{ + const char* const regex = + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- Mutex : dd.sanity3.m1.*" + "--- Next dependency:.*--- Mutex : dd.sanity3.m2.*" + "--- Next dependency:.*--- Mutex : dd.sanity3.m3.*" + "--- Next dependency:.*--- Mutex : dd.sanity3.m4.*" + "=== Cycle completed at.*--- Mutex : dd.sanity3.m1.*" + "###!!! ASSERTION: Potential deadlock detected.*"; + + ASSERT_DEATH_IF_SUPPORTED(Sanity3_Child(), regex); +} +#endif + +static int Sanity4_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + mozilla::gtest::DisableCrashReporter(); + + mozilla::ReentrantMonitor m1 MOZ_UNANNOTATED("dd.sanity4.m1"); + MUTEX m2("dd.sanity4.m2"); + m1.Enter(); + m2.Lock(); + m1.Enter(); + return 0; +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity4DeathTest)) { + const char* const regex = + "Re-entering ReentrantMonitor after acquiring other resources.*" + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- ReentrantMonitor : " + "dd.sanity4.m1.*" + "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*" + "=== Cycle completed at.*--- ReentrantMonitor : dd.sanity4.m1.*" + "###!!! ASSERTION: Potential deadlock detected.*"; + ASSERT_DEATH_IF_SUPPORTED(Sanity4_Child(), regex); +} + +static int Sanity5_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + mozilla::gtest::DisableCrashReporter(); + + mozilla::RecursiveMutex m1 MOZ_UNANNOTATED("dd.sanity4.m1"); + MUTEX m2("dd.sanity4.m2"); + m1.Lock(); + m2.Lock(); + m1.Lock(); + return 0; +} + +#if !defined(DISABLE_STORAGE_SANITY5_DEATH_TEST) +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(Sanity5DeathTest)) { + const char* const regex = + "Re-entering RecursiveMutex after acquiring other resources.*" + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- RecursiveMutex : dd.sanity4.m1.*" + "--- Next dependency:.*--- Mutex : dd.sanity4.m2.*" + "=== Cycle completed at.*--- RecursiveMutex : dd.sanity4.m1.*" + "###!!! ASSERTION: Potential deadlock detected.*"; + ASSERT_DEATH_IF_SUPPORTED(Sanity5_Child(), regex); +} +#endif + +//----------------------------------------------------------------------------- +// Multithreaded tests + +/** + * Helper for passing state to threads in the multithread tests. + */ +struct ThreadState { + /** + * Locks to use during the test. This is just a reference and is owned by + * the main test thread. + */ + const nsTArray& locks; + + /** + * Integer argument used to identify each thread. + */ + int id; +}; + +#if 0 +// Temporarily disabled, see bug 1370644. +static void +TwoThreads_thread(void* arg) MOZ_NO_THREAD_SAFETY_ANALYSIS +{ + ThreadState* state = static_cast(arg); + + MUTEX* ttM1 = state->locks[0]; + MUTEX* ttM2 = state->locks[1]; + + if (state->id) { + ttM1->Lock(); + ttM2->Lock(); + ttM2->Unlock(); + ttM1->Unlock(); + } + else { + ttM2->Lock(); + ttM1->Lock(); + ttM1->Unlock(); + ttM2->Unlock(); + } +} + +int +TwoThreads_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS +{ + mozilla::gtest::DisableCrashReporter(); + + nsTArray locks = { + new MUTEX("dd.twothreads.m1"), + new MUTEX("dd.twothreads.m2") + }; + + ThreadState state_1 {locks, 0}; + PRThread* t1 = spawn(TwoThreads_thread, &state_1); + PR_JoinThread(t1); + + ThreadState state_2 {locks, 1}; + PRThread* t2 = spawn(TwoThreads_thread, &state_2); + PR_JoinThread(t2); + + for (auto& lock : locks) { + delete lock; + } + + return 0; +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(TwoThreadsDeathTest)) +{ + const char* const regex = + "###!!! ERROR: Potential deadlock detected.*" + "=== Cyclical dependency starts at.*--- Mutex : dd.twothreads.m2.*" + "--- Next dependency:.*--- Mutex : dd.twothreads.m1.*" + "=== Cycle completed at.*--- Mutex : dd.twothreads.m2.*" + "###!!! ASSERTION: Potential deadlock detected.*"; + + ASSERT_DEATH_IF_SUPPORTED(TwoThreads_Child(), regex); +} +#endif + +static void ContentionNoDeadlock_thread(void* arg) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + const uint32_t K = 100000; + + ThreadState* state = static_cast(arg); + int32_t starti = static_cast(state->id); + auto& cndMs = state->locks; + + for (uint32_t k = 0; k < K; ++k) { + for (int32_t i = starti; i < (int32_t)cndMs.Length(); ++i) cndMs[i]->Lock(); + // comment out the next two lines for deadlocking fun! + for (int32_t i = cndMs.Length() - 1; i >= starti; --i) cndMs[i]->Unlock(); + + starti = (starti + 1) % 3; + } +} + +static int ContentionNoDeadlock_Child() MOZ_NO_THREAD_SAFETY_ANALYSIS { + const size_t kMutexCount = 4; + + PRThread* threads[3]; + nsTArray locks; + ThreadState states[] = {{locks, 0}, {locks, 1}, {locks, 2}}; + + for (uint32_t i = 0; i < kMutexCount; ++i) + locks.AppendElement(new MUTEX("dd.cnd.ms")); + + for (int32_t i = 0; i < (int32_t)ArrayLength(threads); ++i) + threads[i] = spawn(ContentionNoDeadlock_thread, states + i); + + for (uint32_t i = 0; i < ArrayLength(threads); ++i) PR_JoinThread(threads[i]); + + for (uint32_t i = 0; i < locks.Length(); ++i) delete locks[i]; + + return 0; +} + +TEST_F(TESTNAME(DeadlockDetectorTest), TESTNAME(ContentionNoDeadlock)) { + // Just check that this test runs to completion. + ASSERT_EQ(ContentionNoDeadlock_Child(), 0); +} diff --git a/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp b/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp new file mode 100644 index 0000000000..40519f43ed --- /dev/null +++ b/xpcom/tests/gtest/TestDeadlockDetectorScalability.cpp @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: sw=2 ts=4 et : + * 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/. */ + +// Avoid DMD-specific parts of MOZ_DEFINE_MALLOC_SIZE_OF +#undef MOZ_DMD + +#include "nsIMemoryReporter.h" +#include "mozilla/Mutex.h" + +#include "gtest/gtest.h" + +//----------------------------------------------------------------------------- + +static void AllocLockRecurseUnlockFree(int i) { + if (0 == i) return; + + mozilla::Mutex* lock = new mozilla::Mutex("deadlockDetector.scalability.t1"); + { + mozilla::MutexAutoLock _(*lock); + AllocLockRecurseUnlockFree(i - 1); + } + delete lock; +} + +// This test creates a resource dependency chain N elements long, then +// frees all the resources in the chain. +TEST(DeadlockDetectorScalability, LengthNDepChain) +{ + const int N = 1 << 14; // 16K + AllocLockRecurseUnlockFree(N); + ASSERT_TRUE(true); +} + +//----------------------------------------------------------------------------- + +// This test creates a single lock that is ordered < N resources, then +// repeatedly exercises this order k times. +// +// NB: It takes a minute or two to run so it is disabled by default. +TEST(DeadlockDetectorScalability, DISABLED_OneLockNDeps) +{ + // NB: Using a larger test size to stress our traversal logic. + const int N = 1 << 17; // 131k + const int K = 100; + + mozilla::Mutex* lock = + new mozilla::Mutex("deadlockDetector.scalability.t2.master"); + mozilla::Mutex** locks = new mozilla::Mutex*[N]; + if (!locks) MOZ_CRASH("couldn't allocate lock array"); + + for (int i = 0; i < N; ++i) + locks[i] = new mozilla::Mutex("deadlockDetector.scalability.t2.dep"); + + // establish orders + { + mozilla::MutexAutoLock m(*lock); + for (int i = 0; i < N; ++i) mozilla::MutexAutoLock s(*locks[i]); + } + + // exercise order check + { + mozilla::MutexAutoLock m(*lock); + for (int i = 0; i < K; ++i) + for (int j = 0; j < N; ++j) mozilla::MutexAutoLock s(*locks[i]); + } + + for (int i = 0; i < N; ++i) delete locks[i]; + delete[] locks; + + ASSERT_TRUE(true); +} + +//----------------------------------------------------------------------------- + +// This test creates N resources and adds the theoretical maximum number +// of dependencies, O(N^2). It then repeats that sequence of +// acquisitions k times. Finally, all resources are freed. +// +// It's very difficult to perform well on this test. It's put forth as a +// challenge problem. + +TEST(DeadlockDetectorScalability, MaxDepsNsq) +{ + const int N = 1 << 10; // 1k + const int K = 10; + + mozilla::Mutex** locks = new mozilla::Mutex*[N]; + if (!locks) MOZ_CRASH("couldn't allocate lock array"); + + for (int i = 0; i < N; ++i) + locks[i] = new mozilla::Mutex("deadlockDetector.scalability.t3"); + + for (int i = 0; i < N; ++i) { + mozilla::MutexAutoLock al1(*locks[i]); + for (int j = i + 1; j < N; ++j) mozilla::MutexAutoLock al2(*locks[j]); + } + + for (int i = 0; i < K; ++i) { + for (int j = 0; j < N; ++j) { + mozilla::MutexAutoLock al1(*locks[j]); + for (int k = j + 1; k < N; ++k) mozilla::MutexAutoLock al2(*locks[k]); + } + } + + for (int i = 0; i < N; ++i) delete locks[i]; + delete[] locks; + + ASSERT_TRUE(true); +} + +//----------------------------------------------------------------------------- + +// This test creates a single lock that is ordered < N resources. The +// resources are allocated, exercised K times, and deallocated one at +// a time. + +TEST(DeadlockDetectorScalability, OneLockNDepsUsedSeveralTimes) +{ + const size_t N = 1 << 17; // 131k + const size_t K = 3; + + // Create master lock. + mozilla::Mutex* lock_1 = + new mozilla::Mutex("deadlockDetector.scalability.t4.master"); + for (size_t n = 0; n < N; n++) { + // Create child lock. + mozilla::Mutex* lock_2 = + new mozilla::Mutex("deadlockDetector.scalability.t4.child"); + + // First lock the master. + mozilla::MutexAutoLock m(*lock_1); + + // Now lock and unlock the child a few times. + for (size_t k = 0; k < K; k++) { + mozilla::MutexAutoLock c(*lock_2); + } + + // Destroy the child lock. + delete lock_2; + } + + // Cleanup the master lock. + delete lock_1; + + ASSERT_TRUE(true); +} + +//----------------------------------------------------------------------------- + +MOZ_DEFINE_MALLOC_SIZE_OF(DeadlockDetectorMallocSizeOf) + +// This is a simple test that exercises the deadlock detector memory reporting +// functionality. +TEST(DeadlockDetectorScalability, SizeOf) +{ + size_t memory_used = mozilla::BlockingResourceBase::SizeOfDeadlockDetector( + DeadlockDetectorMallocSizeOf); + + ASSERT_GT(memory_used, size_t(0)); +} diff --git a/xpcom/tests/gtest/TestDelayedRunnable.cpp b/xpcom/tests/gtest/TestDelayedRunnable.cpp new file mode 100644 index 0000000000..b612522b55 --- /dev/null +++ b/xpcom/tests/gtest/TestDelayedRunnable.cpp @@ -0,0 +1,168 @@ +/* -*- 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/DelayedRunnable.h" +#include "mozilla/Atomics.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskQueue.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "MediaTimer.h" +#include "mozilla/media/MediaUtils.h" +#include "VideoUtils.h" + +using mozilla::Atomic; +using mozilla::MakeRefPtr; +using mozilla::Monitor; +using mozilla::MonitorAutoLock; +using mozilla::TaskQueue; + +namespace { +struct ReleaseDetector { + explicit ReleaseDetector(Atomic* aActive) : mActive(aActive) { + *mActive = true; + } + ReleaseDetector(ReleaseDetector&& aOther) noexcept : mActive(aOther.mActive) { + aOther.mActive = nullptr; + } + ReleaseDetector(const ReleaseDetector&) = delete; + ~ReleaseDetector() { + if (mActive) { + *mActive = false; + } + } + Atomic* mActive; +}; +} // namespace + +TEST(DelayedRunnable, TaskQueueShutdownLeak) +{ + Atomic active{false}; + auto taskQueue = TaskQueue::Create( + GetMediaThreadPool(mozilla::MediaThreadType::SUPERVISOR), + "TestDelayedRunnable TaskQueueShutdownLeak"); + taskQueue->DelayedDispatch( + NS_NewRunnableFunction(__func__, [release = ReleaseDetector(&active)] {}), + 60e3 /* 1 minute */); + EXPECT_TRUE(active); + taskQueue->BeginShutdown(); + taskQueue->AwaitIdle(); + // Leaks are often detected after process shutdown. This doesn't wait that + // long, but leaking past thread shutdown would be equally bad since the + // runnable can no longer be released on the target thread. This is also the + // reason why timers assert that they don't release the last reference to + // their callbacks when dispatch fails (like when the target has been + // shutdown). + EXPECT_FALSE(active); +} + +TEST(DelayedRunnable, nsThreadShutdownLeak) +{ + Atomic active{false}; + nsCOMPtr thread; + ASSERT_EQ(NS_NewNamedThread("Test Thread", getter_AddRefs(thread)), NS_OK); + thread->DelayedDispatch( + NS_NewRunnableFunction(__func__, [release = ReleaseDetector(&active)] {}), + 60e3 /* 1 minute */); + EXPECT_TRUE(active); + ASSERT_EQ(thread->Shutdown(), NS_OK); + // Leaks are often detected after process shutdown. This doesn't wait that + // long, but leaking past thread shutdown would be equally bad since the + // runnable can no longer be released on the target thread. This is also the + // reason why timers assert that they don't release the last reference to + // their callbacks when dispatch fails (like when the target has been + // shutdown). + EXPECT_FALSE(active); +} + +/* + * This tests a case where we create a background TaskQueue that lives until + * xpcom shutdown. This test will fail (by assertion failure) if the TaskQueue + * shutdown task is dispatched too late in the shutdown sequence, or: + * If the background thread pool is then empty, the TaskQueue shutdown task will + * when dispatched require creating a new nsThread, which is forbidden too late + * in the shutdown sequence. + */ +TEST(DelayedRunnable, BackgroundTaskQueueShutdownTask) +{ + nsCOMPtr taskQueue; + nsresult rv = NS_CreateBackgroundTaskQueue("TestDelayedRunnable", + getter_AddRefs(taskQueue)); + ASSERT_NS_SUCCEEDED(rv); + + // Leak the queue, so it gets cleaned up by xpcom-shutdown. + nsISerialEventTarget* tq = taskQueue.forget().take(); + mozilla::Unused << tq; +} + +/* + * Like BackgroundTaskQueueShutdownTask but for nsThread, since both background + * TaskQueues and nsThreads are managed by nsThreadManager. For nsThread things + * are different and the shutdown task doesn't use Dispatch, but timings are + * similar. + */ +TEST(DelayedRunnable, nsThreadShutdownTask) +{ + nsCOMPtr thread; + ASSERT_EQ(NS_NewNamedThread("Test Thread", getter_AddRefs(thread)), NS_OK); + + // Leak the thread, so it gets cleaned up by xpcom-shutdown. + nsIThread* t = thread.forget().take(); + mozilla::Unused << t; +} + +TEST(DelayedRunnable, TimerFiresBeforeRunnableRuns) +{ + RefPtr pool = + mozilla::SharedThreadPool::Get("Test Pool"_ns); + auto tailTaskQueue1 = + TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable tailTaskQueue1", + /* aSupportsTailDispatch = */ true); + auto tailTaskQueue2 = + TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable tailTaskQueue2", + /* aSupportsTailDispatch = */ true); + auto noTailTaskQueue = + TaskQueue::Create(do_AddRef(pool), "TestDelayedRunnable noTailTaskQueue", + /* aSupportsTailDispatch = */ false); + enum class State : uint8_t { + Start, + TimerRan, + TasksFinished, + } state = State::Start; + Monitor monitor MOZ_UNANNOTATED(__func__); + MonitorAutoLock lock(monitor); + MOZ_ALWAYS_SUCCEEDS( + tailTaskQueue1->Dispatch(NS_NewRunnableFunction(__func__, [&] { + // This will tail dispatch the delayed runnable, making it prone to + // lose a race against the directly-initiated timer firing (and + // dispatching another non-tail-dispatched runnable). + EXPECT_TRUE(tailTaskQueue1->RequiresTailDispatch(tailTaskQueue2)); + tailTaskQueue2->DelayedDispatch( + NS_NewRunnableFunction(__func__, [&] {}), 1); + MonitorAutoLock lock(monitor); + auto timer = MakeRefPtr(); + timer->WaitFor(mozilla::TimeDuration::FromMilliseconds(1), __func__) + ->Then(noTailTaskQueue, __func__, [&] { + MonitorAutoLock lock(monitor); + state = State::TimerRan; + monitor.NotifyAll(); + }); + // Wait until the timer has run. It should have dispatched the + // TimerEvent to tailTaskQueue2 by then. The tail dispatch happens when + // we leave scope. + while (state != State::TimerRan) { + monitor.Wait(); + } + // Notify main thread that we've finished the async steps. + state = State::TasksFinished; + monitor.Notify(); + }))); + // Wait for async steps before wrapping up the test case. + while (state != State::TasksFinished) { + monitor.Wait(); + } +} diff --git a/xpcom/tests/gtest/TestEncoding.cpp b/xpcom/tests/gtest/TestEncoding.cpp new file mode 100644 index 0000000000..2abbc5d708 --- /dev/null +++ b/xpcom/tests/gtest/TestEncoding.cpp @@ -0,0 +1,108 @@ +/* -*- 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 +#include "nsString.h" +#include "gtest/gtest.h" + +TEST(Encoding, GoodSurrogatePair) +{ + // When this string is decoded, the surrogate pair is U+10302 and the rest of + // the string is specified by indexes 2 onward. + const char16_t goodPairData[] = {0xD800, 0xDF02, 0x65, 0x78, 0x0}; + nsDependentString goodPair16(goodPairData); + + uint32_t byteCount = 0; + char* goodPair8 = ToNewUTF8String(goodPair16, &byteCount); + EXPECT_TRUE(!!goodPair8); + + EXPECT_EQ(byteCount, 6u); + + const unsigned char expected8[] = {0xF0, 0x90, 0x8C, 0x82, 0x65, 0x78, 0x0}; + EXPECT_EQ(0, memcmp(expected8, goodPair8, sizeof(expected8))); + + // This takes a different code path from the above, so test it to make sure + // the UTF-16 enumeration remains in sync with the UTF-8 enumeration. + nsDependentCString expected((const char*)expected8); + EXPECT_EQ(0, CompareUTF8toUTF16(expected, goodPair16)); + + free(goodPair8); +} + +TEST(Encoding, BackwardsSurrogatePair) +{ + // When this string is decoded, the two surrogates are wrongly ordered and + // must each be interpreted as U+FFFD. + const char16_t backwardsPairData[] = {0xDDDD, 0xD863, 0x65, 0x78, 0x0}; + nsDependentString backwardsPair16(backwardsPairData); + + uint32_t byteCount = 0; + char* backwardsPair8 = ToNewUTF8String(backwardsPair16, &byteCount); + EXPECT_TRUE(!!backwardsPair8); + + EXPECT_EQ(byteCount, 8u); + + const unsigned char expected8[] = {0xEF, 0xBF, 0xBD, 0xEF, 0xBF, + 0xBD, 0x65, 0x78, 0x0}; + EXPECT_EQ(0, memcmp(expected8, backwardsPair8, sizeof(expected8))); + + // This takes a different code path from the above, so test it to make sure + // the UTF-16 enumeration remains in sync with the UTF-8 enumeration. + nsDependentCString expected((const char*)expected8); + EXPECT_EQ(0, CompareUTF8toUTF16(expected, backwardsPair16)); + + free(backwardsPair8); +} + +TEST(Encoding, MalformedUTF16OrphanHighSurrogate) +{ + // When this string is decoded, the high surrogate should be replaced and the + // rest of the string is specified by indexes 1 onward. + const char16_t highSurrogateData[] = {0xD863, 0x74, 0x65, 0x78, 0x74, 0x0}; + nsDependentString highSurrogate16(highSurrogateData); + + uint32_t byteCount = 0; + char* highSurrogate8 = ToNewUTF8String(highSurrogate16, &byteCount); + EXPECT_TRUE(!!highSurrogate8); + + EXPECT_EQ(byteCount, 7u); + + const unsigned char expected8[] = {0xEF, 0xBF, 0xBD, 0x74, + 0x65, 0x78, 0x74, 0x0}; + EXPECT_EQ(0, memcmp(expected8, highSurrogate8, sizeof(expected8))); + + // This takes a different code path from the above, so test it to make sure + // the UTF-16 enumeration remains in sync with the UTF-8 enumeration. + nsDependentCString expected((const char*)expected8); + EXPECT_EQ(0, CompareUTF8toUTF16(expected, highSurrogate16)); + + free(highSurrogate8); +} + +TEST(Encoding, MalformedUTF16OrphanLowSurrogate) +{ + // When this string is decoded, the low surrogate should be replaced and the + // rest of the string is specified by indexes 1 onward. + const char16_t lowSurrogateData[] = {0xDDDD, 0x74, 0x65, 0x78, 0x74, 0x0}; + nsDependentString lowSurrogate16(lowSurrogateData); + + uint32_t byteCount = 0; + char* lowSurrogate8 = ToNewUTF8String(lowSurrogate16, &byteCount); + EXPECT_TRUE(!!lowSurrogate8); + + EXPECT_EQ(byteCount, 7u); + + const unsigned char expected8[] = {0xEF, 0xBF, 0xBD, 0x74, + 0x65, 0x78, 0x74, 0x0}; + EXPECT_EQ(0, memcmp(expected8, lowSurrogate8, sizeof(expected8))); + + // This takes a different code path from the above, so test it to make sure + // the UTF-16 enumeration remains in sync with the UTF-8 enumeration. + nsDependentCString expected((const char*)expected8); + EXPECT_EQ(0, CompareUTF8toUTF16(expected, lowSurrogate16)); + + free(lowSurrogate8); +} diff --git a/xpcom/tests/gtest/TestEscape.cpp b/xpcom/tests/gtest/TestEscape.cpp new file mode 100644 index 0000000000..6834d5fa13 --- /dev/null +++ b/xpcom/tests/gtest/TestEscape.cpp @@ -0,0 +1,238 @@ +/* -*- 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 "nsEscape.h" +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "nsNetUtil.h" + +using namespace mozilla; + +// Testing for failure here would be somewhat hard in automation. Locally you +// could use something like ulimit to create a failure. + +TEST(Escape, FallibleNoEscape) +{ + // Tests the fallible version of NS_EscapeURL works as expected when no + // escaping is necessary. + nsCString toEscape("data:,Hello%2C%20World!"); + nsCString escaped; + nsresult rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII, escaped, fallible); + EXPECT_EQ(rv, NS_OK); + // Nothing should have been escaped, they should be the same string. + EXPECT_STREQ(toEscape.BeginReading(), escaped.BeginReading()); + // We expect them to point at the same buffer. + EXPECT_EQ(toEscape.BeginReading(), escaped.BeginReading()); +} + +TEST(Escape, FallibleEscape) +{ + // Tests the fallible version of NS_EscapeURL works as expected when + // escaping is necessary. + nsCString toEscape("data:,Hello%2C%20World!\xC4\x9F"); + nsCString escaped; + nsresult rv = NS_EscapeURL(toEscape, esc_OnlyNonASCII, escaped, fallible); + EXPECT_EQ(rv, NS_OK); + EXPECT_STRNE(toEscape.BeginReading(), escaped.BeginReading()); + const char* const kExpected = "data:,Hello%2C%20World!%C4%9F"; + EXPECT_STREQ(escaped.BeginReading(), kExpected); +} + +TEST(Escape, BadEscapeSequences) +{ + { + char bad[] = "%s\0fa"; + + int32_t count = nsUnescapeCount(bad); + EXPECT_EQ(count, 2); + EXPECT_STREQ(bad, "%s"); + } + { + char bad[] = "%a"; + int32_t count = nsUnescapeCount(bad); + EXPECT_EQ(count, 2); + EXPECT_STREQ(bad, "%a"); + } + { + char bad[] = "%"; + int32_t count = nsUnescapeCount(bad); + EXPECT_EQ(count, 1); + EXPECT_STREQ(bad, "%"); + } + { + char bad[] = "%s/%s"; + int32_t count = nsUnescapeCount(bad); + EXPECT_EQ(count, 5); + EXPECT_STREQ(bad, "%s/%s"); + } +} + +TEST(Escape, nsAppendEscapedHTML) +{ + const char* srcs[] = { + "a", "bcdefgh", "<", ">", "&", "\"", + "'", "'bad'", "Foo& foo", "'\"&>> pairs{ + {"https://chat.mozilla.org/#/room/#macdev:mozilla.org"_ns, + "https://chat.mozilla.org/#/room/%23macdev:mozilla.org"_ns}, + }; + + for (std::pair& pair : pairs) { + nsCString escaped; + nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, pair.first); + EXPECT_EQ(rv, NS_OK); + EXPECT_STREQ(pair.second.BeginReading(), escaped.BeginReading()); + } + + // A list of URLs that should not be changed by encoding. + nsTArray unchangedURLs{ + // '=' In the query + "https://bugzilla.mozilla.org/show_bug.cgi?id=1737854"_ns, + // Escaped character in the fragment + "https://html.spec.whatwg.org/multipage/dom.html#the-document%27s-address"_ns, + // Misc query + "https://www.google.com/search?q=firefox+web+browser&client=firefox-b-1-d&ei=abc&ved=abc&abc=5&oq=firefox+web+browser&gs_lcp=abc&sclient=gws-wiz"_ns, + // Check for double encoding. % encoded octals should not be re-encoded. + "https://chat.mozilla.org/#/room/%23macdev%3Amozilla.org"_ns, + "https://searchfox.org/mozilla-central/search?q=symbol%3AE_%3CT_mozilla%3A%3AWebGLExtensionID%3E_EXT_color_buffer_half_float&path="_ns, + // Other + "https://site.com/script?foo=bar#this_ref"_ns, + }; + + for (nsCString& toEscape : unchangedURLs) { + nsCString escaped; + nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, toEscape); + EXPECT_EQ(rv, NS_OK); + EXPECT_STREQ(toEscape.BeginReading(), escaped.BeginReading()); + } +} + +// Test external handler URLs are properly escaped. +TEST(Escape, EscapeURLExternalHandlerURLs) +{ + const nsCString input[] = { + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;/?:@&=+$,!'()*-._~#[]"_ns, + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"_ns, + "custom_proto:Hello World"_ns, + "custom_proto:Hello%20World"_ns, + "myApp://\"foo\" 'bar' `foo`"_ns, + "translator://en-de?view=übersicht"_ns, + "foo:some\\path\\here"_ns, + "web+foo://user:1234@example.com:8080?foo=bar"_ns, + "ext+bar://id='myId'"_ns}; + + const nsCString expected[] = { + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ;/?:@&=+$,!'()*-._~#[]"_ns, + "%20!%22#$%&'()*+,-./0123456789:;%3C=%3E?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[%5C]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~"_ns, + "custom_proto:Hello%20World"_ns, + "custom_proto:Hello%20World"_ns, + "myApp://%22foo%22%20'bar'%20%60foo%60"_ns, + "translator://en-de?view=%C3%BCbersicht"_ns, + "foo:some%5Cpath%5Chere"_ns, + "web+foo://user:1234@example.com:8080?foo=bar"_ns, + "ext+bar://id='myId'"_ns}; + + for (size_t i = 0; i < ArrayLength(input); i++) { + nsCString src(input[i]); + nsCString dst; + nsresult rv = + NS_EscapeURL(src, esc_ExtHandler | esc_AlwaysCopy, dst, fallible); + EXPECT_EQ(rv, NS_OK); + ASSERT_TRUE(dst.Equals(expected[i])); + } +} diff --git a/xpcom/tests/gtest/TestEventPriorities.cpp b/xpcom/tests/gtest/TestEventPriorities.cpp new file mode 100644 index 0000000000..874fc81a33 --- /dev/null +++ b/xpcom/tests/gtest/TestEventPriorities.cpp @@ -0,0 +1,91 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsXPCOM.h" +#include "nsThreadUtils.h" +#include "gtest/gtest.h" +#include "mozilla/SpinEventLoopUntil.h" + +#include + +using namespace mozilla; + +class TestEvent final : public Runnable, nsIRunnablePriority { + public: + explicit TestEvent(int* aCounter, std::function&& aCheck, + uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL) + : Runnable("TestEvent"), + mCounter(aCounter), + mCheck(std::move(aCheck)), + mPriority(aPriority) {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD GetPriority(uint32_t* aPriority) override { + *aPriority = mPriority; + return NS_OK; + } + + NS_IMETHODIMP Run() override { + (*mCounter)++; + mCheck(); + return NS_OK; + } + + private: + ~TestEvent() = default; + + int* mCounter; + std::function mCheck; + uint32_t mPriority; +}; + +NS_IMPL_ISUPPORTS_INHERITED(TestEvent, Runnable, nsIRunnablePriority) + +TEST(EventPriorities, IdleAfterNormal) +{ + int normalRan = 0, idleRan = 0; + + RefPtr evNormal = + new TestEvent(&normalRan, [&] { ASSERT_EQ(idleRan, 0); }); + RefPtr evIdle = + new TestEvent(&idleRan, [&] { ASSERT_EQ(normalRan, 3); }); + + NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue(do_AddRef(evIdle), EventQueuePriority::Idle); + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evNormal); + + MOZ_ALWAYS_TRUE( + SpinEventLoopUntil("xpcom:TEST(EventPriorities, IdleAfterNormal)"_ns, + [&]() { return normalRan == 3 && idleRan == 3; })); +} + +TEST(EventPriorities, HighNormal) +{ + int normalRan = 0, highRan = 0; + + RefPtr evNormal = new TestEvent( + &normalRan, [&] { ASSERT_TRUE((highRan - normalRan) >= 0); }); + RefPtr evHigh = new TestEvent( + &highRan, [&] { ASSERT_TRUE((highRan - normalRan) >= 0); }, + nsIRunnablePriority::PRIORITY_VSYNC); + + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evNormal); + NS_DispatchToMainThread(evHigh); + NS_DispatchToMainThread(evHigh); + NS_DispatchToMainThread(evHigh); + + MOZ_ALWAYS_TRUE( + SpinEventLoopUntil("xpcom:TEST(EventPriorities, HighNormal)"_ns, + [&]() { return normalRan == 3 && highRan == 3; })); +} diff --git a/xpcom/tests/gtest/TestEventTargetQI.cpp b/xpcom/tests/gtest/TestEventTargetQI.cpp new file mode 100644 index 0000000000..6131b5e63e --- /dev/null +++ b/xpcom/tests/gtest/TestEventTargetQI.cpp @@ -0,0 +1,83 @@ +/* -*- 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/LazyIdleThread.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/ThrottledEventQueue.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsThreadPool.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "nsXPCOMCIDInternal.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +// Cast the pointer to nsISupports* through nsIEventTarget* before doing the QI +// in order to avoid a static assert intended to prevent trivial QIs, while also +// avoiding ambiguous base errors. +template +bool TestQITo(SourcePtr& aPtr1) { + nsCOMPtr aPtr2 = do_QueryInterface( + static_cast(static_cast(aPtr1.get()))); + return (bool)aPtr2; +} + +TEST(TestEventTargetQI, ThreadPool) +{ + nsCOMPtr thing = new nsThreadPool(); + + EXPECT_FALSE(TestQITo(thing)); + + EXPECT_TRUE(TestQITo(thing)); + + thing->Shutdown(); +} + +TEST(TestEventTargetQI, SharedThreadPool) +{ + nsCOMPtr thing = SharedThreadPool::Get("TestPool"_ns, 1); + EXPECT_TRUE(thing); + + EXPECT_FALSE(TestQITo(thing)); + + EXPECT_TRUE(TestQITo(thing)); +} + +TEST(TestEventTargetQI, Thread) +{ + nsCOMPtr thing = do_GetCurrentThread(); + EXPECT_TRUE(thing); + + EXPECT_TRUE(TestQITo(thing)); + + EXPECT_TRUE(TestQITo(thing)); +} + +TEST(TestEventTargetQI, ThrottledEventQueue) +{ + nsCOMPtr thread = do_GetCurrentThread(); + RefPtr thing = + ThrottledEventQueue::Create(thread, "test queue"); + EXPECT_TRUE(thing); + + EXPECT_TRUE(TestQITo(thing)); + + EXPECT_TRUE(TestQITo(thing)); +} + +TEST(TestEventTargetQI, LazyIdleThread) +{ + RefPtr thing = new LazyIdleThread(0, "TestThread"); + EXPECT_TRUE(thing); + + EXPECT_TRUE(TestQITo(thing)); + + EXPECT_TRUE(TestQITo(thing)); + + thing->Shutdown(); +} diff --git a/xpcom/tests/gtest/TestExpirationTracker.cpp b/xpcom/tests/gtest/TestExpirationTracker.cpp new file mode 100644 index 0000000000..4bc00a80aa --- /dev/null +++ b/xpcom/tests/gtest/TestExpirationTracker.cpp @@ -0,0 +1,194 @@ +/* -*- 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 +#include +#include +#include "nsExpirationTracker.h" +#include "nsString.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsXPCOM.h" +#include "prinrval.h" +#include "nsThreadUtils.h" +#include "mozilla/UniquePtr.h" +#include "gtest/gtest.h" + +namespace TestExpirationTracker { + +struct Object { + Object() : mExpired(false) { Touch(); } + void Touch() { + mLastUsed = PR_IntervalNow(); + mExpired = false; + } + + nsExpirationState mExpiration; + nsExpirationState* GetExpirationState() { return &mExpiration; } + + PRIntervalTime mLastUsed; + bool mExpired; +}; + +static bool error; +static uint32_t periodMS = 100; +static uint32_t ops = 1000; +static uint32_t iterations = 2; +static bool logging = 0; +static uint32_t sleepPeriodMS = 50; +static uint32_t upperBoundSlackMS = 1200; // allow this much error +static uint32_t lowerBoundSlackMS = 60; + +template +class Tracker : public nsExpirationTracker { + public: + Tracker() : nsExpirationTracker(periodMS, "Tracker") { + Object* obj = new Object(); + mUniverse.AppendElement(obj); + LogAction(obj, "Created"); + } + + nsTArray> mUniverse; + + void LogAction(Object* aObj, const char* aAction) { + if (logging) { + printf("%d %p(%d): %s\n", PR_IntervalNow(), static_cast(aObj), + aObj->mLastUsed, aAction); + } + } + + void DoRandomOperation() { + using mozilla::UniquePtr; + + Object* obj; + switch (rand() & 0x7) { + case 0: { + if (mUniverse.Length() < 50) { + obj = new Object(); + mUniverse.AppendElement(obj); + nsExpirationTracker::AddObject(obj); + LogAction(obj, "Created and added"); + } + break; + } + case 4: { + if (mUniverse.Length() < 50) { + obj = new Object(); + mUniverse.AppendElement(obj); + LogAction(obj, "Created"); + } + break; + } + case 1: { + UniquePtr& objref = + mUniverse[uint32_t(rand()) % mUniverse.Length()]; + if (objref->mExpiration.IsTracked()) { + nsExpirationTracker::RemoveObject(objref.get()); + LogAction(objref.get(), "Removed"); + } + break; + } + case 2: { + UniquePtr& objref = + mUniverse[uint32_t(rand()) % mUniverse.Length()]; + if (!objref->mExpiration.IsTracked()) { + objref->Touch(); + nsExpirationTracker::AddObject(objref.get()); + LogAction(objref.get(), "Added"); + } + break; + } + case 3: { + UniquePtr& objref = + mUniverse[uint32_t(rand()) % mUniverse.Length()]; + if (objref->mExpiration.IsTracked()) { + objref->Touch(); + nsExpirationTracker::MarkUsed(objref.get()); + LogAction(objref.get(), "Marked used"); + } + break; + } + } + } + + protected: + void NotifyExpired(Object* aObj) override { + LogAction(aObj, "Expired"); + PRIntervalTime now = PR_IntervalNow(); + uint32_t timeDiffMS = (now - aObj->mLastUsed) * 1000 / PR_TicksPerSecond(); + // See the comment for NotifyExpired in nsExpirationTracker.h for these + // bounds + uint32_t lowerBoundMS = (K - 1) * periodMS - lowerBoundSlackMS; + uint32_t upperBoundMS = K * (periodMS + sleepPeriodMS) + upperBoundSlackMS; + if (logging) { + printf("Checking: %d-%d = %d [%d,%d]\n", now, aObj->mLastUsed, timeDiffMS, + lowerBoundMS, upperBoundMS); + } + if (timeDiffMS < lowerBoundMS || timeDiffMS > upperBoundMS) { + EXPECT_LT(timeDiffMS, periodMS); + EXPECT_TRUE(aObj->mExpired); + } + aObj->Touch(); + aObj->mExpired = true; + DoRandomOperation(); + DoRandomOperation(); + DoRandomOperation(); + } +}; + +template +static bool test_random() { + srand(K); + error = false; + + for (uint32_t j = 0; j < iterations; ++j) { + Tracker tracker; + + uint32_t i = 0; + for (i = 0; i < ops; ++i) { + if ((rand() & 0xF) == 0) { + // Simulate work that takes time + if (logging) { + printf("SLEEPING for %dms (%d)\n", sleepPeriodMS, PR_IntervalNow()); + } + PR_Sleep(PR_MillisecondsToInterval(sleepPeriodMS)); + // Process pending timer events + NS_ProcessPendingEvents(nullptr); + } + tracker.DoRandomOperation(); + } + } + + return !error; +} + +static bool test_random3() { return test_random<3>(); } +static bool test_random4() { return test_random<4>(); } +static bool test_random8() { return test_random<8>(); } + +typedef bool (*TestFunc)(); +#define DECL_TEST(name) \ + { \ +# name, name \ + } + +static const struct Test { + const char* name; + TestFunc func; +} tests[] = {DECL_TEST(test_random3), + DECL_TEST(test_random4), + DECL_TEST(test_random8), + {nullptr, nullptr}}; + +TEST(ExpirationTracker, main) +{ + for (const TestExpirationTracker::Test* t = tests; t->name != nullptr; ++t) { + EXPECT_TRUE(t->func()); + } +} + +} // namespace TestExpirationTracker diff --git a/xpcom/tests/gtest/TestFile.cpp b/xpcom/tests/gtest/TestFile.cpp new file mode 100644 index 0000000000..6e95366584 --- /dev/null +++ b/xpcom/tests/gtest/TestFile.cpp @@ -0,0 +1,576 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "prio.h" +#include "prsystem.h" + +#include "nsIFile.h" +#ifdef XP_WIN +# include "nsILocalFileWin.h" +#endif +#include "nsComponentManagerUtils.h" +#include "nsString.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsPrintfCString.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +#ifdef XP_WIN +bool gTestWithPrefix_Win = false; +#endif + +static bool VerifyResult(nsresult aRV, const char* aMsg) { + bool failed = NS_FAILED(aRV); + EXPECT_FALSE(failed) << aMsg << " rv=" << std::hex << (unsigned int)aRV; + return !failed; +} + +#ifdef XP_WIN +static void SetUseDOSDevicePathSyntax(nsIFile* aFile) { + if (gTestWithPrefix_Win) { + nsresult rv; + nsCOMPtr winFile = do_QueryInterface(aFile, &rv); + VerifyResult(rv, "Querying nsILocalFileWin"); + + MOZ_ASSERT(winFile); + winFile->SetUseDOSDevicePathSyntax(true); + } +} +#endif + +static already_AddRefed NewFile(nsIFile* aBase) { + nsresult rv; + nsCOMPtr file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv); + VerifyResult(rv, "Creating nsIFile"); + rv = file->InitWithFile(aBase); + VerifyResult(rv, "InitWithFile"); + +#ifdef XP_WIN + SetUseDOSDevicePathSyntax(file); +#endif + + return file.forget(); +} + +template +static nsTString FixName(const char_type* aName) { + nsTString name; + for (uint32_t i = 0; aName[i]; ++i) { + char_type ch = aName[i]; + // PR_GetPathSeparator returns the wrong value on Mac so don't use it +#if defined(XP_WIN) + if (ch == '/') { + ch = '\\'; + } +#endif + name.Append(ch); + } + return name; +} + +// Test nsIFile::AppendNative, verifying that aName is not a valid file name +static bool TestInvalidFileName(nsIFile* aBase, const char* aName) { + nsCOMPtr file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (NS_SUCCEEDED(rv)) { + EXPECT_NS_FAILED(rv) << "AppendNative with invalid filename " << name.get(); + return false; + } + + return true; +} + +// Test nsIFile::Create, verifying that the file exists and did not exist +// before, and leaving it there for future tests +static bool TestCreate(nsIFile* aBase, const char* aName, int32_t aType, + int32_t aPerm) { + nsCOMPtr file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " already exists"; + if (exists) { + return false; + } + + rv = file->Create(aType, aPerm); + if (!VerifyResult(rv, "Create")) return false; + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_TRUE(exists) << "File " << name.get() << " was not created"; + if (!exists) { + return false; + } + + return true; +} + +// Test nsIFile::CreateUnique, verifying that the new file exists and if it +// existed before, the new file has a different name. The new file is left in +// place. +static bool TestCreateUnique(nsIFile* aBase, const char* aName, int32_t aType, + int32_t aPerm) { + nsCOMPtr file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool existsBefore; + rv = file->Exists(&existsBefore); + if (!VerifyResult(rv, "Exists (before)")) return false; + + rv = file->CreateUnique(aType, aPerm); + if (!VerifyResult(rv, "Create")) return false; + + bool existsAfter; + rv = file->Exists(&existsAfter); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_TRUE(existsAfter) << "File " << name.get() << " was not created"; + if (!existsAfter) { + return false; + } + + if (existsBefore) { + nsAutoCString leafName; + rv = file->GetNativeLeafName(leafName); + if (!VerifyResult(rv, "GetNativeLeafName")) return false; + EXPECT_FALSE(leafName.Equals(name)) + << "File " << name.get() << " was not given a new name by CreateUnique"; + if (leafName.Equals(name)) { + return false; + } + } + + return true; +} + +// Test nsIFile::OpenNSPRFileDesc with DELETE_ON_CLOSE, verifying that the file +// exists and did not exist before, and leaving it there for future tests +static bool TestDeleteOnClose(nsIFile* aBase, const char* aName, int32_t aFlags, + int32_t aPerm) { + nsCOMPtr file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " already exists"; + if (exists) { + return false; + } + + PRFileDesc* fileDesc; + rv = file->OpenNSPRFileDesc(aFlags | nsIFile::DELETE_ON_CLOSE, aPerm, + &fileDesc); + if (!VerifyResult(rv, "OpenNSPRFileDesc")) return false; + PRStatus status = PR_Close(fileDesc); + EXPECT_EQ(status, PR_SUCCESS) + << "File " << name.get() << " could not be closed"; + if (status != PR_SUCCESS) { + return false; + } + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " was not removed on close"; + if (exists) { + return false; + } + + return true; +} + +// Test nsIFile::Remove, verifying that the file does not exist and did before +static bool TestRemove(nsIFile* aBase, const char* aName, bool aRecursive, + uint32_t aExpectedRemoveCount = 1) { + nsCOMPtr file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_TRUE(exists); + if (!exists) { + return false; + } + + uint32_t removeCount = 0; + rv = file->Remove(aRecursive, &removeCount); + if (!VerifyResult(rv, "Remove")) return false; + EXPECT_EQ(removeCount, aExpectedRemoveCount) << "Removal count was wrong"; + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " was not removed"; + if (exists) { + return false; + } + + return true; +} + +// Test nsIFile::MoveToNative, verifying that the file did not exist at the new +// location before and does afterward, and that it does not exist at the old +// location anymore +static bool TestMove(nsIFile* aBase, nsIFile* aDestDir, const char* aName, + const char* aNewName) { + nsCOMPtr file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_TRUE(exists); + if (!exists) { + return false; + } + + nsCOMPtr newFile = NewFile(file); + nsCString newName = FixName(aNewName); + rv = newFile->MoveToNative(aDestDir, newName); + if (!VerifyResult(rv, "MoveToNative")) return false; + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_FALSE(exists) << "File " << name.get() << " was not moved"; + if (exists) { + return false; + } + + file = NewFile(aDestDir); + if (!file) return false; + rv = file->AppendNative(newName); + if (!VerifyResult(rv, "AppendNative")) return false; + bool equal; + rv = file->Equals(newFile, &equal); + if (!VerifyResult(rv, "Equals")) return false; + EXPECT_TRUE(equal) << "File object was not updated to destination"; + if (!equal) { + return false; + } + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (new after)")) return false; + EXPECT_TRUE(exists) << "Destination file " << newName.get() + << " was not created"; + if (!exists) { + return false; + } + + return true; +} + +// Test nsIFile::CopyToNative, verifying that the file did not exist at the new +// location before and does afterward, and that it does exist at the old +// location too +static bool TestCopy(nsIFile* aBase, nsIFile* aDestDir, const char* aName, + const char* aNewName) { + nsCOMPtr file = NewFile(aBase); + if (!file) return false; + + nsCString name = FixName(aName); + nsresult rv = file->AppendNative(name); + if (!VerifyResult(rv, "AppendNative")) return false; + + bool exists; + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (before)")) return false; + EXPECT_TRUE(exists); + if (!exists) { + return false; + } + + nsCOMPtr newFile = NewFile(file); + nsCString newName = FixName(aNewName); + rv = newFile->CopyToNative(aDestDir, newName); + if (!VerifyResult(rv, "MoveToNative")) return false; + bool equal; + rv = file->Equals(newFile, &equal); + if (!VerifyResult(rv, "Equals")) return false; + EXPECT_TRUE(equal) << "File object updated unexpectedly"; + if (!equal) { + return false; + } + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (after)")) return false; + EXPECT_TRUE(exists) << "File " << name.get() << " was removed"; + if (!exists) { + return false; + } + + file = NewFile(aDestDir); + if (!file) return false; + rv = file->AppendNative(newName); + if (!VerifyResult(rv, "AppendNative")) return false; + + rv = file->Exists(&exists); + if (!VerifyResult(rv, "Exists (new after)")) return false; + EXPECT_TRUE(exists) << "Destination file " << newName.get() + << " was not created"; + if (!exists) { + return false; + } + + return true; +} + +// Test nsIFile::GetParent +static bool TestParent(nsIFile* aBase, nsIFile* aStart) { + nsCOMPtr file = NewFile(aStart); + if (!file) return false; + + nsCOMPtr parent; + nsresult rv = file->GetParent(getter_AddRefs(parent)); + VerifyResult(rv, "GetParent"); + + bool equal; + rv = parent->Equals(aBase, &equal); + VerifyResult(rv, "Equals"); + EXPECT_TRUE(equal) << "Incorrect parent"; + if (!equal) { + return false; + } + + return true; +} + +// Test nsIFile::Normalize and native path setting/getting +static bool TestNormalizeNativePath(nsIFile* aBase, nsIFile* aStart) { + nsCOMPtr file = NewFile(aStart); + if (!file) return false; + + auto path = file->NativePath(); +#ifdef XP_WIN + path.Append(FixName(u"/./..")); + nsresult rv = file->InitWithPath(path); + VerifyResult(rv, "InitWithPath"); +#else + path.Append(FixName("/./..")); + nsresult rv = file->InitWithNativePath(path); + VerifyResult(rv, "InitWithNativePath"); +#endif + rv = file->Normalize(); + VerifyResult(rv, "Normalize"); + path = file->NativePath(); + + auto basePath = aBase->NativePath(); + VerifyResult(rv, "GetNativePath (base)"); + + EXPECT_TRUE(path.Equals(basePath)) + << "Incorrect normalization: " << file->HumanReadablePath().get() << " - " + << aBase->HumanReadablePath().get(); + if (!path.Equals(basePath)) { + return false; + } + + return true; +} + +// Test nsIFile::GetDiskSpaceAvailable +static bool TestDiskSpaceAvailable(nsIFile* aBase) { + nsCOMPtr file = NewFile(aBase); + if (!file) return false; + + int64_t diskSpaceAvailable = 0; + nsresult rv = file->GetDiskSpaceAvailable(&diskSpaceAvailable); + VerifyResult(rv, "GetDiskSpaceAvailable"); + + EXPECT_GE(diskSpaceAvailable, 0); + + return true; +} + +// Test nsIFile::GetDiskCapacity +static bool TestDiskCapacity(nsIFile* aBase) { + nsCOMPtr file = NewFile(aBase); + if (!file) return false; + + int64_t diskCapacity = 0; + nsresult rv = file->GetDiskCapacity(&diskCapacity); + VerifyResult(rv, "GetDiskCapacity"); + + EXPECT_GE(diskCapacity, 0); + + return true; +} + +static void SetupAndTestFunctions(const nsAString& aDirName, + bool aTestCreateUnique, bool aTestNormalize) { + nsCOMPtr base; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base)); + ASSERT_TRUE(VerifyResult(rv, "Getting temp directory")); + +#ifdef XP_WIN + SetUseDOSDevicePathSyntax(base); +#endif + + rv = base->Append(aDirName); + ASSERT_TRUE( + VerifyResult(rv, nsPrintfCString("Appending %s to temp directory name", + NS_ConvertUTF16toUTF8(aDirName).get()) + .get())); + + // Remove the directory in case tests failed and left it behind. + // don't check result since it might not be there + base->Remove(true); + + // Now create the working directory we're going to use + rv = base->Create(nsIFile::DIRECTORY_TYPE, 0700); + ASSERT_TRUE(VerifyResult(rv, "Creating temp directory")); + + // Now we can safely normalize the path + if (aTestNormalize) { + rv = base->Normalize(); + ASSERT_TRUE(VerifyResult(rv, "Normalizing temp directory name")); + } + + // Initialize subdir object for later use + nsCOMPtr subdir = NewFile(base); + ASSERT_TRUE(subdir); + + rv = subdir->AppendNative(nsDependentCString("subdir")); + ASSERT_TRUE(VerifyResult(rv, "Appending 'subdir' to test dir name")); + + // --------------- + // End setup code. + // --------------- + + // Test leafName + nsString leafName; + rv = base->GetLeafName(leafName); + ASSERT_TRUE(VerifyResult(rv, "Getting leafName")); + ASSERT_TRUE(leafName.Equals(aDirName)); + + // Test path parsing + ASSERT_TRUE(TestInvalidFileName(base, "a/b")); + ASSERT_TRUE(TestParent(base, subdir)); + + // Test file creation + ASSERT_TRUE(TestCreate(base, "file.txt", nsIFile::NORMAL_FILE_TYPE, 0600)); + ASSERT_TRUE(TestRemove(base, "file.txt", false)); + + // Test directory creation + ASSERT_TRUE(TestCreate(base, "subdir", nsIFile::DIRECTORY_TYPE, 0700)); + + // Test move and copy in the base directory + ASSERT_TRUE(TestCreate(base, "file.txt", nsIFile::NORMAL_FILE_TYPE, 0600)); + ASSERT_TRUE(TestMove(base, base, "file.txt", "file2.txt")); + ASSERT_TRUE(TestCopy(base, base, "file2.txt", "file3.txt")); + + // Test moving across directories + ASSERT_TRUE(TestMove(base, subdir, "file2.txt", "file2.txt")); + + // Test moving across directories and renaming at the same time + ASSERT_TRUE(TestMove(subdir, base, "file2.txt", "file4.txt")); + + // Test copying across directories + ASSERT_TRUE(TestCopy(base, subdir, "file4.txt", "file5.txt")); + + if (aTestNormalize) { + // Run normalization tests while the directory exists + ASSERT_TRUE(TestNormalizeNativePath(base, subdir)); + } + + // Test recursive directory removal + ASSERT_TRUE(TestRemove(base, "subdir", true, 2)); + + if (aTestCreateUnique) { + ASSERT_TRUE(TestCreateUnique(base, "foo", nsIFile::NORMAL_FILE_TYPE, 0600)); + ASSERT_TRUE(TestCreateUnique(base, "foo", nsIFile::NORMAL_FILE_TYPE, 0600)); + ASSERT_TRUE( + TestCreateUnique(base, "bar.xx", nsIFile::DIRECTORY_TYPE, 0700)); + ASSERT_TRUE( + TestCreateUnique(base, "bar.xx", nsIFile::DIRECTORY_TYPE, 0700)); + } + + ASSERT_TRUE( + TestDeleteOnClose(base, "file7.txt", PR_RDWR | PR_CREATE_FILE, 0600)); + + ASSERT_TRUE(TestDiskSpaceAvailable(base)); + ASSERT_TRUE(TestDiskCapacity(base)); + + // Clean up temporary stuff + rv = base->Remove(true); + VerifyResult(rv, "Cleaning up temp directory"); +} + +TEST(TestFile, Unprefixed) +{ +#ifdef XP_WIN + gTestWithPrefix_Win = false; +#endif + + SetupAndTestFunctions(u"mozfiletests"_ns, + /* aTestCreateUnique */ true, + /* aTestNormalize */ true); + +#ifdef XP_WIN + gTestWithPrefix_Win = true; +#endif +} + +// This simulates what QM_NewLocalFile does (NS_NewLocalFiles and then +// SetUseDOSDevicePathSyntax if it's on Windows for NewFile) +TEST(TestFile, PrefixedOnWin) +{ + SetupAndTestFunctions(u"mozfiletests"_ns, + /* aTestCreateUnique */ true, + /* aTestNormalize */ true); +} + +TEST(TestFile, PrefixedOnWin_PathExceedsMaxPath) +{ + // We want to verify if the prefix would allow as to create a file with over + // 260 char for its path. However, on Windows, the maximum length of filename + // is 255. Given the base file path and we are going append some other file + // to the current base file, let's assume the file path will exceed 260 so + // that we are able to verify if the prefix works or not. + nsString dirName; + dirName.AssignLiteral("mozfiletests"); + for (uint32_t i = 255 - dirName.Length(); i > 0; --i) { + dirName.AppendLiteral("a"); + } + + // Bypass the test for CreateUnique because there is a check for the max + // length of the root on all platforms. + SetupAndTestFunctions(dirName, /* aTestCreateUnique */ false, + /* aTestNormalize */ true); +} + +TEST(TestFile, PrefixedOnWin_ComponentEndsWithPeriod) +{ + // Bypass the normalization for this because it would strip the trailing + // period. + SetupAndTestFunctions(u"mozfiletests."_ns, + /* aTestCreateUnique */ true, + /* aTestNormalize */ false); +} diff --git a/xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp b/xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp new file mode 100644 index 0000000000..39b73a7148 --- /dev/null +++ b/xpcom/tests/gtest/TestFileNTFSSpecialPaths.cpp @@ -0,0 +1,289 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "prio.h" +#include "prsystem.h" + +#include "mozilla/gtest/MozAssertions.h" +#include "nsComponentManagerUtils.h" +#include "nsIFile.h" +#include "nsILocalFileWin.h" +#include "nsString.h" + +#define MAX_PATH 260 + +#include "gtest/gtest.h" + +static void CanInitWith(const char* aPath, bool aShouldWork) { + nsCOMPtr file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + nsresult rv = file->InitWithNativePath(nsDependentCString(aPath)); + bool success = aShouldWork ? NS_SUCCEEDED(rv) : NS_FAILED(rv); + EXPECT_TRUE(success) << "'" << aPath << "' rv=" << std::hex + << (unsigned int)rv; +} + +static void CanAppend(const char* aRoot, const char* aPath, bool aShouldWork) { + nsCOMPtr file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + file->InitWithNativePath(nsDependentCString(aRoot)); + nsAutoCString basePath; + file->GetNativeTarget(basePath); + + nsresult rv = file->AppendNative(nsDependentCString(aPath)); + bool success = aShouldWork ? NS_SUCCEEDED(rv) : NS_FAILED(rv); + EXPECT_TRUE(success) << "'" << basePath.get() << "' + '" << aPath + << "' rv=" << std::hex << (unsigned int)rv; +} + +static void CanSetLeafName(const char* aRoot, const char* aPath, + bool aShouldWork) { + nsCOMPtr file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + file->InitWithNativePath(nsDependentCString(aRoot)); + nsAutoCString basePath; + file->GetNativeTarget(basePath); + + nsresult rv = + file->SetLeafName(NS_ConvertUTF8toUTF16(nsDependentCString(aPath))); + bool success = aShouldWork ? NS_SUCCEEDED(rv) : NS_FAILED(rv); + EXPECT_TRUE(success) << "'" << basePath.get() << "' set leaf to '" << aPath + << "' rv=" << std::hex << (unsigned int)rv; +} + +TEST(TestFileNTFSSpecialPaths, PlainPaths) +{ + CanInitWith("C:\\", true); + CanInitWith("C:\\foo", true); + CanInitWith("C:\\bar\\foo", true); + CanInitWith("C:\\bar\\foo\\", true); + + CanAppend("C:\\", "foo", true); + CanAppend("C:\\", "bar", true); + CanAppend("C:\\bar", "foo", true); + + CanSetLeafName("C:\\a", "foo", true); + CanSetLeafName("C:\\a", "bar", true); +} + +TEST(TestFileNTFSSpecialPaths, AllowedSpecialChars) +{ + CanInitWith("C:\\$foo", true); + CanInitWith("C:\\bar\\$foo", true); + CanInitWith("C:\\foo:Zone.Identifier", true); + CanInitWith("C:\\$foo:Zone.Identifier", true); + CanInitWith("C:\\bar\\$foo:Zone.Identifier", true); + + CanAppend("C:\\", "$foo", true); + CanAppend("C:\\bar\\", "$foo", true); + CanAppend("C:\\", "foo:Zone.Identifier", true); + CanAppend("C:\\", "$foo:Zone.Identifier", true); + CanAppend("C:\\bar\\", "$foo:Zone.Identifier", true); + + CanSetLeafName("C:\\a", "$foo", true); + CanSetLeafName("C:\\a", "foo:Zone.Identifier", true); + CanSetLeafName("C:\\a", "$foo:Zone.Identifier", true); +} + +TEST(TestFileNTFSSpecialPaths, ForbiddenAttributes) +{ + CanInitWith("C:\\:$MFT", false); + CanInitWith("C:\\:$mft", false); + CanInitWith("C:\\:$foo", false); + // nsLocalFileWin strips the trailing slash so this should also fail: + CanInitWith("C:\\:$MFT\\", false); + CanInitWith("C:\\:$mft\\", false); + CanInitWith("C:\\:$foo\\", false); + + // We just block these everywhere, not just at the root: + CanInitWith("C:\\bar\\:$mft", false); + CanInitWith("C:\\bar\\:$mft\\", false); + CanInitWith("C:\\bar\\:$foo", false); + CanInitWith("C:\\bar\\:$foo\\", false); + + // Now do the same for appending. + CanAppend("C:\\", ":$MFT", false); + CanAppend("C:\\", ":$mft", false); + CanAppend("C:\\", ":$foo", false); + // nsLocalFileWin strips the trailing slash so this should also fail: + CanAppend("C:\\", ":$MFT\\", false); + CanAppend("C:\\", ":$mft\\", false); + CanAppend("C:\\", ":$foo\\", false); + + // We just block these everywhere, not just at the root: + CanAppend("C:\\bar\\", ":$mft", false); + CanAppend("C:\\bar\\", ":$mft\\", false); + CanAppend("C:\\bar\\", ":$foo", false); + CanAppend("C:\\bar\\", ":$foo\\", false); + + // And the same thing for leaf names: + CanSetLeafName("C:\\a", ":$MFT", false); + CanSetLeafName("C:\\a", ":$mft", false); + CanSetLeafName("C:\\a", ":$foo", false); + + CanSetLeafName("C:\\a", ":$MFT\\", false); + CanSetLeafName("C:\\a", ":$mft\\", false); + CanSetLeafName("C:\\a", ":$foo\\", false); + + CanSetLeafName("C:\\bar\\foo", ":$mft", false); + CanSetLeafName("C:\\bar\\foo", ":$mft\\", false); + CanSetLeafName("C:\\bar\\foo", ":$foo", false); + CanSetLeafName("C:\\bar\\foo", ":$foo\\", false); +} + +TEST(TestFileNTFSSpecialPaths, ForbiddenMetaFiles) +{ + CanInitWith("C:\\$MFT", false); + CanInitWith("C:\\$mft", false); + CanInitWith("C:\\$bitmap", false); + + CanAppend("C:\\", "$MFT", false); + CanAppend("C:\\", "$mft", false); + CanAppend("C:\\", "$bitmap", false); + + CanSetLeafName("C:\\a", "$MFT", false); + CanSetLeafName("C:\\a", "$mft", false); + CanSetLeafName("C:\\a", "$bitmap", false); + + // nsLocalFileWin strips the trailing slash so this should also fail: + CanInitWith("C:\\$MFT\\", false); + CanInitWith("C:\\$mft\\", false); + CanInitWith("C:\\$bitmap\\", false); + + CanAppend("C:\\", "$MFT\\", false); + CanAppend("C:\\", "$mft\\", false); + CanAppend("C:\\", "$bitmap\\", false); + + CanSetLeafName("C:\\a", "$MFT\\", false); + CanSetLeafName("C:\\a", "$mft\\", false); + CanSetLeafName("C:\\a", "$bitmap\\", false); + + // Shouldn't be able to bypass this by asking for ADS stuff: + CanInitWith("C:\\$MFT:Zone.Identifier", false); + CanInitWith("C:\\$mft:Zone.Identifier", false); + CanInitWith("C:\\$bitmap:Zone.Identifier", false); + + CanAppend("C:\\", "$MFT:Zone.Identifier", false); + CanAppend("C:\\", "$mft:Zone.Identifier", false); + CanAppend("C:\\", "$bitmap:Zone.Identifier", false); + + CanSetLeafName("C:\\a", "$MFT:Zone.Identifier", false); + CanSetLeafName("C:\\a", "$mft:Zone.Identifier", false); + CanSetLeafName("C:\\a", "$bitmap:Zone.Identifier", false); +} + +TEST(TestFileNTFSSpecialPaths, ForbiddenMetaFilesOtherRoots) +{ + // Should still block them for UNC and volume roots + CanInitWith("\\\\LOCALHOST\\C$\\$MFT", false); + CanInitWith("\\\\?\\Volume{1234567}\\$MFT", false); + + CanAppend("\\\\LOCALHOST\\", "C$\\$MFT", false); + CanAppend("\\\\LOCALHOST\\C$\\", "$MFT", false); + CanAppend("\\\\?\\Volume{1234567}\\", "$MFT", false); + CanAppend("\\\\Blah\\", "Volume{1234567}\\$MFT", false); + + CanSetLeafName("\\\\LOCALHOST\\C$", "C$\\$MFT", false); + CanSetLeafName("\\\\LOCALHOST\\C$\\foo", "$MFT", false); + CanSetLeafName("\\\\?\\Volume{1234567}\\foo", "$MFT", false); + CanSetLeafName("\\\\Blah\\foo", "Volume{1234567}\\$MFT", false); + + // Root detection should cope with un-normalized paths: + CanInitWith("C:\\foo\\..\\$MFT", false); + CanInitWith("C:\\foo\\..\\$mft\\", false); + CanInitWith("\\\\LOCALHOST\\C$\\blah\\..\\$MFT", false); + CanInitWith("\\\\?\\Volume{13455635}\\blah\\..\\$MFT", false); + // As well as different or duplicated separators: + CanInitWith("C:\\foo\\..\\\\$MFT\\", false); + CanInitWith("\\\\?\\Volume{1234567}/$MFT", false); + CanInitWith("\\\\LOCALHOST\\C$/blah//../$MFT", false); + + // There are no "append" equivalents for the preceding set of tests, + // because append does not allow '..' to be used as a relative path + // component, nor does it allow forward slashes: + CanAppend("C:\\foo", "..\\", false); + CanAppend("C:\\foo", "bar/baz", false); + + // But this is (strangely) allowed for SetLeafName. Yes, really. + CanSetLeafName("C:\\foo\\bar", "..\\$MFT", false); + CanSetLeafName("C:\\foo\\bar", "..\\$mft\\", false); + CanSetLeafName("\\\\LOCALHOST\\C$\\bl", "ah\\..\\$MFT", false); + CanSetLeafName("\\\\?\\Volume{13455635}\\bla", "ah\\..\\$MFT", false); + + CanSetLeafName("C:\\foo\\bar", "..\\\\$MFT\\", false); + CanSetLeafName("\\\\?\\Volume{1234567}\\bar", "/$MFT", false); + CanSetLeafName("\\\\LOCALHOST\\C$/blah/", "\\../$MFT", false); +} + +TEST(TestFileNTFSSpecialPaths, NotQuiteMetaFiles) +{ + // These files should not be blocked away from the root: + CanInitWith("C:\\bar\\$bitmap", true); + CanInitWith("C:\\bar\\$mft", true); + + // Same for append: + CanAppend("C:\\bar\\", "$bitmap", true); + CanAppend("C:\\bar\\", "$mft", true); + + // And SetLeafName: + CanSetLeafName("C:\\bar\\foo", "$bitmap", true); + CanSetLeafName("C:\\bar\\foo", "$mft", true); + + // And we shouldn't block on substring matches: + CanInitWith("C:\\$MFT stocks", true); + CanAppend("C:\\", "$MFT stocks", true); + CanSetLeafName("C:\\", "$MFT stocks", true); +} + +TEST(TestFileNTFSSpecialPaths, Normalization) +{ + // First determine the working directory: + wchar_t workingDir[MAX_PATH]; + if (nullptr == _wgetcwd(workingDir, MAX_PATH - 1)) { + EXPECT_FALSE(true) << "Getting working directory failed."; + return; + } + + nsString normalizedPath(workingDir); + // Need at least 3 chars for the root, at least 2 more to get another subdir + // in there. This test will fail if cwd is the root of a drive. + if (normalizedPath.Length() < 5 || + !mozilla::IsAsciiAlpha(normalizedPath.First()) || + normalizedPath.CharAt(1) != L':' || normalizedPath.CharAt(2) != L'\\') { + EXPECT_FALSE(true) << "Working directory not long enough?!"; + return; + } + + // Copy the drive and colon, but NOT the backslash. + nsAutoString startingFilePath(Substring(normalizedPath, 0, 2)); + normalizedPath.Cut(0, 3); + + // Then determine the number of path components in cwd: + nsAString::const_iterator begin, end; + normalizedPath.BeginReading(begin); + normalizedPath.EndReading(end); + if (!FindCharInReadable(L'\\', begin, end)) { + EXPECT_FALSE(true) << "Working directory was at a root"; + return; + } + auto numberOfComponentsAboveRoot = 1; + while (FindCharInReadable(L'\\', begin, end)) { + begin++; + numberOfComponentsAboveRoot++; + } + + // Then set up a file with that many `..\` components: + startingFilePath.SetCapacity(3 + numberOfComponentsAboveRoot * 3 + 9); + while (numberOfComponentsAboveRoot--) { + startingFilePath.AppendLiteral(u"..\\"); + } + startingFilePath.AppendLiteral(u"$mft"); + + nsCOMPtr file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + // This should fail immediately, rather than waiting for a call to + // nsIFile::Normalize, because normalization doesn't happen reliably, + // and where it does happen consumers often don't check for errors. + nsresult rv = file->InitWithPath(startingFilePath); + EXPECT_NS_FAILED(rv) << " from normalizing '" + << NS_ConvertUTF16toUTF8(startingFilePath).get() + << "' rv=" << std::hex << (unsigned int)rv; +} diff --git a/xpcom/tests/gtest/TestFilePreferencesUnix.cpp b/xpcom/tests/gtest/TestFilePreferencesUnix.cpp new file mode 100644 index 0000000000..915c189b97 --- /dev/null +++ b/xpcom/tests/gtest/TestFilePreferencesUnix.cpp @@ -0,0 +1,208 @@ +#include "gtest/gtest.h" + +#include "mozilla/FilePreferences.h" + +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "mozilla/Preferences.h" +#include "mozilla/ScopeExit.h" +#include "nsIDirectoryEnumerator.h" + +using namespace mozilla; + +const char kForbiddenPathsPref[] = "network.file.path_blacklist"; + +TEST(TestFilePreferencesUnix, Parsing) +{ +#define kForbidden "/tmp/forbidden" +#define kForbiddenDir "/tmp/forbidden/" +#define kForbiddenFile "/tmp/forbidden/file" +#define kOther "/tmp/other" +#define kOtherDir "/tmp/other/" +#define kOtherFile "/tmp/other/file" +#define kAllowed "/tmp/allowed" + + // This is run on exit of this function to make sure we clear the pref + // and that behaviour with the pref cleared is correct. + auto cleanup = MakeScopeExit([&] { + nsresult rv = Preferences::ClearUser(kForbiddenPathsPref); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbidden)), + true); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenDir)), + true); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenFile)), + true); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kAllowed)), true); + }); + + auto CheckPrefs = [](const nsACString& aPaths) { + nsresult rv; + rv = Preferences::SetCString(kForbiddenPathsPref, aPaths); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenDir)), + false); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenDir)), + false); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbiddenFile)), + false); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kForbidden)), + false); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kAllowed)), true); + }; + + CheckPrefs(nsLiteralCString(kForbidden)); + CheckPrefs(nsLiteralCString(kForbidden "," kOther)); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kOtherFile)), + false); + CheckPrefs(nsLiteralCString(kForbidden "," kOther ",")); + ASSERT_EQ(FilePreferences::IsAllowedPath(nsLiteralCString(kOtherFile)), + false); +} + +TEST(TestFilePreferencesUnix, Simple) +{ + nsAutoCString tempPath; + + // This is the directory we will forbid + nsCOMPtr forbiddenDir; + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(forbiddenDir)); + ASSERT_EQ(rv, NS_OK); + rv = forbiddenDir->GetNativePath(tempPath); + ASSERT_EQ(rv, NS_OK); + rv = forbiddenDir->AppendNative("forbidden_dir"_ns); + ASSERT_EQ(rv, NS_OK); + + // This is executed at exit to clean up after ourselves. + auto cleanup = MakeScopeExit([&] { + nsresult rv = Preferences::ClearUser(kForbiddenPathsPref); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + + rv = forbiddenDir->Remove(true); + ASSERT_EQ(rv, NS_OK); + }); + + // Create the directory + rv = forbiddenDir->Create(nsIFile::DIRECTORY_TYPE, 0666); + ASSERT_EQ(rv, NS_OK); + + // This is the file we will try to access + nsCOMPtr forbiddenFile; + rv = forbiddenDir->Clone(getter_AddRefs(forbiddenFile)); + ASSERT_EQ(rv, NS_OK); + rv = forbiddenFile->AppendNative("test_file"_ns); + + // Create the file + ASSERT_EQ(rv, NS_OK); + rv = forbiddenFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666); + + // Get the forbidden path + nsAutoCString forbiddenPath; + rv = forbiddenDir->GetNativePath(forbiddenPath); + ASSERT_EQ(rv, NS_OK); + + // Set the pref and make sure it is enforced + rv = Preferences::SetCString(kForbiddenPathsPref, forbiddenPath); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + + // Check that we can't access some of the file attributes + int64_t size; + rv = forbiddenFile->GetFileSize(&size); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + bool exists; + rv = forbiddenFile->Exists(&exists); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that we can't enumerate the directory + nsCOMPtr dirEnumerator; + rv = forbiddenDir->GetDirectoryEntries(getter_AddRefs(dirEnumerator)); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + nsCOMPtr newPath; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath)); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendNative("."_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendNative("forbidden_dir"_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->Exists(&exists); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + rv = newPath->AppendNative("test_file"_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->Exists(&exists); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that ./ does not bypass the filter + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath)); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendRelativeNativePath("./forbidden_dir/file"_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->Exists(&exists); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that .. does not bypass the filter + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath)); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendRelativeNativePath("allowed/../forbidden_dir/file"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_UNRECOGNIZED_PATH); + + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(newPath)); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendNative("allowed"_ns); + ASSERT_EQ(rv, NS_OK); + rv = newPath->AppendNative(".."_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_UNRECOGNIZED_PATH); + + nsAutoCString trickyPath(tempPath); + trickyPath.AppendLiteral("/allowed/../forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that we can't construct a path that is functionally the same + // as the forbidden one and bypasses the filter. + trickyPath = tempPath; + trickyPath.AppendLiteral("/./forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + trickyPath = tempPath; + trickyPath.AppendLiteral("//forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + trickyPath.Truncate(); + trickyPath.AppendLiteral("//"); + trickyPath.Append(tempPath); + trickyPath.AppendLiteral("/forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + trickyPath.Truncate(); + trickyPath.AppendLiteral("//"); + trickyPath.Append(tempPath); + trickyPath.AppendLiteral("//forbidden_dir/file"); + rv = newPath->InitWithNativePath(trickyPath); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + // Check that if the forbidden string is a directory, we only block access + // to subresources, not the directory itself. + nsAutoCString forbiddenDirPath(forbiddenPath); + forbiddenDirPath.Append("/"); + rv = Preferences::SetCString(kForbiddenPathsPref, forbiddenDirPath); + ASSERT_EQ(rv, NS_OK); + FilePreferences::InitPrefs(); + + // This should work, since we only block subresources + rv = forbiddenDir->Exists(&exists); + ASSERT_EQ(rv, NS_OK); + + rv = forbiddenDir->GetDirectoryEntries(getter_AddRefs(dirEnumerator)); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); +} diff --git a/xpcom/tests/gtest/TestFilePreferencesWin.cpp b/xpcom/tests/gtest/TestFilePreferencesWin.cpp new file mode 100644 index 0000000000..dfee139970 --- /dev/null +++ b/xpcom/tests/gtest/TestFilePreferencesWin.cpp @@ -0,0 +1,196 @@ +#include "gtest/gtest.h" + +#include "mozilla/FilePreferences.h" +#include "nsComponentManagerUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsIFile.h" +#include "nsXPCOMCID.h" + +TEST(FilePreferencesWin, Normalization) +{ + nsAutoString normalized; + + mozilla::FilePreferences::testing::NormalizePath(u"foo"_ns, normalized); + ASSERT_TRUE(normalized == u"foo"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\foo"_ns, normalized); + ASSERT_TRUE(normalized == u"\\foo"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo"_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\foo"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"foo\\some"_ns, normalized); + ASSERT_TRUE(normalized == u"foo\\some"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\foo"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\."_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\"_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\."_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\bar"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\bar\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\."_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\bar\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\.\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\bar\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\..\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\.."_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\foo\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\..\\bar\\..\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\..\\bar"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\bar"_ns); + + mozilla::FilePreferences::testing::NormalizePath(u"\\\\foo\\bar\\..\\..\\"_ns, + normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + mozilla::FilePreferences::testing::NormalizePath( + u"\\\\foo\\bar\\.\\..\\.\\..\\"_ns, normalized); + ASSERT_TRUE(normalized == u"\\\\"_ns); + + bool result; + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\.."_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\..\\"_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\..\\"_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + u"\\\\foo\\\\bar"_ns, normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath( + u"\\\\foo\\bar\\..\\..\\..\\..\\"_ns, normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\\\"_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\.\\\\"_ns, + normalized); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::testing::NormalizePath(u"\\\\..\\\\"_ns, + normalized); + ASSERT_FALSE(result); +} + +TEST(FilePreferencesWin, AccessUNC) +{ + nsCOMPtr lf = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + + nsresult rv; + + mozilla::FilePreferences::testing::SetBlockUNCPaths(false); + + rv = lf->InitWithPath(u"\\\\nice\\..\\evil\\share"_ns); + ASSERT_EQ(rv, NS_OK); + + mozilla::FilePreferences::testing::SetBlockUNCPaths(true); + + rv = lf->InitWithPath(u"\\\\nice\\..\\evil\\share"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + mozilla::FilePreferences::testing::AddDirectoryToAllowlist(u"\\\\nice"_ns); + + rv = lf->InitWithPath(u"\\\\nice\\share"_ns); + ASSERT_EQ(rv, NS_OK); + + rv = lf->InitWithPath(u"\\\\nice\\..\\evil\\share"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); +} + +TEST(FilePreferencesWin, AccessDOSDevicePath) +{ + const auto devicePathSpecifier = u"\\\\?\\"_ns; + + nsCOMPtr lf = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID); + + nsresult rv; + + mozilla::FilePreferences::testing::SetBlockUNCPaths(true); + + rv = lf->InitWithPath(devicePathSpecifier + u"evil\\z:\\share"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + rv = lf->InitWithPath(devicePathSpecifier + u"UNC\\evil\\share"_ns); + ASSERT_EQ(rv, NS_ERROR_FILE_ACCESS_DENIED); + + rv = lf->InitWithPath(devicePathSpecifier + u"C:\\"_ns); + ASSERT_EQ(rv, NS_OK); + + nsCOMPtr base; + rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(base)); + ASSERT_EQ(rv, NS_OK); + + nsAutoString path; + rv = base->GetPath(path); + ASSERT_EQ(rv, NS_OK); + + rv = lf->InitWithPath(devicePathSpecifier + path); + ASSERT_EQ(rv, NS_OK); +} + +TEST(FilePreferencesWin, StartsWithDiskDesignatorAndBackslash) +{ + bool result; + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"\\\\UNC\\path"_ns); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"\\single\\backslash"_ns); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"C:relative"_ns); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"\\\\?\\C:\\"_ns); + ASSERT_FALSE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"C:\\"_ns); + ASSERT_TRUE(result); + + result = mozilla::FilePreferences::StartsWithDiskDesignatorAndBackslash( + u"c:\\"_ns); + ASSERT_TRUE(result); +} diff --git a/xpcom/tests/gtest/TestGCPostBarriers.cpp b/xpcom/tests/gtest/TestGCPostBarriers.cpp new file mode 100644 index 0000000000..f31d0b0402 --- /dev/null +++ b/xpcom/tests/gtest/TestGCPostBarriers.cpp @@ -0,0 +1,162 @@ +/* -*- 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/. */ + +/* + * Tests that generational garbage collection post-barriers are correctly + * implemented for nsTArrays that contain JavaScript Values. + */ + +#include "mozilla/UniquePtr.h" + +#include "jsapi.h" +#include "nsTArray.h" + +#include "gtest/gtest.h" + +#include "js/PropertyAndElement.h" // JS_GetProperty, JS_SetProperty +#include "js/TracingAPI.h" +#include "js/HeapAPI.h" + +#include "mozilla/CycleCollectedJSContext.h" + +using namespace mozilla; + +template +static void TraceArray(JSTracer* trc, void* data) { + ArrayT* array = static_cast(data); + for (unsigned i = 0; i < array->Length(); ++i) { + JS::TraceEdge(trc, &array->ElementAt(i), "array-element"); + } +} + +/* + * Use arrays with initial size much smaller than the final number of elements + * to test that moving Heap elements works correctly. + */ +const size_t ElementCount = 100; +const size_t InitialElements = ElementCount / 10; + +template +static void TestGrow(JSContext* cx) { + JS_GC(cx); + + auto array = MakeUnique(); + ASSERT_TRUE(array != nullptr); + + JS_AddExtraGCRootsTracer(cx, TraceArray, array.get()); + + /* + * Create the array and fill it with new JS objects. With GGC these will be + * allocated in the nursery. + */ + JS::RootedValue value(cx); + const char* property = "foo"; + for (size_t i = 0; i < ElementCount; ++i) { + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + ASSERT_FALSE(JS::ObjectIsTenured(obj)); + value = JS::Int32Value(static_cast(i)); + ASSERT_TRUE(JS_SetProperty(cx, obj, property, value)); + ASSERT_TRUE(array->AppendElement(obj, fallible)); + } + + /* + * If postbarriers are not working, we will crash here when we try to mark + * objects that have been moved to the tenured heap. + */ + JS_GC(cx); + + /* + * Sanity check that our array contains what we expect. + */ + ASSERT_EQ(array->Length(), ElementCount); + for (size_t i = 0; i < array->Length(); i++) { + JS::RootedObject obj(cx, array->ElementAt(i)); + ASSERT_TRUE(JS::ObjectIsTenured(obj)); + ASSERT_TRUE(JS_GetProperty(cx, obj, property, &value)); + ASSERT_TRUE(value.isInt32()); + ASSERT_EQ(static_cast(i), value.toInt32()); + } + + JS_RemoveExtraGCRootsTracer(cx, TraceArray, array.get()); +} + +template +static void TestShrink(JSContext* cx) { + JS_GC(cx); + + auto array = MakeUnique(); + ASSERT_TRUE(array != nullptr); + + JS_AddExtraGCRootsTracer(cx, TraceArray, array.get()); + + /* + * Create the array and fill it with new JS objects. With GGC these will be + * allocated in the nursery. + */ + JS::RootedValue value(cx); + const char* property = "foo"; + for (size_t i = 0; i < ElementCount; ++i) { + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + ASSERT_FALSE(JS::ObjectIsTenured(obj)); + value = JS::Int32Value(static_cast(i)); + ASSERT_TRUE(JS_SetProperty(cx, obj, property, value)); + ASSERT_TRUE(array->AppendElement(obj, fallible)); + } + + /* Shrink and compact the array */ + array->RemoveElementsAt(InitialElements, ElementCount - InitialElements); + array->Compact(); + + JS_GC(cx); + + ASSERT_EQ(array->Length(), InitialElements); + for (size_t i = 0; i < array->Length(); i++) { + JS::RootedObject obj(cx, array->ElementAt(i)); + ASSERT_TRUE(JS::ObjectIsTenured(obj)); + ASSERT_TRUE(JS_GetProperty(cx, obj, property, &value)); + ASSERT_TRUE(value.isInt32()); + ASSERT_EQ(static_cast(i), value.toInt32()); + } + + JS_RemoveExtraGCRootsTracer(cx, TraceArray, array.get()); +} + +template +static void TestArrayType(JSContext* cx) { + TestGrow(cx); + TestShrink(cx); +} + +static void CreateGlobalAndRunTest(JSContext* cx) { + static const JSClass GlobalClass = {"global", JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps}; + + JS::RealmOptions options; + JS::PersistentRootedObject global(cx); + global = JS_NewGlobalObject(cx, &GlobalClass, nullptr, + JS::FireOnNewGlobalHook, options); + ASSERT_TRUE(global != nullptr); + + JS::Realm* oldRealm = JS::EnterRealm(cx, global); + + using ElementT = JS::Heap; + + TestArrayType>(cx); + TestArrayType>(cx); + TestArrayType>(cx); + + JS::LeaveRealm(cx, oldRealm); +} + +TEST(GCPostBarriers, nsTArray) +{ + CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); + ASSERT_TRUE(ccjscx != nullptr); + JSContext* cx = ccjscx->Context(); + ASSERT_TRUE(cx != nullptr); + + CreateGlobalAndRunTest(cx); +} diff --git a/xpcom/tests/gtest/TestHandleWatcher.cpp b/xpcom/tests/gtest/TestHandleWatcher.cpp new file mode 100644 index 0000000000..fd9f3ab95a --- /dev/null +++ b/xpcom/tests/gtest/TestHandleWatcher.cpp @@ -0,0 +1,580 @@ +/* -*- 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 "gtest/gtest.h" + +#include +#include +#include + +#include "mozilla/Assertions.h" +#include "mozilla/ErrorNames.h" +#include "mozilla/Result.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/WinHandleWatcher.h" +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsIEventTarget.h" +#include "nsITargetShutdownTask.h" +#include "nsIThread.h" +#include "nsIThreadShutdown.h" +#include "nsITimer.h" +#include "nsTHashMap.h" +#include "nsThreadUtils.h" +// #include "nscore.h" + +namespace details { +static nsCString MakeTargetName(const char* name) { + const char* testName = + ::testing::UnitTest::GetInstance()->current_test_info()->name(); + nsCString ret; + ret.AppendPrintf("%s: %s", testName, name); + return ret; +} +} // namespace details + +using HandleWatcher = mozilla::HandleWatcher; + +/////////////////////////////////////////////////////////////////////// +// Error handling + +// nsresult_fatal_err_: auxiliary function for testing-macros. +[[noreturn]] void nsresult_fatal_err_(const char* file, size_t line, + const char* expr, nsresult res) { + // implementation details from the MOZ_CRASH* family of macros + MOZ_Crash(file, static_cast(line), + MOZ_CrashPrintf("%s gave nsresult %s(%" PRIX32 ")", expr, + mozilla::GetStaticErrorName(res), res)); +} + +// UNWRAP: testing-oriented variant of Result::unwrap. +// +// We make no use of gtest's `ASSERT_*` family of macros, since they assume +// that a `return;` statement is sufficient to abort the test. +template +T unwrap_impl_(const char* file, size_t line, const char* expr, + mozilla::Result res) { + if (MOZ_LIKELY(res.isOk())) { + return res.unwrap(); + } + nsresult_fatal_err_(file, line, expr, res.unwrapErr()); +} + +#define UNWRAP(expr) unwrap_impl_(__FILE__, __LINE__, #expr, expr) + +/////////////////////////////////////////////////////////////////////// +// Milliseconds() +// +// Convenience declaration for millisecond-based mozilla::TimeDurations. +static mozilla::TimeDuration Milliseconds(double d) { + return mozilla::TimeDuration::FromMilliseconds(d); +} + +/////////////////////////////////////////////////////////////////////// +// TestHandleWatcher +// +// GTest test fixture. Provides shared resources. +class TestHandleWatcher : public testing::Test { + protected: + static void SetUpTestSuite() { sIsLive = true; } + static void TearDownTestSuite() { + sPool = nullptr; + sIsLive = false; + } + + public: + static already_AddRefed GetPool() { + AssertIsLive(); + if (!sPool) { + sPool = mozilla::SharedThreadPool::Get("Test Pool"_ns); + } + return do_AddRef(sPool); + } + + private: + static bool sIsLive; // just for confirmation + static void AssertIsLive() { + MOZ_ASSERT(sIsLive, + "attempted to use `class TestHandleWatcher` outside test group"); + } + + static RefPtr sPool; +}; + +/* static */ +bool TestHandleWatcher::sIsLive = false; +/* static */ +RefPtr TestHandleWatcher::sPool = nullptr; + +/////////////////////////////////////////////////////////////////////// +// WindowsEventObject +// +// Convenient interface to a Windows `event` object. (This is a synchronization +// object that's usually the wrong thing to use.) +struct WindowsEventObject { + HANDLE const handle = ::CreateEvent(nullptr, TRUE, FALSE, nullptr); + WindowsEventObject() = default; + ~WindowsEventObject() { ::CloseHandle(handle); } + + WindowsEventObject(WindowsEventObject const&) = delete; + WindowsEventObject(WindowsEventObject&&) = delete; + WindowsEventObject& operator=(WindowsEventObject const&) = delete; + WindowsEventObject& operator=(WindowsEventObject&&) = delete; + + void Set() { ::SetEvent(handle); } +}; + +/////////////////////////////////////////////////////////////////////// +// SpawnNewThread +// +nsCOMPtr SpawnNewThread(const char* name) { + nsCOMPtr thread; + MOZ_ALWAYS_SUCCEEDS( + NS_NewNamedThread(details::MakeTargetName(name), getter_AddRefs(thread))); + return thread; +} + +/////////////////////////////////////////////////////////////////////// +// SpawnNewBackgroundQueue +// +// (mozilla::TaskQueue expects the supplied name to outlive the queue, so we +// just use a static string.) +RefPtr SpawnNewBackgroundQueue() { + return mozilla::TaskQueue::Create(TestHandleWatcher::GetPool(), + "task queue for TestHandleWatcher"); +} + +/////////////////////////////////////////////////////////////////////// +// SpinEventLoopUntil +// +// Local equivalent of `mozilla::SpinEventLoopUntil`, extended with a timeout. +// +// Spin the current thread's event loop until either a specified predicate is +// satisfied or a specified time-interval has passed. +// +struct SpinEventLoopUntilRet { + enum Value { Ok, TimedOut, InternalError } value; + bool ok() const { return value == Value::Ok; } + bool timedOut() const { return value == Value::TimedOut; } + + MOZ_IMPLICIT SpinEventLoopUntilRet(Value v) : value(v) {} +}; +template +SpinEventLoopUntilRet SpinEventLoopUntil( + Predicate const& aPredicate, + mozilla::TimeDuration aDuration = Milliseconds(500)) { + using Value = SpinEventLoopUntilRet::Value; + nsIThread* currentThread = NS_GetCurrentThread(); + + // Set up timer. + bool timedOut = false; + auto timer = UNWRAP(NS_NewTimerWithCallback( + [&](nsITimer*) { timedOut = true; }, aDuration, nsITimer::TYPE_ONE_SHOT, + "SpinEventLoop timer", currentThread)); + auto onExitCancelTimer = mozilla::MakeScopeExit([&] { timer->Cancel(); }); + + bool const ret = mozilla::SpinEventLoopUntil( + "TestHandleWatcher"_ns, [&] { return timedOut || aPredicate(); }); + if (!ret) return Value::InternalError; + if (timedOut) return Value::TimedOut; + return Value::Ok; +} + +// metatest for `SpinEventLoopUntil` +TEST_F(TestHandleWatcher, SpinEventLoopUntil) { + auto should_fail = SpinEventLoopUntil([] { return false; }, Milliseconds(1)); + ASSERT_TRUE(should_fail.timedOut()); + auto should_pass = SpinEventLoopUntil([] { return true; }, Milliseconds(50)); + ASSERT_TRUE(should_pass.ok()); +} + +/////////////////////////////////////////////////////////////////////// +// PingMainThread +// +// Post a do-nothing message to the main thread's event queue. (This will signal +// it to wake up and check its predicate, if it's waiting for that to happen.) +void PingMainThread() { + MOZ_ALWAYS_SUCCEEDS( + NS_DispatchToMainThread(NS_NewRunnableFunction("Ping", [] {}))); +} + +/////////////////////////////////////////////////////////////////////// +// Individual tests + +// Test basic creation and destruction. +TEST_F(TestHandleWatcher, Trivial) { HandleWatcher hw; } + +// Test interaction before a Watch is created. +TEST_F(TestHandleWatcher, Empty) { + HandleWatcher hw; + ASSERT_TRUE(hw.IsStopped()); + hw.Stop(); +} + +// Start and trigger an HandleWatcher directly from the main thread. +TEST_F(TestHandleWatcher, Simple) { + WindowsEventObject event; + HandleWatcher hw; + + std::atomic run = false; + + hw.Watch( + event.handle, NS_GetCurrentThread(), + NS_NewRunnableFunction("TestHandleWatcher::Simple", [&] { run = true; })); + + ASSERT_FALSE(run.load()); + event.Set(); + // Attempt to force a race below. + ::Sleep(0); + // This should not race. The HandleWatcher doesn't execute its delegate; it + // just queues a mozilla::Task to do that onto our thread's event queue, + // and that Task hasn't been permitted to run yet. + ASSERT_FALSE(run.load()); + + ASSERT_TRUE(SpinEventLoopUntil([&] { return run.load(); }).ok()); +} + +// Test that calling Stop() stops the watcher. +TEST_F(TestHandleWatcher, Stop) { + WindowsEventObject event; + HandleWatcher hw; + std::atomic run = false; + + hw.Watch( + event.handle, NS_GetCurrentThread(), + NS_NewRunnableFunction("TestHandleWatcher::Stop", [&] { run = true; })); + + ASSERT_FALSE(hw.IsStopped()); + hw.Stop(); + ASSERT_TRUE(hw.IsStopped()); + + ASSERT_TRUE(SpinEventLoopUntil([&] { return run.load(); }, Milliseconds(25)) + .timedOut()); +} + +// Test that the target's destruction stops the watch. +TEST_F(TestHandleWatcher, TargetDestroyed) { + WindowsEventObject event; + HandleWatcher hw; + bool run = false; + + auto queue = SpawnNewThread("target thread"); + hw.Watch(event.handle, queue.get(), + NS_NewRunnableFunction("never called", [&] { run = true; })); + + ASSERT_FALSE(hw.IsStopped()); + // synchronous shutdown before destruction + queue->Shutdown(); + + ASSERT_TRUE(hw.IsStopped()); + ASSERT_FALSE(run); +} + +// Test that calling `Watch` again stops the current watch. +TEST_F(TestHandleWatcher, Rewatch) { + WindowsEventObject event; + HandleWatcher hw; + + bool b1 = false; + bool b2 = false; + + { + auto queue = SpawnNewThread("target thread"); + + hw.Watch(event.handle, queue.get(), NS_NewRunnableFunction("b1", [&] { + b1 = true; + PingMainThread(); + })); + hw.Watch(event.handle, queue.get(), NS_NewRunnableFunction("b2", [&] { + b2 = true; + PingMainThread(); + })); + + event.Set(); + + ASSERT_TRUE(SpinEventLoopUntil([&] { return b1 || b2; }).ok()); + queue->Shutdown(); + } + ASSERT_FALSE(b1); + ASSERT_TRUE(b2); +} + +// Test that watching a HANDLE which is _already_ signaled still fires the +// associated task. +TEST_F(TestHandleWatcher, Presignalled) { + WindowsEventObject event; + HandleWatcher hw; + + bool run = false; + event.Set(); + hw.Watch(event.handle, NS_GetCurrentThread(), + NS_NewRunnableFunction("TestHandleWatcher::Presignalled", + [&] { run = true; })); + + ASSERT_TRUE(SpinEventLoopUntil([&] { return run; }).ok()); +} + +/////////////////////////////////////////////////////////////////////// +// Systematic tests: normal activation +// +// Test that a handle becoming signalled on target A correctly enqueues a task +// on target B, regardless of whether A == B. +// +struct ActivationTestSetup { + enum TargetType { Main, Side, Background }; + + WindowsEventObject event; + HandleWatcher watcher; + + std::atomic run = false; + nsCOMPtr sideThread; + nsCOMPtr backgroundQueue; + + private: + nsIEventTarget* GetQueue(TargetType targetTyoe) { + MOZ_ASSERT(NS_IsMainThread()); + switch (targetTyoe) { + case TargetType::Main: + return NS_GetCurrentThread(); + + case TargetType::Side: { + if (!sideThread) { + sideThread = SpawnNewThread("side thread"); + } + return sideThread; + } + + case TargetType::Background: { + if (!backgroundQueue) { + backgroundQueue = SpawnNewBackgroundQueue(); + } + return backgroundQueue.get(); + } + } + } + + void OnSignaled() { + run = true; + // If we're not running on the main thread, it may be blocked waiting for + // events. + PingMainThread(); + } + + public: + void Setup(TargetType from, TargetType to) { + watcher.Watch( + event.handle, GetQueue(to), + NS_NewRunnableFunction("Reaction", [this] { this->OnSignaled(); })); + + MOZ_ALWAYS_SUCCEEDS(GetQueue(from)->Dispatch( + NS_NewRunnableFunction("Action", [this] { event.Set(); }))); + } + + bool Execute() { + MOZ_ASSERT(NS_IsMainThread()); + bool const spin = SpinEventLoopUntil([this] { + MOZ_ASSERT(NS_IsMainThread()); + return run.load(); + }).ok(); + return spin && watcher.IsStopped(); + } + + ~ActivationTestSetup() { watcher.Stop(); } +}; + +#define MOZ_HANDLEWATCHER_GTEST_FROM_TO(FROM, TO) \ + TEST_F(TestHandleWatcher, FROM##To##TO) { \ + ActivationTestSetup s; \ + s.Setup(ActivationTestSetup::TargetType::FROM, \ + ActivationTestSetup::TargetType::TO); \ + ASSERT_TRUE(s.Execute()); \ + } + +// Note that `Main -> Main` is subtly different from `Simple`, above: `Simple` +// sets the event before spinning, while `Main -> Main` merely enqueues a Task +// that will set the event during the spin. +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Main, Main); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Main, Side); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Main, Background); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Side, Main); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Side, Side); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Side, Background); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Background, Main); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Background, Side); +MOZ_HANDLEWATCHER_GTEST_FROM_TO(Background, Background); + +/////////////////////////////////////////////////////////////////////// +// Ad-hoc tests: reentrancy +// +// Test that HandleWatcher neither deadlocks nor loses data if its release of a +// referenced object causes the invocation of another method on HandleWatcher. + +// Reentrancy case 1/2: the event target. +namespace { +class MockEventTarget final : public nsIEventTarget { + NS_DECL_THREADSAFE_ISUPPORTS + + private: + // Map from registered shutdown tasks to whether or not they have been (or are + // being) executed. (This should probably guarantee some deterministic order, + // and also be mutex-protected; but that doesn't matter here.) + nsTHashMap, bool> mShutdownTasks; + // Out-of band task to be run last at destruction time, regardless of anything + // else. + std::function mDeathAction; + + ~MockEventTarget() { + for (auto& task : mShutdownTasks) { + task.SetData(true); + task.GetKey()->TargetShutdown(); + } + if (mDeathAction) { + mDeathAction(); + } + } + + public: + // shutdown task handling + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* task) override { + mShutdownTasks.WithEntryHandle(task, [&](auto entry) { + if (entry.HasEntry()) { + MOZ_CRASH("attempted to double-register shutdown task"); + } + entry.Insert(false); + }); + return NS_OK; + } + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* task) override { + mozilla::Maybe res = mShutdownTasks.Extract(task); + if (!res.isSome()) { + MOZ_CRASH("attempted to unregister non-registered task"); + } + if (res.value()) { + MOZ_CRASH("attempted to unregister already-executed shutdown task"); + } + return NS_OK; + } + void RegisterDeathAction(std::function&& f) { + mDeathAction = std::move(f); + } + + // other nsIEventTarget methods (that we don't actually use) + NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) { return false; } + NS_IMETHOD IsOnCurrentThread(bool* _retval) { + *_retval = false; + return NS_OK; + } + NS_IMETHOD Dispatch(already_AddRefed, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD DispatchFromScript(nsIRunnable*, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD DelayedDispatch(already_AddRefed, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; + } +}; +NS_IMPL_ISUPPORTS(MockEventTarget, nsIEventTarget) +} // anonymous namespace + +// Test that a HandleWatcher neither deadlocks nor loses data if it's invoked +// when it releases its target. +TEST_F(TestHandleWatcher, TargetDestructionRecurrency) { + WindowsEventObject e1, e2; + bool b1 = false, b2 = false; + HandleWatcher hw; + + { + RefPtr p = mozilla::MakeRefPtr(); + + hw.Watch(e1.handle, p.get(), NS_NewRunnableFunction("first callback", [&] { + b1 = true; + PingMainThread(); + })); + + p->RegisterDeathAction([&] { + hw.Watch(e2.handle, mozilla::GetMainThreadSerialEventTarget(), + NS_NewRunnableFunction("second callback", [&] { b2 = true; })); + }); + } + + ASSERT_FALSE(hw.IsStopped()); + hw.Stop(); + ASSERT_FALSE(hw.IsStopped()); // [sic] + + e1.Set(); // should do nothing + e2.Set(); + ASSERT_TRUE(SpinEventLoopUntil([&] { return b1 || b2; }).ok()); + ASSERT_FALSE(b1); + ASSERT_TRUE(b2); +} + +// Reentrancy case 2/2: the runnable. +namespace { +class MockRunnable final : public nsIRunnable { + NS_DECL_THREADSAFE_ISUPPORTS + NS_IMETHOD Run() override { + MOZ_CRASH("MockRunnable was invoked"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + std::function mDeathAction; + + public: + void RegisterDeathAction(std::function&& f) { + mDeathAction = std::move(f); + } + + private: + ~MockRunnable() { + if (mDeathAction) { + mDeathAction(); + } + } +}; +NS_IMPL_ISUPPORTS(MockRunnable, nsIRunnable) +} // anonymous namespace + +// Test that a HandleWatcher neither deadlocks nor loses data if it's invoked +// when it releases its task. +TEST_F(TestHandleWatcher, TaskDestructionRecurrency) { + WindowsEventObject e1, e2; + bool run = false; + HandleWatcher hw; + + auto thread = SpawnNewBackgroundQueue(); + + { + RefPtr runnable = mozilla::MakeRefPtr(); + + runnable->RegisterDeathAction([&] { + hw.Watch(e2.handle, thread, NS_NewRunnableFunction("callback", [&] { + run = true; + PingMainThread(); + })); + }); + + hw.Watch(e1.handle, thread.get(), runnable.forget()); + } + + ASSERT_FALSE(hw.IsStopped()); + hw.Stop(); + ASSERT_FALSE(hw.IsStopped()); // [sic] + + e1.Set(); + // give MockRunnable a chance to run (and therefore crash) if it somehow + // hasn't been discmnnected + ASSERT_TRUE( + SpinEventLoopUntil([&] { return false; }, Milliseconds(10)).timedOut()); + + e2.Set(); + ASSERT_TRUE(SpinEventLoopUntil([&] { return run; }).ok()); + ASSERT_TRUE(run); +} diff --git a/xpcom/tests/gtest/TestHashtables.cpp b/xpcom/tests/gtest/TestHashtables.cpp new file mode 100644 index 0000000000..fe1e6a3611 --- /dev/null +++ b/xpcom/tests/gtest/TestHashtables.cpp @@ -0,0 +1,1617 @@ +/* -*- 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 "nsTHashtable.h" +#include "nsBaseHashtable.h" +#include "nsTHashMap.h" +#include "nsInterfaceHashtable.h" +#include "nsClassHashtable.h" +#include "nsRefCountedHashtable.h" + +#include "nsCOMPtr.h" +#include "nsIMemoryReporter.h" +#include "nsISupports.h" +#include "nsCOMArray.h" +#include "mozilla/Attributes.h" +#include "mozilla/Unused.h" + +#include "gtest/gtest.h" + +#include + +using mozilla::MakeRefPtr; +using mozilla::MakeUnique; +using mozilla::UniquePtr; + +namespace TestHashtables { + +class TestUniChar // for nsClassHashtable +{ + public: + explicit TestUniChar(uint32_t aWord) { mWord = aWord; } + + ~TestUniChar() = default; + + uint32_t GetChar() const { return mWord; } + + private: + uint32_t mWord; +}; + +class TestUniCharDerived : public TestUniChar { + using TestUniChar::TestUniChar; +}; + +class TestUniCharRefCounted // for nsRefPtrHashtable +{ + public: + explicit TestUniCharRefCounted(uint32_t aWord, + uint32_t aExpectedAddRefCnt = 0) + : mExpectedAddRefCnt(aExpectedAddRefCnt), + mAddRefCnt(0), + mRefCnt(0), + mWord(aWord) {} + + uint32_t AddRef() { + mRefCnt++; + mAddRefCnt++; + return mRefCnt; + } + + uint32_t Release() { + EXPECT_TRUE(mRefCnt > 0); + mRefCnt--; + if (mRefCnt == 0) { + delete this; + return 0; + } + return mRefCnt; + } + + uint32_t GetChar() const { return mWord; } + + private: + ~TestUniCharRefCounted() { + if (mExpectedAddRefCnt > 0) { + EXPECT_EQ(mAddRefCnt, mExpectedAddRefCnt); + } + } + + uint32_t mExpectedAddRefCnt; + uint32_t mAddRefCnt; + uint32_t mRefCnt; + uint32_t mWord; +}; + +struct EntityNode { + const char* mStr; // never owns buffer + uint32_t mUnicode; + + bool operator<(const EntityNode& aOther) const { + return mUnicode < aOther.mUnicode || + (mUnicode == aOther.mUnicode && strcmp(mStr, aOther.mStr) < 0); + } +}; + +static const EntityNode gEntities[] = { + {"nbsp", 160}, {"iexcl", 161}, {"cent", 162}, {"pound", 163}, + {"curren", 164}, {"yen", 165}, {"brvbar", 166}, {"sect", 167}, + {"uml", 168}, {"copy", 169}, {"ordf", 170}, {"laquo", 171}, + {"not", 172}, {"shy", 173}, {"reg", 174}, {"macr", 175}}; + +#define ENTITY_COUNT (unsigned(sizeof(gEntities) / sizeof(EntityNode))) + +class EntityToUnicodeEntry : public PLDHashEntryHdr { + public: + typedef const char* KeyType; + typedef const char* KeyTypePointer; + + explicit EntityToUnicodeEntry(const char* aKey) { mNode = nullptr; } + EntityToUnicodeEntry(const EntityToUnicodeEntry& aEntry) { + mNode = aEntry.mNode; + } + ~EntityToUnicodeEntry() = default; + + bool KeyEquals(const char* aEntity) const { + return !strcmp(mNode->mStr, aEntity); + } + static const char* KeyToPointer(const char* aEntity) { return aEntity; } + static PLDHashNumber HashKey(const char* aEntity) { + return mozilla::HashString(aEntity); + } + enum { ALLOW_MEMMOVE = true }; + + const EntityNode* mNode; +}; + +static uint32_t nsTIterPrint(nsTHashtable& hash) { + uint32_t n = 0; + for (auto iter = hash.Iter(); !iter.Done(); iter.Next()) { + n++; + } + return n; +} + +static uint32_t nsTIterPrintRemove(nsTHashtable& hash) { + uint32_t n = 0; + for (auto iter = hash.Iter(); !iter.Done(); iter.Next()) { + iter.Remove(); + n++; + } + return n; +} + +static void testTHashtable(nsTHashtable& hash, + uint32_t numEntries) { + uint32_t i; + for (i = 0; i < numEntries; ++i) { + EntityToUnicodeEntry* entry = hash.PutEntry(gEntities[i].mStr); + + EXPECT_TRUE(entry); + + EXPECT_FALSE(entry->mNode); + entry->mNode = &gEntities[i]; + } + + for (i = 0; i < numEntries; ++i) { + EntityToUnicodeEntry* entry = hash.GetEntry(gEntities[i].mStr); + + EXPECT_TRUE(entry); + } + + EntityToUnicodeEntry* entry = hash.GetEntry("xxxy"); + + EXPECT_FALSE(entry); + + uint32_t count = nsTIterPrint(hash); + EXPECT_EQ(count, numEntries); + + for (const auto& entry : + const_cast&>(hash)) { + static_assert(std::is_same_v); + } + for (auto& entry : hash) { + static_assert(std::is_same_v); + } + + EXPECT_EQ(numEntries == ENTITY_COUNT ? 6 : 0, + std::count_if(hash.cbegin(), hash.cend(), [](const auto& entry) { + return entry.mNode->mUnicode >= 170; + })); +} + +// +// all this nsIFoo stuff was copied wholesale from TestCOMPtr.cpp +// + +#define NS_IFOO_IID \ + { \ + 0x6f7652e0, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +class IFoo final : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + IFoo(); + + NS_IMETHOD_(MozExternalRefCountType) AddRef() override; + NS_IMETHOD_(MozExternalRefCountType) Release() override; + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + + NS_IMETHOD SetString(const nsACString& /*in*/ aString); + NS_IMETHOD GetString(nsACString& /*out*/ aString); + + static void print_totals(); + + private: + ~IFoo(); + + unsigned int refcount_; + + static unsigned int total_constructions_; + static unsigned int total_destructions_; + nsCString mString; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IFoo, NS_IFOO_IID) + +unsigned int IFoo::total_constructions_; +unsigned int IFoo::total_destructions_; + +void IFoo::print_totals() {} + +IFoo::IFoo() : refcount_(0) { ++total_constructions_; } + +IFoo::~IFoo() { ++total_destructions_; } + +MozExternalRefCountType IFoo::AddRef() { + ++refcount_; + return refcount_; +} + +MozExternalRefCountType IFoo::Release() { + int newcount = --refcount_; + if (newcount == 0) { + delete this; + } + + return newcount; +} + +nsresult IFoo::QueryInterface(const nsIID& aIID, void** aResult) { + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(IFoo))) + rawPtr = this; + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +nsresult IFoo::SetString(const nsACString& aString) { + mString = aString; + return NS_OK; +} + +nsresult IFoo::GetString(nsACString& aString) { + aString = mString; + return NS_OK; +} + +static nsresult CreateIFoo(IFoo** result) +// a typical factory function (that calls AddRef) +{ + auto* foop = new IFoo(); + + foop->AddRef(); + *result = foop; + + return NS_OK; +} + +class DefaultConstructible { + public: + // Allow default construction. + DefaultConstructible() = default; + + // Construct/assign from a ref counted char. + explicit DefaultConstructible(RefPtr aChar) + : mChar(std::move(aChar)) {} + + const RefPtr& CharRef() const { return mChar; } + + // DefaultConstructible can be copied and moved. + DefaultConstructible(const DefaultConstructible&) = default; + DefaultConstructible& operator=(const DefaultConstructible&) = default; + DefaultConstructible(DefaultConstructible&&) = default; + DefaultConstructible& operator=(DefaultConstructible&&) = default; + + private: + RefPtr mChar; +}; + +class MovingNonDefaultConstructible; + +class NonDefaultConstructible { + public: + // Construct/assign from a ref counted char. + explicit NonDefaultConstructible(RefPtr aChar) + : mChar(std::move(aChar)) {} + + // Disallow default construction. + NonDefaultConstructible() = delete; + + MOZ_IMPLICIT NonDefaultConstructible(MovingNonDefaultConstructible&& aOther); + + const RefPtr& CharRef() const { return mChar; } + + // NonDefaultConstructible can be copied, but not trivially (efficiently) + // moved. + NonDefaultConstructible(const NonDefaultConstructible&) = default; + NonDefaultConstructible& operator=(const NonDefaultConstructible&) = default; + + private: + RefPtr mChar; +}; + +class MovingNonDefaultConstructible { + public: + // Construct/assign from a ref counted char. + explicit MovingNonDefaultConstructible(RefPtr aChar) + : mChar(std::move(aChar)) {} + + MovingNonDefaultConstructible() = delete; + + MOZ_IMPLICIT MovingNonDefaultConstructible( + const NonDefaultConstructible& aSrc) + : mChar(aSrc.CharRef()) {} + + RefPtr unwrapChar() && { return std::move(mChar); } + + // MovingNonDefaultConstructible can be moved, but not copied. + MovingNonDefaultConstructible(const MovingNonDefaultConstructible&) = delete; + MovingNonDefaultConstructible& operator=( + const MovingNonDefaultConstructible&) = delete; + MovingNonDefaultConstructible(MovingNonDefaultConstructible&&) = default; + MovingNonDefaultConstructible& operator=(MovingNonDefaultConstructible&&) = + default; + + private: + RefPtr mChar; +}; + +NonDefaultConstructible::NonDefaultConstructible( + MovingNonDefaultConstructible&& aOther) + : mChar(std::move(aOther).unwrapChar()) {} + +struct DefaultConstructible_DefaultConstructible { + using DataType = DefaultConstructible; + using UserDataType = DefaultConstructible; + + static constexpr uint32_t kExpectedAddRefCnt_Contains = 1; + static constexpr uint32_t kExpectedAddRefCnt_GetGeneration = 1; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfExcludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfIncludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_Count = 1; + static constexpr uint32_t kExpectedAddRefCnt_IsEmpty = 1; + static constexpr uint32_t kExpectedAddRefCnt_Get_OutputParam = 3; + static constexpr uint32_t kExpectedAddRefCnt_Get = 3; + static constexpr uint32_t kExpectedAddRefCnt_MaybeGet = 3; + static constexpr uint32_t kExpectedAddRefCnt_LookupOrInsert = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Fallible = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible = + 1; + static constexpr uint32_t kExpectedAddRefCnt_Remove_OutputParam = 1; + static constexpr uint32_t kExpectedAddRefCnt_Remove = 1; + static constexpr uint32_t kExpectedAddRefCnt_Extract = 1; + static constexpr uint32_t kExpectedAddRefCnt_RemoveIf = 1; + static constexpr uint32_t kExpectedAddRefCnt_Lookup = 1; + static constexpr uint32_t kExpectedAddRefCnt_Lookup_Remove = 1; + static constexpr uint32_t kExpectedAddRefCnt_LookupForAdd = 1; + static constexpr uint32_t kExpectedAddRefCnt_LookupForAdd_OrInsert = 1; + static constexpr uint32_t kExpectedAddRefCnt_LookupForAdd_OrRemove = 1; + static constexpr uint32_t kExpectedAddRefCnt_Iter = 1; + static constexpr uint32_t kExpectedAddRefCnt_ConstIter = 1; + static constexpr uint32_t kExpectedAddRefCnt_begin_end = 1; + static constexpr uint32_t kExpectedAddRefCnt_cbegin_cend = 1; + static constexpr uint32_t kExpectedAddRefCnt_Clear = 1; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfExcludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfIncludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_SwapElements = 1; + static constexpr uint32_t kExpectedAddRefCnt_MarkImmutable = 1; +}; + +struct NonDefaultConstructible_NonDefaultConstructible { + using DataType = NonDefaultConstructible; + using UserDataType = NonDefaultConstructible; + + static constexpr uint32_t kExpectedAddRefCnt_Contains = 2; + static constexpr uint32_t kExpectedAddRefCnt_GetGeneration = 2; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfExcludingThis = 3; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfIncludingThis = 3; + static constexpr uint32_t kExpectedAddRefCnt_Count = 2; + static constexpr uint32_t kExpectedAddRefCnt_IsEmpty = 2; + static constexpr uint32_t kExpectedAddRefCnt_Get_OutputParam = 5; + static constexpr uint32_t kExpectedAddRefCnt_MaybeGet = 5; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate = 2; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Fallible = 2; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue = 2; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible = + 2; + static constexpr uint32_t kExpectedAddRefCnt_Remove = 2; + static constexpr uint32_t kExpectedAddRefCnt_Extract = 3; + static constexpr uint32_t kExpectedAddRefCnt_RemoveIf = 2; + static constexpr uint32_t kExpectedAddRefCnt_Lookup = 2; + static constexpr uint32_t kExpectedAddRefCnt_Lookup_Remove = 2; + static constexpr uint32_t kExpectedAddRefCnt_Iter = 2; + static constexpr uint32_t kExpectedAddRefCnt_ConstIter = 2; + static constexpr uint32_t kExpectedAddRefCnt_begin_end = 2; + static constexpr uint32_t kExpectedAddRefCnt_cbegin_cend = 2; + static constexpr uint32_t kExpectedAddRefCnt_Clear = 2; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfExcludingThis = 2; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfIncludingThis = 2; + static constexpr uint32_t kExpectedAddRefCnt_SwapElements = 2; + static constexpr uint32_t kExpectedAddRefCnt_MarkImmutable = 2; +}; + +struct NonDefaultConstructible_MovingNonDefaultConstructible { + using DataType = NonDefaultConstructible; + using UserDataType = MovingNonDefaultConstructible; + + static constexpr uint32_t kExpectedAddRefCnt_Contains = 1; + static constexpr uint32_t kExpectedAddRefCnt_GetGeneration = 1; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfExcludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_SizeOfIncludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_Count = 1; + static constexpr uint32_t kExpectedAddRefCnt_IsEmpty = 1; + static constexpr uint32_t kExpectedAddRefCnt_Get_OutputParam = 2; + static constexpr uint32_t kExpectedAddRefCnt_MaybeGet = 2; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Fallible = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue = 1; + static constexpr uint32_t kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible = + 1; + static constexpr uint32_t kExpectedAddRefCnt_Remove = 1; + static constexpr uint32_t kExpectedAddRefCnt_Extract = 2; + static constexpr uint32_t kExpectedAddRefCnt_RemoveIf = 1; + static constexpr uint32_t kExpectedAddRefCnt_Lookup = 1; + static constexpr uint32_t kExpectedAddRefCnt_Lookup_Remove = 1; + static constexpr uint32_t kExpectedAddRefCnt_Iter = 1; + static constexpr uint32_t kExpectedAddRefCnt_ConstIter = 1; + static constexpr uint32_t kExpectedAddRefCnt_begin_end = 1; + static constexpr uint32_t kExpectedAddRefCnt_cbegin_cend = 1; + static constexpr uint32_t kExpectedAddRefCnt_Clear = 1; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfExcludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_ShallowSizeOfIncludingThis = 1; + static constexpr uint32_t kExpectedAddRefCnt_SwapElements = 1; + static constexpr uint32_t kExpectedAddRefCnt_MarkImmutable = 1; +}; + +template +void UnsupportedType() { + static_assert(flag, "Unsupported type!"); +} + +class TypeNames { + public: + template + static std::string GetName(int) { + if constexpr (std::is_same()) { + return "DefaultConstructible_DefaultConstructible"; + } else if constexpr ( + std::is_same()) { + return "NonDefaultConstructible_NonDefaultConstructible"; + } else if constexpr ( + std::is_same()) { + return "NonDefaultConstructible_MovingNonDefaultConstructible"; + } else { + UnsupportedType(); + } + } +}; + +template +auto MakeEmptyBaseHashtable() { + nsBaseHashtable + table; + + return table; +} + +template +auto MakeBaseHashtable(const uint32_t aExpectedAddRefCnt) { + auto table = MakeEmptyBaseHashtable(); + + auto myChar = MakeRefPtr(42, aExpectedAddRefCnt); + + table.InsertOrUpdate(1, typename TypeParam::UserDataType(std::move(myChar))); + + return table; +} + +template +typename TypeParam::DataType GetDataFrom( + typename TypeParam::UserDataType& aUserData) { + if constexpr (std::is_same_v || + std::is_same_v< + TypeParam, + NonDefaultConstructible_NonDefaultConstructible>) { + return aUserData; + } else if constexpr ( + std::is_same_v) { + return std::move(aUserData); + } else { + UnsupportedType(); + } +} + +template +typename TypeParam::DataType GetDataFrom( + mozilla::Maybe& aMaybeUserData) { + return GetDataFrom(*aMaybeUserData); +} + +} // namespace TestHashtables + +using namespace TestHashtables; + +TEST(Hashtable, THashtable) +{ + // check an nsTHashtable + nsTHashtable EntityToUnicode(ENTITY_COUNT); + + testTHashtable(EntityToUnicode, 5); + + uint32_t count = nsTIterPrintRemove(EntityToUnicode); + ASSERT_EQ(count, uint32_t(5)); + + count = nsTIterPrint(EntityToUnicode); + ASSERT_EQ(count, uint32_t(0)); + + testTHashtable(EntityToUnicode, ENTITY_COUNT); + + EntityToUnicode.Clear(); + + count = nsTIterPrint(EntityToUnicode); + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtable, PtrHashtable) +{ + nsTHashtable> hash; + + for (const auto& entry : + const_cast>&>(hash)) { + static_assert(std::is_same_v&>); + } + for (auto& entry : hash) { + static_assert(std::is_same_v&>); + } +} + +TEST(Hashtable, Move) +{ + const void* kPtr = reinterpret_cast(static_cast(0xbadc0de)); + + nsTHashtable> table; + table.PutEntry(kPtr); + + nsTHashtable> moved = std::move(table); + ASSERT_EQ(table.Count(), 0u); + ASSERT_EQ(moved.Count(), 1u); + + EXPECT_TRUE(moved.Contains(kPtr)); + EXPECT_FALSE(table.Contains(kPtr)); +} + +TEST(Hashtable, Keys) +{ + static constexpr uint64_t count = 10; + + nsTHashtable table; + for (uint64_t i = 0; i < count; i++) { + table.PutEntry(i); + } + + nsTArray keys; + for (const uint64_t& key : table.Keys()) { + keys.AppendElement(key); + } + keys.Sort(); + + EXPECT_EQ((nsTArray{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys); +} + +template +class BaseHashtableTest : public ::testing::Test {}; + +TYPED_TEST_SUITE_P(BaseHashtableTest); + +TYPED_TEST_P(BaseHashtableTest, Contains) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_Contains); + + auto res = table.Contains(1); + EXPECT_TRUE(res); +} + +TYPED_TEST_P(BaseHashtableTest, GetGeneration) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_GetGeneration); + + auto res = table.GetGeneration(); + EXPECT_GT(res, 0u); +} + +MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf); + +TYPED_TEST_P(BaseHashtableTest, SizeOfExcludingThis) { + // This doesn't compile at the moment, since nsBaseHashtableET lacks + // SizeOfExcludingThis implementation. Bug 1689214. +#if 0 + auto table = MakeBaseHashtable( + TypeParam::kExpectedAddRefCnt_SizeOfExcludingThis); + + auto res = table.SizeOfExcludingThis(MallocSizeOf); + EXPECT_GT(res, 0u); +#endif +} + +TYPED_TEST_P(BaseHashtableTest, SizeOfIncludingThis) { + // This doesn't compile at the moment, since nsBaseHashtableET lacks + // SizeOfIncludingThis implementation. Bug 1689214. +#if 0 + auto table = MakeBaseHashtable( + TypeParam::kExpectedAddRefCnt_SizeOfIncludingThis); + + auto res = table.SizeOfIncludingThis(MallocSizeOf); + EXPECT_GT(res, 0u); +#endif +} + +TYPED_TEST_P(BaseHashtableTest, Count) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_Count); + + auto res = table.Count(); + EXPECT_EQ(res, 1u); +} + +TYPED_TEST_P(BaseHashtableTest, IsEmpty) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_IsEmpty); + + auto res = table.IsEmpty(); + EXPECT_EQ(res, false); +} + +TYPED_TEST_P(BaseHashtableTest, Get_OutputParam) { + auto table = MakeBaseHashtable( + TypeParam::kExpectedAddRefCnt_Get_OutputParam); + + typename TypeParam::UserDataType userData(nullptr); + auto res = table.Get(1, &userData); + EXPECT_TRUE(res); + + auto data = GetDataFrom(userData); + EXPECT_EQ(data.CharRef()->GetChar(), 42u); +} + +TYPED_TEST_P(BaseHashtableTest, Get) { + // The Get overload can't support non-default-constructible UserDataType. + if constexpr (std::is_default_constructible_v< + typename TypeParam::UserDataType>) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_Get); + + auto userData = table.Get(1); + + auto data = GetDataFrom(userData); + EXPECT_EQ(data.CharRef()->GetChar(), 42u); + } +} + +TYPED_TEST_P(BaseHashtableTest, MaybeGet) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_MaybeGet); + + auto maybeUserData = table.MaybeGet(1); + EXPECT_TRUE(maybeUserData); + + auto data = GetDataFrom(maybeUserData); + EXPECT_EQ(data.CharRef()->GetChar(), 42u); +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsert_Default) { + if constexpr (std::is_default_constructible_v) { + auto table = MakeEmptyBaseHashtable(); + + typename TypeParam::DataType& data = table.LookupOrInsert(1); + EXPECT_EQ(data.CharRef(), nullptr); + + data = typename TypeParam::DataType(MakeRefPtr( + 42, TypeParam::kExpectedAddRefCnt_LookupOrInsert)); + } +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsert_NonDefault) { + auto table = MakeEmptyBaseHashtable(); + + typename TypeParam::DataType& data = table.LookupOrInsert( + 1, typename TypeParam::DataType{MakeRefPtr(42)}); + EXPECT_NE(data.CharRef(), nullptr); +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsert_NonDefault_AlreadyPresent) { + auto table = MakeEmptyBaseHashtable(); + + typename TypeParam::DataType& data1 = table.LookupOrInsert( + 1, typename TypeParam::DataType{MakeRefPtr(42)}); + TestUniCharRefCounted* const address = data1.CharRef(); + typename TypeParam::DataType& data2 = table.LookupOrInsert( + 1, + typename TypeParam::DataType{MakeRefPtr(42, 1)}); + EXPECT_EQ(&data1, &data2); + EXPECT_EQ(address, data2.CharRef()); +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsertWith) { + auto table = MakeEmptyBaseHashtable(); + + typename TypeParam::DataType& data = table.LookupOrInsertWith(1, [] { + return typename TypeParam::DataType{MakeRefPtr(42)}; + }); + EXPECT_NE(data.CharRef(), nullptr); +} + +TYPED_TEST_P(BaseHashtableTest, LookupOrInsertWith_AlreadyPresent) { + auto table = MakeEmptyBaseHashtable(); + + table.LookupOrInsertWith(1, [] { + return typename TypeParam::DataType{MakeRefPtr(42)}; + }); + table.LookupOrInsertWith(1, [] { + ADD_FAILURE(); + return typename TypeParam::DataType{MakeRefPtr(42)}; + }); +} + +TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate) { + auto table = MakeEmptyBaseHashtable(); + + auto myChar = MakeRefPtr( + 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate); + + table.InsertOrUpdate(1, typename TypeParam::UserDataType(std::move(myChar))); +} + +TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate_Fallible) { + auto table = MakeEmptyBaseHashtable(); + + auto myChar = MakeRefPtr( + 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate_Fallible); + + auto res = table.InsertOrUpdate( + 1, typename TypeParam::UserDataType(std::move(myChar)), + mozilla::fallible); + EXPECT_TRUE(res); +} + +TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate_Rvalue) { + auto table = MakeEmptyBaseHashtable(); + + auto myChar = MakeRefPtr( + 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate_Rvalue); + + table.InsertOrUpdate( + 1, std::move(typename TypeParam::UserDataType(std::move(myChar)))); +} + +TYPED_TEST_P(BaseHashtableTest, InsertOrUpdate_Rvalue_Fallible) { + auto table = MakeEmptyBaseHashtable(); + + auto myChar = MakeRefPtr( + 42, TypeParam::kExpectedAddRefCnt_InsertOrUpdate_Rvalue_Fallible); + + auto res = table.InsertOrUpdate( + 1, std::move(typename TypeParam::UserDataType(std::move(myChar))), + mozilla::fallible); + EXPECT_TRUE(res); +} + +TYPED_TEST_P(BaseHashtableTest, Remove_OutputParam) { + // The Remove overload can't support non-default-constructible DataType. + if constexpr (std::is_default_constructible_v) { + auto table = MakeBaseHashtable( + TypeParam::kExpectedAddRefCnt_Remove_OutputParam); + + typename TypeParam::DataType data; + auto res = table.Remove(1, &data); + EXPECT_TRUE(res); + EXPECT_EQ(data.CharRef()->GetChar(), 42u); + } +} + +TYPED_TEST_P(BaseHashtableTest, Remove) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_Remove); + + auto res = table.Remove(1); + EXPECT_TRUE(res); +} + +TYPED_TEST_P(BaseHashtableTest, Extract) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_Extract); + + auto maybeData = table.Extract(1); + EXPECT_TRUE(maybeData); + EXPECT_EQ(maybeData->CharRef()->GetChar(), 42u); +} + +TYPED_TEST_P(BaseHashtableTest, RemoveIf) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_RemoveIf); + + table.RemoveIf([](const auto&) { return true; }); +} + +TYPED_TEST_P(BaseHashtableTest, Lookup) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_Lookup); + + auto res = table.Lookup(1); + EXPECT_TRUE(res); + EXPECT_EQ(res.Data().CharRef()->GetChar(), 42u); +} + +TYPED_TEST_P(BaseHashtableTest, Lookup_Remove) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_Lookup_Remove); + + auto res = table.Lookup(1); + EXPECT_TRUE(res); + EXPECT_EQ(res.Data().CharRef()->GetChar(), 42u); + + res.Remove(); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NoOp) { + auto table = MakeEmptyBaseHashtable(); + + table.WithEntryHandle(1, [](auto&&) {}); + + EXPECT_FALSE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrInsert) { + auto table = MakeEmptyBaseHashtable(); + + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsert(typename TypeParam::UserDataType( + MakeRefPtr(42))); + }); + + EXPECT_TRUE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrInsertFrom) { + auto table = MakeEmptyBaseHashtable(); + + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsertWith([] { + return typename TypeParam::UserDataType( + MakeRefPtr(42)); + }); + }); + + EXPECT_TRUE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrInsertFrom_Exists) { + auto table = MakeEmptyBaseHashtable(); + + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsertWith([] { + return typename TypeParam::UserDataType( + MakeRefPtr(42)); + }); + }); + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsertWith([]() -> typename TypeParam::UserDataType { + ADD_FAILURE(); + return typename TypeParam::UserDataType( + MakeRefPtr(42)); + }); + }); + + EXPECT_TRUE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrRemove) { + auto table = MakeEmptyBaseHashtable(); + + table.WithEntryHandle(1, [](auto&& entry) { entry.OrRemove(); }); + + EXPECT_FALSE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, WithEntryHandle_NotFound_OrRemove_Exists) { + auto table = MakeEmptyBaseHashtable(); + + table.WithEntryHandle(1, [](auto&& entry) { + entry.OrInsertWith([] { + return typename TypeParam::UserDataType( + MakeRefPtr(42)); + }); + }); + table.WithEntryHandle(1, [](auto&& entry) { entry.OrRemove(); }); + + EXPECT_FALSE(table.Contains(1)); +} + +TYPED_TEST_P(BaseHashtableTest, Iter) { + auto table = MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_Iter); + + for (auto iter = table.Iter(); !iter.Done(); iter.Next()) { + EXPECT_EQ(iter.Data().CharRef()->GetChar(), 42u); + } +} + +TYPED_TEST_P(BaseHashtableTest, ConstIter) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_ConstIter); + + for (auto iter = table.ConstIter(); !iter.Done(); iter.Next()) { + EXPECT_EQ(iter.Data().CharRef()->GetChar(), 42u); + } +} + +TYPED_TEST_P(BaseHashtableTest, begin_end) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_begin_end); + + auto res = std::count_if(table.begin(), table.end(), [](const auto& entry) { + return entry.GetData().CharRef()->GetChar() == 42; + }); + EXPECT_EQ(res, 1); +} + +TYPED_TEST_P(BaseHashtableTest, cbegin_cend) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_cbegin_cend); + + auto res = std::count_if(table.cbegin(), table.cend(), [](const auto& entry) { + return entry.GetData().CharRef()->GetChar() == 42; + }); + EXPECT_EQ(res, 1); +} + +TYPED_TEST_P(BaseHashtableTest, Clear) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_Clear); + + table.Clear(); +} + +TYPED_TEST_P(BaseHashtableTest, ShallowSizeOfExcludingThis) { + auto table = MakeBaseHashtable( + TypeParam::kExpectedAddRefCnt_ShallowSizeOfExcludingThis); + + auto res = table.ShallowSizeOfExcludingThis(MallocSizeOf); + EXPECT_GT(res, 0u); +} + +TYPED_TEST_P(BaseHashtableTest, ShallowSizeOfIncludingThis) { + // Make this work with ASAN builds, bug 1689549. +#if !defined(MOZ_ASAN) + auto table = MakeBaseHashtable( + TypeParam::kExpectedAddRefCnt_ShallowSizeOfIncludingThis); + + auto res = table.ShallowSizeOfIncludingThis(MallocSizeOf); + EXPECT_GT(res, 0u); +#endif +} + +TYPED_TEST_P(BaseHashtableTest, SwapElements) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_SwapElements); + + auto table2 = MakeEmptyBaseHashtable(); + + table.SwapElements(table2); +} + +TYPED_TEST_P(BaseHashtableTest, MarkImmutable) { + auto table = + MakeBaseHashtable(TypeParam::kExpectedAddRefCnt_MarkImmutable); + + table.MarkImmutable(); +} + +REGISTER_TYPED_TEST_SUITE_P( + BaseHashtableTest, Contains, GetGeneration, SizeOfExcludingThis, + SizeOfIncludingThis, Count, IsEmpty, Get_OutputParam, Get, MaybeGet, + LookupOrInsert_Default, LookupOrInsert_NonDefault, + LookupOrInsert_NonDefault_AlreadyPresent, LookupOrInsertWith, + LookupOrInsertWith_AlreadyPresent, InsertOrUpdate, InsertOrUpdate_Fallible, + InsertOrUpdate_Rvalue, InsertOrUpdate_Rvalue_Fallible, Remove_OutputParam, + Remove, Extract, RemoveIf, Lookup, Lookup_Remove, WithEntryHandle_NoOp, + WithEntryHandle_NotFound_OrInsert, WithEntryHandle_NotFound_OrInsertFrom, + WithEntryHandle_NotFound_OrInsertFrom_Exists, + WithEntryHandle_NotFound_OrRemove, WithEntryHandle_NotFound_OrRemove_Exists, + Iter, ConstIter, begin_end, cbegin_cend, Clear, ShallowSizeOfExcludingThis, + ShallowSizeOfIncludingThis, SwapElements, MarkImmutable); + +using BaseHashtableTestTypes = + ::testing::Types; + +INSTANTIATE_TYPED_TEST_SUITE_P(Hashtables, BaseHashtableTest, + BaseHashtableTestTypes, TypeNames); + +TEST(Hashtables, DataHashtable) +{ + // check a data-hashtable + nsTHashMap UniToEntity(ENTITY_COUNT); + + for (auto& entity : gEntities) { + UniToEntity.InsertOrUpdate(entity.mUnicode, entity.mStr); + } + + const char* str; + + for (auto& entity : gEntities) { + ASSERT_TRUE(UniToEntity.Get(entity.mUnicode, &str)); + } + + ASSERT_FALSE(UniToEntity.Get(99446, &str)); + + uint32_t count = 0; + for (auto iter = UniToEntity.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + UniToEntity.Clear(); + + count = 0; + for (auto iter = UniToEntity.Iter(); !iter.Done(); iter.Next()) { + printf(" enumerated %u = \"%s\"\n", iter.Key(), iter.Data()); + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, DataHashtable_STLIterators) +{ + using mozilla::Unused; + + nsTHashMap UniToEntity(ENTITY_COUNT); + + for (auto& entity : gEntities) { + UniToEntity.InsertOrUpdate(entity.mUnicode, entity.mStr); + } + + // operators, including conversion from iterator to const_iterator + nsTHashMap::const_iterator ci = + UniToEntity.begin(); + ++ci; + ASSERT_EQ(1, std::distance(UniToEntity.cbegin(), ci++)); + ASSERT_EQ(2, std::distance(UniToEntity.cbegin(), ci)); + ASSERT_TRUE(ci == ci); + auto otherCi = ci; + ++otherCi; + ++ci; + ASSERT_TRUE(&*ci == &*otherCi); + + // STL algorithms (just to check that the iterator sufficiently conforms + // with the actual syntactical requirements of those algorithms). + std::for_each(UniToEntity.cbegin(), UniToEntity.cend(), + [](const auto& entry) {}); + Unused << std::find_if( + UniToEntity.cbegin(), UniToEntity.cend(), + [](const auto& entry) { return entry.GetKey() == 42; }); + Unused << std::accumulate( + UniToEntity.cbegin(), UniToEntity.cend(), 0u, + [](size_t sum, const auto& entry) { return sum + entry.GetKey(); }); + Unused << std::any_of(UniToEntity.cbegin(), UniToEntity.cend(), + [](const auto& entry) { return entry.GetKey() == 42; }); + Unused << std::max_element(UniToEntity.cbegin(), UniToEntity.cend(), + [](const auto& lhs, const auto& rhs) { + return lhs.GetKey() > rhs.GetKey(); + }); + + // const range-based for + { + std::set entities(gEntities, gEntities + ENTITY_COUNT); + for (const auto& entity : + const_cast&>( + UniToEntity)) { + ASSERT_EQ(1u, + entities.erase(EntityNode{entity.GetData(), entity.GetKey()})); + } + ASSERT_TRUE(entities.empty()); + } + + // non-const range-based for + { + std::set entities(gEntities, gEntities + ENTITY_COUNT); + for (auto& entity : UniToEntity) { + ASSERT_EQ(1u, + entities.erase(EntityNode{entity.GetData(), entity.GetKey()})); + + entity.SetData(nullptr); + ASSERT_EQ(nullptr, entity.GetData()); + } + ASSERT_TRUE(entities.empty()); + } +} + +TEST(Hashtables, DataHashtable_RemoveIf) +{ + // check a data-hashtable + nsTHashMap UniToEntity(ENTITY_COUNT); + + for (auto& entity : gEntities) { + UniToEntity.InsertOrUpdate(entity.mUnicode, entity.mStr); + } + + UniToEntity.RemoveIf([](const auto& iter) { return iter.Key() >= 170; }); + + ASSERT_EQ(10u, UniToEntity.Count()); +} + +TEST(Hashtables, ClassHashtable) +{ + // check a class-hashtable + nsClassHashtable EntToUniClass(ENTITY_COUNT); + + for (auto& entity : gEntities) { + // Insert a sub-class of TestUniChar to test if this is accepted by + // InsertOrUpdate. + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + mozilla::MakeUnique(entity.mUnicode)); + } + + TestUniChar* myChar; + + for (auto& entity : gEntities) { + ASSERT_TRUE(EntToUniClass.Get(nsDependentCString(entity.mStr), &myChar)); + } + + ASSERT_FALSE(EntToUniClass.Get("xxxx"_ns, &myChar)); + + uint32_t count = 0; + for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + EntToUniClass.Clear(); + + count = 0; + for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, ClassHashtable_RangeBasedFor) +{ + // check a class-hashtable + nsClassHashtable EntToUniClass(ENTITY_COUNT); + + for (auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate(nsDependentCString(entity.mStr), + MakeUnique(entity.mUnicode)); + } + + // const range-based for + { + std::set entities(gEntities, gEntities + ENTITY_COUNT); + for (const auto& entity : + const_cast&>( + EntToUniClass)) { + const char* str; + entity.GetKey().GetData(&str); + ASSERT_EQ(1u, + entities.erase(EntityNode{str, entity.GetData()->GetChar()})); + } + ASSERT_TRUE(entities.empty()); + } + + // non-const range-based for + { + std::set entities(gEntities, gEntities + ENTITY_COUNT); + for (auto& entity : EntToUniClass) { + const char* str; + entity.GetKey().GetData(&str); + ASSERT_EQ(1u, + entities.erase(EntityNode{str, entity.GetData()->GetChar()})); + + entity.SetData(UniquePtr{}); + ASSERT_EQ(nullptr, entity.GetData()); + } + ASSERT_TRUE(entities.empty()); + } +} + +TEST(Hashtables, DataHashtableWithInterfaceKey) +{ + // check a data-hashtable with an interface key + nsTHashMap EntToUniClass2(ENTITY_COUNT); + + nsCOMArray fooArray; + + for (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + nsCOMPtr foo; + CreateIFoo(getter_AddRefs(foo)); + foo->SetString(nsDependentCString(gEntities[i].mStr)); + + fooArray.InsertObjectAt(foo, i); + + EntToUniClass2.InsertOrUpdate(foo, gEntities[i].mUnicode); + } + + uint32_t myChar2; + + for (uint32_t i = 0; i < ENTITY_COUNT; ++i) { + ASSERT_TRUE(EntToUniClass2.Get(fooArray[i], &myChar2)); + } + + ASSERT_FALSE(EntToUniClass2.Get((nsISupports*)0x55443316, &myChar2)); + + uint32_t count = 0; + for (auto iter = EntToUniClass2.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString s; + nsCOMPtr foo = do_QueryInterface(iter.Key()); + foo->GetString(s); + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + EntToUniClass2.Clear(); + + count = 0; + for (auto iter = EntToUniClass2.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString s; + nsCOMPtr foo = do_QueryInterface(iter.Key()); + foo->GetString(s); + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, InterfaceHashtable) +{ + // check an interface-hashtable with an uint32_t key + nsInterfaceHashtable UniToEntClass2(ENTITY_COUNT); + + for (auto& entity : gEntities) { + nsCOMPtr foo; + CreateIFoo(getter_AddRefs(foo)); + foo->SetString(nsDependentCString(entity.mStr)); + + UniToEntClass2.InsertOrUpdate(entity.mUnicode, foo); + } + + for (auto& entity : gEntities) { + nsCOMPtr myEnt; + ASSERT_TRUE(UniToEntClass2.Get(entity.mUnicode, getter_AddRefs(myEnt))); + + nsAutoCString myEntStr; + myEnt->GetString(myEntStr); + } + + nsCOMPtr myEnt; + ASSERT_FALSE(UniToEntClass2.Get(9462, getter_AddRefs(myEnt))); + + uint32_t count = 0; + for (auto iter = UniToEntClass2.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString s; + iter.UserData()->GetString(s); + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + UniToEntClass2.Clear(); + + count = 0; + for (auto iter = UniToEntClass2.Iter(); !iter.Done(); iter.Next()) { + nsAutoCString s; + iter.Data()->GetString(s); + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, DataHashtable_WithEntryHandle) +{ + // check WithEntryHandle/OrInsertWith + nsTHashMap UniToEntity(ENTITY_COUNT); + + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, [&entity](auto&& entry) { + EXPECT_FALSE(entry); + const char* const val = + entry.OrInsertWith([&entity]() { return entity.mStr; }); + EXPECT_TRUE(entry); + EXPECT_TRUE(val == entity.mStr); + EXPECT_TRUE(entry.Data() == entity.mStr); + }); + } + + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, + [](auto&& entry) { EXPECT_TRUE(entry); }); + } + + // 0 should not be found + size_t count = UniToEntity.Count(); + UniToEntity.Lookup(0U).Remove(); + ASSERT_TRUE(count == UniToEntity.Count()); + + // Lookup should find all entries + count = 0; + for (auto& entity : gEntities) { + if (UniToEntity.Lookup(entity.mUnicode)) { + count++; + } + } + ASSERT_TRUE(count == UniToEntity.Count()); + + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, + [](auto&& entry) { EXPECT_TRUE(entry); }); + } + + // Lookup().Remove() should remove all entries. + for (auto& entity : gEntities) { + if (auto entry = UniToEntity.Lookup(entity.mUnicode)) { + entry.Remove(); + } + } + ASSERT_TRUE(0 == UniToEntity.Count()); + + // Remove newly added entries via OrRemove. + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, [](auto&& entry) { + EXPECT_FALSE(entry); + entry.OrRemove(); + }); + } + ASSERT_TRUE(0 == UniToEntity.Count()); + + // Remove existing entries via OrRemove. + for (auto& entity : gEntities) { + UniToEntity.WithEntryHandle(entity.mUnicode, [&entity](auto&& entry) { + EXPECT_FALSE(entry); + const char* const val = entry.OrInsert(entity.mStr); + EXPECT_TRUE(entry); + EXPECT_TRUE(val == entity.mStr); + EXPECT_TRUE(entry.Data() == entity.mStr); + }); + + UniToEntity.WithEntryHandle(entity.mUnicode, [](auto&& entry) { + EXPECT_TRUE(entry); + entry.OrRemove(); + }); + } + ASSERT_TRUE(0 == UniToEntity.Count()); +} + +TEST(Hashtables, ClassHashtable_WithEntryHandle) +{ + // check a class-hashtable WithEntryHandle with null values + nsClassHashtable EntToUniClass(ENTITY_COUNT); + + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle( + nsDependentCString(entity.mStr), [](auto&& entry) { + EXPECT_FALSE(entry); + const TestUniChar* val = entry.OrInsert(nullptr).get(); + EXPECT_TRUE(entry); + EXPECT_TRUE(val == nullptr); + EXPECT_TRUE(entry.Data() == nullptr); + }); + } + + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr), + [](auto&& entry) { EXPECT_TRUE(entry); }); + EntToUniClass.WithEntryHandle( + nsDependentCString(entity.mStr), + [](auto&& entry) { EXPECT_TRUE(entry.Data() == nullptr); }); + } + + // "" should not be found + size_t count = EntToUniClass.Count(); + EntToUniClass.Lookup(nsDependentCString("")).Remove(); + ASSERT_TRUE(count == EntToUniClass.Count()); + + // Lookup should find all entries. + count = 0; + for (auto& entity : gEntities) { + if (EntToUniClass.Lookup(nsDependentCString(entity.mStr))) { + count++; + } + } + ASSERT_TRUE(count == EntToUniClass.Count()); + + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr), + [](auto&& entry) { EXPECT_TRUE(entry); }); + } + + // Lookup().Remove() should remove all entries. + for (auto& entity : gEntities) { + if (auto entry = EntToUniClass.Lookup(nsDependentCString(entity.mStr))) { + entry.Remove(); + } + } + ASSERT_TRUE(0 == EntToUniClass.Count()); + + // Remove newly added entries via OrRemove. + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr), + [](auto&& entry) { + EXPECT_FALSE(entry); + entry.OrRemove(); + }); + } + ASSERT_TRUE(0 == EntToUniClass.Count()); + + // Remove existing entries via OrRemove. + for (auto& entity : gEntities) { + EntToUniClass.WithEntryHandle( + nsDependentCString(entity.mStr), [](auto&& entry) { + EXPECT_FALSE(entry); + const TestUniChar* val = entry.OrInsert(nullptr).get(); + EXPECT_TRUE(entry); + EXPECT_TRUE(val == nullptr); + EXPECT_TRUE(entry.Data() == nullptr); + }); + + EntToUniClass.WithEntryHandle(nsDependentCString(entity.mStr), + [](auto&& entry) { + EXPECT_TRUE(entry); + entry.OrRemove(); + }); + } + ASSERT_TRUE(0 == EntToUniClass.Count()); +} + +TEST(Hashtables, ClassHashtable_GetOrInsertNew_Present) +{ + nsClassHashtable EntToUniClass(ENTITY_COUNT); + + for (const auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + mozilla::MakeUnique(entity.mUnicode)); + } + + auto* entry = EntToUniClass.GetOrInsertNew("uml"_ns, 42); + EXPECT_EQ(168u, entry->GetChar()); +} + +TEST(Hashtables, ClassHashtable_GetOrInsertNew_NotPresent) +{ + nsClassHashtable EntToUniClass(ENTITY_COUNT); + + // This is going to insert a TestUniChar. + auto* entry = EntToUniClass.GetOrInsertNew("uml"_ns, 42); + EXPECT_EQ(42u, entry->GetChar()); +} + +TEST(Hashtables, ClassHashtable_LookupOrInsertWith_Present) +{ + nsClassHashtable EntToUniClass(ENTITY_COUNT); + + for (const auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + mozilla::MakeUnique(entity.mUnicode)); + } + + const auto& entry = EntToUniClass.LookupOrInsertWith( + "uml"_ns, [] { return mozilla::MakeUnique(42); }); + EXPECT_EQ(168u, entry->GetChar()); +} + +TEST(Hashtables, ClassHashtable_LookupOrInsertWith_NotPresent) +{ + nsClassHashtable EntToUniClass(ENTITY_COUNT); + + // This is going to insert a TestUniCharDerived. + const auto& entry = EntToUniClass.LookupOrInsertWith( + "uml"_ns, [] { return mozilla::MakeUnique(42); }); + EXPECT_EQ(42u, entry->GetChar()); +} + +TEST(Hashtables, RefPtrHashtable) +{ + // check a RefPtr-hashtable + nsRefPtrHashtable EntToUniClass( + ENTITY_COUNT); + + for (auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + MakeRefPtr(entity.mUnicode)); + } + + TestUniCharRefCounted* myChar; + + for (auto& entity : gEntities) { + ASSERT_TRUE(EntToUniClass.Get(nsDependentCString(entity.mStr), &myChar)); + } + + ASSERT_FALSE(EntToUniClass.Get("xxxx"_ns, &myChar)); + + uint32_t count = 0; + for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, ENTITY_COUNT); + + EntToUniClass.Clear(); + + count = 0; + for (auto iter = EntToUniClass.Iter(); !iter.Done(); iter.Next()) { + count++; + } + ASSERT_EQ(count, uint32_t(0)); +} + +TEST(Hashtables, RefPtrHashtable_Clone) +{ + // check a RefPtr-hashtable + nsRefPtrHashtable EntToUniClass( + ENTITY_COUNT); + + for (auto& entity : gEntities) { + EntToUniClass.InsertOrUpdate( + nsDependentCString(entity.mStr), + MakeRefPtr(entity.mUnicode)); + } + + auto clone = EntToUniClass.Clone(); + static_assert(std::is_same_v); + + EXPECT_EQ(clone.Count(), EntToUniClass.Count()); + + for (const auto& entry : EntToUniClass) { + auto cloneEntry = clone.Lookup(entry.GetKey()); + + EXPECT_TRUE(cloneEntry); + EXPECT_EQ(cloneEntry.Data(), entry.GetWeak()); + } +} + +TEST(Hashtables, Clone) +{ + static constexpr uint64_t count = 10; + + nsTHashMap table; + for (uint64_t i = 0; i < count; i++) { + table.InsertOrUpdate(42 + i, i); + } + + auto clone = table.Clone(); + + static_assert(std::is_same_v); + + EXPECT_EQ(clone.Count(), table.Count()); + + for (const auto& entry : table) { + auto cloneEntry = clone.Lookup(entry.GetKey()); + + EXPECT_TRUE(cloneEntry); + EXPECT_EQ(cloneEntry.Data(), entry.GetData()); + } +} + +TEST(Hashtables, Values) +{ + static constexpr uint64_t count = 10; + + nsTHashMap table; + for (uint64_t i = 0; i < count; i++) { + table.InsertOrUpdate(42 + i, i); + } + + nsTArray values; + for (const uint64_t& value : table.Values()) { + values.AppendElement(value); + } + values.Sort(); + + EXPECT_EQ((nsTArray{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), values); +} diff --git a/xpcom/tests/gtest/TestID.cpp b/xpcom/tests/gtest/TestID.cpp new file mode 100644 index 0000000000..65b41a2b26 --- /dev/null +++ b/xpcom/tests/gtest/TestID.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsID.h" + +#include "gtest/gtest.h" + +static const char* const ids[] = { + "5C347B10-D55C-11D1-89B7-006008911B81", + "{5C347B10-D55C-11D1-89B7-006008911B81}", + "5c347b10-d55c-11d1-89b7-006008911b81", + "{5c347b10-d55c-11d1-89b7-006008911b81}", + + "FC347B10-D55C-F1D1-F9B7-006008911B81", + "{FC347B10-D55C-F1D1-F9B7-006008911B81}", + "fc347b10-d55c-f1d1-f9b7-006008911b81", + "{fc347b10-d55c-f1d1-f9b7-006008911b81}", +}; +#define NUM_IDS ((int)(sizeof(ids) / sizeof(ids[0]))) + +TEST(nsID, StringConversion) +{ + nsID id; + for (int i = 0; i < NUM_IDS; i++) { + const char* idstr = ids[i]; + ASSERT_TRUE(id.Parse(idstr)); + + auto cp = id.ToString(); + ASSERT_STREQ(cp.get(), ids[4 * (i / 4) + 3]); + } +} diff --git a/xpcom/tests/gtest/TestIDUtils.cpp b/xpcom/tests/gtest/TestIDUtils.cpp new file mode 100644 index 0000000000..adf6a96611 --- /dev/null +++ b/xpcom/tests/gtest/TestIDUtils.cpp @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsID.h" +#include "nsIDUtils.h" + +#include "gtest/gtest.h" + +static const char* const bare_ids[] = { + "5c347b10-d55c-11d1-89b7-006008911b81", + "fc347b10-d55c-f1d1-f9b7-006008911b81", +}; + +TEST(nsIDUtils, NSID_TrimBracketsUTF16) +{ + nsID id{}; + for (const auto* idstr : bare_ids) { + ASSERT_TRUE(id.Parse(idstr)); + + NSID_TrimBracketsUTF16 trimmed(id); + ASSERT_TRUE(trimmed.EqualsASCII(idstr)); + } +} + +TEST(nsIDUtils, NSID_TrimBracketsASCII) +{ + nsID id{}; + for (const auto* idstr : bare_ids) { + ASSERT_TRUE(id.Parse(idstr)); + + NSID_TrimBracketsASCII trimmed(id); + ASSERT_TRUE(trimmed.EqualsASCII(idstr)); + } +} diff --git a/xpcom/tests/gtest/TestInputStreamLengthHelper.cpp b/xpcom/tests/gtest/TestInputStreamLengthHelper.cpp new file mode 100644 index 0000000000..5bb3f2bbe4 --- /dev/null +++ b/xpcom/tests/gtest/TestInputStreamLengthHelper.cpp @@ -0,0 +1,161 @@ +#include "gtest/gtest.h" + +#include "mozilla/InputStreamLengthHelper.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "Helpers.h" + +using namespace mozilla; + +TEST(TestInputStreamLengthHelper, NonLengthStream) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(int64_t(buf.Length()), aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, NonLengthStream)"_ns, + [&]() { return called; })); +} + +class LengthStream final : public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public nsIInputStream { + public: + NS_DECL_ISUPPORTS + + LengthStream(int64_t aLength, nsresult aLengthRv, uint64_t aAvailable, + bool aIsAsyncLength) + : mLength(aLength), + mLengthRv(aLengthRv), + mAvailable(aAvailable), + mIsAsyncLength(aIsAsyncLength) {} + + NS_IMETHOD Close(void) override { MOZ_CRASH("Invalid call!"); } + NS_IMETHOD Read(char* aBuf, uint32_t aCount, uint32_t* _retval) override { + MOZ_CRASH("Invalid call!"); + } + NS_IMETHOD ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval) override { + MOZ_CRASH("Invalid call!"); + } + NS_IMETHOD IsNonBlocking(bool* _retval) override { + MOZ_CRASH("Invalid call!"); + } + + NS_IMETHOD Length(int64_t* aLength) override { + *aLength = mLength; + return mLengthRv; + } + + NS_IMETHOD AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) override { + if (aCallback) { + aCallback->OnInputStreamLengthReady(this, mLength); + } + return NS_OK; + } + + NS_IMETHOD Available(uint64_t* aAvailable) override { + *aAvailable = mAvailable; + return NS_OK; + } + + NS_IMETHOD StreamStatus() override { return NS_OK; } + + private: + ~LengthStream() = default; + + int64_t mLength; + nsresult mLengthRv; + uint64_t mAvailable; + + bool mIsAsyncLength; +}; + +NS_IMPL_ADDREF(LengthStream); +NS_IMPL_RELEASE(LengthStream); + +NS_INTERFACE_MAP_BEGIN(LengthStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, mIsAsyncLength) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +TEST(TestInputStreamLengthHelper, LengthStream) +{ + nsCOMPtr stream = new LengthStream(42, NS_OK, 0, false); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(42, aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, LengthStream)"_ns, + [&]() { return called; })); +} + +TEST(TestInputStreamLengthHelper, InvalidLengthStream) +{ + nsCOMPtr stream = + new LengthStream(42, NS_ERROR_NOT_AVAILABLE, 0, false); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(-1, aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, InvalidLengthStream)"_ns, + [&]() { return called; })); +} + +TEST(TestInputStreamLengthHelper, AsyncLengthStream) +{ + nsCOMPtr stream = + new LengthStream(22, NS_BASE_STREAM_WOULD_BLOCK, 123, true); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(22, aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, AsyncLengthStream)"_ns, + [&]() { return called; })); +} + +TEST(TestInputStreamLengthHelper, FallbackLengthStream) +{ + nsCOMPtr stream = + new LengthStream(-1, NS_BASE_STREAM_WOULD_BLOCK, 123, false); + + bool called = false; + InputStreamLengthHelper::GetAsyncLength(stream, [&](int64_t aLength) { + ASSERT_EQ(123, aLength); + called = true; + }); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestInputStreamLengthHelper, FallbackLengthStream)"_ns, + [&]() { return called; })); +} diff --git a/xpcom/tests/gtest/TestJSHolderMap.cpp b/xpcom/tests/gtest/TestJSHolderMap.cpp new file mode 100644 index 0000000000..2255e2e773 --- /dev/null +++ b/xpcom/tests/gtest/TestJSHolderMap.cpp @@ -0,0 +1,345 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/CycleCollectedJSRuntime.h" +#include "mozilla/Maybe.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" + +#include "nsCycleCollectionParticipant.h" +#include "nsCycleCollector.h" + +#include "js/GCAPI.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +enum HolderKind { SingleZone, MultiZone }; + +class MyHolder final : public nsScriptObjectTracer { + public: + explicit MyHolder(HolderKind kind = SingleZone, size_t value = 0) + : nsScriptObjectTracer(FlagsForKind(kind)), value(value) {} + + const size_t value; + + NS_IMETHOD_(void) Root(void*) override { MOZ_CRASH(); } + NS_IMETHOD_(void) Unlink(void*) override { MOZ_CRASH(); } + NS_IMETHOD_(void) Unroot(void*) override { MOZ_CRASH(); } + NS_IMETHOD_(void) DeleteCycleCollectable(void*) override { MOZ_CRASH(); } + NS_IMETHOD_(void) + Trace(void* aPtr, const TraceCallbacks& aCb, void* aClosure) override { + MOZ_CRASH(); + } + NS_IMETHOD TraverseNative(void* aPtr, + nsCycleCollectionTraversalCallback& aCb) override { + MOZ_CRASH(); + } + + NS_DECL_CYCLE_COLLECTION_CLASS_NAME_METHOD(MyHolder) + + private: + Flags FlagsForKind(HolderKind kind) { + return kind == MultiZone ? FlagMultiZoneJSHolder + : FlagMaybeSingleZoneJSHolder; + } +}; + +static size_t CountEntries(JSHolderMap& map) { + size_t count = 0; + for (JSHolderMap::Iter i(map); !i.Done(); i.Next()) { + MOZ_RELEASE_ASSERT(i->mHolder); + MOZ_RELEASE_ASSERT(i->mTracer); + count++; + } + return count; +} + +JS::Zone* DummyZone = reinterpret_cast(1); + +JS::Zone* ZoneForKind(HolderKind kind) { + return kind == MultiZone ? nullptr : DummyZone; +} + +TEST(JSHolderMap, Empty) +{ + JSHolderMap map; + ASSERT_EQ(CountEntries(map), 0u); +} + +static void TestAddAndRemove(HolderKind kind) { + JSHolderMap map; + + MyHolder holder(kind); + nsScriptObjectTracer* tracer = &holder; + + ASSERT_FALSE(map.Has(&holder)); + ASSERT_EQ(map.Extract(&holder), nullptr); + + map.Put(&holder, tracer, ZoneForKind(kind)); + ASSERT_TRUE(map.Has(&holder)); + ASSERT_EQ(CountEntries(map), 1u); + ASSERT_EQ(map.Get(&holder), tracer); + + ASSERT_EQ(map.Extract(&holder), tracer); + ASSERT_EQ(map.Extract(&holder), nullptr); + ASSERT_FALSE(map.Has(&holder)); + ASSERT_EQ(CountEntries(map), 0u); +} + +TEST(JSHolderMap, AddAndRemove) +{ + TestAddAndRemove(SingleZone); + TestAddAndRemove(MultiZone); +} + +static void TestIterate(HolderKind kind) { + JSHolderMap map; + + MyHolder holder(kind, 0); + nsScriptObjectTracer* tracer = &holder; + + Maybe iter; + + // Iterate an empty map. + iter.emplace(map); + ASSERT_TRUE(iter->Done()); + iter.reset(); + + // Iterate a map with one entry. + map.Put(&holder, tracer, ZoneForKind(kind)); + iter.emplace(map); + ASSERT_FALSE(iter->Done()); + ASSERT_EQ(iter->Get().mHolder, &holder); + iter->Next(); + ASSERT_TRUE(iter->Done()); + iter.reset(); + + // Iterate a map with 10 entries. + constexpr size_t count = 10; + Vector, 0, InfallibleAllocPolicy> holders; + bool seen[count] = {}; + for (size_t i = 1; i < count; i++) { + MOZ_ALWAYS_TRUE( + holders.emplaceBack(mozilla::MakeUnique(kind, i))); + map.Put(holders.back().get(), tracer, ZoneForKind(kind)); + } + for (iter.emplace(map); !iter->Done(); iter->Next()) { + MyHolder* holder = static_cast(iter->Get().mHolder); + size_t value = holder->value; + ASSERT_TRUE(value < count); + ASSERT_FALSE(seen[value]); + seen[value] = true; + } + for (const auto& s : seen) { + ASSERT_TRUE(s); + } +} + +TEST(JSHolderMap, Iterate) +{ + TestIterate(SingleZone); + TestIterate(MultiZone); +} + +static void TestAddRemoveMany(HolderKind kind, size_t count) { + JSHolderMap map; + + Vector, 0, InfallibleAllocPolicy> holders; + for (size_t i = 0; i < count; i++) { + MOZ_ALWAYS_TRUE(holders.emplaceBack(mozilla::MakeUnique(kind))); + } + + for (size_t i = 0; i < count; i++) { + MyHolder* holder = holders[i].get(); + map.Put(holder, holder, ZoneForKind(kind)); + } + + ASSERT_EQ(CountEntries(map), count); + + for (size_t i = 0; i < count; i++) { + MyHolder* holder = holders[i].get(); + ASSERT_EQ(map.Extract(holder), holder); + } + + ASSERT_EQ(CountEntries(map), 0u); +} + +TEST(JSHolderMap, TestAddRemoveMany) +{ + TestAddRemoveMany(SingleZone, 10000); + TestAddRemoveMany(MultiZone, 10000); +} + +static void TestRemoveWhileIterating(HolderKind kind, size_t count) { + JSHolderMap map; + Vector, 0, InfallibleAllocPolicy> holders; + Maybe iter; + + for (size_t i = 0; i < count; i++) { + MOZ_ALWAYS_TRUE(holders.emplaceBack(MakeUnique(kind))); + } + + // Iterate a map with one entry but remove it before we get to it. + MyHolder* holder = holders[0].get(); + map.Put(holder, holder, ZoneForKind(kind)); + iter.emplace(map); + ASSERT_FALSE(iter->Done()); + ASSERT_EQ(map.Extract(holder), holder); + iter->UpdateForRemovals(); + ASSERT_TRUE(iter->Done()); + + // Check UpdateForRemovals is safe to call on a done iterator. + iter->UpdateForRemovals(); + ASSERT_TRUE(iter->Done()); + iter.reset(); + + // Add many holders and remove them mid way through iteration. + + for (size_t i = 0; i < count; i++) { + MyHolder* holder = holders[i].get(); + map.Put(holder, holder, ZoneForKind(kind)); + } + + iter.emplace(map); + for (size_t i = 0; i < count / 2; i++) { + iter->Next(); + ASSERT_FALSE(iter->Done()); + } + + for (size_t i = 0; i < count; i++) { + MyHolder* holder = holders[i].get(); + ASSERT_EQ(map.Extract(holder), holder); + } + + iter->UpdateForRemovals(); + + ASSERT_TRUE(iter->Done()); + iter.reset(); + + ASSERT_EQ(CountEntries(map), 0u); +} + +TEST(JSHolderMap, TestRemoveWhileIterating) +{ + TestRemoveWhileIterating(SingleZone, 10000); + TestRemoveWhileIterating(MultiZone, 10000); +} + +class ObjectHolder final { + public: + ObjectHolder() { HoldJSObjects(this); } + + NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ObjectHolder) + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(ObjectHolder) + + void SetObject(JSObject* aObject) { mObject = aObject; } + + void ClearObject() { mObject = nullptr; } + + JSObject* GetObject() const { return mObject; } + JSObject* GetObjectUnbarriered() const { return mObject.unbarrieredGet(); } + + bool ObjectIsGray() const { + JSObject* obj = mObject.unbarrieredGet(); + MOZ_RELEASE_ASSERT(obj); + return JS::GCThingIsMarkedGray(JS::GCCellPtr(obj)); + } + + private: + JS::Heap mObject; + + ~ObjectHolder() { DropJSObjects(this); } +}; + +NS_IMPL_CYCLE_COLLECTION_CLASS(ObjectHolder) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ObjectHolder) + tmp->ClearObject(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(ObjectHolder) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(ObjectHolder) + NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mObject) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +// Test GC things stored in JS holders are marked as gray roots by the GC. +static void TestHoldersAreMarkedGray(JSContext* cx) { + RefPtr holder(new ObjectHolder); + + JSObject* obj = JS_NewPlainObject(cx); + ASSERT_TRUE(obj); + holder->SetObject(obj); + obj = nullptr; + + JS_GC(cx); + + ASSERT_TRUE(holder->ObjectIsGray()); +} + +// Test GC things stored in JS holders are updated by compacting GC. +static void TestHoldersAreMoved(JSContext* cx, bool singleZone) { + JS::RootedObject obj(cx, JS_NewPlainObject(cx)); + ASSERT_TRUE(obj); + + // Set a property so we can check we have the same object at the end. + const char* PropertyName = "answer"; + const int32_t PropertyValue = 42; + JS::RootedValue value(cx, JS::Int32Value(PropertyValue)); + ASSERT_TRUE(JS_SetProperty(cx, obj, PropertyName, value)); + + // Ensure the object is tenured. + JS_GC(cx); + + RefPtr holder(new ObjectHolder); + holder->SetObject(obj); + + uintptr_t original = uintptr_t(obj.get()); + + if (singleZone) { + JS::PrepareZoneForGC(cx, js::GetContextZone(cx)); + } else { + JS::PrepareForFullGC(cx); + } + + JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::DEBUG_GC); + + // Shrinking DEBUG_GC should move all GC things. + ASSERT_NE(uintptr_t(holder->GetObject()), original); + + // Both root and holder should have been updated. + ASSERT_EQ(obj, holder->GetObject()); + + // Check it's the object we expect. + value.setUndefined(); + ASSERT_TRUE(JS_GetProperty(cx, obj, PropertyName, &value)); + ASSERT_EQ(value, JS::Int32Value(PropertyValue)); +} + +TEST(JSHolderMap, GCIntegration) +{ + CycleCollectedJSContext* ccjscx = CycleCollectedJSContext::Get(); + ASSERT_NE(ccjscx, nullptr); + JSContext* cx = ccjscx->Context(); + ASSERT_NE(cx, nullptr); + + static const JSClass GlobalClass = {"global", JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps}; + + JS::RealmOptions options; + JS::RootedObject global(cx); + global = JS_NewGlobalObject(cx, &GlobalClass, nullptr, + JS::FireOnNewGlobalHook, options); + ASSERT_NE(global, nullptr); + + JSAutoRealm ar(cx, global); + + TestHoldersAreMarkedGray(cx); + TestHoldersAreMoved(cx, true); + TestHoldersAreMoved(cx, false); +} diff --git a/xpcom/tests/gtest/TestLogCommandLineHandler.cpp b/xpcom/tests/gtest/TestLogCommandLineHandler.cpp new file mode 100644 index 0000000000..ebec4854dd --- /dev/null +++ b/xpcom/tests/gtest/TestLogCommandLineHandler.cpp @@ -0,0 +1,183 @@ +/* -*- 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 "LogCommandLineHandler.h" + +#include +#include "nsString.h" +#include "nsTArray.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +template +constexpr size_t array_size(T (&)[N]) { + return N; +} + +TEST(LogCommandLineHandler, Empty) +{ + bool callbackInvoked = false; + auto callback = [&](nsACString const& env) mutable { + callbackInvoked = true; + }; + + mozilla::LoggingHandleCommandLineArgs(0, nullptr, callback); + EXPECT_FALSE(callbackInvoked); + + char const* argv1[] = {""}; + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_FALSE(callbackInvoked); +} + +TEST(LogCommandLineHandler, MOZ_LOG_regular) +{ + nsTArray results; + + auto callback = [&](nsACString const& env) mutable { + results.AppendElement(env); + }; + + char const* argv1[] = {"", "-MOZ_LOG", "module1:5,module2:4,sync,timestamp"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE( + "MOZ_LOG=module1:5,module2:4,sync,timestamp"_ns.Equals(results[0])); + + char const* argv2[] = {"", "-MOZ_LOG=modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + + char const* argv3[] = {"", "--MOZ_LOG", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + + char const* argv4[] = {"", "--MOZ_LOG=modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); +} + +TEST(LogCommandLineHandler, MOZ_LOG_and_FILE_regular) +{ + nsTArray results; + + auto callback = [&](nsACString const& env) mutable { + results.AppendElement(env); + }; + + char const* argv1[] = {"", "-MOZ_LOG", "modules", "-MOZ_LOG_FILE", + "c:\\file/path"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=c:\\file/path"_ns.Equals(results[1])); + + char const* argv2[] = {"", "-MOZ_LOG=modules", "-MOZ_LOG_FILE=file"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1])); + + char const* argv3[] = {"", "--MOZ_LOG", "modules", "--MOZ_LOG_FILE", "file"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1])); + + char const* argv4[] = {"", "--MOZ_LOG=modules", "--MOZ_LOG_FILE=file"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1])); + + char const* argv5[] = {"", "--MOZ_LOG", "modules", "-P", + "foo", "--MOZ_LOG_FILE", "file"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv5), argv5, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG_FILE=file"_ns.Equals(results[1])); +} + +TEST(LogCommandLineHandler, MOZ_LOG_fuzzy) +{ + nsTArray results; + + auto callback = [&](nsACString const& env) mutable { + results.AppendElement(env); + }; + + char const* argv1[] = {"", "-MOZ_LOG"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_TRUE(results.Length() == 0); + + char const* argv2[] = {"", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback); + EXPECT_TRUE(results.Length() == 0); + + char const* argv3[] = {"", "-MOZ_LOG,modules", "-MOZ_LOG"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback); + EXPECT_TRUE(results.Length() == 0); + + char const* argv4[] = {"", "-MOZ_LOG", "-MOZ_LOG", "-MOZ_LOG"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback); + EXPECT_TRUE(results.Length() == 0); + + char const* argv5[] = {"", "-MOZ_LOG", "-diffent_command", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv5), argv5, callback); + EXPECT_TRUE(results.Length() == 0); +} + +TEST(LogCommandLineHandler, MOZ_LOG_overlapping) +{ + nsTArray results; + + auto callback = [&](nsACString const& env) mutable { + results.AppendElement(env); + }; + + char const* argv1[] = {"", "-MOZ_LOG=modules1", "-MOZ_LOG=modules2"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv1), argv1, callback); + EXPECT_TRUE(results.Length() == 2); + EXPECT_TRUE("MOZ_LOG=modules1"_ns.Equals(results[0])); + EXPECT_TRUE("MOZ_LOG=modules2"_ns.Equals(results[1])); + + char const* argv2[] = {"", "-MOZ_LOG", "--MOZ_LOG", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv2), argv2, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + + char const* argv3[] = {"", "-MOZ_LOG_FILE", "-MOZ_LOG", "modules"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv3), argv3, callback); + EXPECT_TRUE(results.Length() == 1); + EXPECT_TRUE("MOZ_LOG=modules"_ns.Equals(results[0])); + + char const* argv4[] = {"", "-MOZ_LOG", "-MOZ_LOG_FILE", "-MOZ_LOG"}; + results.Clear(); + mozilla::LoggingHandleCommandLineArgs(array_size(argv4), argv4, callback); + EXPECT_TRUE(results.Length() == 0); +} diff --git a/xpcom/tests/gtest/TestLogging.cpp b/xpcom/tests/gtest/TestLogging.cpp new file mode 100644 index 0000000000..0eb2b7a152 --- /dev/null +++ b/xpcom/tests/gtest/TestLogging.cpp @@ -0,0 +1,182 @@ +/* -*- 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/Logging.h" +#include "mozilla/Sprintf.h" +#include "gtest/gtest.h" + +namespace mozilla::detail { +bool LimitFileToLessThanSize(const char* aFilename, uint32_t aSize, + uint16_t aLongLineSize); +} + +// These format strings result in 1024 byte lines on disk regardless +// of OS, which makes various file sizes OS-agnostic. +#ifdef XP_WIN +# define WHOLE_LINE "%01022d\n" +# define SHORT_LINE "%0510d\n" +#else +# define WHOLE_LINE "%01023d\n" +# define SHORT_LINE "%0511d\n" +#endif + +// Write the given number of 1k lines to the given file name. +void WriteTestLogFile(const char* name, uint32_t numLines) { + FILE* f = fopen(name, "w"); + ASSERT_NE(f, (FILE*)nullptr); + + for (uint32_t i = 0; i < numLines; i++) { + char buf[1024 + 1]; + SprintfLiteral(buf, WHOLE_LINE, i); + EXPECT_TRUE(fputs(buf, f) >= 0); + } + + uint64_t size = static_cast(ftell(f)); + + // Close before asserting. + EXPECT_FALSE(fclose(f)); + + ASSERT_EQ(numLines * 1024, size); +} + +// Assert that the given file name has the expected size and that its +// first line is the expected line. +void AssertSizeAndFirstLine(const char* name, uint32_t expectedSize, + const char* expectedLine) { + FILE* f = fopen(name, "r"); + ASSERT_NE(f, (FILE*)nullptr); + + EXPECT_FALSE(fseek(f, 0, SEEK_END)); + uint64_t size = static_cast(ftell(f)); + + EXPECT_FALSE(fseek(f, 0, SEEK_SET)); + + char line[1024 + 1]; + const char* result = fgets(line, sizeof(line), f); + + // Close before asserting. + EXPECT_FALSE(fclose(f)); + + ASSERT_NE(result, nullptr); + ASSERT_EQ(expectedSize, size); + ASSERT_STREQ(expectedLine, line); +} + +TEST(Logging, DoesNothingWhenNotNeededExact) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 256); + + // Here the log file is exactly the allowed size. It shouldn't be limited. + ASSERT_TRUE( + mozilla::detail::LimitFileToLessThanSize(nameBuf, 256 * 1024, 1024)); + + char expectedLine[1024 + 1]; + SprintfLiteral(expectedLine, WHOLE_LINE, 0); + + AssertSizeAndFirstLine(nameBuf, 256 * 1024, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} + +TEST(Logging, DoesNothingWhenNotNeededInexact) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 200); + + // Here the log file is strictly less than the allowed size. It shouldn't be + // limited. + ASSERT_TRUE( + mozilla::detail::LimitFileToLessThanSize(nameBuf, 256 * 1024, 1024)); + + char expectedLine[1024 + 1]; + SprintfLiteral(expectedLine, WHOLE_LINE, 0); + + AssertSizeAndFirstLine(nameBuf, 200 * 1024, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} + +TEST(Logging, LimitsToLessThanSize) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 300); + + ASSERT_TRUE( + mozilla::detail::LimitFileToLessThanSize(nameBuf, 256 * 1024, 1024)); + + char expectedLine[1024 + 1]; + SprintfLiteral(expectedLine, WHOLE_LINE, 300 - 256); + + AssertSizeAndFirstLine(nameBuf, 256 * 1024, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} + +TEST(Logging, MayCutLongLinesExact) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 300); + + char expectedLine[1024 + 1]; + + ASSERT_TRUE(mozilla::detail::LimitFileToLessThanSize( + nameBuf, (256 * 1024) - 512, 512)); + + SprintfLiteral(expectedLine, SHORT_LINE, 300 - 256); + + // The line to be cut ends "...044\n." We read 512 bytes (the + // buffer size), so we're left with 512 bytes, one of which is the + // newline. + AssertSizeAndFirstLine(nameBuf, 256 * 1024 - 512, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} + +TEST(Logging, MayCutLongLinesInexact) +{ + char nameBuf[2048]; + SprintfLiteral( + nameBuf, "%s_%s.moz_log", + testing::UnitTest::GetInstance()->current_test_info()->test_case_name(), + testing::UnitTest::GetInstance()->current_test_info()->name()); + + WriteTestLogFile(nameBuf, 300); + + char expectedLine[1024 + 1]; + + ASSERT_TRUE(mozilla::detail::LimitFileToLessThanSize( + nameBuf, (256 * 1024) - 512, 512)); + + SprintfLiteral(expectedLine, SHORT_LINE, 300 - 256); + + // We read 512 bytes (the buffer size), so we're left with 512 + // bytes, one of which is the newline. Notice that the limited size + // is smaller than the requested size. + AssertSizeAndFirstLine(nameBuf, 256 * 1024 - 512, expectedLine); + + EXPECT_FALSE(remove(nameBuf)); +} diff --git a/xpcom/tests/gtest/TestMacNSURLEscaping.mm b/xpcom/tests/gtest/TestMacNSURLEscaping.mm new file mode 100644 index 0000000000..35f5bcb3e5 --- /dev/null +++ b/xpcom/tests/gtest/TestMacNSURLEscaping.mm @@ -0,0 +1,139 @@ +#include "nsCocoaUtils.h" +#include "nsEscape.h" +#include "nsNetUtil.h" +#include "gtest/gtest.h" + +#include + +// +// For the macOS File->Share menu, we must create an NSURL. However, NSURL is +// more strict than the browser about the encoding of URLs it accepts. +// Therefore additional encoding must be done on a URL before it is used to +// create an NSURL object. These tests aim to exercise the code used to +// perform additional encoding on a URL used to create NSURL objects. +// + +// Ensure nsCocoaUtils::ToNSURL() didn't change the URL. +// Create an NSURL with the provided string and then read the URL out of +// the NSURL and test it matches the provided string. +void ExpectUnchangedByNSURL(nsCString& aEncoded) { + NSURL* macURL = nsCocoaUtils::ToNSURL(NS_ConvertUTF8toUTF16(aEncoded)); + NSString* macURLString = [macURL absoluteString]; + + nsString geckoURLString; + nsCocoaUtils::GetStringForNSString(macURLString, geckoURLString); + EXPECT_STREQ(aEncoded.BeginReading(), NS_ConvertUTF16toUTF8(geckoURLString).get()); +} + +// Test escaping of URLs to ensure that +// 1) We escape URLs in such a way that macOS's NSURL code accepts the URL as +// valid. +// 2) The encoding encoded the expected characters only. +// 2) NSURL not modify the URL. Check this by reading the URL back out of the +// NSURL object and comparing it. If the URL is changed by creating an +// NSURL, it may indicate the encoding is incorrect. +// +// It is not a requirement that NSURL not change the URL, but we don't +// expect that for these test cases. +TEST(NSURLEscaping, NSURLEscapingTests) +{ + // Per RFC2396, URI "unreserved" characters. These are allowed in URIs and + // can be escaped without changing the semantics of the URI, but the escaping + // should only be done if the URI is used in a context that requires it. + // + // "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" + // + // These are the URI general "reserved" characters. Their reserved purpose + // is as delimters so they don't need to be escaped unless used in a URI + // component in a way that conflicts with the reserved purpose. i.e., + // whether or not they must be encoded depends on the component. + // + // ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," + // + // Characters considered excluded from URI use and should be escaped. + // "#" when not used to delimit the start of the fragment identifier. + // "%" when not used to escape a character. + // + // "<" | ">" | "#" | "%" | <"> | "{" | "}" | "|" | "\" | "^" | "[" | "]" | + // "`" + + // Pairs of URLs of the form (un-encoded, expected encoded result) to verify. + nsTArray> pairs{ + {// '#' in ref + "https://chat.mozilla.org/#/room/#macdev:mozilla.org"_ns, + "https://chat.mozilla.org/#/room/%23macdev:mozilla.org"_ns}, + { + // '"' in ref + "https://example.com/path#ref_with_#_and\""_ns, + "https://example.com/path#ref_with_%23_and%22"_ns, + }, + { + // '[]{}|' in ref + "https://example.com/path#ref_with_[foo]_{and}_|"_ns, + "https://example.com/path#ref_with_%5Bfoo%5D_%7Band%7D_%7C"_ns, + }, + { + // Unreserved characters in path, query, and ref + "https://example.com/path-_.!~&'(x)?x=y&x=z-_.!~&'(x)#ref-_.!~&'(x)"_ns, + "https://example.com/path-_.!~&'(x)?x=y&x=z-_.!~&%27(x)#ref-_.!~&'(x)"_ns, + }, + { + // All excluded characters in the ref. + "https://example.com/path#ref \"#<>[]\\^`{|}ref"_ns, + "https://example.com/path#ref%20%22%23%3C%3E%5B%5D%5C%5E%60%7B%7C%7Dref"_ns, + }, + /* + * Bug 1739533: + * This test fails because the '%' character needs to be escaped before + * the URI is passed to [NSURL URLWithString]. '<' brackets are + * already escaped by the browser to their "%xx" form so the encoding must + * be added in a way that ignores characters already % encoded "%xx". + { + // Unreserved characters in path, query, and ref + // https://example.com/path/with/%/and"#frag_with_#_and" + "https://example.com/path/with/%/and\"#frag_with_#_and\""_ns, + "https://example.com/path/with%3Cmore%3E/%25/and%22#frag_with_%23_and%22"_ns, + }, + */ + }; + + for (std::pair& pair : pairs) { + nsCString escaped; + nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, pair.first); + EXPECT_EQ(rv, NS_OK); + EXPECT_STREQ(pair.second.BeginReading(), escaped.BeginReading()); + ExpectUnchangedByNSURL(escaped); + } + + // A list of URLs that should not be changed by encoding. + nsTArray unchangedURLs{ + // '=' In the query + "https://bugzilla.mozilla.org/show_bug.cgi?id=1737854"_ns, + "https://bugzilla.mozilla.org/show_bug.cgi?id=1737854#ref"_ns, + "https://bugzilla.mozilla.org/allinref#show_bug.cgi?id=1737854ref"_ns, + "https://example.com/script?foo=bar#this_ref"_ns, + // Escaped character in the ref + "https://html.spec.whatwg.org/multipage/dom.html#the-document%27s-address"_ns, + // Misc query + "https://www.google.com/search?q=firefox+web+browser&client=firefox-b-1-d&ei=abc&ved=abc&abc=5&oq=firefox+web+browser&gs_lcp=abc&sclient=gws-wiz"_ns, + // Check for double encoding. % encoded octals should not be re-encoded. + "https://chat.mozilla.org/#/room/%23macdev%3Amozilla.org"_ns, + "https://searchfox.org/mozilla-central/search?q=symbol%3AE_%3CT_mozilla%3A%3AWebGLExtensionID%3E_EXT_color_buffer_half_float&path="_ns, + // Unreserved and reserved that don't need encoding in ref. + "https://example.com/path#ref!$&'(foo),:;=?@~"_ns, + // Unreserved and reserved that don't need encoding in path. + "https://example.com/path-_.!~&'(x)#ref"_ns, + // Unreserved and reserved that don't need encoding in path and ref. + "https://example.com/path-_.!~&'(x)#ref-_.!~&'(x)"_ns, + // Reserved in query. + "https://example.com/path?a=b&;=/&/=?&@=a+b,$"_ns, + }; + + for (nsCString& toEscape : unchangedURLs) { + nsCString escaped; + nsresult rv = NS_GetSpecWithNSURLEncoding(escaped, toEscape); + EXPECT_EQ(rv, NS_OK); + EXPECT_STREQ(toEscape.BeginReading(), escaped.BeginReading()); + ExpectUnchangedByNSURL(escaped); + } +} diff --git a/xpcom/tests/gtest/TestMemoryPressure.cpp b/xpcom/tests/gtest/TestMemoryPressure.cpp new file mode 100644 index 0000000000..8435848aa4 --- /dev/null +++ b/xpcom/tests/gtest/TestMemoryPressure.cpp @@ -0,0 +1,199 @@ +/* -*- 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 +#include "gtest/gtest.h" + +#include "mozilla/Atomics.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsMemoryPressure.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +namespace { + +enum class MemoryPressureEventType : int { + LowMemory, + LowMemoryOngoing, + Stop, +}; + +class MemoryPressureObserver final : public nsIObserver { + nsCOMPtr mObserverSvc; + Vector mEvents; + + ~MemoryPressureObserver() { + EXPECT_TRUE( + NS_SUCCEEDED(mObserverSvc->RemoveObserver(this, kTopicMemoryPressure))); + EXPECT_TRUE(NS_SUCCEEDED( + mObserverSvc->RemoveObserver(this, kTopicMemoryPressureStop))); + } + + public: + NS_DECL_ISUPPORTS + + MemoryPressureObserver() + : mObserverSvc(do_GetService(NS_OBSERVERSERVICE_CONTRACTID)) { + EXPECT_TRUE(NS_SUCCEEDED(mObserverSvc->AddObserver( + this, kTopicMemoryPressure, /* ownsWeak */ false))); + EXPECT_TRUE(NS_SUCCEEDED(mObserverSvc->AddObserver( + this, kTopicMemoryPressureStop, /* ownsWeak */ false))); + } + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + Maybe event; + if (strcmp(aTopic, kTopicMemoryPressure) == 0) { + if (nsDependentString(aData) == kSubTopicLowMemoryNew) { + event = Some(MemoryPressureEventType::LowMemory); + } else if (nsDependentString(aData) == kSubTopicLowMemoryOngoing) { + event = Some(MemoryPressureEventType::LowMemoryOngoing); + } else { + fprintf(stderr, "Unexpected subtopic: %S\n", + reinterpret_cast(aData)); + EXPECT_TRUE(false); + } + } else if (strcmp(aTopic, kTopicMemoryPressureStop) == 0) { + event = Some(MemoryPressureEventType::Stop); + } else { + fprintf(stderr, "Unexpected topic: %s\n", aTopic); + EXPECT_TRUE(false); + } + + if (event) { + Unused << mEvents.emplaceBack(event.value()); + } + return NS_OK; + } + + uint32_t GetCount() const { return mEvents.length(); } + void Reset() { mEvents.clear(); } + MemoryPressureEventType Top() const { return mEvents[0]; } + + bool ValidateTransitions() const { + if (mEvents.length() == 0) { + return true; + } + + for (size_t i = 1; i < mEvents.length(); ++i) { + MemoryPressureEventType eventFrom = mEvents[i - 1]; + MemoryPressureEventType eventTo = mEvents[i]; + if ((eventFrom == MemoryPressureEventType::LowMemory && + eventTo == MemoryPressureEventType::LowMemoryOngoing) || + (eventFrom == MemoryPressureEventType::LowMemoryOngoing && + eventTo == MemoryPressureEventType::LowMemoryOngoing) || + (eventFrom == MemoryPressureEventType::Stop && + eventTo == MemoryPressureEventType::LowMemory) || + (eventFrom == MemoryPressureEventType::LowMemoryOngoing && + eventTo == MemoryPressureEventType::Stop) || + (eventFrom == MemoryPressureEventType::LowMemory && + eventTo == MemoryPressureEventType::Stop)) { + // Only these transitions are valid. + continue; + } + + fprintf(stderr, "Invalid transition: %d -> %d\n", + static_cast(eventFrom), static_cast(eventTo)); + return false; + } + return true; + } +}; + +NS_IMPL_ISUPPORTS(MemoryPressureObserver, nsIObserver) + +template +void PressureSender(Atomic& aContinue) { + while (aContinue) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + NS_NotifyOfEventualMemoryPressure(State); + } +} + +template +void PressureSenderQuick(Atomic& aContinue) { + while (aContinue) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + NS_NotifyOfMemoryPressure(State); + } +} + +} // anonymous namespace + +TEST(MemoryPressure, Singlethread) +{ + RefPtr observer(new MemoryPressureObserver); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 1"_ns, + [&observer]() { return observer->GetCount() == 1; }); + EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemory); + + observer->Reset(); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 2"_ns, + [&observer]() { return observer->GetCount() == 1; }); + EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemoryOngoing); + + observer->Reset(); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::LowMemory); + SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 3"_ns, + [&observer]() { return observer->GetCount() == 1; }); + EXPECT_EQ(observer->Top(), MemoryPressureEventType::LowMemoryOngoing); + + observer->Reset(); + NS_NotifyOfEventualMemoryPressure(MemoryPressureState::NoPressure); + SpinEventLoopUntil("xpcom:TEST(MemoryPressure, Singlethread) 4"_ns, + [&observer]() { return observer->GetCount() == 1; }); + EXPECT_EQ(observer->Top(), MemoryPressureEventType::Stop); +} + +TEST(MemoryPressure, Multithread) +{ + // Start |kNumThreads| threads each for the following thread type: + // - LowMemory via NS_NotifyOfEventualMemoryPressure + // - LowMemory via NS_NotifyOfMemoryPressure + // - LowMemoryOngoing via NS_NotifyOfEventualMemoryPressure + // - LowMemoryOngoing via NS_NotifyOfMemoryPressure + // and keep them running until |kNumEventsToValidate| memory-pressure events + // are received. + constexpr int kNumThreads = 5; + constexpr int kNumEventsToValidate = 200; + + Atomic shouldContinue(true); + Vector threads; + for (int i = 0; i < kNumThreads; ++i) { + Unused << threads.emplaceBack( + PressureSender, + std::ref(shouldContinue)); + Unused << threads.emplaceBack( + PressureSender, + std::ref(shouldContinue)); + Unused << threads.emplaceBack( + PressureSenderQuick, + std::ref(shouldContinue)); + Unused << threads.emplaceBack( + PressureSenderQuick, + std::ref(shouldContinue)); + } + + RefPtr observer(new MemoryPressureObserver); + + // We cannot sleep here because the main thread needs to keep running. + SpinEventLoopUntil( + "xpcom:TEST(MemoryPressure, Multithread)"_ns, + [&observer]() { return observer->GetCount() >= kNumEventsToValidate; }); + + shouldContinue = false; + for (auto& thread : threads) { + thread.join(); + } + + EXPECT_TRUE(observer->ValidateTransitions()); +} diff --git a/xpcom/tests/gtest/TestMoveString.cpp b/xpcom/tests/gtest/TestMoveString.cpp new file mode 100644 index 0000000000..cdfacdbbac --- /dev/null +++ b/xpcom/tests/gtest/TestMoveString.cpp @@ -0,0 +1,266 @@ +/* -*- 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 +#include +#include "nsASCIIMask.h" +#include "nsString.h" +#include "nsStringBuffer.h" +#include "nsReadableUtils.h" +#include "nsCRTGlue.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Unused.h" +#include "gtest/gtest.h" + +namespace TestMoveString { + +#define NEW_VAL "**new value**" +#define OLD_VAL "old value" + +typedef mozilla::detail::StringDataFlags Df; + +static void SetAsOwned(nsACString& aStr, const char* aValue) { + size_t len = strlen(aValue); + char* data = new char[len + 1]; + memcpy(data, aValue, len + 1); + aStr.Adopt(data, len); + EXPECT_EQ(aStr.GetDataFlags(), Df::OWNED | Df::TERMINATED); + EXPECT_STREQ(aStr.BeginReading(), aValue); +} + +static void ExpectTruncated(const nsACString& aStr) { + EXPECT_EQ(aStr.Length(), uint32_t(0)); + EXPECT_STREQ(aStr.BeginReading(), ""); + EXPECT_EQ(aStr.GetDataFlags(), Df::TERMINATED); +} + +static void ExpectNew(const nsACString& aStr) { + EXPECT_EQ(aStr.Length(), strlen(NEW_VAL)); + EXPECT_TRUE(aStr.EqualsASCII(NEW_VAL)); +} + +TEST(MoveString, SharedIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsCString in; + in.Assign(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, OwnedIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsCString in; + SetAsOwned(in, NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::OWNED | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, LiteralIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsCString in; + in.AssignLiteral(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::LITERAL | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::LITERAL | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, AutoIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsAutoCString in; + in.Assign(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::INLINE | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + EXPECT_NE(out.get(), data); +} + +TEST(MoveString, DepIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsDependentCSubstring in(NEW_VAL "garbage after", strlen(NEW_VAL)); + EXPECT_EQ(in.GetDataFlags(), Df(0)); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); +} + +TEST(MoveString, VoidIntoOwned) +{ + nsCString out; + SetAsOwned(out, OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + + nsCString in = VoidCString(); + EXPECT_EQ(in.GetDataFlags(), Df::VOIDED | Df::TERMINATED); + + out.Assign(std::move(in)); + ExpectTruncated(in); + + EXPECT_EQ(out.Length(), 0u); + EXPECT_STREQ(out.get(), ""); + EXPECT_EQ(out.GetDataFlags(), Df::VOIDED | Df::TERMINATED); +} + +TEST(MoveString, SharedIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsCString in; + in.Assign(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::REFCOUNTED | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, OwnedIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsCString in; + SetAsOwned(in, NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::OWNED | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::OWNED | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, LiteralIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsCString in; + in.AssignLiteral(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::LITERAL | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::LITERAL | Df::TERMINATED); + EXPECT_EQ(out.get(), data); +} + +TEST(MoveString, AutoIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsAutoCString in; + in.Assign(NEW_VAL); + EXPECT_EQ(in.GetDataFlags(), Df::INLINE | Df::TERMINATED); + const char* data = in.get(); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + EXPECT_NE(out.get(), data); +} + +TEST(MoveString, DepIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsDependentCSubstring in(NEW_VAL "garbage after", strlen(NEW_VAL)); + EXPECT_EQ(in.GetDataFlags(), Df(0)); + + out.Assign(std::move(in)); + ExpectTruncated(in); + ExpectNew(out); + + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); +} + +TEST(MoveString, VoidIntoAuto) +{ + nsAutoCString out; + out.Assign(OLD_VAL); + EXPECT_EQ(out.GetDataFlags(), Df::INLINE | Df::TERMINATED); + + nsCString in = VoidCString(); + EXPECT_EQ(in.GetDataFlags(), Df::VOIDED | Df::TERMINATED); + + out.Assign(std::move(in)); + ExpectTruncated(in); + + EXPECT_EQ(out.Length(), 0u); + EXPECT_STREQ(out.get(), ""); + EXPECT_EQ(out.GetDataFlags(), Df::VOIDED | Df::TERMINATED); +} + +#undef NEW_VAL +#undef OLD_VAL + +} // namespace TestMoveString diff --git a/xpcom/tests/gtest/TestMozPromise.cpp b/xpcom/tests/gtest/TestMozPromise.cpp new file mode 100644 index 0000000000..bb7273cc1f --- /dev/null +++ b/xpcom/tests/gtest/TestMozPromise.cpp @@ -0,0 +1,756 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "VideoUtils.h" +#include "base/message_loop.h" +#include "gtest/gtest.h" +#include "mozilla/MozPromise.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Unused.h" +#include "nsISupportsImpl.h" + +using namespace mozilla; + +typedef MozPromise TestPromise; +typedef MozPromise TestPromiseExcl; +typedef TestPromise::ResolveOrRejectValue RRValue; + +class MOZ_STACK_CLASS AutoTaskQueue { + public: + AutoTaskQueue() + : mTaskQueue( + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestMozPromise AutoTaskQueue")) {} + + ~AutoTaskQueue() { mTaskQueue->AwaitShutdownAndIdle(); } + + TaskQueue* Queue() { return mTaskQueue; } + + private: + RefPtr mTaskQueue; +}; + +class DelayedResolveOrReject : public Runnable { + public: + DelayedResolveOrReject(TaskQueue* aTaskQueue, TestPromise::Private* aPromise, + const TestPromise::ResolveOrRejectValue& aValue, + int aIterations) + : mozilla::Runnable("DelayedResolveOrReject"), + mTaskQueue(aTaskQueue), + mPromise(aPromise), + mValue(aValue), + mIterations(aIterations) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn()); + if (!mPromise) { + // Canceled. + return NS_OK; + } + + if (--mIterations == 0) { + mPromise->ResolveOrReject(mValue, __func__); + return NS_OK; + } + + nsCOMPtr r = this; + return mTaskQueue->Dispatch(r.forget()); + } + + void Cancel() { mPromise = nullptr; } + + protected: + ~DelayedResolveOrReject() = default; + + private: + RefPtr mTaskQueue; + RefPtr mPromise; + TestPromise::ResolveOrRejectValue mValue; + int mIterations; +}; + +template +void RunOnTaskQueue(TaskQueue* aQueue, FunctionType aFun) { + nsCOMPtr r = NS_NewRunnableFunction("RunOnTaskQueue", aFun); + Unused << aQueue->Dispatch(r.forget()); +} + +// std::function can't come soon enough. :-( +#define DO_FAIL \ + []() { \ + EXPECT_TRUE(false); \ + return TestPromise::CreateAndReject(0, __func__); \ + } + +TEST(MozPromise, BasicResolve) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + TestPromise::CreateAndResolve(42, __func__) + ->Then( + queue, __func__, + [queue](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + queue->BeginShutdown(); + }, + DO_FAIL); + }); +} + +TEST(MozPromise, BasicReject) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + TestPromise::CreateAndReject(42.0, __func__) + ->Then(queue, __func__, DO_FAIL, [queue](int aRejectValue) -> void { + EXPECT_EQ(aRejectValue, 42.0); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, BasicResolveOrRejectResolved) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + TestPromise::CreateAndResolve(42, __func__) + ->Then( + queue, __func__, + [queue](const TestPromise::ResolveOrRejectValue& aValue) -> void { + EXPECT_TRUE(aValue.IsResolve()); + EXPECT_FALSE(aValue.IsReject()); + EXPECT_FALSE(aValue.IsNothing()); + EXPECT_EQ(aValue.ResolveValue(), 42); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, BasicResolveOrRejectRejected) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + TestPromise::CreateAndReject(42.0, __func__) + ->Then( + queue, __func__, + [queue](const TestPromise::ResolveOrRejectValue& aValue) -> void { + EXPECT_TRUE(aValue.IsReject()); + EXPECT_FALSE(aValue.IsResolve()); + EXPECT_FALSE(aValue.IsNothing()); + EXPECT_EQ(aValue.RejectValue(), 42.0); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, AsyncResolve) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + RefPtr p = new TestPromise::Private(__func__); + + // Kick off three racing tasks, and make sure we get the one that finishes + // earliest. + RefPtr a = + new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(32), 10); + RefPtr b = + new DelayedResolveOrReject(queue, p, RRValue::MakeResolve(42), 5); + RefPtr c = + new DelayedResolveOrReject(queue, p, RRValue::MakeReject(32.0), 7); + + nsCOMPtr ref = a.get(); + Unused << queue->Dispatch(ref.forget()); + ref = b.get(); + Unused << queue->Dispatch(ref.forget()); + ref = c.get(); + Unused << queue->Dispatch(ref.forget()); + + p->Then( + queue, __func__, + [queue, a, b, c](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + a->Cancel(); + b->Cancel(); + c->Cancel(); + queue->BeginShutdown(); + }, + DO_FAIL); + }); +} + +TEST(MozPromise, CompletionPromises) +{ + bool invokedPass = false; + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue, &invokedPass]() -> void { + TestPromise::CreateAndResolve(40, __func__) + ->Then( + queue, __func__, + [](int aVal) -> RefPtr { + return TestPromise::CreateAndResolve(aVal + 10, __func__); + }, + DO_FAIL) + ->Then( + queue, __func__, + [&invokedPass](int aVal) { + invokedPass = true; + return TestPromise::CreateAndResolve(aVal, __func__); + }, + DO_FAIL) + ->Then( + queue, __func__, + [queue](int aVal) -> RefPtr { + RefPtr p = + new TestPromise::Private(__func__); + nsCOMPtr resolver = new DelayedResolveOrReject( + queue, p, RRValue::MakeResolve(aVal - 8), 10); + Unused << queue->Dispatch(resolver.forget()); + return RefPtr(p); + }, + DO_FAIL) + ->Then( + queue, __func__, + [](int aVal) -> RefPtr { + return TestPromise::CreateAndReject(double(aVal - 42) + 42.0, + __func__); + }, + DO_FAIL) + ->Then(queue, __func__, DO_FAIL, + [queue, &invokedPass](double aVal) -> void { + EXPECT_EQ(aVal, 42.0); + EXPECT_TRUE(invokedPass); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, PromiseAllResolve) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray> promises; + promises.AppendElement(TestPromise::CreateAndResolve(22, __func__)); + promises.AppendElement(TestPromise::CreateAndResolve(32, __func__)); + promises.AppendElement(TestPromise::CreateAndResolve(42, __func__)); + + TestPromise::All(queue, promises) + ->Then( + queue, __func__, + [queue](const CopyableTArray& aResolveValues) -> void { + EXPECT_EQ(aResolveValues.Length(), 3UL); + EXPECT_EQ(aResolveValues[0], 22); + EXPECT_EQ(aResolveValues[1], 32); + EXPECT_EQ(aResolveValues[2], 42); + queue->BeginShutdown(); + }, + []() { EXPECT_TRUE(false); }); + }); +} + +TEST(MozPromise, PromiseAllResolveAsync) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray> promises; + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(22, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(32, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(42, __func__); + })); + + TestPromise::All(queue, promises) + ->Then( + queue, __func__, + [queue](const CopyableTArray& aResolveValues) -> void { + EXPECT_EQ(aResolveValues.Length(), 3UL); + EXPECT_EQ(aResolveValues[0], 22); + EXPECT_EQ(aResolveValues[1], 32); + EXPECT_EQ(aResolveValues[2], 42); + queue->BeginShutdown(); + }, + []() { EXPECT_TRUE(false); }); + }); +} + +TEST(MozPromise, PromiseAllReject) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray> promises; + promises.AppendElement(TestPromise::CreateAndResolve(22, __func__)); + promises.AppendElement(TestPromise::CreateAndReject(32.0, __func__)); + promises.AppendElement(TestPromise::CreateAndResolve(42, __func__)); + // Ensure that more than one rejection doesn't cause a crash (bug #1207312) + promises.AppendElement(TestPromise::CreateAndReject(52.0, __func__)); + + TestPromise::All(queue, promises) + ->Then( + queue, __func__, []() { EXPECT_TRUE(false); }, + [queue](float aRejectValue) -> void { + EXPECT_EQ(aRejectValue, 32.0); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, PromiseAllRejectAsync) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray> promises; + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(22, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndReject(32.0, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(42, __func__); + })); + // Ensure that more than one rejection doesn't cause a crash (bug #1207312) + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndReject(52.0, __func__); + })); + + TestPromise::All(queue, promises) + ->Then( + queue, __func__, []() { EXPECT_TRUE(false); }, + [queue](float aRejectValue) -> void { + EXPECT_EQ(aRejectValue, 32.0); + queue->BeginShutdown(); + }); + }); +} + +TEST(MozPromise, PromiseAllSettled) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray> promises; + promises.AppendElement(TestPromise::CreateAndResolve(22, __func__)); + promises.AppendElement(TestPromise::CreateAndReject(32.0, __func__)); + promises.AppendElement(TestPromise::CreateAndResolve(42, __func__)); + promises.AppendElement(TestPromise::CreateAndReject(52.0, __func__)); + + TestPromise::AllSettled(queue, promises) + ->Then( + queue, __func__, + [queue](const TestPromise::AllSettledPromiseType::ResolveValueType& + aResolveValues) -> void { + EXPECT_EQ(aResolveValues.Length(), 4UL); + EXPECT_TRUE(aResolveValues[0].IsResolve()); + EXPECT_EQ(aResolveValues[0].ResolveValue(), 22); + EXPECT_FALSE(aResolveValues[1].IsResolve()); + EXPECT_EQ(aResolveValues[1].RejectValue(), 32.0); + EXPECT_TRUE(aResolveValues[2].IsResolve()); + EXPECT_EQ(aResolveValues[2].ResolveValue(), 42); + EXPECT_FALSE(aResolveValues[3].IsResolve()); + EXPECT_EQ(aResolveValues[3].RejectValue(), 52.0); + queue->BeginShutdown(); + }, + []() { EXPECT_TRUE(false); }); + }); +} + +TEST(MozPromise, PromiseAllSettledAsync) +{ + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + + RunOnTaskQueue(queue, [queue]() -> void { + nsTArray> promises; + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(22, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndReject(32.0, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndResolve(42, __func__); + })); + promises.AppendElement(InvokeAsync(queue, __func__, []() { + return TestPromise::CreateAndReject(52.0, __func__); + })); + + TestPromise::AllSettled(queue, promises) + ->Then( + queue, __func__, + [queue](const TestPromise::AllSettledPromiseType::ResolveValueType& + aResolveValues) -> void { + EXPECT_EQ(aResolveValues.Length(), 4UL); + EXPECT_TRUE(aResolveValues[0].IsResolve()); + EXPECT_EQ(aResolveValues[0].ResolveValue(), 22); + EXPECT_FALSE(aResolveValues[1].IsResolve()); + EXPECT_EQ(aResolveValues[1].RejectValue(), 32.0); + EXPECT_TRUE(aResolveValues[2].IsResolve()); + EXPECT_EQ(aResolveValues[2].ResolveValue(), 42); + EXPECT_FALSE(aResolveValues[3].IsResolve()); + EXPECT_EQ(aResolveValues[3].RejectValue(), 52.0); + queue->BeginShutdown(); + }, + []() { EXPECT_TRUE(false); }); + }); +} + +// Test we don't hit the assertions in MozPromise when exercising promise +// chaining upon task queue shutdown. +TEST(MozPromise, Chaining) +{ + // We declare this variable before |atq| to ensure + // the destructor is run after |holder.Disconnect()|. + MozPromiseRequestHolder holder; + + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + + RunOnTaskQueue(queue, [queue, &holder]() { + auto p = TestPromise::CreateAndResolve(42, __func__); + const size_t kIterations = 100; + for (size_t i = 0; i < kIterations; ++i) { + p = p->Then( + queue, __func__, + [](int aVal) { + EXPECT_EQ(aVal, 42); + return TestPromise::CreateAndResolve(aVal, __func__); + }, + [](double aVal) { + return TestPromise::CreateAndReject(aVal, __func__); + }); + + if (i == kIterations / 2) { + p->Then( + queue, __func__, + [queue, &holder]() { + holder.Disconnect(); + queue->BeginShutdown(); + }, + DO_FAIL); + } + } + // We will hit the assertion if we don't disconnect the leaf Request + // in the promise chain. + p->Then( + queue, __func__, []() {}, []() {}) + ->Track(holder); + }); +} + +TEST(MozPromise, ResolveOrRejectValue) +{ + using MyPromise = MozPromise, bool, false>; + using RRValue = MyPromise::ResolveOrRejectValue; + + RRValue val; + EXPECT_TRUE(val.IsNothing()); + EXPECT_FALSE(val.IsResolve()); + EXPECT_FALSE(val.IsReject()); + + val.SetResolve(MakeUnique(87)); + EXPECT_FALSE(val.IsNothing()); + EXPECT_TRUE(val.IsResolve()); + EXPECT_FALSE(val.IsReject()); + EXPECT_EQ(87, *val.ResolveValue()); + + // IsResolve() should remain true after std::move(). + UniquePtr i = std::move(val.ResolveValue()); + EXPECT_EQ(87, *i); + EXPECT_TRUE(val.IsResolve()); + EXPECT_EQ(val.ResolveValue().get(), nullptr); +} + +TEST(MozPromise, MoveOnlyType) +{ + using MyPromise = MozPromise, bool, true>; + using RRValue = MyPromise::ResolveOrRejectValue; + + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + + MyPromise::CreateAndResolve(MakeUnique(87), __func__) + ->Then( + queue, __func__, [](UniquePtr aVal) { EXPECT_EQ(87, *aVal); }, + []() { EXPECT_TRUE(false); }); + + MyPromise::CreateAndResolve(MakeUnique(87), __func__) + ->Then(queue, __func__, [queue](RRValue&& aVal) { + EXPECT_FALSE(aVal.IsNothing()); + EXPECT_TRUE(aVal.IsResolve()); + EXPECT_FALSE(aVal.IsReject()); + EXPECT_EQ(87, *aVal.ResolveValue()); + + // std::move() shouldn't change the resolve/reject state of aVal. + RRValue val = std::move(aVal); + EXPECT_TRUE(aVal.IsResolve()); + EXPECT_EQ(nullptr, aVal.ResolveValue().get()); + EXPECT_EQ(87, *val.ResolveValue()); + + queue->BeginShutdown(); + }); +} + +TEST(MozPromise, HeterogeneousChaining) +{ + using Promise1 = MozPromise, bool, true>; + using Promise2 = MozPromise, bool, true>; + using RRValue1 = Promise1::ResolveOrRejectValue; + using RRValue2 = Promise2::ResolveOrRejectValue; + + MozPromiseRequestHolder holder; + + AutoTaskQueue atq; + RefPtr queue = atq.Queue(); + + RunOnTaskQueue(queue, [queue, &holder]() { + Promise1::CreateAndResolve(MakeUnique(0), __func__) + ->Then(queue, __func__, + [&holder]() { + holder.Disconnect(); + return Promise2::CreateAndResolve(MakeUnique(0), + __func__); + }) + ->Then(queue, __func__, + []() { + // Shouldn't be called for we've disconnected the request. + EXPECT_FALSE(true); + }) + ->Track(holder); + }); + + Promise1::CreateAndResolve(MakeUnique(87), __func__) + ->Then( + queue, __func__, + [](UniquePtr aVal) { + EXPECT_EQ(87, *aVal); + return Promise2::CreateAndResolve(MakeUnique(94), __func__); + }, + []() { + return Promise2::CreateAndResolve(MakeUnique(95), __func__); + }) + ->Then( + queue, __func__, [](UniquePtr aVal) { EXPECT_EQ(94, *aVal); }, + []() { EXPECT_FALSE(true); }); + + Promise1::CreateAndResolve(MakeUnique(87), __func__) + ->Then(queue, __func__, + [](RRValue1&& aVal) { + EXPECT_EQ(87, *aVal.ResolveValue()); + return Promise2::CreateAndResolve(MakeUnique(94), __func__); + }) + ->Then(queue, __func__, [queue](RRValue2&& aVal) { + EXPECT_EQ(94, *aVal.ResolveValue()); + queue->BeginShutdown(); + }); +} + +TEST(MozPromise, XPCOMEventTarget) +{ + TestPromise::CreateAndResolve(42, __func__) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [](int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); }, + DO_FAIL); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, MessageLoopEventTarget) +{ + TestPromise::CreateAndResolve(42, __func__) + ->Then( + MessageLoop::current()->SerialEventTarget(), __func__, + [](int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); }, + DO_FAIL); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, ChainTo) +{ + RefPtr promise1 = TestPromise::CreateAndResolve(42, __func__); + RefPtr promise2 = new TestPromise::Private(__func__); + promise2->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { EXPECT_EQ(aResolveValue, 42); }, + DO_FAIL); + + promise1->ChainTo(promise2.forget(), __func__); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, SynchronousTaskDispatch1) +{ + bool value = false; + RefPtr promise = + new TestPromiseExcl::Private(__func__); + promise->UseSynchronousTaskDispatch(__func__); + promise->Resolve(42, __func__); + EXPECT_EQ(value, false); + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + value = true; + }, + DO_FAIL); + EXPECT_EQ(value, true); +} + +TEST(MozPromise, SynchronousTaskDispatch2) +{ + bool value = false; + RefPtr promise = + new TestPromiseExcl::Private(__func__); + promise->UseSynchronousTaskDispatch(__func__); + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + value = true; + }, + DO_FAIL); + EXPECT_EQ(value, false); + promise->Resolve(42, __func__); + EXPECT_EQ(value, true); +} + +TEST(MozPromise, DirectTaskDispatch) +{ + bool value1 = false; + bool value2 = false; + + // For direct task dispatch to be working, we must be within a + // nested event loop. So the test itself must be dispatched within + // a task. + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction("test", [&]() { + GetCurrentSerialEventTarget()->Dispatch( + NS_NewRunnableFunction("test", [&]() { + EXPECT_EQ(value1, true); + value2 = true; + })); + + RefPtr promise = new TestPromise::Private(__func__); + promise->UseDirectTaskDispatch(__func__); + promise->Resolve(42, __func__); + EXPECT_EQ(value1, false); + promise->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + EXPECT_EQ(value2, false); + value1 = true; + }, + DO_FAIL); + EXPECT_EQ(value1, false); + })); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, ChainedDirectTaskDispatch) +{ + bool value1 = false; + bool value2 = false; + + // For direct task dispatch to be working, we must be within a + // nested event loop. So the test itself must be dispatched within + // a task. + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction("test", [&]() { + GetCurrentSerialEventTarget()->Dispatch( + NS_NewRunnableFunction("test", [&]() { + EXPECT_EQ(value1, true); + value2 = true; + })); + + RefPtr promise1 = new TestPromise::Private(__func__); + promise1->UseDirectTaskDispatch(__func__); + promise1->Resolve(42, __func__); + EXPECT_EQ(value1, false); + promise1 + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> RefPtr { + EXPECT_EQ(aResolveValue, 42); + EXPECT_EQ(value2, false); + RefPtr promise2 = + new TestPromise::Private(__func__); + promise2->UseDirectTaskDispatch(__func__); + promise2->Resolve(43, __func__); + return promise2; + }, + DO_FAIL) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 43); + EXPECT_EQ(value2, false); + value1 = true; + }, + DO_FAIL); + EXPECT_EQ(value1, false); + })); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +TEST(MozPromise, ChainToDirectTaskDispatch) +{ + bool value1 = false; + bool value2 = false; + + // For direct task dispatch to be working, we must be within a + // nested event loop. So the test itself must be dispatched within + // a task. + GetCurrentSerialEventTarget()->Dispatch(NS_NewRunnableFunction("test", [&]() { + GetCurrentSerialEventTarget()->Dispatch( + NS_NewRunnableFunction("test", [&]() { + EXPECT_EQ(value1, true); + value2 = true; + })); + + RefPtr promise1 = new TestPromise::Private(__func__); + promise1->UseDirectTaskDispatch(__func__); + + RefPtr promise2 = new TestPromise::Private(__func__); + promise2->Then( + GetCurrentSerialEventTarget(), __func__, + [&](int aResolveValue) -> void { + EXPECT_EQ(aResolveValue, 42); + EXPECT_EQ(value2, false); + value1 = true; + }, + DO_FAIL); + + promise1->ChainTo(promise2.forget(), __func__); + EXPECT_EQ(value1, false); + promise1->Resolve(42, __func__); + })); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +#undef DO_FAIL diff --git a/xpcom/tests/gtest/TestMruCache.cpp b/xpcom/tests/gtest/TestMruCache.cpp new file mode 100644 index 0000000000..8cf97408d1 --- /dev/null +++ b/xpcom/tests/gtest/TestMruCache.cpp @@ -0,0 +1,395 @@ +/* -*- 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 "gtest/gtest.h" + +#include "mozilla/MruCache.h" +#include "nsString.h" + +using namespace mozilla; + +// A few MruCache implementations to use during testing. +struct IntMap : public MruCache { + static HashNumber Hash(const KeyType& aKey) { return aKey - 1; } + static bool Match(const KeyType& aKey, const ValueType& aVal) { + return aKey == aVal; + } +}; + +struct UintPtrMap : public MruCache { + static HashNumber Hash(const KeyType& aKey) { return aKey - 1; } + static bool Match(const KeyType& aKey, const ValueType& aVal) { + return aKey == (KeyType)aVal; + } +}; + +struct StringStruct { + nsCString mKey; + nsCString mOther; +}; + +struct StringStructMap + : public MruCache { + static HashNumber Hash(const KeyType& aKey) { + return *aKey.BeginReading() - 1; + } + static bool Match(const KeyType& aKey, const ValueType& aVal) { + return aKey == aVal.mKey; + } +}; + +// Helper for emulating convertable holders such as RefPtr. +template +struct Convertable { + T mItem; + operator T() const { return mItem; } +}; + +// Helper to create a StringStructMap key. +static nsCString MakeStringKey(char aKey) { + nsCString key; + key.Append(aKey); + return key; +} + +TEST(MruCache, TestNullChecker) +{ + using mozilla::detail::EmptyChecker; + + { + int test = 0; + EXPECT_TRUE(EmptyChecker::IsNotEmpty(test)); + + test = 42; + EXPECT_TRUE(EmptyChecker::IsNotEmpty(test)); + } + + { + const char* test = "abc"; + EXPECT_TRUE(EmptyChecker::IsNotEmpty(test)); + + test = nullptr; + EXPECT_FALSE(EmptyChecker::IsNotEmpty(test)); + } + + { + int foo = 42; + int* test = &foo; + EXPECT_TRUE(EmptyChecker::IsNotEmpty(test)); + + test = nullptr; + EXPECT_FALSE(EmptyChecker::IsNotEmpty(test)); + } +} + +TEST(MruCache, TestEmptyCache) +{ + { + // Test a basic empty cache. + IntMap mru; + + // Make sure the default values are set. + for (int i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Shouldn't be found. + EXPECT_FALSE(p); + } + } + + { + // Test an empty cache with pointer values. + UintPtrMap mru; + + // Make sure the default values are set. + for (uintptr_t i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Shouldn't be found. + EXPECT_FALSE(p); + } + } + + { + // Test an empty cache with more complex structure. + StringStructMap mru; + + // Make sure the default values are set. + for (char i = 1; i < 32; i++) { + const nsCString key = MakeStringKey(i); + auto p = mru.Lookup(key); + + // Shouldn't be found. + EXPECT_FALSE(p); + } + } +} + +TEST(MruCache, TestPut) +{ + IntMap mru; + + // Fill it up. + for (int i = 1; i < 32; i++) { + mru.Put(i, i); + } + + // Now check each value. + for (int i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Should be found. + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), i); + } +} + +TEST(MruCache, TestPutConvertable) +{ + UintPtrMap mru; + + // Fill it up. + for (uintptr_t i = 1; i < 32; i++) { + Convertable val{(int*)i}; + mru.Put(i, val); + } + + // Now check each value. + for (uintptr_t i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Should be found. + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), (int*)i); + } +} + +TEST(MruCache, TestOverwriting) +{ + // Test overwrting + IntMap mru; + + // 1-31 should be overwritten by 32-63 + for (int i = 1; i < 63; i++) { + mru.Put(i, i); + } + + // Look them up. + for (int i = 32; i < 63; i++) { + auto p = mru.Lookup(i); + + // Should be found. + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), i); + } +} + +TEST(MruCache, TestRemove) +{ + { + IntMap mru; + + // Fill it up. + for (int i = 1; i < 32; i++) { + mru.Put(i, i); + } + + // Now remove each value. + for (int i = 1; i < 32; i++) { + // Should be present. + auto p = mru.Lookup(i); + EXPECT_TRUE(p); + + mru.Remove(i); + + // Should no longer match. + p = mru.Lookup(i); + EXPECT_FALSE(p); + } + } + + { + UintPtrMap mru; + + // Fill it up. + for (uintptr_t i = 1; i < 32; i++) { + mru.Put(i, (int*)i); + } + + // Now remove each value. + for (uintptr_t i = 1; i < 32; i++) { + // Should be present. + auto p = mru.Lookup(i); + EXPECT_TRUE(p); + + mru.Remove(i); + + // Should no longer match. + p = mru.Lookup(i); + EXPECT_FALSE(p); + } + } + + { + StringStructMap mru; + + // Fill it up. + for (char i = 1; i < 32; i++) { + const nsCString key = MakeStringKey(i); + mru.Put(key, StringStruct{key, "foo"_ns}); + } + + // Now remove each value. + for (char i = 1; i < 32; i++) { + const nsCString key = MakeStringKey(i); + + // Should be present. + auto p = mru.Lookup(key); + EXPECT_TRUE(p); + + mru.Remove(key); + + // Should no longer match. + p = mru.Lookup(key); + EXPECT_FALSE(p); + } + } +} + +TEST(MruCache, TestClear) +{ + IntMap mru; + + // Fill it up. + for (int i = 1; i < 32; i++) { + mru.Put(i, i); + } + + // Empty it. + mru.Clear(); + + // Now check each value. + for (int i = 1; i < 32; i++) { + auto p = mru.Lookup(i); + + // Should not be found. + EXPECT_FALSE(p); + } +} + +TEST(MruCache, TestLookupMissingAndSet) +{ + IntMap mru; + + // Value not found. + auto p = mru.Lookup(1); + EXPECT_FALSE(p); + + // Set it. + p.Set(1); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 1); + + // Look it up again. + p = mru.Lookup(1); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 1); + + // Test w/ a convertable value. + p = mru.Lookup(2); + EXPECT_FALSE(p); + + // Set it. + Convertable val{2}; + p.Set(val); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 2); + + // Look it up again. + p = mru.Lookup(2); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 2); +} + +TEST(MruCache, TestLookupAndOverwrite) +{ + IntMap mru; + + // Set 1. + mru.Put(1, 1); + + // Lookup a key that maps the 1's entry. + auto p = mru.Lookup(32); + EXPECT_FALSE(p); // not a match + + // Now overwrite the entry. + p.Set(32); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 32); + + // 1 should be gone now. + p = mru.Lookup(1); + EXPECT_FALSE(p); + + // 32 should be found. + p = mru.Lookup(32); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 32); +} + +TEST(MruCache, TestLookupAndRemove) +{ + IntMap mru; + + // Set 1. + mru.Put(1, 1); + + auto p = mru.Lookup(1); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 1); + + // Now remove it. + p.Remove(); + EXPECT_FALSE(p); + + p = mru.Lookup(1); + EXPECT_FALSE(p); +} + +TEST(MruCache, TestLookupNotMatchedAndRemove) +{ + IntMap mru; + + // Set 1. + mru.Put(1, 1); + + // Lookup a key that matches 1's entry. + auto p = mru.Lookup(32); + EXPECT_FALSE(p); + + // Now attempt to remove it. + p.Remove(); + + // Make sure 1 is still there. + p = mru.Lookup(1); + EXPECT_TRUE(p); + EXPECT_EQ(p.Data(), 1); +} + +TEST(MruCache, TestLookupAndSetWithMove) +{ + StringStructMap mru; + + const nsCString key = MakeStringKey((char)1); + StringStruct val{key, "foo"_ns}; + + auto p = mru.Lookup(key); + EXPECT_FALSE(p); + p.Set(std::move(val)); + + EXPECT_TRUE(p.Data().mKey == key); + EXPECT_TRUE(p.Data().mOther == "foo"_ns); +} diff --git a/xpcom/tests/gtest/TestMultiplexInputStream.cpp b/xpcom/tests/gtest/TestMultiplexInputStream.cpp new file mode 100644 index 0000000000..7c422b068b --- /dev/null +++ b/xpcom/tests/gtest/TestMultiplexInputStream.cpp @@ -0,0 +1,958 @@ +/* -*- 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 "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/ipc/DataPipe.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsIAsyncInputStream.h" +#include "nsComponentManagerUtils.h" +#include "nsIAsyncOutputStream.h" +#include "nsIInputStream.h" +#include "nsIMultiplexInputStream.h" +#include "nsIPipe.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "nsIThread.h" +#include "Helpers.h" + +using mozilla::GetCurrentSerialEventTarget; +using mozilla::SpinEventLoopUntil; + +TEST(MultiplexInputStream, Seek_SET) +{ + nsCString buf1; + nsCString buf2; + nsCString buf3; + buf1.AssignLiteral("Hello world"); + buf2.AssignLiteral("The quick brown fox jumped over the lazy dog"); + buf3.AssignLiteral("Foo bar"); + + nsCOMPtr inputStream1; + nsCOMPtr inputStream2; + nsCOMPtr inputStream3; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream3); + ASSERT_NS_SUCCEEDED(rv); + + int64_t tell; + uint64_t length; + uint32_t count; + char readBuf[4096]; + nsCOMPtr seekStream = do_QueryInterface(multiplexStream); + ASSERT_TRUE(seekStream); + + // Seek forward in first input stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 1); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 1, + length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 1); + + // Check read is correct + rv = stream->Read(readBuf, 3, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)3, count); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 4, + length); + ASSERT_EQ(0, strncmp(readBuf, "ell", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 4); + + // Seek to start of third input stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, + buf1.Length() + buf2.Length()); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf3.Length(), length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length())); + + // Check read is correct + rv = stream->Read(readBuf, 5, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)5, count); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf3.Length() - 5, length); + ASSERT_EQ(0, strncmp(readBuf, "Foo b", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length() + 5)); + + // Seek back to start of second stream (covers bug 1272371) + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, buf1.Length()); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf2.Length() + buf3.Length(), length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length())); + + // Check read is correct + rv = stream->Read(readBuf, 6, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)6, count); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf2.Length() - 6 + buf3.Length(), length); + ASSERT_EQ(0, strncmp(readBuf, "The qu", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + 6)); +} + +TEST(MultiplexInputStream, Seek_CUR) +{ + nsCString buf1; + nsCString buf2; + nsCString buf3; + buf1.AssignLiteral("Hello world"); + buf2.AssignLiteral("The quick brown fox jumped over the lazy dog"); + buf3.AssignLiteral("Foo bar"); + + nsCOMPtr inputStream1; + nsCOMPtr inputStream2; + nsCOMPtr inputStream3; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream3); + ASSERT_NS_SUCCEEDED(rv); + + int64_t tell; + uint64_t length; + uint32_t count; + char readBuf[4096]; + nsCOMPtr seekStream = do_QueryInterface(multiplexStream); + ASSERT_TRUE(seekStream); + + // Seek forward in first input stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, 1); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 1, + length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 1); + + // Check read is correct + rv = stream->Read(readBuf, 3, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)3, count); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)buf1.Length() + buf2.Length() + buf3.Length() - 4, + length); + ASSERT_EQ(0, strncmp(readBuf, "ell", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 4); + + // Let's go to the second stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, 11); + ASSERT_NS_SUCCEEDED(rv); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 15); + rv = stream->Read(readBuf, 3, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(readBuf, "qui", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 18); + + // Let's go back to the first stream + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_CUR, -9); + ASSERT_NS_SUCCEEDED(rv); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 9); + rv = stream->Read(readBuf, 3, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(readBuf, "ldT", count)); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 12); +} + +TEST(MultiplexInputStream, Seek_END) +{ + nsCString buf1; + nsCString buf2; + nsCString buf3; + buf1.AssignLiteral("Hello world"); + buf2.AssignLiteral("The quick brown fox jumped over the lazy dog"); + buf3.AssignLiteral("Foo bar"); + + nsCOMPtr inputStream1; + nsCOMPtr inputStream2; + nsCOMPtr inputStream3; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream2), buf2); + ASSERT_NS_SUCCEEDED(rv); + rv = NS_NewCStringInputStream(getter_AddRefs(inputStream3), buf3); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_TRUE(multiplexStream); + nsCOMPtr stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(stream); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_NS_SUCCEEDED(rv); + rv = multiplexStream->AppendStream(inputStream3); + ASSERT_NS_SUCCEEDED(rv); + + int64_t tell; + uint64_t length; + nsCOMPtr seekStream = do_QueryInterface(multiplexStream); + ASSERT_TRUE(seekStream); + + // SEEK_END wants <=0 values + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, 1); + ASSERT_NS_FAILED(rv); + + // Let's go to the end. + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, 0); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)0, length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length() + buf3.Length())); + + // -1 + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, -1); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ((uint64_t)1, length); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, int64_t(buf1.Length() + buf2.Length() + buf3.Length() - 1)); + + // Almost at the beginning + tell = 1; + tell -= buf1.Length(); + tell -= buf2.Length(); + tell -= buf3.Length(); + rv = seekStream->Seek(nsISeekableStream::NS_SEEK_END, tell); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->Available(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(length, buf1.Length() + buf2.Length() + buf3.Length() - 1); + rv = seekStream->Tell(&tell); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(tell, 1); +} + +static already_AddRefed CreateStreamHelper() { + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + nsCString buf1; + buf1.AssignLiteral("Hello"); + + nsCOMPtr inputStream1 = new testing::AsyncStringStream(buf1); + multiplexStream->AppendStream(inputStream1); + + nsCString buf2; + buf2.AssignLiteral(" "); + + nsCOMPtr inputStream2 = new testing::AsyncStringStream(buf2); + multiplexStream->AppendStream(inputStream2); + + nsCString buf3; + buf3.AssignLiteral("World!"); + + nsCOMPtr inputStream3 = new testing::AsyncStringStream(buf3); + multiplexStream->AppendStream(inputStream3); + + nsCOMPtr stream(do_QueryInterface(multiplexStream)); + return stream.forget(); +} + +// AsyncWait - without EventTarget +TEST(MultiplexInputStream, AsyncWait_withoutEventTarget) +{ + nsCOMPtr is = CreateStreamHelper(); + + nsCOMPtr ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, 0, 0, nullptr)); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - with EventTarget +TEST(MultiplexInputStream, AsyncWait_withEventTarget) +{ + nsCOMPtr is = CreateStreamHelper(); + + nsCOMPtr ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr cb = new testing::InputStreamCallback(); + nsCOMPtr thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, 0, 0, thread)); + + ASSERT_FALSE(cb->Called()); + + // Eventually it is called. + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "xpcom:TEST(MultiplexInputStream, AsyncWait_withEventTarget)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - without EventTarget - closureOnly +TEST(MultiplexInputStream, AsyncWait_withoutEventTarget_closureOnly) +{ + nsCOMPtr is = CreateStreamHelper(); + + nsCOMPtr ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, + nullptr)); + ASSERT_FALSE(cb->Called()); + + ais->CloseWithStatus(NS_ERROR_FAILURE); + ASSERT_TRUE(cb->Called()); +} + +// AsyncWait - with EventTarget - closureOnly +TEST(MultiplexInputStream, AsyncWait_withEventTarget_closureOnly) +{ + nsCOMPtr is = CreateStreamHelper(); + + nsCOMPtr ais = do_QueryInterface(is); + ASSERT_TRUE(!!ais); + + RefPtr cb = new testing::InputStreamCallback(); + nsCOMPtr thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, ais->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, + thread)); + + ASSERT_FALSE(cb->Called()); + ais->CloseWithStatus(NS_ERROR_FAILURE); + ASSERT_FALSE(cb->Called()); + + // Eventually it is called. + MOZ_ALWAYS_TRUE(mozilla::SpinEventLoopUntil( + "xpcom:TEST(MultiplexInputStream, AsyncWait_withEventTarget_closureOnly)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +class ClosedStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + ClosedStream() = default; + + NS_IMETHOD + Available(uint64_t* aLength) override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + StreamStatus() override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + MOZ_CRASH("This should not be called!"); + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { return NS_OK; } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = true; + return NS_OK; + } + + private: + ~ClosedStream() = default; +}; + +NS_IMPL_ISUPPORTS(ClosedStream, nsIInputStream) + +class AsyncStream final : public nsIAsyncInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit AsyncStream(int64_t aSize) : mState(eBlocked), mSize(aSize) {} + + void Unblock() { mState = eUnblocked; } + + NS_IMETHOD + Available(uint64_t* aLength) override { + *aLength = mState == eBlocked ? 0 : mSize; + return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_OK; + } + + NS_IMETHOD + StreamStatus() override { + return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_OK; + } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + MOZ_CRASH("This should not be called!"); + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { + mState = eClosed; + return NS_OK; + } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = true; + return NS_OK; + } + + NS_IMETHOD + AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override { + MOZ_CRASH("This should not be called!"); + return NS_OK; + } + + NS_IMETHOD + CloseWithStatus(nsresult aStatus) override { return NS_OK; } + + private: + ~AsyncStream() = default; + + enum { eBlocked, eUnblocked, eClosed } mState; + + uint64_t mSize; +}; + +NS_IMPL_ISUPPORTS(AsyncStream, nsIInputStream, nsIAsyncInputStream) + +class BlockingStream final : public nsIInputStream { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + BlockingStream() = default; + + NS_IMETHOD + Available(uint64_t* aLength) override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + StreamStatus() override { return NS_BASE_STREAM_CLOSED; } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + // We are actually empty. + *aReadCount = 0; + return NS_OK; + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { return NS_OK; } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = false; + return NS_OK; + } + + private: + ~BlockingStream() = default; +}; + +NS_IMPL_ISUPPORTS(BlockingStream, nsIInputStream) + +TEST(MultiplexInputStream, Available) +{ + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + nsCOMPtr s = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!s); + + nsCOMPtr as = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!as); + + uint64_t length; + + // The stream returns NS_BASE_STREAM_CLOSED if there are no substreams. + nsresult rv = s->Available(&length); + ASSERT_EQ(NS_BASE_STREAM_CLOSED, rv); + + rv = multiplexStream->AppendStream(new ClosedStream()); + ASSERT_EQ(NS_OK, rv); + + uint64_t asyncSize = 2; + RefPtr asyncStream = new AsyncStream(2); + rv = multiplexStream->AppendStream(asyncStream); + ASSERT_EQ(NS_OK, rv); + + nsCString buffer; + buffer.Assign("World!!!"); + + nsCOMPtr stringStream; + rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), buffer); + ASSERT_EQ(NS_OK, rv); + + rv = multiplexStream->AppendStream(stringStream); + ASSERT_EQ(NS_OK, rv); + + // Now we are async. + as = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!as); + + // Available should skip the closed stream and return 0 because the + // asyncStream returns 0 and it's async. + rv = s->Available(&length); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ((uint64_t)0, length); + + asyncStream->Unblock(); + + // Now we should return only the size of the async stream because we don't + // know when this is completed. + rv = s->Available(&length); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(asyncSize, length); + + asyncStream->Close(); + + rv = s->Available(&length); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(buffer.Length(), length); +} + +class NonBufferableStringStream final : public nsIInputStream { + nsCOMPtr mStream; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit NonBufferableStringStream(const nsACString& aBuffer) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + return mStream->IsNonBlocking(aNonBlocking); + } + + private: + ~NonBufferableStringStream() = default; +}; + +NS_IMPL_ISUPPORTS(NonBufferableStringStream, nsIInputStream) + +TEST(MultiplexInputStream, Bufferable) +{ + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + nsCOMPtr s = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!s); + + nsCString buf1; + buf1.AssignLiteral("Hello "); + nsCOMPtr inputStream1; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream1), buf1); + ASSERT_NS_SUCCEEDED(rv); + + nsCString buf2; + buf2.AssignLiteral("world"); + nsCOMPtr inputStream2 = new NonBufferableStringStream(buf2); + + rv = multiplexStream->AppendStream(inputStream1); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(inputStream2); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr stream(do_QueryInterface(multiplexStream)); + ASSERT_TRUE(!!stream); + + char buf3[1024]; + uint32_t size = 0; + rv = stream->ReadSegments(NS_CopySegmentToBuffer, buf3, sizeof(buf3), &size); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_EQ(size, buf1.Length() + buf2.Length()); + ASSERT_TRUE(!strncmp(buf3, "Hello world", size)); +} + +TEST(MultiplexInputStream, QILengthInputStream) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + // nsMultiplexInputStream doesn't expose nsIInputStreamLength if there are + // no nsIInputStreamLength sub streams. + { + nsCOMPtr inputStream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream), buf); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(inputStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!fsis); + + nsCOMPtr afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!afsis); + } + + // nsMultiplexInputStream exposes nsIInputStreamLength if there is one or + // more nsIInputStreamLength sub streams. + { + RefPtr inputStream = + new testing::LengthInputStream(buf, true, false); + + nsresult rv = multiplexStream->AppendStream(inputStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!fsis); + + nsCOMPtr afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!afsis); + } + + // nsMultiplexInputStream exposes nsIAsyncInputStreamLength if there is one + // or more nsIAsyncInputStreamLength sub streams. + { + RefPtr inputStream = + new testing::LengthInputStream(buf, true, true); + + nsresult rv = multiplexStream->AppendStream(inputStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!fsis); + + nsCOMPtr afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!afsis); + } +} + +TEST(MultiplexInputStream, LengthInputStream) +{ + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + + // First stream is a a simple one. + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr inputStream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(inputStream), buf); + ASSERT_NS_SUCCEEDED(rv); + + rv = multiplexStream->AppendStream(inputStream); + ASSERT_NS_SUCCEEDED(rv); + + // A LengthInputStream, non-async. + RefPtr lengthStream = + new testing::LengthInputStream(buf, true, false); + + rv = multiplexStream->AppendStream(lengthStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr fsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!fsis); + + // Size is the sum of the 2 streams. + int64_t length; + rv = fsis->Length(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(int64_t(buf.Length() * 2), length); + + // An async LengthInputStream. + RefPtr asyncLengthStream = + new testing::LengthInputStream(buf, true, true, + NS_BASE_STREAM_WOULD_BLOCK); + + rv = multiplexStream->AppendStream(asyncLengthStream); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr afsis = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!afsis); + + // Now it would block. + rv = fsis->Length(&length); + ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv); + + // Let's read the size async. + RefPtr callback = new testing::LengthCallback(); + rv = afsis->AsyncLengthWait(callback, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(MultiplexInputStream, LengthInputStream) 1"_ns, + [&]() { return callback->Called(); })); + ASSERT_EQ(int64_t(buf.Length() * 3), callback->Size()); + + // Now a negative stream + lengthStream = new testing::LengthInputStream(buf, true, false, NS_OK, true); + + rv = multiplexStream->AppendStream(lengthStream); + ASSERT_NS_SUCCEEDED(rv); + + rv = fsis->Length(&length); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(-1, length); + + // Another async LengthInputStream. + asyncLengthStream = new testing::LengthInputStream( + buf, true, true, NS_BASE_STREAM_WOULD_BLOCK); + + rv = multiplexStream->AppendStream(asyncLengthStream); + ASSERT_NS_SUCCEEDED(rv); + + afsis = do_QueryInterface(multiplexStream); + ASSERT_TRUE(!!afsis); + + // Let's read the size async. + RefPtr callback1 = new testing::LengthCallback(); + rv = afsis->AsyncLengthWait(callback1, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + RefPtr callback2 = new testing::LengthCallback(); + rv = afsis->AsyncLengthWait(callback2, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(MultiplexInputStream, LengthInputStream) 2"_ns, + [&]() { return callback2->Called(); })); + ASSERT_FALSE(callback1->Called()); + ASSERT_TRUE(callback2->Called()); +} + +void TestMultiplexStreamReadWhileWaiting(nsIAsyncInputStream* pipeIn, + nsIAsyncOutputStream* pipeOut) { + // We had an issue where a stream which was read while a message was in-flight + // to report the stream was ready, meaning that the stream reported 0 bytes + // available when checked in the MultiplexInputStream's callback, and was + // skipped over. + + nsCOMPtr mainThread = NS_GetCurrentThread(); + + nsCOMPtr multiplexStream = + do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1"); + ASSERT_NS_SUCCEEDED(multiplexStream->AppendStream(pipeIn)); + + nsCOMPtr stringStream; + ASSERT_TRUE(NS_SUCCEEDED( + NS_NewCStringInputStream(getter_AddRefs(stringStream), "xxxx\0"_ns))); + ASSERT_NS_SUCCEEDED(multiplexStream->AppendStream(stringStream)); + + nsCOMPtr asyncMultiplex = + do_QueryInterface(multiplexStream); + ASSERT_TRUE(asyncMultiplex); + + RefPtr cb = new testing::InputStreamCallback(); + ASSERT_NS_SUCCEEDED(asyncMultiplex->AsyncWait(cb, 0, 0, mainThread)); + EXPECT_FALSE(cb->Called()); + + NS_ProcessPendingEvents(mainThread); + EXPECT_FALSE(cb->Called()); + + uint64_t available; + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 0u); + + // Write some data to the pipe, which should wake up the async wait message to + // be delivered. + char toWrite[] = "1234"; + uint32_t written; + ASSERT_NS_SUCCEEDED(pipeOut->Write(toWrite, sizeof(toWrite), &written)); + EXPECT_EQ(written, sizeof(toWrite)); + EXPECT_FALSE(cb->Called()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, sizeof(toWrite)); + + // Read that data from the stream + char toRead[sizeof(toWrite)]; + uint32_t read; + ASSERT_TRUE( + NS_SUCCEEDED(asyncMultiplex->Read(toRead, sizeof(toRead), &read))); + EXPECT_EQ(read, sizeof(toRead)); + EXPECT_STREQ(toRead, toWrite); + EXPECT_FALSE(cb->Called()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 0u); + + // The multiplex stream will have detected the read and prevented the callback + // from having been called yet. + NS_ProcessPendingEvents(mainThread); + EXPECT_FALSE(cb->Called()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 0u); + + // Write more data and close, then make sure we can read everything else in + // the stream. + char toWrite2[] = "56789"; + ASSERT_TRUE( + NS_SUCCEEDED(pipeOut->Write(toWrite2, sizeof(toWrite2), &written))); + EXPECT_EQ(written, sizeof(toWrite2)); + EXPECT_FALSE(cb->Called()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, sizeof(toWrite2)); + + ASSERT_NS_SUCCEEDED(pipeOut->Close()); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + // XXX: Theoretically if the multiplex stream could detect it, we could report + // `sizeof(toWrite2) + 4` because the stream is complete, but there's no way + // for the multiplex stream to know. + EXPECT_EQ(available, sizeof(toWrite2)); + + NS_ProcessPendingEvents(mainThread); + EXPECT_TRUE(cb->Called()); + + // Read that final bit of data and make sure we read it. + char toRead2[sizeof(toWrite2)]; + ASSERT_TRUE( + NS_SUCCEEDED(asyncMultiplex->Read(toRead2, sizeof(toRead2), &read))); + EXPECT_EQ(read, sizeof(toRead2)); + EXPECT_STREQ(toRead2, toWrite2); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 5u); + + // Read the extra data as well. + char extraRead[5]; + ASSERT_TRUE( + NS_SUCCEEDED(asyncMultiplex->Read(extraRead, sizeof(extraRead), &read))); + EXPECT_EQ(read, sizeof(extraRead)); + EXPECT_STREQ(extraRead, "xxxx"); + ASSERT_NS_SUCCEEDED(asyncMultiplex->Available(&available)); + EXPECT_EQ(available, 0u); +} + +TEST(MultiplexInputStream, ReadWhileWaiting_nsPipe) +{ + nsCOMPtr pipeIn; + nsCOMPtr pipeOut; + NS_NewPipe2(getter_AddRefs(pipeIn), getter_AddRefs(pipeOut), true, true); + TestMultiplexStreamReadWhileWaiting(pipeIn, pipeOut); +} + +TEST(MultiplexInputStream, ReadWhileWaiting_DataPipe) +{ + RefPtr pipeIn; + RefPtr pipeOut; + ASSERT_TRUE(NS_SUCCEEDED(mozilla::ipc::NewDataPipe( + mozilla::ipc::kDefaultDataPipeCapacity, getter_AddRefs(pipeOut), + getter_AddRefs(pipeIn)))); + TestMultiplexStreamReadWhileWaiting(pipeIn, pipeOut); +} diff --git a/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp b/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp new file mode 100644 index 0000000000..2294794ad2 --- /dev/null +++ b/xpcom/tests/gtest/TestNSPRLogModulesParser.cpp @@ -0,0 +1,167 @@ +/* -*- 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 "NSPRLogModulesParser.h" +#include "mozilla/ArrayUtils.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +TEST(NSPRLogModulesParser, Empty) +{ + bool callbackInvoked = false; + auto callback = [&](const char*, mozilla::LogLevel, int32_t) mutable { + callbackInvoked = true; + }; + + mozilla::NSPRLogModulesParser(nullptr, callback); + EXPECT_FALSE(callbackInvoked); + + mozilla::NSPRLogModulesParser("", callback); + EXPECT_FALSE(callbackInvoked); +} + +TEST(NSPRLogModulesParser, DefaultLevel) +{ + bool callbackInvoked = false; + auto callback = [&](const char* aName, mozilla::LogLevel aLevel, int32_t) { + EXPECT_STREQ("Foo", aName); + EXPECT_EQ(mozilla::LogLevel::Error, aLevel); + callbackInvoked = true; + }; + + mozilla::NSPRLogModulesParser("Foo", callback); + EXPECT_TRUE(callbackInvoked); + + callbackInvoked = false; + mozilla::NSPRLogModulesParser("Foo:", callback); + EXPECT_TRUE(callbackInvoked); +} + +TEST(NSPRLogModulesParser, LevelSpecified) +{ + std::pair expected[] = { + {"Foo:0", mozilla::LogLevel::Disabled}, + {"Foo:1", mozilla::LogLevel::Error}, + {"Foo:2", mozilla::LogLevel::Warning}, + {"Foo:3", mozilla::LogLevel::Info}, + {"Foo:4", mozilla::LogLevel::Debug}, + {"Foo:5", mozilla::LogLevel::Verbose}, + {"Foo:25", mozilla::LogLevel::Verbose}, // too high + {"Foo:-12", mozilla::LogLevel::Disabled} // too low + }; + + auto* currTest = expected; + + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(expected); i++) { + bool callbackInvoked = false; + mozilla::NSPRLogModulesParser( + currTest->first, + [&](const char* aName, mozilla::LogLevel aLevel, int32_t) { + EXPECT_STREQ("Foo", aName); + EXPECT_EQ(currTest->second, aLevel); + callbackInvoked = true; + }); + EXPECT_TRUE(callbackInvoked); + currTest++; + } +} + +TEST(NSPRLogModulesParser, Multiple) +{ + std::pair expected[] = { + {"timestamp", mozilla::LogLevel::Error}, + {"Foo", mozilla::LogLevel::Info}, + {"Bar", mozilla::LogLevel::Error}, + {"Baz", mozilla::LogLevel::Warning}, + {"Qux", mozilla::LogLevel::Verbose}, + }; + + const size_t kExpectedCount = MOZ_ARRAY_LENGTH(expected); + + auto* currTest = expected; + + size_t count = 0; + mozilla::NSPRLogModulesParser( + "timestamp,Foo:3, Bar,Baz:2, Qux:5", + [&](const char* aName, mozilla::LogLevel aLevel, int32_t) mutable { + ASSERT_LT(count, kExpectedCount); + EXPECT_STREQ(currTest->first, aName); + EXPECT_EQ(currTest->second, aLevel); + currTest++; + count++; + }); + + EXPECT_EQ(kExpectedCount, count); +} + +TEST(NSPRLogModulesParser, Characters) +{ + std::pair expected[] = { + {"valid.name", mozilla::LogLevel::Verbose}, + {"valid_name", mozilla::LogLevel::Debug}, + {"invalid", mozilla::LogLevel::Error}, + }; + + const size_t kExpectedCount = MOZ_ARRAY_LENGTH(expected); + + auto* currTest = expected; + + size_t count = 0; + mozilla::NSPRLogModulesParser( + "valid.name:5,valid_name:4,invalid/name:3,aborts-everything:2", + [&](const char* aName, mozilla::LogLevel aLevel, int32_t) mutable { + ASSERT_LT(count, kExpectedCount); + EXPECT_STREQ(currTest->first, aName); + EXPECT_EQ(currTest->second, aLevel); + currTest++; + count++; + }); + + EXPECT_EQ(kExpectedCount, count); +} + +TEST(NSPRLogModulesParser, RawArg) +{ + bool callbackInvoked = false; + auto callback = [&](const char* aName, mozilla::LogLevel aLevel, + int32_t aRawValue) { + EXPECT_STREQ("Foo", aName); + EXPECT_EQ(mozilla::LogLevel::Verbose, aLevel); + EXPECT_EQ(1000, aRawValue); + callbackInvoked = true; + }; + + mozilla::NSPRLogModulesParser("Foo:1000", callback); + EXPECT_TRUE(callbackInvoked); +} + +TEST(NSPRLogModulesParser, RustModules) +{ + std::pair expected[] = { + {"timestamp", mozilla::LogLevel::Error}, + {"crate::mod::file1", mozilla::LogLevel::Error}, + {"crate::mod::file2", mozilla::LogLevel::Verbose}, + {"crate::mod::*", mozilla::LogLevel::Info}, + }; + + const size_t kExpectedCount = MOZ_ARRAY_LENGTH(expected); + + auto* currTest = expected; + + size_t count = 0; + mozilla::NSPRLogModulesParser( + "timestamp,crate::mod::file1, crate::mod::file2:5, crate::mod::*:3", + [&](const char* aName, mozilla::LogLevel aLevel, int32_t) mutable { + ASSERT_LT(count, kExpectedCount); + EXPECT_STREQ(currTest->first, aName); + EXPECT_EQ(currTest->second, aLevel); + currTest++; + count++; + }); + + EXPECT_EQ(kExpectedCount, count); +} diff --git a/xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp b/xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp new file mode 100644 index 0000000000..8301adf6c8 --- /dev/null +++ b/xpcom/tests/gtest/TestNonBlockingAsyncInputStream.cpp @@ -0,0 +1,379 @@ +#include "gtest/gtest.h" + +#include "mozilla/NonBlockingAsyncInputStream.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsIAsyncInputStream.h" +#include "nsIThread.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "Helpers.h" + +using mozilla::NonBlockingAsyncInputStream; +using mozilla::SpinEventLoopUntil; + +TEST(TestNonBlockingAsyncInputStream, Simple) +{ + nsCString data; + data.Assign("Hello world!"); + + // It should not be async. + bool nonBlocking = false; + nsCOMPtr async; + + { + // Let's create a test string inputStream + nsCOMPtr stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + async = do_QueryInterface(stream); + ASSERT_EQ(nullptr, async); + + // It must be non-blocking + ASSERT_EQ(NS_OK, stream->IsNonBlocking(&nonBlocking)); + ASSERT_TRUE(nonBlocking); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // Still non-blocking + ASSERT_EQ(NS_OK, async->IsNonBlocking(&nonBlocking)); + ASSERT_TRUE(nonBlocking); + + // Testing ::Available() + uint64_t length; + ASSERT_EQ(NS_OK, async->Available(&length)); + ASSERT_EQ(data.Length(), length); + + // Read works fine. + char buffer[1024]; + uint32_t read = 0; + ASSERT_EQ(NS_OK, async->Read(buffer, sizeof(buffer), &read)); + ASSERT_EQ(data.Length(), read); + ASSERT_TRUE(data.Equals(nsCString(buffer, read))); +} + +class ReadSegmentsData { + public: + ReadSegmentsData(nsIInputStream* aStream, char* aBuffer) + : mStream(aStream), mBuffer(aBuffer) {} + + nsIInputStream* mStream; + char* mBuffer; +}; + +static nsresult ReadSegmentsFunction(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + ReadSegmentsData* data = static_cast(aClosure); + if (aInStr != data->mStream) return NS_ERROR_FAILURE; + memcpy(&data->mBuffer[aOffset], aBuffer, aCount); + *aCountWritten = aCount; + return NS_OK; +} + +TEST(TestNonBlockingAsyncInputStream, ReadSegments) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr async; + { + // Let's create a test string inputStream + nsCOMPtr stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + + // Read works fine. + char buffer[1024]; + uint32_t read = 0; + ReadSegmentsData closure(async, buffer); + ASSERT_EQ(NS_OK, async->ReadSegments(ReadSegmentsFunction, &closure, + sizeof(buffer), &read)); + ASSERT_EQ(data.Length(), read); + ASSERT_TRUE(data.Equals(nsCString(buffer, read))); +} + +TEST(TestNonBlockingAsyncInputStream, AsyncWait_Simple) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr async; + { + // Let's create a test string inputStream + nsCOMPtr stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // Testing ::Available() + uint64_t length; + ASSERT_EQ(NS_OK, async->Available(&length)); + ASSERT_EQ(data.Length(), length); + + // Testing ::AsyncWait - without EventTarget + RefPtr cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, async->AsyncWait(cb, 0, 0, nullptr)); + ASSERT_TRUE(cb->Called()); + + // Testing ::AsyncWait - with EventTarget + cb = new testing::InputStreamCallback(); + nsCOMPtr thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, async->AsyncWait(cb, 0, 0, thread)); + ASSERT_FALSE(cb->Called()); + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestNonBlockingAsyncInputStream, AsyncWait_Simple)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); + + // Read works fine. + char buffer[1024]; + uint32_t read = 0; + ASSERT_EQ(NS_OK, async->Read(buffer, sizeof(buffer), &read)); + ASSERT_EQ(data.Length(), read); + ASSERT_TRUE(data.Equals(nsCString(buffer, read))); +} + +TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withoutEventTarget) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr async; + { + // Let's create a test string inputStream + nsCOMPtr stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // Testing ::AsyncWait - no eventTarget + RefPtr cb = new testing::InputStreamCallback(); + + ASSERT_EQ(NS_OK, async->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, + 0, nullptr)); + + ASSERT_FALSE(cb->Called()); + ASSERT_EQ(NS_OK, async->Close()); + ASSERT_TRUE(cb->Called()); +} + +TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withEventTarget) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr async; + { + // Let's create a test string inputStream + nsCOMPtr stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // Testing ::AsyncWait - with EventTarget + RefPtr cb = new testing::InputStreamCallback(); + nsCOMPtr thread = do_GetCurrentThread(); + + ASSERT_EQ(NS_OK, async->AsyncWait(cb, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, + 0, thread)); + + ASSERT_FALSE(cb->Called()); + ASSERT_EQ(NS_OK, async->Close()); + ASSERT_FALSE(cb->Called()); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestNonBlockingAsyncInputStream, AsyncWait_ClosureOnly_withEventTarget)"_ns, + [&]() { return cb->Called(); })); + ASSERT_TRUE(cb->Called()); +} + +TEST(TestNonBlockingAsyncInputStream, Helper) +{ + nsCString data; + data.Assign("Hello world!"); + + nsCOMPtr async; + { + // Let's create a test string inputStream + nsCOMPtr stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + + // Here the non-blocking stream. + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + ASSERT_TRUE(!!async); + + // This should return the same object because async is already non-blocking + // and async. + nsCOMPtr result; + nsCOMPtr asyncTmp = async; + ASSERT_EQ(NS_OK, NS_MakeAsyncNonBlockingInputStream(asyncTmp.forget(), + getter_AddRefs(result))); + ASSERT_EQ(async, result); + + // This will use NonBlockingAsyncInputStream wrapper. + { + nsCOMPtr stream; + ASSERT_EQ(NS_OK, NS_NewCStringInputStream(getter_AddRefs(stream), data)); + ASSERT_EQ(NS_OK, NS_MakeAsyncNonBlockingInputStream( + stream.forget(), getter_AddRefs(result))); + } + ASSERT_TRUE(async != result); + ASSERT_TRUE(async); +} + +class QIInputStream final : public nsIInputStream, + public nsICloneableInputStream, + public nsIIPCSerializableInputStream, + public nsISeekableStream { + public: + NS_DECL_ISUPPORTS + + QIInputStream(bool aNonBlockingError, bool aCloneable, bool aIPCSerializable, + bool aSeekable) + : mNonBlockingError(aNonBlockingError), + mCloneable(aCloneable), + mIPCSerializable(aIPCSerializable), + mSeekable(aSeekable) {} + + // nsIInputStream + NS_IMETHOD Close() override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD Available(uint64_t*) override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD StreamStatus() override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD Read(char*, uint32_t, uint32_t*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD ReadSegments(nsWriteSegmentFun, void*, uint32_t, + uint32_t*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD IsNonBlocking(bool* aNonBlocking) override { + *aNonBlocking = true; + return mNonBlockingError ? NS_ERROR_FAILURE : NS_OK; + } + + // nsICloneableInputStream + NS_IMETHOD GetCloneable(bool*) override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD Clone(nsIInputStream**) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // nsIIPCSerializableInputStream + void SerializedComplexity(uint32_t, uint32_t*, uint32_t*, + uint32_t*) override {} + void Serialize(mozilla::ipc::InputStreamParams&, uint32_t, + uint32_t*) override {} + bool Deserialize(const mozilla::ipc::InputStreamParams&) override { + return false; + } + + // nsISeekableStream + NS_IMETHOD Seek(int32_t, int64_t) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD SetEOF() override { return NS_ERROR_NOT_IMPLEMENTED; } + + // nsITellableStream + NS_IMETHOD Tell(int64_t*) override { return NS_ERROR_NOT_IMPLEMENTED; } + + private: + ~QIInputStream() = default; + + bool mNonBlockingError; + bool mCloneable; + bool mIPCSerializable; + bool mSeekable; +}; + +NS_IMPL_ADDREF(QIInputStream); +NS_IMPL_RELEASE(QIInputStream); + +NS_INTERFACE_MAP_BEGIN(QIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, mCloneable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + mIPCSerializable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream, mSeekable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream, mSeekable) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +TEST(TestNonBlockingAsyncInputStream, QI) +{ + // Let's test ::Create() returning error. + + nsCOMPtr async; + { + nsCOMPtr stream = new QIInputStream(true, true, true, true); + + ASSERT_EQ(NS_ERROR_FAILURE, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + + // Let's test the QIs + for (int i = 0; i < 8; ++i) { + bool shouldBeCloneable = !!(i & 0x01); + bool shouldBeSerializable = !!(i & 0x02); + bool shouldBeSeekable = !!(i & 0x04); + + nsCOMPtr cloneable; + nsCOMPtr ipcSerializable; + nsCOMPtr seekable; + + { + nsCOMPtr stream = new QIInputStream( + false, shouldBeCloneable, shouldBeSerializable, shouldBeSeekable); + + cloneable = do_QueryInterface(stream); + ASSERT_EQ(shouldBeCloneable, !!cloneable); + + ipcSerializable = do_QueryInterface(stream); + ASSERT_EQ(shouldBeSerializable, !!ipcSerializable); + + seekable = do_QueryInterface(stream); + ASSERT_EQ(shouldBeSeekable, !!seekable); + + ASSERT_EQ(NS_OK, NonBlockingAsyncInputStream::Create( + stream.forget(), getter_AddRefs(async))); + } + + // The returned async stream should be cloneable only if the underlying + // stream is. + cloneable = do_QueryInterface(async); + ASSERT_EQ(shouldBeCloneable, !!cloneable); + + // The returned async stream should be serializable only if the underlying + // stream is. + ipcSerializable = do_QueryInterface(async); + ASSERT_EQ(shouldBeSerializable, !!ipcSerializable); + + // The returned async stream should be seekable only if the underlying + // stream is. + seekable = do_QueryInterface(async); + ASSERT_EQ(shouldBeSeekable, !!seekable); + } +} diff --git a/xpcom/tests/gtest/TestNsDeque.cpp b/xpcom/tests/gtest/TestNsDeque.cpp new file mode 100644 index 0000000000..af37246647 --- /dev/null +++ b/xpcom/tests/gtest/TestNsDeque.cpp @@ -0,0 +1,594 @@ +/* -*- 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 "gtest/gtest.h" +#include "nsDeque.h" +#include "nsCRT.h" +#include "mozilla/RefPtr.h" +#include +#include +#include +#include + +/************************************************************** + Now define the token deallocator class... + **************************************************************/ +namespace TestNsDeque { + +template +class _Dealloc : public nsDequeFunctor { + virtual void operator()(T* aObject) {} +}; + +static bool VerifyContents(const nsDeque& aDeque, const int* aContents, + size_t aLength) { + for (size_t i = 0; i < aLength; ++i) { + if (*aDeque.ObjectAt(i) != aContents[i]) { + return false; + } + } + return true; +} + +class Deallocator : public nsDequeFunctor { + virtual void operator()(int* aObject) { + if (aObject) { + // Set value to -1, to use in test function. + *(aObject) = -1; + } + } +}; + +class ForEachAdder : public nsDequeFunctor { + virtual void operator()(int* aObject) { + if (aObject) { + sum += *(int*)aObject; + } + } + + private: + int sum = 0; + + public: + int GetSum() { return sum; } +}; + +static uint32_t sFreeCount = 0; + +class RefCountedClass { + public: + RefCountedClass() : mRefCnt(0), mVal(0) {} + + ~RefCountedClass() { ++sFreeCount; } + + NS_METHOD_(MozExternalRefCountType) AddRef() { + mRefCnt++; + return mRefCnt; + } + NS_METHOD_(MozExternalRefCountType) Release() { + NS_ASSERTION(mRefCnt > 0, ""); + mRefCnt--; + if (mRefCnt == 0) { + delete this; + } + return 0; + } + + inline uint32_t GetRefCount() const { return mRefCnt; } + + inline void SetVal(int aVal) { mVal = aVal; } + + inline int GetVal() const { return mVal; } + + private: + uint32_t mRefCnt; + int mVal; +}; + +class ForEachRefPtr : public nsDequeFunctor { + virtual void operator()(RefCountedClass* aObject) { + if (aObject) { + aObject->SetVal(mVal); + } + } + + private: + int mVal = 0; + + public: + explicit ForEachRefPtr(int aVal) : mVal(aVal) {} +}; + +} // namespace TestNsDeque + +using namespace TestNsDeque; + +TEST(NsDeque, OriginalTest) +{ + const size_t size = 200; + int ints[size]; + size_t i = 0; + int temp; + nsDeque theDeque(new _Dealloc); // construct a simple one... + + // ints = [0...199] + for (i = 0; i < size; i++) { // initialize'em + ints[i] = static_cast(i); + } + // queue = [0...69] + for (i = 0; i < 70; i++) { + theDeque.Push(&ints[i]); + temp = *theDeque.Peek(); + EXPECT_EQ(static_cast(i), temp) << "Verify end after push #1"; + EXPECT_EQ(i + 1, theDeque.GetSize()) << "Verify size after push #1"; + } + + EXPECT_EQ(70u, theDeque.GetSize()) << "Verify overall size after pushes #1"; + + // queue = [0...14] + for (i = 1; i <= 55; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(70 - static_cast(i), temp) << "Verify end after pop # 1"; + EXPECT_EQ(70u - i, theDeque.GetSize()) << "Verify size after pop # 1"; + } + EXPECT_EQ(15u, theDeque.GetSize()) << "Verify overall size after pops"; + + // queue = [0...14,0...54] + for (i = 0; i < 55; i++) { + theDeque.Push(&ints[i]); + temp = *theDeque.Peek(); + EXPECT_EQ(static_cast(i), temp) << "Verify end after push #2"; + EXPECT_EQ(i + 15u + 1, theDeque.GetSize()) << "Verify size after push # 2"; + } + EXPECT_EQ(70u, theDeque.GetSize()) + << "Verify size after end of all pushes #2"; + + // queue = [0...14,0...19] + for (i = 1; i <= 35; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(55 - static_cast(i), temp) << "Verify end after pop # 2"; + EXPECT_EQ(70u - i, theDeque.GetSize()) << "Verify size after pop #2"; + } + EXPECT_EQ(35u, theDeque.GetSize()) + << "Verify overall size after end of all pops #2"; + + // queue = [0...14,0...19,0...34] + for (i = 0; i < 35; i++) { + theDeque.Push(&ints[i]); + temp = *theDeque.Peek(); + EXPECT_EQ(static_cast(i), temp) << "Verify end after push # 3"; + EXPECT_EQ(35u + 1u + i, theDeque.GetSize()) << "Verify size after push #3"; + } + + // queue = [0...14,0...19] + for (i = 0; i < 35; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(34 - static_cast(i), temp) << "Verify end after pop # 3"; + } + + // queue = [0...14] + for (i = 0; i < 20; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(19 - static_cast(i), temp) << "Verify end after pop # 4"; + } + + // queue = [] + for (i = 0; i < 15; i++) { + temp = *theDeque.Pop(); + EXPECT_EQ(14 - static_cast(i), temp) << "Verify end after pop # 5"; + } + + EXPECT_EQ(0u, theDeque.GetSize()) << "Deque should finish empty."; +} + +TEST(NsDeque, OriginalFlaw) +{ + int ints[200]; + int i = 0; + int temp; + nsDeque d(new _Dealloc); + /** + * Test 1. Origin near end, semi full, call Peek(). + * you start, mCapacity is 8 + */ + for (i = 0; i < 30; i++) ints[i] = i; + + for (i = 0; i < 6; i++) { + d.Push(&ints[i]); + temp = *d.Peek(); + EXPECT_EQ(i, temp) << "OriginalFlaw push #1"; + } + EXPECT_EQ(6u, d.GetSize()) << "OriginalFlaw size check #1"; + + for (i = 0; i < 4; i++) { + temp = *d.PopFront(); + EXPECT_EQ(i, temp) << "PopFront test"; + } + // d = [4,5] + EXPECT_EQ(2u, d.GetSize()) << "OriginalFlaw size check #2"; + + for (i = 0; i < 4; i++) { + d.Push(&ints[6 + i]); + } + + // d = [4...9] + for (i = 4; i <= 9; i++) { + temp = *d.PopFront(); + EXPECT_EQ(i, temp) << "OriginalFlaw empty check"; + } +} + +TEST(NsDeque, TestObjectAt) +{ + nsDeque d; + const int count = 10; + int ints[count]; + for (int i = 0; i < count; i++) { + ints[i] = i; + } + + for (int i = 0; i < 6; i++) { + d.Push(&ints[i]); + } + // d = [0...5] + d.PopFront(); + d.PopFront(); + + // d = [2..5] + for (size_t i = 2; i <= 5; i++) { + int t = *d.ObjectAt(i - 2); + EXPECT_EQ(static_cast(i), t) << "Verify ObjectAt()"; + } +} + +TEST(NsDeque, TestPushFront) +{ + // PushFront has some interesting corner cases, primarily we're interested in + // whether: + // - wrapping around works properly + // - growing works properly + + nsDeque d; + + const int kPoolSize = 10; + const size_t kMaxSizeBeforeGrowth = 8; + + int pool[kPoolSize]; + for (int i = 0; i < kPoolSize; i++) { + pool[i] = i; + } + + for (size_t i = 0; i < kMaxSizeBeforeGrowth; i++) { + d.PushFront(pool + i); + } + + EXPECT_EQ(kMaxSizeBeforeGrowth, d.GetSize()) << "verify size"; + + static const int t1[] = {7, 6, 5, 4, 3, 2, 1, 0}; + EXPECT_TRUE(VerifyContents(d, t1, kMaxSizeBeforeGrowth)) + << "verify pushfront 1"; + + // Now push one more so it grows + d.PushFront(pool + kMaxSizeBeforeGrowth); + EXPECT_EQ(kMaxSizeBeforeGrowth + 1, d.GetSize()) << "verify size"; + + static const int t2[] = {8, 7, 6, 5, 4, 3, 2, 1, 0}; + EXPECT_TRUE(VerifyContents(d, t2, kMaxSizeBeforeGrowth + 1)) + << "verify pushfront 2"; + + // And one more so that it wraps again + d.PushFront(pool + kMaxSizeBeforeGrowth + 1); + EXPECT_EQ(kMaxSizeBeforeGrowth + 2, d.GetSize()) << "verify size"; + + static const int t3[] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}; + EXPECT_TRUE(VerifyContents(d, t3, kMaxSizeBeforeGrowth + 2)) + << "verify pushfront 3"; +} + +template +static void CheckIfQueueEmpty(nsDeque& d) { + EXPECT_EQ(0u, d.GetSize()) << "Size should be 0"; + EXPECT_EQ(nullptr, d.Pop()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.PopFront()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.Peek()) << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.PeekFront()) + << "Invalid operation should return nullptr"; + EXPECT_EQ(nullptr, d.ObjectAt(0u)) + << "Invalid operation should return nullptr"; +} + +TEST(NsDeque, TestEmpty) +{ + // Make sure nsDeque gives sane results if it's empty. + nsDeque d; + size_t numberOfEntries = 8; + + CheckIfQueueEmpty(d); + + // Fill it up and drain it. + for (size_t i = 0; i < numberOfEntries; i++) { + d.Push((void*)0xAA); + } + + EXPECT_EQ(numberOfEntries, d.GetSize()); + + for (size_t i = 0; i < numberOfEntries; i++) { + (void)d.Pop(); + } + + // Now check it again. + CheckIfQueueEmpty(d); +} + +TEST(NsDeque, TestEraseMethod) +{ + nsDeque d; + const size_t numberOfEntries = 8; + + // Fill it up before calling Erase + for (size_t i = 0; i < numberOfEntries; i++) { + d.Push((void*)0xAA); + } + + // Call Erase + d.Erase(); + + // Now check it again. + CheckIfQueueEmpty(d); +} + +TEST(NsDeque, TestEraseShouldCallDeallocator) +{ + nsDeque d(new Deallocator()); + const size_t NumTestValues = 8; + + int* testArray[NumTestValues]; + for (size_t i = 0; i < NumTestValues; i++) { + testArray[i] = new int(); + *(testArray[i]) = i; + d.Push(testArray[i]); + } + + d.Erase(); + + // Now check it again. + CheckIfQueueEmpty(d); + + for (size_t i = 0; i < NumTestValues; i++) { + EXPECT_EQ(-1, *(testArray[i])) + << "Erase should call deallocator: " << *(testArray[i]); + } +} + +TEST(NsDeque, TestForEach) +{ + nsDeque d(new Deallocator()); + const size_t NumTestValues = 8; + int sum = 0; + + int* testArray[NumTestValues]; + for (size_t i = 0; i < NumTestValues; i++) { + testArray[i] = new int(); + *(testArray[i]) = i; + sum += i; + d.Push(testArray[i]); + } + + ForEachAdder adder; + d.ForEach(adder); + EXPECT_EQ(sum, adder.GetSum()) << "For each should iterate over values"; + + d.Erase(); +} + +TEST(NsDeque, TestConstRangeFor) +{ + nsDeque d(new Deallocator()); + + const size_t NumTestValues = 3; + for (size_t i = 0; i < NumTestValues; ++i) { + d.Push(new int(i + 1)); + } + + static_assert( + std::is_same_v::ConstDequeIterator, + decltype(std::declval&>().begin())>, + "(const nsDeque).begin() should return ConstDequeIterator"); + static_assert( + std::is_same_v::ConstDequeIterator, + decltype(std::declval&>().end())>, + "(const nsDeque).end() should return ConstDequeIterator"); + + int sum = 0; + for (int* ob : const_cast&>(d)) { + sum += *ob; + } + EXPECT_EQ(1 + 2 + 3, sum) << "Const-range-for should iterate over values"; +} + +TEST(NsDeque, TestRangeFor) +{ + const size_t NumTestValues = 3; + struct Test { + size_t runAfterLoopCount; + std::function&)> function; + int expectedSum; + const char* description; + }; + // Note: All tests start with a deque containing 3 pointers to ints 1, 2, 3. + Test tests[] = { + {0, [](nsDeque& d) {}, 1 + 2 + 3, "no changes"}, + + {1, [](nsDeque& d) { d.Pop(); }, 1 + 2, "Pop after 1st loop"}, + {2, [](nsDeque& d) { d.Pop(); }, 1 + 2, "Pop after 2nd loop"}, + {3, [](nsDeque& d) { d.Pop(); }, 1 + 2 + 3, "Pop after 3rd loop"}, + + {1, [](nsDeque& d) { d.PopFront(); }, 1 + 3, + "PopFront after 1st loop"}, + {2, [](nsDeque& d) { d.PopFront(); }, 1 + 2, + "PopFront after 2nd loop"}, + {3, [](nsDeque& d) { d.PopFront(); }, 1 + 2 + 3, + "PopFront after 3rd loop"}, + + {1, [](nsDeque& d) { d.Push(new int(4)); }, 1 + 2 + 3 + 4, + "Push after 1st loop"}, + {2, [](nsDeque& d) { d.Push(new int(4)); }, 1 + 2 + 3 + 4, + "Push after 2nd loop"}, + {3, [](nsDeque& d) { d.Push(new int(4)); }, 1 + 2 + 3 + 4, + "Push after 3rd loop"}, + {4, [](nsDeque& d) { d.Push(new int(4)); }, 1 + 2 + 3, + "Push after would-be-4th loop"}, + + {1, [](nsDeque& d) { d.PushFront(new int(4)); }, 1 + 1 + 2 + 3, + "PushFront after 1st loop"}, + {2, [](nsDeque& d) { d.PushFront(new int(4)); }, 1 + 2 + 2 + 3, + "PushFront after 2nd loop"}, + {3, [](nsDeque& d) { d.PushFront(new int(4)); }, 1 + 2 + 3 + 3, + "PushFront after 3rd loop"}, + {4, [](nsDeque& d) { d.PushFront(new int(4)); }, 1 + 2 + 3, + "PushFront after would-be-4th loop"}, + + {1, [](nsDeque& d) { d.Erase(); }, 1, "Erase after 1st loop"}, + {2, [](nsDeque& d) { d.Erase(); }, 1 + 2, "Erase after 2nd loop"}, + {3, [](nsDeque& d) { d.Erase(); }, 1 + 2 + 3, + "Erase after 3rd loop"}, + + {1, + [](nsDeque& d) { + d.Erase(); + d.Push(new int(4)); + }, + 1, "Erase after 1st loop, Push 4"}, + {1, + [](nsDeque& d) { + d.Erase(); + d.Push(new int(4)); + d.Push(new int(5)); + }, + 1 + 5, "Erase after 1st loop, Push 4,5"}, + {2, + [](nsDeque& d) { + d.Erase(); + d.Push(new int(4)); + }, + 1 + 2, "Erase after 2nd loop, Push 4"}, + {2, + [](nsDeque& d) { + d.Erase(); + d.Push(new int(4)); + d.Push(new int(5)); + }, + 1 + 2, "Erase after 2nd loop, Push 4,5"}, + {2, + [](nsDeque& d) { + d.Erase(); + d.Push(new int(4)); + d.Push(new int(5)); + d.Push(new int(6)); + }, + 1 + 2 + 6, "Erase after 2nd loop, Push 4,5,6"}}; + + for (const Test& test : tests) { + nsDeque d(new Deallocator()); + + for (size_t i = 0; i < NumTestValues; ++i) { + d.Push(new int(i + 1)); + } + + static_assert( + std::is_same_v::ConstIterator, decltype(d.begin())>, + "(non-const nsDeque).begin() should return ConstIterator"); + static_assert( + std::is_same_v::ConstIterator, decltype(d.end())>, + "(non-const nsDeque).end() should return ConstIterator"); + + int sum = 0; + size_t loopCount = 0; + for (int* ob : d) { + sum += *ob; + if (++loopCount == test.runAfterLoopCount) { + test.function(d); + } + } + EXPECT_EQ(test.expectedSum, sum) + << "Range-for should iterate over values in test '" << test.description + << "'"; + } +} + +TEST(NsDeque, RefPtrDeque) +{ + sFreeCount = 0; + nsRefPtrDeque deque; + RefPtr ptr1 = new RefCountedClass(); + EXPECT_EQ(1u, ptr1->GetRefCount()); + + deque.Push(ptr1); + EXPECT_EQ(2u, ptr1->GetRefCount()); + + { + auto* peekPtr1 = deque.Peek(); + EXPECT_TRUE(peekPtr1); + EXPECT_EQ(2u, ptr1->GetRefCount()); + EXPECT_EQ(1u, deque.GetSize()); + } + + { + RefPtr ptr2 = new RefCountedClass(); + deque.PushFront(ptr2.forget()); + EXPECT_TRUE(deque.PeekFront()); + ptr2 = deque.PopFront(); + EXPECT_EQ(ptr1, deque.PeekFront()); + } + EXPECT_EQ(1u, sFreeCount); + + { + RefPtr popPtr1 = deque.Pop(); + EXPECT_TRUE(popPtr1); + EXPECT_EQ(2u, ptr1->GetRefCount()); + EXPECT_EQ(0u, deque.GetSize()); + } + + EXPECT_EQ(1u, ptr1->GetRefCount()); + deque.Erase(); + EXPECT_EQ(0u, deque.GetSize()); + ptr1 = nullptr; + EXPECT_EQ(2u, sFreeCount); +} + +TEST(NsDeque, RefPtrDequeTestIterator) +{ + sFreeCount = 0; + nsRefPtrDeque deque; + const uint32_t cnt = 10; + for (uint32_t i = 0; i < cnt; ++i) { + RefPtr ptr = new RefCountedClass(); + deque.Push(ptr.forget()); + EXPECT_TRUE(deque.Peek()); + } + EXPECT_EQ(cnt, deque.GetSize()); + + int val = 100; + ForEachRefPtr functor(val); + deque.ForEach(functor); + + uint32_t pos = 0; + for (nsRefPtrDeque::ConstIterator it = deque.begin(); + it != deque.end(); ++it) { + RefPtr cur = *it; + EXPECT_TRUE(cur); + EXPECT_EQ(cur, deque.ObjectAt(pos++)); + EXPECT_EQ(val, cur->GetVal()); + } + + EXPECT_EQ(deque.ObjectAt(0), deque.PeekFront()); + EXPECT_EQ(deque.ObjectAt(cnt - 1), deque.Peek()); + + deque.Erase(); + + EXPECT_EQ(0u, deque.GetSize()); + EXPECT_EQ(cnt, sFreeCount); +} diff --git a/xpcom/tests/gtest/TestNsRefPtr.cpp b/xpcom/tests/gtest/TestNsRefPtr.cpp new file mode 100644 index 0000000000..cd89f2e86f --- /dev/null +++ b/xpcom/tests/gtest/TestNsRefPtr.cpp @@ -0,0 +1,444 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/RefPtr.h" +#include "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsQueryObject.h" +#include "mozilla/Unused.h" + +#include "gtest/gtest.h" + +namespace TestNsRefPtr { + +#define NS_FOO_IID \ + { \ + 0x6f7652e0, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +class Foo : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_FOO_IID) + + public: + Foo(); + // virtual dtor because Bar uses our Release() + virtual ~Foo(); + + NS_IMETHOD_(MozExternalRefCountType) AddRef() override; + NS_IMETHOD_(MozExternalRefCountType) Release() override; + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + void MemberFunction(int, int*, int&); + virtual void VirtualMemberFunction(int, int*, int&); + virtual void VirtualConstMemberFunction(int, int*, int&) const; + + void NonconstMethod() {} + void ConstMethod() const {} + + int refcount_; + + static int total_constructions_; + static int total_destructions_; + static int total_addrefs_; + static int total_queries_; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(Foo, NS_FOO_IID) + +int Foo::total_constructions_; +int Foo::total_destructions_; +int Foo::total_addrefs_; +int Foo::total_queries_; + +Foo::Foo() : refcount_(0) { ++total_constructions_; } + +Foo::~Foo() { ++total_destructions_; } + +MozExternalRefCountType Foo::AddRef() { + ++refcount_; + ++total_addrefs_; + return refcount_; +} + +MozExternalRefCountType Foo::Release() { + int newcount = --refcount_; + if (newcount == 0) { + delete this; + } + + return newcount; +} + +nsresult Foo::QueryInterface(const nsIID& aIID, void** aResult) { + ++total_queries_; + + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(Foo))) + rawPtr = this; + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +void Foo::MemberFunction(int aArg1, int* aArgPtr, int& aArgRef) {} + +void Foo::VirtualMemberFunction(int aArg1, int* aArgPtr, int& aArgRef) {} + +void Foo::VirtualConstMemberFunction(int aArg1, int* aArgPtr, + int& aArgRef) const {} + +static nsresult CreateFoo(void** result) +// a typical factory function (that calls AddRef) +{ + auto* foop = new Foo; + + foop->AddRef(); + *result = foop; + + return NS_OK; +} + +static void set_a_Foo(RefPtr* result) { + assert(result); + + RefPtr foop(do_QueryObject(new Foo)); + *result = foop; +} + +static RefPtr return_a_Foo() { + RefPtr foop(do_QueryObject(new Foo)); + return foop; +} + +#define NS_BAR_IID \ + { \ + 0x6f7652e1, 0xee43, 0x11d1, { \ + 0x9c, 0xc3, 0x00, 0x60, 0x08, 0x8c, 0xa6, 0xb3 \ + } \ + } + +class Bar : public Foo { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_BAR_IID) + + public: + Bar(); + ~Bar() override; + + NS_IMETHOD QueryInterface(const nsIID&, void**) override; + + void VirtualMemberFunction(int, int*, int&) override; + void VirtualConstMemberFunction(int, int*, int&) const override; + + static int total_constructions_; + static int total_destructions_; + static int total_queries_; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(Bar, NS_BAR_IID) + +int Bar::total_constructions_; +int Bar::total_destructions_; +int Bar::total_queries_; + +Bar::Bar() { ++total_constructions_; } + +Bar::~Bar() { ++total_destructions_; } + +nsresult Bar::QueryInterface(const nsID& aIID, void** aResult) { + ++total_queries_; + + nsISupports* rawPtr = 0; + nsresult status = NS_OK; + + if (aIID.Equals(NS_GET_IID(Bar))) + rawPtr = this; + else if (aIID.Equals(NS_GET_IID(Foo))) + rawPtr = static_cast(this); + else { + nsID iid_of_ISupports = NS_ISUPPORTS_IID; + if (aIID.Equals(iid_of_ISupports)) + rawPtr = static_cast(this); + else + status = NS_ERROR_NO_INTERFACE; + } + + NS_IF_ADDREF(rawPtr); + *aResult = rawPtr; + + return status; +} + +void Bar::VirtualMemberFunction(int aArg1, int* aArgPtr, int& aArgRef) {} +void Bar::VirtualConstMemberFunction(int aArg1, int* aArgPtr, + int& aArgRef) const {} + +} // namespace TestNsRefPtr + +using namespace TestNsRefPtr; + +TEST(nsRefPtr, AddRefAndRelease) +{ + Foo::total_constructions_ = 0; + Foo::total_destructions_ = 0; + + { + RefPtr foop(do_QueryObject(new Foo)); + ASSERT_EQ(Foo::total_constructions_, 1); + ASSERT_EQ(Foo::total_destructions_, 0); + ASSERT_EQ(foop->refcount_, 1); + + foop = do_QueryObject(new Foo); + ASSERT_EQ(Foo::total_constructions_, 2); + ASSERT_EQ(Foo::total_destructions_, 1); + + // [Shouldn't compile] Is it a compile time error to try to |AddRef| by + // hand? + // foop->AddRef(); + + // [Shouldn't compile] Is it a compile time error to try to |Release| be + // hand? + // foop->Release(); + + // [Shouldn't compile] Is it a compile time error to try to |delete| an + // |nsCOMPtr|? + // delete foop; + + static_cast(foop)->AddRef(); + ASSERT_EQ(foop->refcount_, 2); + + static_cast(foop)->Release(); + ASSERT_EQ(foop->refcount_, 1); + } + + ASSERT_EQ(Foo::total_destructions_, 2); + + { + RefPtr fooP(do_QueryObject(new Foo)); + ASSERT_EQ(Foo::total_constructions_, 3); + ASSERT_EQ(Foo::total_destructions_, 2); + ASSERT_EQ(fooP->refcount_, 1); + + Foo::total_addrefs_ = 0; + RefPtr fooP2 = std::move(fooP); + mozilla::Unused << fooP2; + ASSERT_EQ(Foo::total_addrefs_, 0); + } +} + +TEST(nsRefPtr, VirtualDestructor) +{ + Bar::total_destructions_ = 0; + + { + RefPtr foop(do_QueryObject(new Bar)); + mozilla::Unused << foop; + } + + ASSERT_EQ(Bar::total_destructions_, 1); +} + +TEST(nsRefPtr, Equality) +{ + Foo::total_constructions_ = 0; + Foo::total_destructions_ = 0; + + { + RefPtr foo1p(do_QueryObject(new Foo)); + RefPtr foo2p(do_QueryObject(new Foo)); + + ASSERT_EQ(Foo::total_constructions_, 2); + ASSERT_EQ(Foo::total_destructions_, 0); + + ASSERT_NE(foo1p, foo2p); + + ASSERT_NE(foo1p, nullptr); + ASSERT_NE(nullptr, foo1p); + ASSERT_FALSE(foo1p == nullptr); + ASSERT_FALSE(nullptr == foo1p); + + ASSERT_NE(foo1p, foo2p.get()); + + foo1p = foo2p; + + ASSERT_EQ(Foo::total_constructions_, 2); + ASSERT_EQ(Foo::total_destructions_, 1); + ASSERT_EQ(foo1p, foo2p); + + ASSERT_EQ(foo2p, foo2p.get()); + + ASSERT_EQ(RefPtr(foo2p.get()), foo2p); + + ASSERT_TRUE(foo1p); + } + + ASSERT_EQ(Foo::total_constructions_, 2); + ASSERT_EQ(Foo::total_destructions_, 2); +} + +TEST(nsRefPtr, AddRefHelpers) +{ + Foo::total_addrefs_ = 0; + + { + auto* raw_foo1p = new Foo; + raw_foo1p->AddRef(); + + auto* raw_foo2p = new Foo; + raw_foo2p->AddRef(); + + ASSERT_EQ(Foo::total_addrefs_, 2); + + RefPtr foo1p(dont_AddRef(raw_foo1p)); + + ASSERT_EQ(Foo::total_addrefs_, 2); + + RefPtr foo2p; + foo2p = dont_AddRef(raw_foo2p); + + ASSERT_EQ(Foo::total_addrefs_, 2); + } + + { + // Test that various assignment helpers compile. + RefPtr foop; + CreateFoo(RefPtrGetterAddRefs(foop)); + CreateFoo(getter_AddRefs(foop)); + set_a_Foo(address_of(foop)); + foop = return_a_Foo(); + } +} + +TEST(nsRefPtr, QueryInterface) +{ + Foo::total_queries_ = 0; + Bar::total_queries_ = 0; + + { + RefPtr fooP; + fooP = do_QueryObject(new Foo); + ASSERT_EQ(Foo::total_queries_, 1); + } + + { + RefPtr fooP; + fooP = do_QueryObject(new Foo); + ASSERT_EQ(Foo::total_queries_, 2); + + RefPtr foo2P; + foo2P = fooP; + ASSERT_EQ(Foo::total_queries_, 2); + } + + { + RefPtr barP(do_QueryObject(new Bar)); + ASSERT_EQ(Bar::total_queries_, 1); + + RefPtr fooP(do_QueryObject(barP)); + ASSERT_TRUE(fooP); + ASSERT_EQ(Foo::total_queries_, 2); + ASSERT_EQ(Bar::total_queries_, 2); + } +} + +// ------------------------------------------------------------------------- +// TODO(ER): The following tests should be moved to MFBT. + +#define NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING(_class) \ + public: \ + NS_METHOD_(MozExternalRefCountType) AddRef(void) const { \ + MOZ_ASSERT_TYPE_OK_FOR_REFCOUNTING(_class) \ + MOZ_RELEASE_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); \ + nsrefcnt count = ++mRefCnt; \ + return (nsrefcnt)count; \ + } \ + NS_METHOD_(MozExternalRefCountType) Release(void) const { \ + MOZ_RELEASE_ASSERT(int32_t(mRefCnt) > 0, "dup release"); \ + nsrefcnt count = --mRefCnt; \ + if (count == 0) { \ + delete (this); \ + return 0; \ + } \ + return count; \ + } \ + \ + protected: \ + mutable ::mozilla::ThreadSafeAutoRefCnt mRefCnt; \ + \ + public: + +class ObjectForConstPtr { + private: + // Reference-counted classes cannot have public destructors. + ~ObjectForConstPtr() = default; + + public: + NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING(ObjectForConstPtr) + void ConstMemberFunction(int aArg1, int* aArgPtr, int& aArgRef) const {} +}; +#undef NS_INLINE_DECL_THREADSAFE_MUTABLE_REFCOUNTING + +namespace TestNsRefPtr { +static void AnFooPtrPtrContext(Foo**) {} +static void AVoidPtrPtrContext(void**) {} +} // namespace TestNsRefPtr + +TEST(nsRefPtr, RefPtrCompilationTests) +{ + { + RefPtr fooP; + + AnFooPtrPtrContext(getter_AddRefs(fooP)); + AVoidPtrPtrContext(getter_AddRefs(fooP)); + } + + { + RefPtr fooP(new Foo); + RefPtr constFooP = fooP; + constFooP->ConstMethod(); + + // [Shouldn't compile] Is it a compile time error to call a non-const method + // on an |RefPtr|? + // constFooP->NonconstMethod(); + + // [Shouldn't compile] Is it a compile time error to construct an |RefPtr + // from an |RefPtr|? + // RefPtr otherFooP(constFooP); + } + + { + RefPtr foop = new Foo; + RefPtr foop2 = new Bar; + RefPtr foop3 = new ObjectForConstPtr; + int test = 1; + void (Foo::*fPtr)(int, int*, int&) = &Foo::MemberFunction; + void (Foo::*fVPtr)(int, int*, int&) = &Foo::VirtualMemberFunction; + void (Foo::*fVCPtr)(int, int*, int&) const = + &Foo::VirtualConstMemberFunction; + void (ObjectForConstPtr::*fCPtr2)(int, int*, int&) const = + &ObjectForConstPtr::ConstMemberFunction; + + (foop->*fPtr)(test, &test, test); + (foop2->*fVPtr)(test, &test, test); + (foop2->*fVCPtr)(test, &test, test); + (foop3->*fCPtr2)(test, &test, test); + } + + // Looks like everything ran. + ASSERT_TRUE(true); +} diff --git a/xpcom/tests/gtest/TestObserverArray.cpp b/xpcom/tests/gtest/TestObserverArray.cpp new file mode 100644 index 0000000000..8a4744d06b --- /dev/null +++ b/xpcom/tests/gtest/TestObserverArray.cpp @@ -0,0 +1,573 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:ts=4:et:sw=4: +/* 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 "nsTObserverArray.h" +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/UniquePtr.h" + +using namespace mozilla; + +typedef nsTObserverArray IntArray; + +#define DO_TEST(_type, _exp, _code) \ + do { \ + ++testNum; \ + count = 0; \ + IntArray::_type iter(arr); \ + while (iter.HasMore() && count != ArrayLength(_exp)) { \ + _code int next = iter.GetNext(); \ + int expected = _exp[count++]; \ + ASSERT_EQ(next, expected) \ + << "During test " << testNum << " at position " << count - 1; \ + } \ + ASSERT_FALSE(iter.HasMore()) \ + << "During test " << testNum << ", iterator ran over"; \ + ASSERT_EQ(count, ArrayLength(_exp)) \ + << "During test " << testNum << ", iterator finished too early"; \ + } while (0) + +// XXX Split this up into independent test cases +TEST(ObserverArray, Tests) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t count; + int testNum = 0; + + // Basic sanity + static int test1Expected[] = {3, 4}; + DO_TEST(ForwardIterator, test1Expected, {/* nothing */}); + + // Appends + static int test2Expected[] = {3, 4, 2}; + DO_TEST(ForwardIterator, test2Expected, + if (count == 1) arr.AppendElement(2);); + DO_TEST(ForwardIterator, test2Expected, {/* nothing */}); + + DO_TEST(EndLimitedIterator, test2Expected, + if (count == 1) arr.AppendElement(5);); + + static int test5Expected[] = {3, 4, 2, 5}; + DO_TEST(ForwardIterator, test5Expected, {/* nothing */}); + + // Removals + DO_TEST(ForwardIterator, test5Expected, + if (count == 1) arr.RemoveElementAt(0);); + + static int test7Expected[] = {4, 2, 5}; + DO_TEST(ForwardIterator, test7Expected, {/* nothing */}); + + static int test8Expected[] = {4, 5}; + DO_TEST(ForwardIterator, test8Expected, + if (count == 1) arr.RemoveElementAt(1);); + DO_TEST(ForwardIterator, test8Expected, {/* nothing */}); + + arr.AppendElement(2); + arr.AppendElementUnlessExists(6); + static int test10Expected[] = {4, 5, 2, 6}; + DO_TEST(ForwardIterator, test10Expected, {/* nothing */}); + + arr.AppendElementUnlessExists(5); + DO_TEST(ForwardIterator, test10Expected, {/* nothing */}); + + static int test12Expected[] = {4, 5, 6}; + DO_TEST(ForwardIterator, test12Expected, + if (count == 1) arr.RemoveElementAt(2);); + DO_TEST(ForwardIterator, test12Expected, {/* nothing */}); + + // Removals + Appends + static int test14Expected[] = {4, 6, 7}; + DO_TEST( + ForwardIterator, test14Expected, if (count == 1) { + arr.RemoveElementAt(1); + arr.AppendElement(7); + }); + DO_TEST(ForwardIterator, test14Expected, {/* nothing */}); + + arr.AppendElement(2); + static int test16Expected[] = {4, 6, 7, 2}; + DO_TEST(ForwardIterator, test16Expected, {/* nothing */}); + + static int test17Expected[] = {4, 7, 2}; + DO_TEST( + EndLimitedIterator, test17Expected, if (count == 1) { + arr.RemoveElementAt(1); + arr.AppendElement(8); + }); + + static int test18Expected[] = {4, 7, 2, 8}; + DO_TEST(ForwardIterator, test18Expected, {/* nothing */}); + + // Prepends + arr.PrependElementUnlessExists(3); + static int test19Expected[] = {3, 4, 7, 2, 8}; + DO_TEST(ForwardIterator, test19Expected, {/* nothing */}); + + arr.PrependElementUnlessExists(7); + DO_TEST(ForwardIterator, test19Expected, {/* nothing */}); + + DO_TEST( + ForwardIterator, test19Expected, + if (count == 1) { arr.PrependElementUnlessExists(9); }); + + static int test22Expected[] = {9, 3, 4, 7, 2, 8}; + DO_TEST(ForwardIterator, test22Expected, {}); + + // BackwardIterator + static int test23Expected[] = {8, 2, 7, 4, 3, 9}; + DO_TEST(BackwardIterator, test23Expected, ); + + // Removals + static int test24Expected[] = {8, 2, 7, 4, 9}; + DO_TEST(BackwardIterator, test24Expected, + if (count == 1) arr.RemoveElementAt(1);); + + // Appends + DO_TEST(BackwardIterator, test24Expected, + if (count == 1) arr.AppendElement(1);); + + static int test26Expected[] = {1, 8, 2, 7, 4, 9}; + DO_TEST(BackwardIterator, test26Expected, ); + + // Prepends + static int test27Expected[] = {1, 8, 2, 7, 4, 9, 3}; + DO_TEST(BackwardIterator, test27Expected, + if (count == 1) arr.PrependElementUnlessExists(3);); + + // Removal using Iterator + DO_TEST(BackwardIterator, test27Expected, + // when this code runs, |GetNext()| has only been called once, so + // this actually removes the very first element + if (count == 1) iter.Remove();); + + static int test28Expected[] = {8, 2, 7, 4, 9, 3}; + DO_TEST(BackwardIterator, test28Expected, ); + + /** + * Note: _code is executed before the call to GetNext(), it can therefore not + * test the case of prepending when the BackwardIterator already returned the + * first element. + * In that case BackwardIterator does not traverse the newly prepended Element + */ +} + +TEST(ObserverArray, ForwardIterator_Remove) +{ + static const int expected[] = {3, 4}; + + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t count = 0; + for (auto iter = IntArray::ForwardIterator{arr}; iter.HasMore();) { + const int next = iter.GetNext(); + iter.Remove(); + + ASSERT_EQ(expected[count++], next); + } + ASSERT_EQ(2u, count); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_NonEmpty) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.ForwardRange()) { + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_RemoveCurrent) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.ForwardRange()) { + sum += element; + ++iterations; + arr.RemoveElementAt(0); + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_Append) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.ForwardRange()) { + if (!iterations) { + arr.AppendElement(5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(3u, iterations); + EXPECT_EQ(12, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_Prepend) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.ForwardRange()) { + if (!iterations) { + arr.InsertElementAt(0, 5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Forward_Empty) +{ + IntArray arr; + + size_t iterations = 0; + for (int element : arr.ForwardRange()) { + (void)element; + ++iterations; + } + + EXPECT_EQ(0u, iterations); +} + +TEST(ObserverArray, RangeBasedFor_Reference_Forward_NonEmpty) +{ + const auto arr = [] { + nsTObserverArray> arr; + arr.AppendElement(MakeUnique(3)); + arr.AppendElement(MakeUnique(4)); + return arr; + }(); + + size_t iterations = 0; + int sum = 0; + for (const UniquePtr& element : arr.ForwardRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_NonConstReference_Forward_NonEmpty) +{ + nsTObserverArray> arr; + arr.AppendElement(MakeUnique(3)); + arr.AppendElement(MakeUnique(4)); + + size_t iterations = 0; + int sum = 0; + for (UniquePtr& element : arr.ForwardRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_NonEmpty) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.BackwardRange()) { + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_RemoveCurrent) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.BackwardRange()) { + sum += element; + ++iterations; + arr.RemoveElementAt(arr.Length() - 1); + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_Append) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.BackwardRange()) { + if (!iterations) { + arr.AppendElement(5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_Prepend) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.BackwardRange()) { + if (!iterations) { + arr.InsertElementAt(0, 5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(3u, iterations); + EXPECT_EQ(12, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_Backward_Empty) +{ + IntArray arr; + + size_t iterations = 0; + for (int element : arr.BackwardRange()) { + (void)element; + ++iterations; + } + + EXPECT_EQ(0u, iterations); +} + +TEST(ObserverArray, RangeBasedFor_Reference_Backward_NonEmpty) +{ + const auto arr = [] { + nsTObserverArray> arr; + arr.AppendElement(MakeUnique(3)); + arr.AppendElement(MakeUnique(4)); + return arr; + }(); + + size_t iterations = 0; + int sum = 0; + for (const UniquePtr& element : arr.BackwardRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_NonConstReference_Backward_NonEmpty) +{ + nsTObserverArray> arr; + arr.AppendElement(MakeUnique(3)); + arr.AppendElement(MakeUnique(4)); + + size_t iterations = 0; + int sum = 0; + for (UniquePtr& element : arr.BackwardRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_NonEmpty) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.EndLimitedRange()) { + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_RemoveCurrent) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.EndLimitedRange()) { + sum += element; + ++iterations; + arr.RemoveElementAt(0); + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_Append) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.EndLimitedRange()) { + if (!iterations) { + arr.AppendElement(5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_Prepend) +{ + IntArray arr; + arr.AppendElement(3); + arr.AppendElement(4); + + size_t iterations = 0; + int sum = 0; + for (int element : arr.EndLimitedRange()) { + if (!iterations) { + arr.InsertElementAt(0, 5); + } + sum += element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Value_EndLimited_Empty) +{ + IntArray arr; + + size_t iterations = 0; + for (int element : arr.EndLimitedRange()) { + (void)element; + ++iterations; + } + + EXPECT_EQ(0u, iterations); +} + +TEST(ObserverArray, RangeBasedFor_Reference_EndLimited_NonEmpty) +{ + const auto arr = [] { + nsTObserverArray> arr; + arr.AppendElement(MakeUnique(3)); + arr.AppendElement(MakeUnique(4)); + return arr; + }(); + + size_t iterations = 0; + int sum = 0; + for (const UniquePtr& element : arr.EndLimitedRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_NonConstReference_EndLimited_NonEmpty) +{ + nsTObserverArray> arr; + arr.AppendElement(MakeUnique(3)); + arr.AppendElement(MakeUnique(4)); + + size_t iterations = 0; + int sum = 0; + for (UniquePtr& element : arr.EndLimitedRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +TEST(ObserverArray, RangeBasedFor_Reference_NonObserving_NonEmpty) +{ + const auto arr = [] { + nsTObserverArray> arr; + arr.AppendElement(MakeUnique(3)); + arr.AppendElement(MakeUnique(4)); + return arr; + }(); + + size_t iterations = 0; + int sum = 0; + for (const UniquePtr& element : arr.NonObservingRange()) { + sum += *element; + ++iterations; + } + + EXPECT_EQ(2u, iterations); + EXPECT_EQ(7, sum); +} + +// TODO add tests for EndLimitedIterator diff --git a/xpcom/tests/gtest/TestObserverService.cpp b/xpcom/tests/gtest/TestObserverService.cpp new file mode 100644 index 0000000000..4126815f1f --- /dev/null +++ b/xpcom/tests/gtest/TestObserverService.cpp @@ -0,0 +1,281 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.h" +#include "nsIObserverService.h" +#include "nsIObserver.h" +#include "nsISimpleEnumerator.h" +#include "nsComponentManagerUtils.h" + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsWeakReference.h" + +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/RefPtr.h" + +#include "gtest/gtest.h" + +static void testResult(nsresult rv) { + EXPECT_TRUE(NS_SUCCEEDED(rv)) << "0x" << std::hex << (int)rv; +} + +class TestObserver final : public nsIObserver, public nsSupportsWeakReference { + public: + explicit TestObserver(const nsAString& name) + : mName(name), mObservations(0) {} + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + nsString mName; + int mObservations; + static int sTotalObservations; + + nsString mExpectedData; + + private: + ~TestObserver() = default; +}; + +NS_IMPL_ISUPPORTS(TestObserver, nsIObserver, nsISupportsWeakReference) + +int TestObserver::sTotalObservations; + +NS_IMETHODIMP +TestObserver::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* someData) { + mObservations++; + sTotalObservations++; + + if (!mExpectedData.IsEmpty()) { + EXPECT_TRUE(mExpectedData.Equals(someData)); + } + + return NS_OK; +} + +static nsISupports* ToSupports(TestObserver* aObs) { + return static_cast(aObs); +} + +static void TestExpectedCount(nsIObserverService* svc, const char* topic, + size_t expected) { + nsCOMPtr e; + nsresult rv = svc->EnumerateObservers(topic, getter_AddRefs(e)); + testResult(rv); + EXPECT_TRUE(e); + + bool hasMore = false; + rv = e->HasMoreElements(&hasMore); + testResult(rv); + + if (expected == 0) { + EXPECT_FALSE(hasMore); + return; + } + + size_t count = 0; + while (hasMore) { + count++; + + // Grab the element. + nsCOMPtr supports; + e->GetNext(getter_AddRefs(supports)); + ASSERT_TRUE(supports); + + // Move on. + rv = e->HasMoreElements(&hasMore); + testResult(rv); + } + + EXPECT_EQ(count, expected); +} + +TEST(ObserverService, Creation) +{ + nsresult rv; + nsCOMPtr svc = + do_CreateInstance("@mozilla.org/observer-service;1", &rv); + + ASSERT_EQ(rv, NS_OK); + ASSERT_TRUE(svc); +} + +TEST(ObserverService, AddObserver) +{ + nsCOMPtr svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + // Add a strong ref. + RefPtr a = new TestObserver(u"A"_ns); + nsresult rv = svc->AddObserver(a, "Foo", false); + testResult(rv); + + // Add a few weak ref. + RefPtr b = new TestObserver(u"B"_ns); + rv = svc->AddObserver(b, "Bar", true); + testResult(rv); +} + +TEST(ObserverService, RemoveObserver) +{ + nsCOMPtr svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + RefPtr a = new TestObserver(u"A"_ns); + RefPtr b = new TestObserver(u"B"_ns); + RefPtr c = new TestObserver(u"C"_ns); + + svc->AddObserver(a, "Foo", false); + svc->AddObserver(b, "Foo", true); + + // Remove from non-existent topic. + nsresult rv = svc->RemoveObserver(a, "Bar"); + ASSERT_NS_FAILED(rv); + + // Remove a. + testResult(svc->RemoveObserver(a, "Foo")); + + // Remove b. + testResult(svc->RemoveObserver(b, "Foo")); + + // Attempt to remove c. + rv = svc->RemoveObserver(c, "Foo"); + ASSERT_NS_FAILED(rv); +} + +TEST(ObserverService, EnumerateEmpty) +{ + nsCOMPtr svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + // Try with no observers. + TestExpectedCount(svc, "A", 0); + + // Now add an observer and enumerate an unobserved topic. + RefPtr a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Foo", false)); + + TestExpectedCount(svc, "A", 0); +} + +TEST(ObserverService, Enumerate) +{ + nsCOMPtr svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + const size_t kFooCount = 10; + for (size_t i = 0; i < kFooCount; i++) { + RefPtr a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Foo", false)); + } + + const size_t kBarCount = kFooCount / 2; + for (size_t i = 0; i < kBarCount; i++) { + RefPtr a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Bar", false)); + } + + // Enumerate "Foo". + TestExpectedCount(svc, "Foo", kFooCount); + + // Enumerate "Bar". + TestExpectedCount(svc, "Bar", kBarCount); +} + +TEST(ObserverService, EnumerateWeakRefs) +{ + nsCOMPtr svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + const size_t kFooCount = 10; + for (size_t i = 0; i < kFooCount; i++) { + RefPtr a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Foo", true)); + } + + // All refs are out of scope, expect enumeration to be empty. + TestExpectedCount(svc, "Foo", 0); + + // Now test a mixture. + for (size_t i = 0; i < kFooCount; i++) { + RefPtr a = new TestObserver(u"A"_ns); + RefPtr b = new TestObserver(u"B"_ns); + + // Register a as weak for "Foo". + testResult(svc->AddObserver(a, "Foo", true)); + + // Register b as strong for "Foo". + testResult(svc->AddObserver(b, "Foo", false)); + } + + // Expect the b instances to stick around. + TestExpectedCount(svc, "Foo", kFooCount); + + // Now add a couple weak refs, but don't go out of scope. + RefPtr a = new TestObserver(u"A"_ns); + testResult(svc->AddObserver(a, "Foo", true)); + RefPtr b = new TestObserver(u"B"_ns); + testResult(svc->AddObserver(b, "Foo", true)); + + // Expect all the observers from before and the two new ones. + TestExpectedCount(svc, "Foo", kFooCount + 2); +} + +TEST(ObserverService, TestNotify) +{ + nsCString topicA; + topicA.Assign("topic-A"); + nsCString topicB; + topicB.Assign("topic-B"); + + nsCOMPtr svc = + do_CreateInstance("@mozilla.org/observer-service;1"); + + RefPtr aObserver = new TestObserver(u"Observer-A"_ns); + RefPtr bObserver = new TestObserver(u"Observer-B"_ns); + + // Add two observers for topicA. + testResult(svc->AddObserver(aObserver, topicA.get(), false)); + testResult(svc->AddObserver(bObserver, topicA.get(), false)); + + // Add one observer for topicB. + testResult(svc->AddObserver(bObserver, topicB.get(), false)); + + // Notify topicA. + const char16_t* dataA = u"Testing Notify(observer-A, topic-A)"; + aObserver->mExpectedData = dataA; + bObserver->mExpectedData = dataA; + nsresult rv = + svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 1); + + // Notify topicB. + const char16_t* dataB = u"Testing Notify(observer-B, topic-B)"; + bObserver->mExpectedData = dataB; + rv = svc->NotifyObservers(ToSupports(bObserver), topicB.get(), dataB); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 2); + + // Remove one of the topicA observers, make sure it's not notified. + testResult(svc->RemoveObserver(aObserver, topicA.get())); + + // Notify topicA, only bObserver is expected to be notified. + bObserver->mExpectedData = dataA; + rv = svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 3); + + // Remove the other topicA observer, make sure none are notified. + testResult(svc->RemoveObserver(bObserver, topicA.get())); + rv = svc->NotifyObservers(ToSupports(aObserver), topicA.get(), dataA); + testResult(rv); + ASSERT_EQ(aObserver->mObservations, 1); + ASSERT_EQ(bObserver->mObservations, 3); +} diff --git a/xpcom/tests/gtest/TestOwningNonNull.cpp b/xpcom/tests/gtest/TestOwningNonNull.cpp new file mode 100644 index 0000000000..5f82c7b37b --- /dev/null +++ b/xpcom/tests/gtest/TestOwningNonNull.cpp @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/OwningNonNull.h" +#include "mozilla/RefCounted.h" +#include "mozilla/RefPtr.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +struct OwnedRefCounted : public RefCounted { + MOZ_DECLARE_REFCOUNTED_TYPENAME(OwnedRefCounted) + + OwnedRefCounted() = default; +}; + +TEST(OwningNonNull, Move) +{ + auto refptr = MakeRefPtr(); + OwningNonNull owning(std::move(refptr)); + EXPECT_FALSE(!!refptr); +} diff --git a/xpcom/tests/gtest/TestPLDHash.cpp b/xpcom/tests/gtest/TestPLDHash.cpp new file mode 100644 index 0000000000..d302e72595 --- /dev/null +++ b/xpcom/tests/gtest/TestPLDHash.cpp @@ -0,0 +1,407 @@ +/* -*- 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 "PLDHashTable.h" +#include "gtest/gtest.h" +#include "mozilla/gtest/MozHelpers.h" + +// This test mostly focuses on edge cases. But more coverage of normal +// operations wouldn't be a bad thing. + +#ifdef XP_UNIX +# include +# include +# include +#endif + +// We can test that certain operations cause expected aborts by forking +// and then checking that the child aborted in the expected way (i.e. via +// MOZ_CRASH). We skip this for the following configurations. +// - On Windows, because it doesn't have fork(). +// - On non-DEBUG builds, because the crashes cause the crash reporter to pop +// up when running this test locally, which is surprising and annoying. +// - On ASAN builds, because ASAN alters the way a MOZ_CRASHing process +// terminates, which makes it harder to test if the right thing has occurred. +static void TestCrashyOperation(const char* label, void (*aCrashyOperation)()) { +#if defined(XP_UNIX) && defined(DEBUG) && !defined(MOZ_ASAN) + // We're about to trigger a crash. When it happens don't pause to allow GDB + // to be attached. + SAVE_GDB_SLEEP_LOCAL(); + + int pid = fork(); + ASSERT_NE(pid, -1); + + if (pid == 0) { + // Disable the crashreporter -- writing a crash dump in the child will + // prevent the parent from writing a subsequent dump. Crashes here are + // expected, so we don't want their stacks to show up in the log anyway. + mozilla::gtest::DisableCrashReporter(); + + // Child: perform the crashy operation. + FILE* stderr_dup = fdopen(dup(fileno(stderr)), "w"); + // We don't want MOZ_CRASH from the crashy operation to print out its + // error message and stack-trace, which would be confusing and irrelevant. + fclose(stderr); + aCrashyOperation(); + fprintf(stderr_dup, "TestCrashyOperation %s: didn't crash?!\n", label); + ASSERT_TRUE(false); // shouldn't reach here + } + + // Parent: check that child crashed as expected. + int status; + ASSERT_NE(waitpid(pid, &status, 0), -1); + + // The path taken here depends on the platform and configuration. + ASSERT_TRUE(WIFEXITED(status) || WTERMSIG(status)); + if (WIFEXITED(status)) { + // This occurs if the ah_crap_handler() is run, i.e. we caught the crash. + // It returns the number of the caught signal. + int signum = WEXITSTATUS(status); + if (signum != SIGSEGV && signum != SIGBUS) { + fprintf(stderr, "TestCrashyOperation %s: 'exited' failure: %d\n", label, + signum); + ASSERT_TRUE(false); + } + } else if (WIFSIGNALED(status)) { + // This one occurs if we didn't catch the crash. The exit code is the + // number of the terminating signal. + int signum = WTERMSIG(status); + if (signum != SIGSEGV && signum != SIGBUS) { + fprintf(stderr, "TestCrashyOperation %s: 'signaled' failure: %d\n", label, + signum); + ASSERT_TRUE(false); + } + } + + RESTORE_GDB_SLEEP_LOCAL(); +#endif +} + +static void InitCapacityOk_InitialLengthTooBig() { + PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub), + PLDHashTable::kMaxInitialLength + 1); +} + +static void InitCapacityOk_InitialEntryStoreTooBig() { + // Try the smallest disallowed power-of-two entry store size, which is 2^32 + // bytes (which overflows to 0). (Note that the 2^23 *length* gets converted + // to a 2^24 *capacity*.) + PLDHashTable t(PLDHashTable::StubOps(), (uint32_t)1 << 8, (uint32_t)1 << 23); +} + +static void InitCapacityOk_EntrySizeTooBig() { + // Try the smallest disallowed entry size, which is 256 bytes. + PLDHashTable t(PLDHashTable::StubOps(), 256); +} + +TEST(PLDHashTableTest, InitCapacityOk) +{ + // Try the largest allowed capacity. With kMaxCapacity==1<<26, this + // would allocate (if we added an element) 0.5GB of entry store on 32-bit + // platforms and 1GB on 64-bit platforms. + PLDHashTable t1(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub), + PLDHashTable::kMaxInitialLength); + + // Try the largest allowed power-of-two entry store size, which is 2^31 bytes + // (Note that the 2^23 *length* gets converted to a 2^24 *capacity*.) + PLDHashTable t2(PLDHashTable::StubOps(), (uint32_t)1 << 7, (uint32_t)1 << 23); + + // Try a too-large capacity (which aborts). + TestCrashyOperation("length too big", InitCapacityOk_InitialLengthTooBig); + + // Try a large capacity combined with a large entry size that when multiplied + // overflow (causing abort). + TestCrashyOperation("entry store too big", + InitCapacityOk_InitialEntryStoreTooBig); + + // Try the largest allowed entry size. + PLDHashTable t3(PLDHashTable::StubOps(), 255); + + // Try an overly large entry size. + TestCrashyOperation("entry size too big", InitCapacityOk_EntrySizeTooBig); + + // Ideally we'd also try a large-but-ok capacity that almost but doesn't + // quite overflow, but that would result in allocating slightly less than 4 + // GiB of entry storage. That would be very likely to fail on 32-bit + // platforms, so such a test wouldn't be reliable. +} + +TEST(PLDHashTableTest, LazyStorage) +{ + PLDHashTable t(PLDHashTable::StubOps(), sizeof(PLDHashEntryStub)); + + // PLDHashTable allocates entry storage lazily. Check that all the non-add + // operations work appropriately when the table is empty and the storage + // hasn't yet been allocated. + + ASSERT_EQ(t.Capacity(), 0u); + ASSERT_EQ(t.EntrySize(), sizeof(PLDHashEntryStub)); + ASSERT_EQ(t.EntryCount(), 0u); + ASSERT_EQ(t.Generation(), 0u); + + ASSERT_TRUE(!t.Search((const void*)1)); + + // No result to check here, but call it to make sure it doesn't crash. + t.Remove((const void*)2); + + for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { + ASSERT_TRUE(false); // shouldn't hit this on an empty table + } + + ASSERT_EQ(t.ShallowSizeOfExcludingThis(moz_malloc_size_of), 0u); +} + +// A trivial hash function is good enough here. It's also super-fast for the +// GrowToMaxCapacity test because we insert the integers 0.., which means it's +// collision-free. +static PLDHashNumber TrivialHash(const void* key) { + return (PLDHashNumber)(size_t)key; +} + +static void TrivialInitEntry(PLDHashEntryHdr* aEntry, const void* aKey) { + auto entry = static_cast(aEntry); + entry->key = aKey; +} + +static const PLDHashTableOps trivialOps = { + TrivialHash, PLDHashTable::MatchEntryStub, PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, TrivialInitEntry}; + +TEST(PLDHashTableTest, MoveSemantics) +{ + PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub)); + t1.Add((const void*)88); + PLDHashTable t2(&trivialOps, sizeof(PLDHashEntryStub)); + t2.Add((const void*)99); + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wself-move" +#endif + t1 = std::move(t1); // self-move +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + + t1 = std::move(t2); // empty overwritten with empty + + PLDHashTable t3(&trivialOps, sizeof(PLDHashEntryStub)); + PLDHashTable t4(&trivialOps, sizeof(PLDHashEntryStub)); + t3.Add((const void*)88); + + t3 = std::move(t4); // non-empty overwritten with empty + + PLDHashTable t5(&trivialOps, sizeof(PLDHashEntryStub)); + PLDHashTable t6(&trivialOps, sizeof(PLDHashEntryStub)); + t6.Add((const void*)88); + + t5 = std::move(t6); // empty overwritten with non-empty + + PLDHashTable t7(&trivialOps, sizeof(PLDHashEntryStub)); + PLDHashTable t8(std::move(t7)); // new table constructed with uninited + + PLDHashTable t9(&trivialOps, sizeof(PLDHashEntryStub)); + t9.Add((const void*)88); + PLDHashTable t10(std::move(t9)); // new table constructed with inited +} + +TEST(PLDHashTableTest, Clear) +{ + PLDHashTable t1(&trivialOps, sizeof(PLDHashEntryStub)); + + t1.Clear(); + ASSERT_EQ(t1.EntryCount(), 0u); + + t1.ClearAndPrepareForLength(100); + ASSERT_EQ(t1.EntryCount(), 0u); + + t1.Add((const void*)77); + t1.Add((const void*)88); + t1.Add((const void*)99); + ASSERT_EQ(t1.EntryCount(), 3u); + + t1.Clear(); + ASSERT_EQ(t1.EntryCount(), 0u); + + t1.Add((const void*)55); + t1.Add((const void*)66); + t1.Add((const void*)77); + t1.Add((const void*)88); + t1.Add((const void*)99); + ASSERT_EQ(t1.EntryCount(), 5u); + + t1.ClearAndPrepareForLength(8192); + ASSERT_EQ(t1.EntryCount(), 0u); +} + +TEST(PLDHashTableTest, Iterator) +{ + PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub)); + + // Explicitly test the move constructor. We do this because, due to copy + // elision, compilers might optimize away move constructor calls for normal + // iterator use. + { + PLDHashTable::Iterator iter1(&t); + PLDHashTable::Iterator iter2(std::move(iter1)); + } + + // Iterate through the empty table. + for (PLDHashTable::Iterator iter(&t); !iter.Done(); iter.Next()) { + (void)iter.Get(); + ASSERT_TRUE(false); // shouldn't hit this + } + + // Add three entries. + t.Add((const void*)77); + t.Add((const void*)88); + t.Add((const void*)99); + + // Check the iterator goes through each entry once. + bool saw77 = false, saw88 = false, saw99 = false; + int n = 0; + for (auto iter(t.Iter()); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + if (entry->key == (const void*)77) { + saw77 = true; + } + if (entry->key == (const void*)88) { + saw88 = true; + } + if (entry->key == (const void*)99) { + saw99 = true; + } + n++; + } + ASSERT_TRUE(saw77 && saw88 && saw99 && n == 3); + + t.Clear(); + + // First, we insert 64 items, which results in a capacity of 128, and a load + // factor of 50%. + for (intptr_t i = 0; i < 64; i++) { + t.Add((const void*)i); + } + ASSERT_EQ(t.EntryCount(), 64u); + ASSERT_EQ(t.Capacity(), 128u); + + // The first removing iterator does no removing; capacity and entry count are + // unchanged. + for (PLDHashTable::Iterator iter(&t); !iter.Done(); iter.Next()) { + (void)iter.Get(); + } + ASSERT_EQ(t.EntryCount(), 64u); + ASSERT_EQ(t.Capacity(), 128u); + + // The second removing iterator removes 16 items. This reduces the load + // factor to 37.5% (48 / 128), which isn't low enough to shrink the table. + for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + if ((intptr_t)(entry->key) % 4 == 0) { + iter.Remove(); + } + } + ASSERT_EQ(t.EntryCount(), 48u); + ASSERT_EQ(t.Capacity(), 128u); + + // The third removing iterator removes another 16 items. This reduces + // the load factor to 25% (32 / 128), so the table is shrunk. + for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + if ((intptr_t)(entry->key) % 2 == 0) { + iter.Remove(); + } + } + ASSERT_EQ(t.EntryCount(), 32u); + ASSERT_EQ(t.Capacity(), 64u); + + // The fourth removing iterator removes all remaining items. This reduces + // the capacity to the minimum. + for (auto iter = t.Iter(); !iter.Done(); iter.Next()) { + iter.Remove(); + } + ASSERT_EQ(t.EntryCount(), 0u); + ASSERT_EQ(t.Capacity(), unsigned(PLDHashTable::kMinCapacity)); +} + +TEST(PLDHashTableTest, WithEntryHandle) +{ + PLDHashTable t(&trivialOps, sizeof(PLDHashEntryStub)); + + PLDHashEntryHdr* entry1 = + t.WithEntryHandle((const void*)88, [](auto entryHandle) { + EXPECT_FALSE(entryHandle); + + bool initEntryCalled = false; + PLDHashEntryHdr* entry = + entryHandle.OrInsert([&initEntryCalled](PLDHashEntryHdr* entry) { + EXPECT_TRUE(entry); + TrivialInitEntry(entry, (const void*)88); + initEntryCalled = true; + }); + EXPECT_TRUE(initEntryCalled); + EXPECT_EQ(entryHandle.Entry(), entry); + + return entry; + }); + ASSERT_TRUE(entry1); + ASSERT_EQ(t.EntryCount(), 1u); + + PLDHashEntryHdr* entry2 = + t.WithEntryHandle((const void*)88, [](auto entryHandle) { + EXPECT_TRUE(entryHandle); + + bool initEntryCalled = false; + PLDHashEntryHdr* entry = + entryHandle.OrInsert([&initEntryCalled](PLDHashEntryHdr* entry) { + EXPECT_TRUE(entry); + TrivialInitEntry(entry, (const void*)88); + initEntryCalled = true; + }); + EXPECT_FALSE(initEntryCalled); + EXPECT_EQ(entryHandle.Entry(), entry); + + return entry; + }); + ASSERT_TRUE(entry2); + ASSERT_EQ(t.EntryCount(), 1u); + + ASSERT_EQ(entry1, entry2); +} + +// This test involves resizing a table repeatedly up to 512 MiB in size. On +// 32-bit platforms (Win32, Android) it sometimes OOMs, causing the test to +// fail. (See bug 931062 and bug 1267227.) Therefore, we only run it on 64-bit +// platforms where OOM is much less likely. +// +// Also, it's slow, and so should always be last. +#ifdef HAVE_64BIT_BUILD +TEST(PLDHashTableTest, GrowToMaxCapacity) +{ + // This is infallible. + PLDHashTable* t = + new PLDHashTable(&trivialOps, sizeof(PLDHashEntryStub), 128); + + // Keep inserting elements until failure occurs because the table is full. + size_t numInserted = 0; + while (true) { + if (!t->Add((const void*)numInserted, mozilla::fallible)) { + break; + } + numInserted++; + } + + // We stop when the element count is 96.875% of PLDHashTable::kMaxCapacity + // (see MaxLoadOnGrowthFailure()). + if (numInserted != + PLDHashTable::kMaxCapacity - (PLDHashTable::kMaxCapacity >> 5)) { + delete t; + ASSERT_TRUE(false); + } + + delete t; +} +#endif diff --git a/xpcom/tests/gtest/TestPipes.cpp b/xpcom/tests/gtest/TestPipes.cpp new file mode 100644 index 0000000000..a4f0ebc7a5 --- /dev/null +++ b/xpcom/tests/gtest/TestPipes.cpp @@ -0,0 +1,1031 @@ +/* -*- 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 +#include "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Printf.h" +#include "nsCOMPtr.h" +#include "nsCRT.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIBufferedStreams.h" +#include "nsIClassInfo.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIPipe.h" +#include "nsITellableStream.h" +#include "nsIThread.h" +#include "nsIRunnable.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "prinrval.h" + +using namespace mozilla; + +#define ITERATIONS 33333 +char kTestPattern[] = "My hovercraft is full of eels.\n"; + +bool gTrace = false; + +static nsresult WriteAll(nsIOutputStream* os, const char* buf, uint32_t bufLen, + uint32_t* lenWritten) { + const char* p = buf; + *lenWritten = 0; + while (bufLen) { + uint32_t n; + nsresult rv = os->Write(p, bufLen, &n); + if (NS_FAILED(rv)) return rv; + p += n; + bufLen -= n; + *lenWritten += n; + } + return NS_OK; +} + +class nsReceiver final : public Runnable { + public: + NS_IMETHOD Run() override { + nsresult rv; + char buf[101]; + uint32_t count; + PRIntervalTime start = PR_IntervalNow(); + while (true) { + rv = mIn->Read(buf, 100, &count); + if (NS_FAILED(rv)) { + printf("read failed\n"); + break; + } + if (count == 0) { + // printf("EOF count = %d\n", mCount); + break; + } + + if (gTrace) { + buf[count] = '\0'; + printf("read: %s\n", buf); + } + mCount += count; + } + PRIntervalTime end = PR_IntervalNow(); + printf("read %d bytes, time = %dms\n", mCount, + PR_IntervalToMilliseconds(end - start)); + return rv; + } + + explicit nsReceiver(nsIInputStream* in) + : Runnable("nsReceiver"), mIn(in), mCount(0) {} + + uint32_t GetBytesRead() { return mCount; } + + private: + ~nsReceiver() = default; + + protected: + nsCOMPtr mIn; + uint32_t mCount; +}; + +static nsresult TestPipe(nsIInputStream* in, nsIOutputStream* out) { + RefPtr receiver = new nsReceiver(in); + nsresult rv; + + nsCOMPtr thread; + rv = NS_NewNamedThread("TestPipe", getter_AddRefs(thread), receiver); + if (NS_FAILED(rv)) return rv; + + uint32_t total = 0; + PRIntervalTime start = PR_IntervalNow(); + for (uint32_t i = 0; i < ITERATIONS; i++) { + uint32_t writeCount; + SmprintfPointer buf = mozilla::Smprintf("%d %s", i, kTestPattern); + uint32_t len = strlen(buf.get()); + rv = WriteAll(out, buf.get(), len, &writeCount); + if (gTrace) { + printf("wrote: "); + for (uint32_t j = 0; j < writeCount; j++) { + putc(buf.get()[j], stdout); + } + printf("\n"); + } + if (NS_FAILED(rv)) return rv; + total += writeCount; + } + rv = out->Close(); + if (NS_FAILED(rv)) return rv; + + PRIntervalTime end = PR_IntervalNow(); + + thread->Shutdown(); + + printf("wrote %d bytes, time = %dms\n", total, + PR_IntervalToMilliseconds(end - start)); + EXPECT_EQ(receiver->GetBytesRead(), total); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsShortReader final : public Runnable { + public: + NS_IMETHOD Run() override { + nsresult rv; + char buf[101]; + uint32_t count; + uint32_t total = 0; + while (true) { + // if (gTrace) + // printf("calling Read\n"); + rv = mIn->Read(buf, 100, &count); + if (NS_FAILED(rv)) { + printf("read failed\n"); + break; + } + if (count == 0) { + break; + } + + if (gTrace) { + // For next |printf()| call and possible others elsewhere. + buf[count] = '\0'; + + printf("read %d bytes: %s\n", count, buf); + } + + Received(count); + total += count; + } + printf("read %d bytes\n", total); + return rv; + } + + explicit nsShortReader(nsIInputStream* in) + : Runnable("nsShortReader"), mIn(in), mReceived(0) { + mMon = new ReentrantMonitor("nsShortReader"); + } + + void Received(uint32_t count) { + ReentrantMonitorAutoEnter mon(*mMon); + mReceived += count; + mon.Notify(); + } + + uint32_t WaitForReceipt(const uint32_t aWriteCount) { + ReentrantMonitorAutoEnter mon(*mMon); + uint32_t result = mReceived; + + while (result < aWriteCount) { + mon.Wait(); + + EXPECT_TRUE(mReceived > result); + result = mReceived; + } + + mReceived = 0; + return result; + } + + private: + ~nsShortReader() = default; + + protected: + nsCOMPtr mIn; + uint32_t mReceived; + ReentrantMonitor* mMon; +}; + +static nsresult TestShortWrites(nsIInputStream* in, nsIOutputStream* out) { + RefPtr receiver = new nsShortReader(in); + nsresult rv; + + nsCOMPtr thread; + rv = NS_NewNamedThread("TestShortWrites", getter_AddRefs(thread), receiver); + if (NS_FAILED(rv)) return rv; + + uint32_t total = 0; + for (uint32_t i = 0; i < ITERATIONS; i++) { + uint32_t writeCount; + SmprintfPointer buf = mozilla::Smprintf("%d %s", i, kTestPattern); + uint32_t len = strlen(buf.get()); + len = len * rand() / RAND_MAX; + len = std::min(1u, len); + rv = WriteAll(out, buf.get(), len, &writeCount); + if (NS_FAILED(rv)) return rv; + EXPECT_EQ(writeCount, len); + total += writeCount; + + if (gTrace) printf("wrote %d bytes: %s\n", writeCount, buf.get()); + // printf("calling Flush\n"); + out->Flush(); + // printf("calling WaitForReceipt\n"); + +#ifdef DEBUG + const uint32_t received = receiver->WaitForReceipt(writeCount); + EXPECT_EQ(received, writeCount); +#endif + } + rv = out->Close(); + if (NS_FAILED(rv)) return rv; + + thread->Shutdown(); + + printf("wrote %d bytes\n", total); + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +class nsPump final : public Runnable { + public: + NS_IMETHOD Run() override { + nsresult rv; + uint32_t count; + while (true) { + rv = mOut->WriteFrom(mIn, ~0U, &count); + if (NS_FAILED(rv)) { + printf("Write failed\n"); + break; + } + if (count == 0) { + printf("EOF count = %d\n", mCount); + break; + } + + if (gTrace) { + printf("Wrote: %d\n", count); + } + mCount += count; + } + mOut->Close(); + return rv; + } + + nsPump(nsIInputStream* in, nsIOutputStream* out) + : Runnable("nsPump"), mIn(in), mOut(out), mCount(0) {} + + private: + ~nsPump() = default; + + protected: + nsCOMPtr mIn; + nsCOMPtr mOut; + uint32_t mCount; +}; + +TEST(Pipes, ChainedPipes) +{ + nsresult rv; + if (gTrace) { + printf("TestChainedPipes\n"); + } + + nsCOMPtr in1; + nsCOMPtr out1; + NS_NewPipe(getter_AddRefs(in1), getter_AddRefs(out1), 20, 1999); + + nsCOMPtr in2; + nsCOMPtr out2; + NS_NewPipe(getter_AddRefs(in2), getter_AddRefs(out2), 200, 401); + + RefPtr pump = new nsPump(in1, out2); + if (pump == nullptr) return; + + nsCOMPtr thread; + rv = NS_NewNamedThread("ChainedPipePump", getter_AddRefs(thread), pump); + if (NS_FAILED(rv)) return; + + RefPtr receiver = new nsReceiver(in2); + if (receiver == nullptr) return; + + nsCOMPtr receiverThread; + rv = NS_NewNamedThread("ChainedPipeRecv", getter_AddRefs(receiverThread), + receiver); + if (NS_FAILED(rv)) return; + + uint32_t total = 0; + for (uint32_t i = 0; i < ITERATIONS; i++) { + uint32_t writeCount; + SmprintfPointer buf = mozilla::Smprintf("%d %s", i, kTestPattern); + uint32_t len = strlen(buf.get()); + len = len * rand() / RAND_MAX; + len = std::max(1u, len); + rv = WriteAll(out1, buf.get(), len, &writeCount); + if (NS_FAILED(rv)) return; + EXPECT_EQ(writeCount, len); + total += writeCount; + + if (gTrace) printf("wrote %d bytes: %s\n", writeCount, buf.get()); + } + if (gTrace) { + printf("wrote total of %d bytes\n", total); + } + rv = out1->Close(); + if (NS_FAILED(rv)) return; + + thread->Shutdown(); + receiverThread->Shutdown(); +} + +//////////////////////////////////////////////////////////////////////////////// + +static void RunTests(uint32_t segSize, uint32_t segCount) { + nsresult rv; + nsCOMPtr in; + nsCOMPtr out; + uint32_t bufSize = segSize * segCount; + if (gTrace) { + printf("Testing New Pipes: segment size %d buffer size %d\n", segSize, + bufSize); + printf("Testing long writes...\n"); + } + NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize); + rv = TestPipe(in, out); + EXPECT_NS_SUCCEEDED(rv); + + if (gTrace) { + printf("Testing short writes...\n"); + } + NS_NewPipe(getter_AddRefs(in), getter_AddRefs(out), segSize, bufSize); + rv = TestShortWrites(in, out); + EXPECT_NS_SUCCEEDED(rv); +} + +TEST(Pipes, Main) +{ + RunTests(16, 1); + RunTests(4096, 16); +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +static const uint32_t DEFAULT_SEGMENT_SIZE = 4 * 1024; + +// An alternate pipe testing routing that uses NS_ConsumeStream() instead of +// manual read loop. +static void TestPipe2(uint32_t aNumBytes, + uint32_t aSegmentSize = DEFAULT_SEGMENT_SIZE) { + nsCOMPtr reader; + nsCOMPtr writer; + + uint32_t maxSize = std::max(aNumBytes, aSegmentSize); + + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), aSegmentSize, + maxSize); + + nsTArray inputData; + testing::CreateData(aNumBytes, inputData); + testing::WriteAllAndClose(writer, inputData); + testing::ConsumeAndValidateStream(reader, inputData); +} + +} // namespace + +TEST(Pipes, Blocking_32k) +{ TestPipe2(32 * 1024); } + +TEST(Pipes, Blocking_64k) +{ TestPipe2(64 * 1024); } + +TEST(Pipes, Blocking_128k) +{ TestPipe2(128 * 1024); } + +//////////////////////////////////////////////////////////////////////////////// + +namespace { + +// Utility routine to validate pipe clone before. There are many knobs. +// +// aTotalBytes Total number of bytes to write to the pipe. +// aNumWrites How many separate write calls should be made. Bytes +// are evenly distributed over these write calls. +// aNumInitialClones How many clones of the pipe input stream should be +// made before writing begins. +// aNumToCloseAfterWrite How many streams should be closed after each write. +// One stream is always kept open. This verifies that +// closing one stream does not effect other open +// streams. +// aNumToCloneAfterWrite How many clones to create after each write. Occurs +// after closing any streams. This tests cloning +// active streams on a pipe that is being written to. +// aNumStreamToReadPerWrite How many streams to read fully after each write. +// This tests reading cloned streams at different rates +// while the pipe is being written to. +static void TestPipeClone(uint32_t aTotalBytes, uint32_t aNumWrites, + uint32_t aNumInitialClones, + uint32_t aNumToCloseAfterWrite, + uint32_t aNumToCloneAfterWrite, + uint32_t aNumStreamsToReadPerWrite, + uint32_t aSegmentSize = DEFAULT_SEGMENT_SIZE) { + nsCOMPtr reader; + nsCOMPtr writer; + + uint32_t maxSize = std::max(aTotalBytes, aSegmentSize); + + // Use async input streams so we can NS_ConsumeStream() the current data + // while the pipe is still being written to. + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), aSegmentSize, + maxSize, true, false); // non-blocking - reader, writer + + nsCOMPtr cloneable = do_QueryInterface(reader); + ASSERT_TRUE(cloneable); + ASSERT_TRUE(cloneable->GetCloneable()); + + nsTArray outputDataList; + + nsTArray> streamList; + + // first stream is our original reader from the pipe + streamList.AppendElement(reader); + outputDataList.AppendElement(); + + // Clone the initial input stream the specified number of times + // before performing any writes. + nsresult rv; + for (uint32_t i = 0; i < aNumInitialClones; ++i) { + nsCOMPtr* clone = streamList.AppendElement(); + rv = cloneable->Clone(getter_AddRefs(*clone)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(*clone); + + outputDataList.AppendElement(); + } + + nsTArray inputData; + testing::CreateData(aTotalBytes, inputData); + + const uint32_t bytesPerWrite = ((aTotalBytes - 1) / aNumWrites) + 1; + uint32_t offset = 0; + uint32_t remaining = aTotalBytes; + uint32_t nextStreamToRead = 0; + + while (remaining) { + uint32_t numToWrite = std::min(bytesPerWrite, remaining); + testing::Write(writer, inputData, offset, numToWrite); + offset += numToWrite; + remaining -= numToWrite; + + // Close the specified number of streams. This allows us to + // test that one closed clone does not break other open clones. + for (uint32_t i = 0; i < aNumToCloseAfterWrite && streamList.Length() > 1; + ++i) { + uint32_t lastIndex = streamList.Length() - 1; + streamList[lastIndex]->Close(); + streamList.RemoveElementAt(lastIndex); + outputDataList.RemoveElementAt(lastIndex); + + if (nextStreamToRead >= streamList.Length()) { + nextStreamToRead = 0; + } + } + + // Create the specified number of clones. This lets us verify + // that we can create clones in the middle of pipe reading and + // writing. + for (uint32_t i = 0; i < aNumToCloneAfterWrite; ++i) { + nsCOMPtr* clone = streamList.AppendElement(); + rv = cloneable->Clone(getter_AddRefs(*clone)); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(*clone); + + // Initialize the new output data to make whats been read to data for + // the original stream. First stream is always the original stream. + nsCString* outputData = outputDataList.AppendElement(); + *outputData = outputDataList[0]; + } + + // Read the specified number of streams. This lets us verify that we + // can read from the clones at different rates while the pipe is being + // written to. + for (uint32_t i = 0; i < aNumStreamsToReadPerWrite; ++i) { + nsCOMPtr& stream = streamList[nextStreamToRead]; + nsCString& outputData = outputDataList[nextStreamToRead]; + + // Can't use ConsumeAndValidateStream() here because we're not + // guaranteed the exact amount read. It should just be at least + // as many as numToWrite. + nsAutoCString tmpOutputData; + rv = NS_ConsumeStream(stream, UINT32_MAX, tmpOutputData); + ASSERT_TRUE(rv == NS_BASE_STREAM_WOULD_BLOCK || NS_SUCCEEDED(rv)); + ASSERT_GE(tmpOutputData.Length(), numToWrite); + + outputData += tmpOutputData; + + nextStreamToRead += 1; + if (nextStreamToRead >= streamList.Length()) { + // Note: When we wrap around on the streams being read, its possible + // we will trigger a segment to be deleted from the pipe. It + // would be nice to validate this here, but we don't have any + // QI'able interface that would let us check easily. + + nextStreamToRead = 0; + } + } + } + + rv = writer->Close(); + ASSERT_NS_SUCCEEDED(rv); + + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + // Finally, read the remaining bytes from each stream. This may be + // different amounts of data depending on how much reading we did while + // writing. Verify that the end result matches the input data. + for (uint32_t i = 0; i < streamList.Length(); ++i) { + nsCOMPtr& stream = streamList[i]; + nsCString& outputData = outputDataList[i]; + + nsAutoCString tmpOutputData; + rv = NS_ConsumeStream(stream, UINT32_MAX, tmpOutputData); + ASSERT_TRUE(rv == NS_BASE_STREAM_WOULD_BLOCK || NS_SUCCEEDED(rv)); + stream->Close(); + + // Append to total amount read from the stream + outputData += tmpOutputData; + + ASSERT_EQ(inputString.Length(), outputData.Length()); + ASSERT_TRUE(inputString.Equals(outputData)); + } +} + +} // namespace + +TEST(Pipes, Clone_BeforeWrite_ReadAtEnd) +{ + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 3, // num initial clones + 0, // num streams to close after each write + 0, // num clones to add after each write + 0); // num streams to read after each write +} + +TEST(Pipes, Clone_BeforeWrite_ReadDuringWrite) +{ + // Since this reads all streams on every write, it should trigger the + // pipe cursor roll back optimization. Currently we can only verify + // this with logging. + + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 3, // num initial clones + 0, // num streams to close after each write + 0, // num clones to add after each write + 4); // num streams to read after each write +} + +TEST(Pipes, Clone_DuringWrite_ReadAtEnd) +{ + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 0, // num initial clones + 0, // num streams to close after each write + 1, // num clones to add after each write + 0); // num streams to read after each write +} + +TEST(Pipes, Clone_DuringWrite_ReadDuringWrite) +{ + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 0, // num initial clones + 0, // num streams to close after each write + 1, // num clones to add after each write + 1); // num streams to read after each write +} + +TEST(Pipes, Clone_DuringWrite_ReadDuringWrite_CloseDuringWrite) +{ + // Since this reads streams faster than we clone new ones, it should + // trigger pipe segment deletion periodically. Currently we can + // only verify this with logging. + + TestPipeClone(32 * 1024, // total bytes + 16, // num writes + 1, // num initial clones + 1, // num streams to close after each write + 2, // num clones to add after each write + 3); // num streams to read after each write +} + +TEST(Pipes, Write_AsyncWait) +{ + nsCOMPtr reader; + nsCOMPtr writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + uint32_t numWritten = 0; + nsresult rv = + writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv); + + RefPtr cb = + new testing::OutputStreamCallback(); + + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + testing::ConsumeAndValidateStream(reader, inputData); + + ASSERT_TRUE(cb->Called()); +} + +TEST(Pipes, Write_AsyncWait_Clone) +{ + nsCOMPtr reader; + nsCOMPtr writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsCOMPtr clone; + nsresult rv = NS_CloneInputStream(reader, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // This attempts to write data beyond the original pipe size limit. It + // should fail since neither side of the clone has been read yet. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv); + + RefPtr cb = + new testing::OutputStreamCallback(); + + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + // Consume data on the original stream, but the clone still has not been read. + testing::ConsumeAndValidateStream(reader, inputData); + + // A clone that is not being read should not stall the other input stream + // reader. Therefore the writer callback should trigger when the fastest + // reader drains the other input stream. + ASSERT_TRUE(cb->Called()); + + // Attempt to write data. This will buffer data beyond the pipe size limit in + // order for the clone stream to still work. This is allowed because the + // other input stream has drained its buffered segments and is ready for more + // data. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // Again, this should fail since the origin stream has not been read again. + // The pipe size should still restrict how far ahead we can buffer even + // when there is a cloned stream not being read. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_FAILED(rv); + + cb = new testing::OutputStreamCallback(); + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + // The write should again be blocked since we have written data and the + // main reader is at its maximum advance buffer. + ASSERT_FALSE(cb->Called()); + + nsTArray expectedCloneData; + expectedCloneData.AppendElements(inputData); + expectedCloneData.AppendElements(inputData); + + // We should now be able to consume the entire backlog of buffered data on + // the cloned stream. + testing::ConsumeAndValidateStream(clone, expectedCloneData); + + // Draining the clone side should also trigger the AsyncWait() writer + // callback + ASSERT_TRUE(cb->Called()); + + // Finally, we should be able to consume the remaining data on the original + // reader. + testing::ConsumeAndValidateStream(reader, inputData); +} + +TEST(Pipes, Write_AsyncWait_Clone_CloseOriginal) +{ + nsCOMPtr reader; + nsCOMPtr writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsCOMPtr clone; + nsresult rv = NS_CloneInputStream(reader, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // This attempts to write data beyond the original pipe size limit. It + // should fail since neither side of the clone has been read yet. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_EQ(NS_BASE_STREAM_WOULD_BLOCK, rv); + + RefPtr cb = + new testing::OutputStreamCallback(); + + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + // Consume data on the original stream, but the clone still has not been read. + testing::ConsumeAndValidateStream(reader, inputData); + + // A clone that is not being read should not stall the other input stream + // reader. Therefore the writer callback should trigger when the fastest + // reader drains the other input stream. + ASSERT_TRUE(cb->Called()); + + // Attempt to write data. This will buffer data beyond the pipe size limit in + // order for the clone stream to still work. This is allowed because the + // other input stream has drained its buffered segments and is ready for more + // data. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // Again, this should fail since the origin stream has not been read again. + // The pipe size should still restrict how far ahead we can buffer even + // when there is a cloned stream not being read. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_FAILED(rv); + + cb = new testing::OutputStreamCallback(); + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + // The write should again be blocked since we have written data and the + // main reader is at its maximum advance buffer. + ASSERT_FALSE(cb->Called()); + + // Close the original reader input stream. This was the fastest reader, + // so we should have a single stream that is buffered beyond our nominal + // limit. + reader->Close(); + + // Because the clone stream is still buffered the writable callback should + // not be fired. + ASSERT_FALSE(cb->Called()); + + // And we should not be able to perform a write. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_FAILED(rv); + + // Create another clone stream. Now we have two streams that exceed our + // maximum size limit + nsCOMPtr clone2; + rv = NS_CloneInputStream(clone, getter_AddRefs(clone2)); + ASSERT_NS_SUCCEEDED(rv); + + nsTArray expectedCloneData; + expectedCloneData.AppendElements(inputData); + expectedCloneData.AppendElements(inputData); + + // We should now be able to consume the entire backlog of buffered data on + // the cloned stream. + testing::ConsumeAndValidateStream(clone, expectedCloneData); + + // The pipe should now be writable because we have two open streams, one of + // which is completely drained. + ASSERT_TRUE(cb->Called()); + + // Write again to reach our limit again. + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + // The stream is again non-writeable. + cb = new testing::OutputStreamCallback(); + rv = writer->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_FALSE(cb->Called()); + + // Close the empty stream. This is different from our previous close since + // before we were closing a stream with some data still buffered. + clone->Close(); + + // The pipe should not be writable. The second clone is still fully buffered + // over our limit. + ASSERT_FALSE(cb->Called()); + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_FAILED(rv); + + // Finally consume all of the buffered data on the second clone. + expectedCloneData.AppendElements(inputData); + testing::ConsumeAndValidateStream(clone2, expectedCloneData); + + // Draining the final clone should make the pipe writable again. + ASSERT_TRUE(cb->Called()); +} + +TEST(Pipes, Read_AsyncWait) +{ + nsCOMPtr reader; + nsCOMPtr writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + RefPtr cb = new testing::InputStreamCallback(); + + nsresult rv = reader->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_TRUE(cb->Called()); + + testing::ConsumeAndValidateStream(reader, inputData); +} + +TEST(Pipes, Read_AsyncWait_Clone) +{ + nsCOMPtr reader; + nsCOMPtr writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsCOMPtr clone; + nsresult rv = NS_CloneInputStream(reader, getter_AddRefs(clone)); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr asyncClone = do_QueryInterface(clone); + ASSERT_TRUE(asyncClone); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + RefPtr cb = new testing::InputStreamCallback(); + + RefPtr cb2 = new testing::InputStreamCallback(); + + rv = reader->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + rv = asyncClone->AsyncWait(cb2, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb2->Called()); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_TRUE(cb->Called()); + ASSERT_TRUE(cb2->Called()); + + testing::ConsumeAndValidateStream(reader, inputData); +} + +namespace { + +nsresult CloseDuringReadFunc(nsIInputStream* aReader, void* aClosure, + const char* aFromSegment, uint32_t aToOffset, + uint32_t aCount, uint32_t* aWriteCountOut) { + MOZ_RELEASE_ASSERT(aReader); + MOZ_RELEASE_ASSERT(aClosure); + MOZ_RELEASE_ASSERT(aFromSegment); + MOZ_RELEASE_ASSERT(aWriteCountOut); + MOZ_RELEASE_ASSERT(aToOffset == 0); + + // This is insanity and you probably should not do this under normal + // conditions. We want to simulate the case where the pipe is closed + // (possibly from other end on another thread) simultaneously with the + // read. This is the easiest way to do trigger this case in a synchronous + // gtest. + MOZ_ALWAYS_SUCCEEDS(aReader->Close()); + + nsTArray* buffer = static_cast*>(aClosure); + buffer->AppendElements(aFromSegment, aCount); + + *aWriteCountOut = aCount; + + return NS_OK; +} + +void TestCloseDuringRead(uint32_t aSegmentSize, uint32_t aDataSize) { + nsCOMPtr reader; + nsCOMPtr writer; + + const uint32_t maxSize = aSegmentSize; + + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer), aSegmentSize, + maxSize); + + nsTArray inputData; + + testing::CreateData(aDataSize, inputData); + + uint32_t numWritten = 0; + nsresult rv = + writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + nsTArray outputData; + + uint32_t numRead = 0; + rv = reader->ReadSegments(CloseDuringReadFunc, &outputData, + inputData.Length(), &numRead); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(inputData.Length(), numRead); + + ASSERT_EQ(inputData, outputData); + + uint64_t available; + rv = reader->Available(&available); + ASSERT_EQ(NS_BASE_STREAM_CLOSED, rv); +} + +} // namespace + +TEST(Pipes, Close_During_Read_Partial_Segment) +{ TestCloseDuringRead(1024, 512); } + +TEST(Pipes, Close_During_Read_Full_Segment) +{ TestCloseDuringRead(1024, 1024); } + +TEST(Pipes, Interfaces) +{ + nsCOMPtr reader; + nsCOMPtr writer; + + NS_NewPipe(getter_AddRefs(reader), getter_AddRefs(writer)); + + nsCOMPtr readerType1 = do_QueryInterface(reader); + ASSERT_TRUE(readerType1); + + nsCOMPtr readerType2 = do_QueryInterface(reader); + ASSERT_TRUE(readerType2); + + nsCOMPtr readerType3 = do_QueryInterface(reader); + ASSERT_TRUE(readerType3); + + nsCOMPtr readerType4 = do_QueryInterface(reader); + ASSERT_TRUE(readerType4); + + nsCOMPtr readerType5 = do_QueryInterface(reader); + ASSERT_TRUE(readerType5); + + nsCOMPtr readerType6 = do_QueryInterface(reader); + ASSERT_TRUE(readerType6); +} diff --git a/xpcom/tests/gtest/TestPriorityQueue.cpp b/xpcom/tests/gtest/TestPriorityQueue.cpp new file mode 100644 index 0000000000..c5f59072da --- /dev/null +++ b/xpcom/tests/gtest/TestPriorityQueue.cpp @@ -0,0 +1,73 @@ +/* -*- 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 "nsTPriorityQueue.h" +#include +#include +#include "gtest/gtest.h" + +template +void CheckPopSequence(const nsTPriorityQueue& aQueue, + const T* aExpectedSequence, + const uint32_t aSequenceLength) { + nsTPriorityQueue copy = aQueue.Clone(); + + for (uint32_t i = 0; i < aSequenceLength; i++) { + EXPECT_FALSE(copy.IsEmpty()); + + T pop = copy.Pop(); + EXPECT_EQ(pop, aExpectedSequence[i]); + } + + EXPECT_TRUE(copy.IsEmpty()); +} + +template +class MaxCompare { + public: + bool LessThan(const A& a, const A& b) { return a > b; } +}; + +TEST(PriorityQueue, Main) +{ + nsTPriorityQueue queue; + + EXPECT_TRUE(queue.IsEmpty()); + + queue.Push(8); + queue.Push(6); + queue.Push(4); + queue.Push(2); + queue.Push(10); + queue.Push(6); + EXPECT_EQ(queue.Top(), 2); + EXPECT_EQ(queue.Length(), 6u); + EXPECT_FALSE(queue.IsEmpty()); + int expected[] = {2, 4, 6, 6, 8, 10}; + CheckPopSequence(queue, expected, sizeof(expected) / sizeof(expected[0])); + + // copy ctor is tested by using CheckPopSequence, but check default assignment + // operator + nsTPriorityQueue queue2; + queue2 = queue.Clone(); + CheckPopSequence(queue2, expected, sizeof(expected) / sizeof(expected[0])); + + queue.Clear(); + EXPECT_TRUE(queue.IsEmpty()); + + // try same sequence with a max heap + nsTPriorityQueue > max_queue; + max_queue.Push(8); + max_queue.Push(6); + max_queue.Push(4); + max_queue.Push(2); + max_queue.Push(10); + max_queue.Push(6); + EXPECT_EQ(max_queue.Top(), 10); + int expected_max[] = {10, 8, 6, 6, 4, 2}; + CheckPopSequence(max_queue, expected_max, + sizeof(expected_max) / sizeof(expected_max[0])); +} diff --git a/xpcom/tests/gtest/TestQueue.cpp b/xpcom/tests/gtest/TestQueue.cpp new file mode 100644 index 0000000000..e6d8c07dd2 --- /dev/null +++ b/xpcom/tests/gtest/TestQueue.cpp @@ -0,0 +1,186 @@ +/* -*- 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/Queue.h" +#include "gtest/gtest.h" +#include + +using namespace mozilla; + +namespace TestQueue { + +struct Movable { + Movable() : mDestructionCounter(nullptr) {} + explicit Movable(uint32_t* aDestructionCounter) + : mDestructionCounter(aDestructionCounter) {} + + ~Movable() { + if (mDestructionCounter) { + (*mDestructionCounter)++; + } + } + + Movable(Movable&& aOther) : mDestructionCounter(aOther.mDestructionCounter) { + aOther.mDestructionCounter = nullptr; + } + + uint32_t* mDestructionCounter; +}; + +template +void PushMovables(Queue& aQueue, + std::array& aDestructionCounters) { + auto oldDestructionCounters = aDestructionCounters; + auto oldCount = aQueue.Count(); + for (uint32_t i = 0; i < N; ++i) { + aQueue.Push(Movable(&aDestructionCounters[i])); + } + for (uint32_t i = 0; i < N; ++i) { + EXPECT_EQ(aDestructionCounters[i], oldDestructionCounters[i]); + } + EXPECT_EQ(aQueue.Count(), oldCount + N); + EXPECT_FALSE(aQueue.IsEmpty()); +} + +template +void ExpectCounts(const std::array& aDestructionCounters, + uint32_t aExpected) { + for (const auto& counters : aDestructionCounters) { + EXPECT_EQ(counters, aExpected); + } +} + +TEST(Queue, Clear) +{ + std::array counts{0}; + + Queue queue; + PushMovables(queue, counts); + queue.Clear(); + ExpectCounts(counts, 1); + EXPECT_EQ(queue.Count(), 0u); + EXPECT_TRUE(queue.IsEmpty()); +} + +TEST(Queue, Destroy) +{ + std::array counts{0}; + + { + Queue queue; + PushMovables(queue, counts); + } + ExpectCounts(counts, 1u); +} + +TEST(Queue, MoveConstruct) +{ + std::array counts{0}; + + { + Queue queue; + PushMovables(queue, counts); + + Queue queue2(std::move(queue)); + EXPECT_EQ(queue2.Count(), 32u); + EXPECT_FALSE(queue2.IsEmpty()); + // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move) + EXPECT_EQ(queue.Count(), 0u); + // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move) + EXPECT_TRUE(queue.IsEmpty()); + ExpectCounts(counts, 0u); + } + ExpectCounts(counts, 1u); +} + +TEST(Queue, MoveAssign) +{ + std::array counts{0}; + std::array counts2{0}; + + { + Queue queue; + PushMovables(queue, counts); + + { + Queue queue2; + PushMovables(queue2, counts2); + + queue = std::move(queue2); + ExpectCounts(counts, 1); + ExpectCounts(counts2, 0); + EXPECT_EQ(queue.Count(), 32u); + EXPECT_FALSE(queue.IsEmpty()); + // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move) + EXPECT_EQ(queue2.Count(), 0u); + // NOLINTNEXTLINE(bugprone-use-after-move, clang-analyzer-cplusplus.Move) + EXPECT_TRUE(queue2.IsEmpty()); + } + ExpectCounts(counts, 1); + ExpectCounts(counts2, 0); + EXPECT_EQ(queue.Count(), 32u); + EXPECT_FALSE(queue.IsEmpty()); + } + ExpectCounts(counts, 1); + ExpectCounts(counts2, 1); +} + +TEST(Queue, PopOrder) +{ + std::array counts{0}; + Queue queue; + PushMovables(queue, counts); + + for (auto& count : counts) { + EXPECT_EQ(count, 0u); + { + Movable popped = queue.Pop(); + EXPECT_EQ(popped.mDestructionCounter, &count); + EXPECT_EQ(count, 0u); + } + EXPECT_EQ(count, 1u); + } + EXPECT_TRUE(queue.IsEmpty()); + EXPECT_EQ(queue.Count(), 0u); +} + +void DoPushPopSequence(Queue& aQueue, uint32_t& aInSerial, + uint32_t& aOutSerial, uint32_t aPush, uint32_t aPop) { + auto initialCount = aQueue.Count(); + for (uint32_t i = 0; i < aPush; ++i) { + aQueue.Push(aInSerial++); + } + EXPECT_EQ(aQueue.Count(), initialCount + aPush); + for (uint32_t i = 0; i < aPop; ++i) { + uint32_t popped = aQueue.Pop(); + EXPECT_EQ(popped, aOutSerial++); + } + EXPECT_EQ(aQueue.Count(), initialCount + aPush - aPop); +} + +void PushPopPushPop(uint32_t aPush1, uint32_t aPop1, uint32_t aPush2, + uint32_t aPop2) { + Queue queue; + uint32_t inSerial = 0; + uint32_t outSerial = 0; + DoPushPopSequence(queue, inSerial, outSerial, aPush1, aPop1); + DoPushPopSequence(queue, inSerial, outSerial, aPush2, aPop2); +} + +TEST(Queue, PushPopSequence) +{ + for (uint32_t push1 = 0; push1 < 16; ++push1) { + for (uint32_t pop1 = 0; pop1 < push1; ++pop1) { + for (uint32_t push2 = 0; push2 < 16; ++push2) { + for (uint32_t pop2 = 0; pop2 < push1 - pop1 + push2; ++pop2) { + PushPopPushPop(push1, pop1, push2, pop2); + } + } + } + } +} + +} // namespace TestQueue diff --git a/xpcom/tests/gtest/TestRWLock.cpp b/xpcom/tests/gtest/TestRWLock.cpp new file mode 100644 index 0000000000..eee392f709 --- /dev/null +++ b/xpcom/tests/gtest/TestRWLock.cpp @@ -0,0 +1,214 @@ +/* -*- 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 "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/RWLock.h" +#include "mozilla/SyncRunnable.h" +#include "nsIThread.h" +#include "gtest/gtest.h" + +using mozilla::AutoReadLock; +using mozilla::AutoTryReadLock; +using mozilla::AutoTryWriteLock; +using mozilla::AutoWriteLock; +using mozilla::RWLock; + +static const size_t sNumThreads = 4; +static const size_t sOuterIterations = 100; +static const size_t sInnerIterations = 100; +static const size_t sWriteLockIteration = 10; + +// Based on example code from _Programming with POSIX Threads_. Not an actual +// test of correctness, but more of a "does this work at all" sort of test. + +class RWLockRunnable : public mozilla::Runnable { + public: + RWLockRunnable(RWLock* aRWLock, mozilla::Atomic* aSharedData) + : mozilla::Runnable("RWLockRunnable"), + mRWLock(aRWLock), + mSharedData(aSharedData) {} + + NS_DECL_NSIRUNNABLE + + private: + ~RWLockRunnable() = default; + + RWLock* mRWLock; + mozilla::Atomic* mSharedData; +}; + +NS_IMETHODIMP +RWLockRunnable::Run() { + for (size_t i = 0; i < sOuterIterations; ++i) { + if (i % sWriteLockIteration == 0) { + mozilla::AutoWriteLock lock(*mRWLock); + + ++(*mSharedData); + } else { + mozilla::AutoReadLock lock(*mRWLock); + + // Loop and try to force other threads to run, but check that our + // shared data isn't being modified by them. + size_t initialValue = *mSharedData; + for (size_t j = 0; j < sInnerIterations; ++j) { + EXPECT_EQ(initialValue, *mSharedData); + + // This is a magic yield call. + PR_Sleep(PR_INTERVAL_NO_WAIT); + } + } + } + + return NS_OK; +} + +TEST(RWLock, SmokeTest) +{ + nsCOMPtr threads[sNumThreads]; + RWLock rwlock MOZ_UNANNOTATED("test lock"); + mozilla::Atomic data(0); + + for (size_t i = 0; i < sNumThreads; ++i) { + nsCOMPtr event = new RWLockRunnable(&rwlock, &data); + NS_NewNamedThread("RWLockTester", getter_AddRefs(threads[i]), event); + } + + // Wait for all the threads to finish. + for (size_t i = 0; i < sNumThreads; ++i) { + nsresult rv = threads[i]->Shutdown(); + EXPECT_NS_SUCCEEDED(rv); + } + + EXPECT_EQ(data, (sOuterIterations / sWriteLockIteration) * sNumThreads); +} + +template +static std::invoke_result_t RunOnBackgroundThread( + Function&& aFunction) { + using Result = std::invoke_result_t; + nsCOMPtr thread; + MOZ_ALWAYS_SUCCEEDS(NS_CreateBackgroundTaskQueue( + "TestRWLock Background Thread", getter_AddRefs(thread))); + mozilla::Maybe tryResult; + RefPtr runnable = + NS_NewRunnableFunction(__func__, [&] { tryResult.emplace(aFunction()); }); + MOZ_ALWAYS_SUCCEEDS( + mozilla::SyncRunnable::DispatchToThread(thread.get(), runnable)); + return *tryResult; +} + +TEST(RWLock, AutoTryReadLock) +{ + RWLock l1 MOZ_UNANNOTATED("autotryreadlock"); + { + AutoTryReadLock autol1(l1); + + EXPECT_TRUE(autol1); + + AutoTryReadLock autol2(l1); + EXPECT_TRUE(autol2); + + EXPECT_TRUE(RunOnBackgroundThread([&] { + AutoTryReadLock lock(l1); + return !!lock; + })); + + EXPECT_TRUE(autol1); + EXPECT_TRUE(autol2); + + { + RWLock l2 MOZ_UNANNOTATED("autotryreadlock2"); + AutoTryReadLock autol3(l2); + + EXPECT_TRUE(autol3); + } + + EXPECT_TRUE(autol1); + EXPECT_TRUE(autol2); + } + + { + AutoWriteLock autol4(l1); + MOZ_ASSERT(l1.LockedForWritingByCurrentThread()); + + AutoTryReadLock autol5(l1); + EXPECT_FALSE(autol5); + + EXPECT_FALSE(RunOnBackgroundThread([&] { + AutoTryReadLock lock(l1); + return !!lock; + })); + } + + AutoTryReadLock autol6(l1); + EXPECT_TRUE(autol6); + + EXPECT_TRUE(RunOnBackgroundThread([&] { + AutoTryReadLock lock(l1); + return !!lock; + })); +} + +TEST(RWLock, AutoTryWriteLock) +{ + RWLock l1 MOZ_UNANNOTATED("autotrywritelock"); + { + AutoTryWriteLock autol1(l1); + + EXPECT_TRUE(autol1); + + AutoTryReadLock autol2(l1); + EXPECT_FALSE(autol2); + + EXPECT_FALSE(RunOnBackgroundThread([&] { + AutoTryWriteLock lock(l1); + return !!lock; + })); + + EXPECT_TRUE(autol1); + EXPECT_FALSE(autol2); + + { + RWLock l2 MOZ_UNANNOTATED("autotrywritelock2"); + AutoTryWriteLock autol3(l2); + + EXPECT_TRUE(autol3); + } + + EXPECT_TRUE(autol1); + EXPECT_FALSE(autol2); + } + + { + AutoReadLock autol4(l1); + + AutoTryWriteLock autol5(l1); + EXPECT_FALSE(autol5); + + EXPECT_FALSE(RunOnBackgroundThread([&] { + AutoTryWriteLock lock(l1); + return !!lock; + })); + } + + { + AutoWriteLock autol6(l1); + MOZ_ASSERT(l1.LockedForWritingByCurrentThread()); + + AutoTryWriteLock autol7(l1); + EXPECT_FALSE(autol7); + + EXPECT_FALSE(RunOnBackgroundThread([&] { + AutoTryWriteLock lock(l1); + return !!lock; + })); + } + + AutoTryWriteLock autol8(l1); + EXPECT_TRUE(autol8); +} diff --git a/xpcom/tests/gtest/TestRacingServiceManager.cpp b/xpcom/tests/gtest/TestRacingServiceManager.cpp new file mode 100644 index 0000000000..ad6c08d97b --- /dev/null +++ b/xpcom/tests/gtest/TestRacingServiceManager.cpp @@ -0,0 +1,260 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIFactory.h" +#include "nsXULAppAPI.h" +#include "nsIThread.h" + +#include "nsComponentManager.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "nsXPCOMCIDInternal.h" +#include "pratom.h" +#include "prmon.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/gtest/MozAssertions.h" + +#include "mozilla/ReentrantMonitor.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +/* f93f6bdc-88af-42d7-9d64-1b43c649a3e5 */ +#define FACTORY_CID1 \ + { \ + 0xf93f6bdc, 0x88af, 0x42d7, { \ + 0x9d, 0x64, 0x1b, 0x43, 0xc6, 0x49, 0xa3, 0xe5 \ + } \ + } +NS_DEFINE_CID(kFactoryCID1, FACTORY_CID1); + +/* ef38ad65-6595-49f0-8048-e819f81d15e2 */ +#define FACTORY_CID2 \ + { \ + 0xef38ad65, 0x6595, 0x49f0, { \ + 0x80, 0x48, 0xe8, 0x19, 0xf8, 0x1d, 0x15, 0xe2 \ + } \ + } +NS_DEFINE_CID(kFactoryCID2, FACTORY_CID2); + +#define FACTORY_CONTRACTID "TestRacingThreadManager/factory;1" + +namespace TestRacingServiceManager { +int32_t gComponent1Count = 0; +int32_t gComponent2Count = 0; + +ReentrantMonitor* gReentrantMonitor = nullptr; + +bool gCreateInstanceCalled = false; +bool gMainThreadWaiting = false; + +class AutoCreateAndDestroyReentrantMonitor { + public: + explicit AutoCreateAndDestroyReentrantMonitor( + ReentrantMonitor** aReentrantMonitorPtr) + : mReentrantMonitorPtr(aReentrantMonitorPtr) { + *aReentrantMonitorPtr = + new ReentrantMonitor("TestRacingServiceManager::AutoMon"); + MOZ_RELEASE_ASSERT(*aReentrantMonitorPtr, "Out of memory!"); + } + + ~AutoCreateAndDestroyReentrantMonitor() { + if (*mReentrantMonitorPtr) { + delete *mReentrantMonitorPtr; + *mReentrantMonitorPtr = nullptr; + } + } + + private: + ReentrantMonitor** mReentrantMonitorPtr; +}; + +class Factory final : public nsIFactory { + ~Factory() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + Factory() : mFirstComponentCreated(false) {} + + NS_IMETHOD CreateInstance(const nsIID& aIID, void** aResult) override; + + bool mFirstComponentCreated; +}; + +NS_IMPL_ISUPPORTS(Factory, nsIFactory) + +class Component1 final : public nsISupports { + ~Component1() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + Component1() { + // This is the real test - make sure that only one instance is ever created. + int32_t count = PR_AtomicIncrement(&gComponent1Count); + MOZ_RELEASE_ASSERT(count == 1, "Too many components created!"); + } +}; + +NS_IMPL_ADDREF(Component1) +NS_IMPL_RELEASE(Component1) + +NS_INTERFACE_MAP_BEGIN(Component1) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +class Component2 final : public nsISupports { + ~Component2() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + Component2() { + // This is the real test - make sure that only one instance is ever created. + int32_t count = PR_AtomicIncrement(&gComponent2Count); + EXPECT_EQ(count, int32_t(1)) << "Too many components created!"; + } +}; + +NS_IMPL_ADDREF(Component2) +NS_IMPL_RELEASE(Component2) + +NS_INTERFACE_MAP_BEGIN(Component2) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP +Factory::CreateInstance(const nsIID& aIID, void** aResult) { + // Make sure that the second thread beat the main thread to the getService + // call. + MOZ_RELEASE_ASSERT(!NS_IsMainThread(), "Wrong thread!"); + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + gCreateInstanceCalled = true; + mon.Notify(); + + mon.Wait(PR_MillisecondsToInterval(3000)); + } + + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr instance; + + if (!mFirstComponentCreated) { + instance = new Component1(); + } else { + instance = new Component2(); + } + NS_ENSURE_TRUE(instance, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = instance->QueryInterface(aIID, aResult); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +class TestRunnable : public Runnable { + public: + NS_DECL_NSIRUNNABLE + + TestRunnable() + : mozilla::Runnable("TestRacingServiceManager::TestRunnable"), + mFirstRunnableDone(false) {} + + bool mFirstRunnableDone; +}; + +NS_IMETHODIMP +TestRunnable::Run() { + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + while (!gMainThreadWaiting) { + mon.Wait(); + } + } + + nsresult rv; + nsCOMPtr component; + + if (!mFirstRunnableDone) { + component = do_GetService(kFactoryCID1, &rv); + } else { + component = do_GetService(FACTORY_CONTRACTID, &rv); + } + EXPECT_TRUE(NS_SUCCEEDED(rv)) << "GetService failed!"; + + return NS_OK; +} + +static Factory* gFactory; + +TEST(RacingServiceManager, Test) +{ + nsresult rv; + + gFactory = new Factory(); + NS_ADDREF(gFactory); + + nsComponentManagerImpl::gComponentManager->RegisterFactory( + kFactoryCID2, "factory1", FACTORY_CONTRACTID, gFactory); + nsComponentManagerImpl::gComponentManager->RegisterFactory( + kFactoryCID1, "factory2", nullptr, gFactory); + + AutoCreateAndDestroyReentrantMonitor mon1(&gReentrantMonitor); + + RefPtr runnable = new TestRunnable(); + ASSERT_TRUE(runnable); + + // Run the classID test + nsCOMPtr newThread; + rv = NS_NewNamedThread("RacingServMan", getter_AddRefs(newThread), runnable); + ASSERT_NS_SUCCEEDED(rv); + + { + ReentrantMonitorAutoEnter mon2(*gReentrantMonitor); + + gMainThreadWaiting = true; + mon2.Notify(); + + while (!gCreateInstanceCalled) { + mon2.Wait(); + } + } + + nsCOMPtr component(do_GetService(kFactoryCID1, &rv)); + ASSERT_NS_SUCCEEDED(rv); + + // Reset for the contractID test + gMainThreadWaiting = gCreateInstanceCalled = false; + gFactory->mFirstComponentCreated = runnable->mFirstRunnableDone = true; + component = nullptr; + + rv = newThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + ASSERT_NS_SUCCEEDED(rv); + + { + ReentrantMonitorAutoEnter mon3(*gReentrantMonitor); + + gMainThreadWaiting = true; + mon3.Notify(); + + while (!gCreateInstanceCalled) { + mon3.Wait(); + } + } + + component = do_GetService(FACTORY_CONTRACTID, &rv); + ASSERT_NS_SUCCEEDED(rv); + + NS_RELEASE(gFactory); +} + +} // namespace TestRacingServiceManager diff --git a/xpcom/tests/gtest/TestRecursiveMutex.cpp b/xpcom/tests/gtest/TestRecursiveMutex.cpp new file mode 100644 index 0000000000..57fb6fc038 --- /dev/null +++ b/xpcom/tests/gtest/TestRecursiveMutex.cpp @@ -0,0 +1,25 @@ +/* -*- 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 "nsThreadUtils.h" +#include "mozilla/RecursiveMutex.h" +#include "gtest/gtest.h" + +using mozilla::RecursiveMutex; +using mozilla::RecursiveMutexAutoLock; + +// Basic test to make sure the underlying implementation of RecursiveMutex is, +// well, actually recursively acquirable. + +TEST(RecursiveMutex, SmokeTest) +MOZ_NO_THREAD_SAFETY_ANALYSIS { + RecursiveMutex mutex("testing mutex"); + + RecursiveMutexAutoLock lock1(mutex); + RecursiveMutexAutoLock lock2(mutex); + + //...and done. +} diff --git a/xpcom/tests/gtest/TestRustRegex.cpp b/xpcom/tests/gtest/TestRustRegex.cpp new file mode 100644 index 0000000000..38f7119b6b --- /dev/null +++ b/xpcom/tests/gtest/TestRustRegex.cpp @@ -0,0 +1,181 @@ +/* -*- 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 "gtest/gtest.h" +#include "mozilla/RustRegex.h" + +// This file is adapted from the test.c file in the `rure` crate, but modified +// to use gtest and the `RustRegex` wrapper. + +namespace mozilla { + +TEST(TestRustRegex, IsMatch) +{ + RustRegex re("\\p{So}$"); + ASSERT_TRUE(re.IsValid()); + ASSERT_TRUE(re.IsMatch("snowman: \xE2\x98\x83")); +} + +TEST(TestRustRegex, ShortestMatch) +{ + RustRegex re("a+"); + ASSERT_TRUE(re.IsValid()); + + Maybe match = re.ShortestMatch("aaaaa"); + ASSERT_TRUE(match); + EXPECT_EQ(*match, 1u); +} + +TEST(TestRustRegex, Find) +{ + RustRegex re("\\p{So}$"); + ASSERT_TRUE(re.IsValid()); + + auto match = re.Find("snowman: \xE2\x98\x83"); + ASSERT_TRUE(match); + EXPECT_EQ(match->start, 9u); + EXPECT_EQ(match->end, 12u); +} + +TEST(TestRustRegex, Captures) +{ + RustRegex re(".(.*(?P\\p{So}))$"); + ASSERT_TRUE(re); + + auto captures = re.FindCaptures("snowman: \xE2\x98\x83"); + ASSERT_TRUE(captures); + EXPECT_EQ(captures.Length(), 3u); + EXPECT_EQ(re.CaptureNameIndex("snowman"), 2); + + auto match = captures[2]; + ASSERT_TRUE(match); + EXPECT_EQ(match->start, 9u); + EXPECT_EQ(match->end, 12u); +} + +TEST(TestRustRegex, Iter) +{ + RustRegex re("\\w+(\\w)"); + ASSERT_TRUE(re); + + auto it = re.IterMatches("abc xyz"); + ASSERT_TRUE(it); + + auto match = it.Next(); + ASSERT_TRUE(match); + EXPECT_EQ(match->start, 0u); + EXPECT_EQ(match->end, 3u); + + auto captures = it.NextCaptures(); + ASSERT_TRUE(captures); + + auto capture = captures[1]; + ASSERT_TRUE(capture); + EXPECT_EQ(capture->start, 6u); + EXPECT_EQ(capture->end, 7u); +} + +TEST(TestRustRegex, IterCaptureNames) +{ + RustRegex re("(?P\\d{4})-(?P\\d{2})-(?P\\d{2})"); + ASSERT_TRUE(re); + + auto it = re.IterCaptureNames(); + Maybe result = it.Next(); + ASSERT_TRUE(result.isSome()); + EXPECT_STREQ(*result, ""); + + result = it.Next(); + ASSERT_TRUE(result.isSome()); + EXPECT_STREQ(*result, "year"); + + result = it.Next(); + ASSERT_TRUE(result.isSome()); + EXPECT_STREQ(*result, "month"); + + result = it.Next(); + ASSERT_TRUE(result.isSome()); + EXPECT_STREQ(*result, "day"); + + result = it.Next(); + ASSERT_TRUE(result.isNothing()); +} + +/* + * This tests whether we can set the flags correctly. In this case, we disable + * all flags, which includes disabling Unicode mode. When we disable Unicode + * mode, we can match arbitrary possibly invalid UTF-8 bytes, such as \xFF. + * (When Unicode mode is enabled, \xFF won't match .) + */ +TEST(TestRustRegex, Flags) +{ + { + RustRegex re("."); + ASSERT_TRUE(re); + ASSERT_FALSE(re.IsMatch("\xFF")); + } + { + RustRegex re(".", RustRegexOptions().Unicode(false)); + ASSERT_TRUE(re); + ASSERT_TRUE(re.IsMatch("\xFF")); + } +} + +TEST(TestRustRegex, CompileErrorSizeLimit) +{ + RustRegex re("\\w{100}", RustRegexOptions().SizeLimit(0)); + EXPECT_FALSE(re); +} + +TEST(TestRustRegex, SetMatches) +{ + RustRegexSet set(nsTArray{"foo", "barfoo", "\\w+", "\\d+", + "foobar", "bar"}); + + ASSERT_TRUE(set); + EXPECT_EQ(set.Length(), 6u); + EXPECT_TRUE(set.IsMatch("foobar")); + EXPECT_FALSE(set.IsMatch("")); + + auto matches = set.Matches("foobar"); + EXPECT_TRUE(matches.matchedAny); + EXPECT_EQ(matches.matches.Length(), 6u); + + nsTArray expectedMatches{true, false, true, false, true, true}; + EXPECT_EQ(matches.matches, expectedMatches); +} + +TEST(TestRustRegex, SetMatchStart) +{ + RustRegexSet re(nsTArray{"foo", "bar", "fooo"}); + EXPECT_TRUE(re); + EXPECT_EQ(re.Length(), 3u); + + EXPECT_FALSE(re.IsMatch("foobiasdr", 2)); + + { + auto matches = re.Matches("fooobar"); + EXPECT_TRUE(matches.matchedAny); + nsTArray expectedMatches{true, true, true}; + EXPECT_EQ(matches.matches, expectedMatches); + } + + { + auto matches = re.Matches("fooobar", 1); + EXPECT_TRUE(matches.matchedAny); + nsTArray expectedMatches{false, true, false}; + EXPECT_EQ(matches.matches, expectedMatches); + } +} + +TEST(TestRustRegex, RegexSetOptions) +{ + RustRegexSet re(nsTArray{"\\w{100}"}, + RustRegexOptions().SizeLimit(0)); + EXPECT_FALSE(re); +} + +} // namespace mozilla diff --git a/xpcom/tests/gtest/TestSTLWrappers.cpp b/xpcom/tests/gtest/TestSTLWrappers.cpp new file mode 100644 index 0000000000..31b658f764 --- /dev/null +++ b/xpcom/tests/gtest/TestSTLWrappers.cpp @@ -0,0 +1,65 @@ +#include + +#include +#ifndef mozilla_algorithm_h +# error "failed to wrap " +#endif + +#include +#ifndef mozilla_vector_h +# error "failed to wrap " +#endif + +// gcc errors out if we |try ... catch| with -fno-exceptions, but we +// can still test on windows +#ifdef _MSC_VER +// C4530 will be generated whenever try...catch is used without +// enabling exceptions. We know we don't enbale exceptions. +# pragma warning(disable : 4530) +# define TRY try +# define CATCH(e) catch (e) +#else +# define TRY +# define CATCH(e) if (0) +#endif + +#include "gtest/gtest.h" + +#include "mozilla/gtest/MozHelpers.h" + +void ShouldAbort() { + ZERO_GDB_SLEEP(); + + mozilla::gtest::DisableCrashReporter(); + + std::vector v; + + TRY { + // v.at(1) on empty v should abort; NOT throw an exception + + (void)v.at(1); + } + CATCH(const std::out_of_range&) { + fputs("TEST-FAIL | TestSTLWrappers.cpp | caught an exception?\n", stderr); + return; + } + + fputs("TEST-FAIL | TestSTLWrappers.cpp | didn't abort()?\n", stderr); +} + +#if defined(XP_WIN) || (defined(XP_MACOSX) && !defined(MOZ_DEBUG)) +TEST(STLWrapper, DISABLED_ShouldAbortDeathTest) +#else +TEST(STLWrapper, ShouldAbortDeathTest) +#endif +{ + ASSERT_DEATH_IF_SUPPORTED(ShouldAbort(), +#ifdef __GLIBCXX__ + // Only libstdc++ will print this message. + "terminate called after throwing an instance of " + "'std::out_of_range'|vector::_M_range_check" +#else + "" +#endif + ); +} diff --git a/xpcom/tests/gtest/TestSegmentedBuffer.cpp b/xpcom/tests/gtest/TestSegmentedBuffer.cpp new file mode 100644 index 0000000000..136e35d489 --- /dev/null +++ b/xpcom/tests/gtest/TestSegmentedBuffer.cpp @@ -0,0 +1,41 @@ +/* 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 "gtest/gtest.h" +#include "../../io/nsSegmentedBuffer.h" +#include "nsIEventTarget.h" + +using namespace mozilla; + +TEST(SegmentedBuffer, AppendAndDelete) +{ + auto buf = MakeUnique(); + buf->Init(4); + char* seg; + bool empty; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + seg = buf->AppendNewSegment(); + EXPECT_TRUE(seg) << "AppendNewSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(!empty) << "DeleteFirstSegment failed"; + empty = buf->DeleteFirstSegment(); + EXPECT_TRUE(empty) << "DeleteFirstSegment failed"; +} diff --git a/xpcom/tests/gtest/TestSlicedInputStream.cpp b/xpcom/tests/gtest/TestSlicedInputStream.cpp new file mode 100644 index 0000000000..a2c1a077e4 --- /dev/null +++ b/xpcom/tests/gtest/TestSlicedInputStream.cpp @@ -0,0 +1,665 @@ +#include "gtest/gtest.h" + +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/SlicedInputStream.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" +#include "nsIPipe.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "Helpers.h" + +using namespace mozilla; + +// This helper class is used to call OnInputStreamReady with the right stream +// as argument. +class InputStreamCallback final : public nsIInputStreamCallback { + nsCOMPtr mStream; + nsCOMPtr mCallback; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + InputStreamCallback(nsIAsyncInputStream* aStream, + nsIInputStreamCallback* aCallback) + : mStream(aStream), mCallback(aCallback) {} + + NS_IMETHOD + OnInputStreamReady(nsIAsyncInputStream* aStream) override { + return mCallback->OnInputStreamReady(mStream); + } + + private: + ~InputStreamCallback() = default; +}; + +NS_IMPL_ISUPPORTS(InputStreamCallback, nsIInputStreamCallback) + +/* We want to ensure that sliced streams work with both seekable and + * non-seekable input streams. As our string streams are seekable, we need to + * provide a string stream that doesn't permit seeking, so we can test the + * logic that emulates seeking in sliced input streams. + */ +class NonSeekableStringStream final : public nsIAsyncInputStream { + nsCOMPtr mStream; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit NonSeekableStringStream(const nsACString& aBuffer) { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + explicit NonSeekableStringStream(nsIInputStream* aStream) + : mStream(aStream) {} + + NS_IMETHOD + Available(uint64_t* aLength) override { return mStream->Available(aLength); } + + NS_IMETHOD + StreamStatus() override { return mStream->StreamStatus(); } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, + uint32_t* aResult) override { + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + Close() override { return mStream->Close(); } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override { + return mStream->IsNonBlocking(aNonBlocking); + } + + NS_IMETHOD + CloseWithStatus(nsresult aStatus) override { + nsCOMPtr async = do_QueryInterface(mStream); + if (!async) { + MOZ_CRASH("This should not happen."); + return NS_ERROR_FAILURE; + } + + return async->CloseWithStatus(aStatus); + } + + NS_IMETHOD + AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, + uint32_t aRequestedCount, nsIEventTarget* aEventTarget) override { + nsCOMPtr async = do_QueryInterface(mStream); + if (!async) { + MOZ_CRASH("This should not happen."); + return NS_ERROR_FAILURE; + } + + RefPtr callback = + new InputStreamCallback(this, aCallback); + + return async->AsyncWait(callback, aFlags, aRequestedCount, aEventTarget); + } + + private: + ~NonSeekableStringStream() = default; +}; + +NS_IMPL_ISUPPORTS(NonSeekableStringStream, nsIInputStream, nsIAsyncInputStream) + +// Helper function for creating a seekable nsIInputStream + a SlicedInputStream. +static SlicedInputStream* CreateSeekableStreams(uint32_t aSize, uint64_t aStart, + uint64_t aLength, + nsCString& aBuffer) { + aBuffer.SetLength(aSize); + for (uint32_t i = 0; i < aSize; ++i) { + aBuffer.BeginWriting()[i] = i % 10; + } + + nsCOMPtr stream; + NS_NewCStringInputStream(getter_AddRefs(stream), aBuffer); + return new SlicedInputStream(stream.forget(), aStart, aLength); +} + +// Helper function for creating a non-seekable nsIInputStream + a +// SlicedInputStream. +static SlicedInputStream* CreateNonSeekableStreams(uint32_t aSize, + uint64_t aStart, + uint64_t aLength, + nsCString& aBuffer) { + aBuffer.SetLength(aSize); + for (uint32_t i = 0; i < aSize; ++i) { + aBuffer.BeginWriting()[i] = i % 10; + } + + RefPtr stream = new NonSeekableStringStream(aBuffer); + return new SlicedInputStream(stream.forget(), aStart, aLength); +} + +// Same start, same length. +TEST(TestSlicedInputStream, Simple) +{ + const size_t kBufSize = 4096; + + nsCString buf; + RefPtr sis = + CreateSeekableStreams(kBufSize, 0, kBufSize, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + char buf2[kBufSize]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, buf.Length()); + ASSERT_TRUE(nsCString(buf.get()).Equals(nsCString(buf2))); +} + +// Simple sliced stream - seekable +TEST(TestSlicedInputStream, Sliced) +{ + const size_t kBufSize = 4096; + + nsCString buf; + RefPtr sis = CreateSeekableStreams(kBufSize, 10, 100, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)100, length); + + char buf2[kBufSize / 2]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)100, count); + ASSERT_TRUE(nsCString(buf.get() + 10, count).Equals(nsCString(buf2, count))); +} + +// Simple sliced stream - non seekable +TEST(TestSlicedInputStream, SlicedNoSeek) +{ + const size_t kBufSize = 4096; + + nsCString buf; + RefPtr sis = + CreateNonSeekableStreams(kBufSize, 10, 100, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)100, length); + + char buf2[kBufSize / 2]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)100, count); + ASSERT_TRUE(nsCString(buf.get() + 10, count).Equals(nsCString(buf2, count))); +} + +// Big inputStream - seekable +TEST(TestSlicedInputStream, BigSliced) +{ + const size_t kBufSize = 4096 * 40; + + nsCString buf; + RefPtr sis = + CreateSeekableStreams(kBufSize, 4096 * 5, 4096 * 10, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)4096 * 10, length); + + char buf2[kBufSize / 2]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)4096 * 10, count); + ASSERT_TRUE( + nsCString(buf.get() + 4096 * 5, count).Equals(nsCString(buf2, count))); +} + +// Big inputStream - non seekable +TEST(TestSlicedInputStream, BigSlicedNoSeek) +{ + const size_t kBufSize = 4096 * 40; + + nsCString buf; + RefPtr sis = + CreateNonSeekableStreams(kBufSize, 4096 * 5, 4096 * 10, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)4096 * 10, length); + + char buf2[kBufSize / 2]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)4096 * 10, count); + ASSERT_TRUE( + nsCString(buf.get() + 4096 * 5, count).Equals(nsCString(buf2, count))); +} + +// Available size. +TEST(TestSlicedInputStream, Available) +{ + nsCString buf; + RefPtr sis = + CreateNonSeekableStreams(500000, 4, 400000, buf); + + uint64_t toRead = 400000; + for (uint32_t i = 0; i < 400; ++i) { + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ(toRead, length); + + char buf2[1000]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)1000, count); + ASSERT_TRUE(nsCString(buf.get() + 4 + (1000 * i), count) + .Equals(nsCString(buf2, count))); + + toRead -= count; + } + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)0, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)0, count); +} + +// What if start is > then the size of the buffer? +TEST(TestSlicedInputStream, StartBiggerThan) +{ + nsCString buf; + RefPtr sis = CreateNonSeekableStreams(500, 4000, 1, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)0, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)0, count); +} + +// What if the length is > than the size of the buffer? +TEST(TestSlicedInputStream, LengthBiggerThan) +{ + nsCString buf; + RefPtr sis = CreateNonSeekableStreams(500, 0, 500000, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)500, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)500, count); +} + +// What if the length is 0? +TEST(TestSlicedInputStream, Length0) +{ + nsCString buf; + RefPtr sis = CreateNonSeekableStreams(500, 0, 0, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)0, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)0, count); +} + +// Seek test NS_SEEK_SET +TEST(TestSlicedInputStream, Seek_SET) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + RefPtr sis; + { + nsCOMPtr stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + sis = new SlicedInputStream(stream.forget(), 1, buf.Length()); + } + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_SET, 1)); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)buf.Length() - 2, length); + + char buf2[4096]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)buf.Length() - 2, count); + ASSERT_EQ(0, strncmp(buf2, "llo world", count)); +} + +// Seek test NS_SEEK_CUR +TEST(TestSlicedInputStream, Seek_CUR) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + RefPtr sis; + { + nsCOMPtr stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + + sis = new SlicedInputStream(stream.forget(), 1, buf.Length()); + } + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_CUR, 1)); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)buf.Length() - 2, length); + + char buf2[3]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(buf2, "llo", count)); + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_CUR, 1)); + + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(buf2, "wor", count)); +} + +// Seek test NS_SEEK_END - length > real one +TEST(TestSlicedInputStream, Seek_END_Bigger) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + RefPtr sis; + { + nsCOMPtr stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + + sis = new SlicedInputStream(stream.forget(), 2, buf.Length()); + } + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_END, -5)); + + nsCOMPtr stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + nsCOMPtr seekStream = do_QueryInterface(stream); + ASSERT_EQ(NS_OK, seekStream->Seek(nsISeekableStream::NS_SEEK_END, -5)); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)5, length); + + ASSERT_EQ(NS_OK, stream->Available(&length)); + ASSERT_EQ((uint64_t)5, length); + + char buf2[5]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)5, count); + ASSERT_EQ(0, strncmp(buf2, "world", count)); + + ASSERT_EQ(NS_OK, stream->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)5, count); + ASSERT_EQ(0, strncmp(buf2, "world", count)); +} + +// Seek test NS_SEEK_END - length < real one +TEST(TestSlicedInputStream, Seek_END_Lower) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + RefPtr sis; + { + nsCOMPtr stream; + NS_NewCStringInputStream(getter_AddRefs(stream), buf); + + sis = new SlicedInputStream(stream.forget(), 2, 6); + } + + ASSERT_EQ(NS_OK, sis->Seek(nsISeekableStream::NS_SEEK_END, -3)); + + uint64_t length; + ASSERT_EQ(NS_OK, sis->Available(&length)); + ASSERT_EQ((uint64_t)3, length); + + char buf2[5]; + uint32_t count; + ASSERT_EQ(NS_OK, sis->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ((uint64_t)3, count); + ASSERT_EQ(0, strncmp(buf2, " wo", count)); +} + +// Check the nsIAsyncInputStream interface +TEST(TestSlicedInputStream, NoAsyncInputStream) +{ + const size_t kBufSize = 4096; + + nsCString buf; + nsCOMPtr sis = + CreateSeekableStreams(kBufSize, 0, kBufSize, buf); + + // If the stream is not asyncInputStream, also SIS is not. + nsCOMPtr async = do_QueryInterface(sis); + ASSERT_TRUE(!async); +} + +TEST(TestSlicedInputStream, AsyncInputStream) +{ + nsCOMPtr reader; + nsCOMPtr writer; + + const uint32_t segmentSize = 1024; + const uint32_t numSegments = 1; + + NS_NewPipe2(getter_AddRefs(reader), getter_AddRefs(writer), true, + true, // non-blocking - reader, writer + segmentSize, numSegments); + + nsTArray inputData; + testing::CreateData(segmentSize, inputData); + + // We have to wrap the reader because it implements only a partial + // nsISeekableStream interface. When ::Seek() is called, it does a MOZ_CRASH. + nsCOMPtr sis; + { + RefPtr wrapper = + new NonSeekableStringStream(reader); + + sis = new SlicedInputStream(wrapper.forget(), 500, 500); + } + + nsCOMPtr async = do_QueryInterface(sis); + ASSERT_TRUE(!!async); + + RefPtr cb = new testing::InputStreamCallback(); + + nsresult rv = async->AsyncWait(cb, 0, 0, nullptr); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_FALSE(cb->Called()); + + uint32_t numWritten = 0; + rv = writer->Write(inputData.Elements(), inputData.Length(), &numWritten); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_TRUE(cb->Called()); + + inputData.RemoveElementsAt(0, 500); + inputData.RemoveElementsAt(500, 24); + + testing::ConsumeAndValidateStream(async, inputData); +} + +TEST(TestSlicedInputStream, QIInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + for (int i = 0; i < 4; i++) { + nsCOMPtr sis; + { + RefPtr stream = + new testing::LengthInputStream(buf, i % 2, i > 1); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + { + nsCOMPtr qi = do_QueryInterface(sis); + ASSERT_EQ(!!(i % 2), !!qi); + } + + { + nsCOMPtr qi = do_QueryInterface(sis); + ASSERT_EQ(i > 1, !!qi); + } + } +} + +TEST(TestSlicedInputStream, InputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr sis; + { + RefPtr stream = + new testing::LengthInputStream(buf, true, false); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + int64_t size; + nsresult rv = qi->Length(&size); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(5, size); +} + +TEST(TestSlicedInputStream, NegativeInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr sis; + { + RefPtr stream = + new testing::LengthInputStream(buf, true, false, NS_OK, true); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + int64_t size; + nsresult rv = qi->Length(&size); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(-1, size); +} + +TEST(TestSlicedInputStream, AsyncInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr sis; + { + RefPtr stream = + new testing::LengthInputStream(buf, false, true); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + RefPtr callback = new testing::LengthCallback(); + + nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestSlicedInputStream, AsyncInputStreamLength)"_ns, + [&]() { return callback->Called(); })); + ASSERT_EQ(5, callback->Size()); +} + +TEST(TestSlicedInputStream, NegativeAsyncInputStreamLength) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr sis; + { + RefPtr stream = + new testing::LengthInputStream(buf, false, true, NS_OK, true); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + RefPtr callback = new testing::LengthCallback(); + + nsresult rv = qi->AsyncLengthWait(callback, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestSlicedInputStream, NegativeAsyncInputStreamLength)"_ns, + [&]() { return callback->Called(); })); + ASSERT_EQ(-1, callback->Size()); +} + +TEST(TestSlicedInputStream, AbortLengthCallback) +{ + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr sis; + { + RefPtr stream = + new testing::LengthInputStream(buf, false, true, NS_OK, true); + + sis = new SlicedInputStream(stream.forget(), 0, 5); + } + + nsCOMPtr qi = do_QueryInterface(sis); + ASSERT_TRUE(!!qi); + + RefPtr callback1 = new testing::LengthCallback(); + nsresult rv = qi->AsyncLengthWait(callback1, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + RefPtr callback2 = new testing::LengthCallback(); + rv = qi->AsyncLengthWait(callback2, GetCurrentSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(TestSlicedInputStream, AbortLengthCallback)"_ns, + [&]() { return callback2->Called(); })); + ASSERT_TRUE(!callback1->Called()); + ASSERT_EQ(-1, callback2->Size()); +} diff --git a/xpcom/tests/gtest/TestSmallArrayLRUCache.cpp b/xpcom/tests/gtest/TestSmallArrayLRUCache.cpp new file mode 100644 index 0000000000..10e2b71a69 --- /dev/null +++ b/xpcom/tests/gtest/TestSmallArrayLRUCache.cpp @@ -0,0 +1,368 @@ +/* -*- 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 "gtest/gtest.h" + +#include "mozilla/SmallArrayLRUCache.h" + +#include +#include +#include + +using Key = unsigned; + +struct Value { + Value() : m(unsigned(-1)) {} + explicit Value(unsigned a) : m(a) {} + + bool operator==(const Value& aOther) const { return m == aOther.m; } + bool operator!=(const Value& aOther) const { return m != aOther.m; } + + unsigned m; +}; + +constexpr static unsigned CacheSize = 8; + +using TestCache = mozilla::SmallArrayLRUCache; + +// This struct embeds a given object type between two "guard" objects, to check +// if anything is written out of bounds. +template +struct Boxed { + constexpr static size_t GuardSize = std::max(sizeof(T), size_t(256)); + + // A Guard is a character array with a pre-set content that can be checked for + // unwanted changes. + struct Guard { + char mGuard[GuardSize]; + explicit Guard(char aValue) { memset(&mGuard, aValue, GuardSize); } + void Check(char aValue) { + for (const char& c : mGuard) { + ASSERT_EQ(c, aValue); + } + } + }; + + Guard mGuardBefore; + T mObject; + Guard mGuardAfter; + + template + explicit Boxed(Ts&&... aTs) + : mGuardBefore(0x5a), + mObject(std::forward(aTs)...), + mGuardAfter(0xa5) { + Check(); + } + + ~Boxed() { Check(); } + + T& Object() { return mObject; } + const T& Object() const { return mObject; } + + void Check() { + mGuardBefore.Check(0x5a); + mGuardAfter.Check(0xa5); + } +}; + +TEST(SmallArrayLRUCache, FetchOrAdd_KeysFitInCache) +{ + // We're going to add-or-fetch between 1 and CacheSize keys, so they all fit + // in the cache. + for (Key keys = 1; keys <= CacheSize; ++keys) { + Boxed boxedCache; + TestCache& cache = boxedCache.Object(); + for (Key i = 0; i < keys; ++i) { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i, [&]() { + valueFunctionCalled = true; + return Value{i}; + }); + ASSERT_EQ(v, Value{i}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Fetching any key should never call the value function. + for (Key i = 0; i < CacheSize * 3; ++i) { + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + // Fetching the same key again will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + } + } +} + +TEST(SmallArrayLRUCache, Add_FetchOrAdd_KeysFitInCache) +{ + // We're going to add between 1 and CacheSize keys, so they all fit in the + // cache. + for (Key keys = 1; keys <= CacheSize; ++keys) { + Boxed boxedCache; + TestCache& cache = boxedCache.Object(); + for (Key i = 0; i < keys; ++i) { + cache.Add(i, Value{i}); + boxedCache.Check(); + } + + // Fetching any key should never call the value function. + for (Key i = 0; i < CacheSize * 3; ++i) { + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + // Fetching the same key again will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + } + } +} + +TEST(SmallArrayLRUCache, FetchOrAdd_KeysDoNotFitInCache) +{ + // We're going to add-or-fetch strictly more than CacheSize keys, so they + // cannot fit in the cache, only the last `CacheSize` ones are kept. + for (Key keys = CacheSize + 1; keys <= CacheSize * 2; ++keys) { + Boxed boxedCache; + TestCache& cache = boxedCache.Object(); + for (Key i = 0; i < keys; ++i) { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i, [&]() { + valueFunctionCalled = true; + return Value{i}; + }); + ASSERT_EQ(v, Value{i}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Fetching keys from 0 should always call the function value: + // - 0 is the oldest key, it must have been pushed out when `CacheSize` + // was added. + // - Once we've fetched 0, it's pushed out the old (smallest) key. + // Etc. + for (Key i = 0; i < CacheSize * 3; ++i) { + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + // Fetching the same key again will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + } + } +} + +TEST(SmallArrayLRUCache, Add_FetchOrAdd_KeysDoNotFitInCache) +{ + // We're going to add strictly more than CacheSize keys, so they cannot fit in + // the cache, only the last `CacheSize` ones are kept. + for (Key keys = CacheSize + 1; keys <= CacheSize * 2; ++keys) { + Boxed boxedCache; + TestCache& cache = boxedCache.Object(); + for (Key i = 0; i < keys; ++i) { + cache.Add(i, Value{i}); + boxedCache.Check(); + } + + // Fetching keys from 0 should always call the function value: + // - 0 is the oldest key, it must have been pushed out when `CacheSize` + // was added. + // - Once we've fetched 0, it's pushed out the old (smallest) key. + // Etc. + for (Key i = 0; i < CacheSize * 3; ++i) { + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + // Fetching the same key again will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(i % keys, [&]() { + valueFunctionCalled = true; + return Value{i % keys}; + }); + ASSERT_EQ(v, Value{i % keys}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + } + } +} + +TEST(SmallArrayLRUCache, Clear) +{ + Boxed boxedCache; + TestCache& cache = boxedCache.Object(); + + // First fetch will always call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Second fetch will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + + cache.Clear(); + + // After Clear(), first fetch will always call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Next fetch will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } +} + +TEST(SmallArrayLRUCache, Shutdown) +{ + Boxed boxedCache; + TestCache& cache = boxedCache.Object(); + + // First fetch will always call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + + // Second fetch will never call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_FALSE(valueFunctionCalled); + boxedCache.Check(); + } + + cache.Shutdown(); + + // After Shutdown(), any fetch will always call the function value. + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } + cache.Add(42, Value{4242}); + boxedCache.Check(); + { + bool valueFunctionCalled = false; + Value v = cache.FetchOrAdd(42, [&]() { + valueFunctionCalled = true; + return Value{4242}; + }); + ASSERT_EQ(v, Value{4242}); + ASSERT_TRUE(valueFunctionCalled); + boxedCache.Check(); + } +} diff --git a/xpcom/tests/gtest/TestSnappyStreams.cpp b/xpcom/tests/gtest/TestSnappyStreams.cpp new file mode 100644 index 0000000000..b7e7e7cf73 --- /dev/null +++ b/xpcom/tests/gtest/TestSnappyStreams.cpp @@ -0,0 +1,162 @@ +/* -*- 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 +#include "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/SnappyCompressOutputStream.h" +#include "mozilla/SnappyUncompressInputStream.h" +#include "nsIPipe.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "nsTArray.h" + +namespace { + +using mozilla::SnappyCompressOutputStream; +using mozilla::SnappyUncompressInputStream; + +static already_AddRefed CompressPipe( + nsIInputStream** aReaderOut) { + nsCOMPtr pipeWriter; + NS_NewPipe(aReaderOut, getter_AddRefs(pipeWriter)); + + nsCOMPtr compress = + new SnappyCompressOutputStream(pipeWriter); + return compress.forget(); +} + +// Verify the given number of bytes compresses to a smaller number of bytes. +static void TestCompress(uint32_t aNumBytes) { + // Don't permit this test on small data sizes as snappy can slightly + // bloat very small content. + ASSERT_GT(aNumBytes, 1024u); + + nsCOMPtr pipeReader; + nsCOMPtr compress = CompressPipe(getter_AddRefs(pipeReader)); + ASSERT_TRUE(compress); + + nsTArray inputData; + testing::CreateData(aNumBytes, inputData); + + testing::WriteAllAndClose(compress, inputData); + + nsAutoCString outputData; + nsresult rv = NS_ConsumeStream(pipeReader, UINT32_MAX, outputData); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_LT(outputData.Length(), inputData.Length()); +} + +// Verify that the given number of bytes can be compressed and uncompressed +// successfully. +static void TestCompressUncompress(uint32_t aNumBytes) { + nsCOMPtr pipeReader; + nsCOMPtr compress = CompressPipe(getter_AddRefs(pipeReader)); + ASSERT_TRUE(compress); + + nsCOMPtr uncompress = + new SnappyUncompressInputStream(pipeReader); + + nsTArray inputData; + testing::CreateData(aNumBytes, inputData); + + testing::WriteAllAndClose(compress, inputData); + + nsAutoCString outputData; + nsresult rv = NS_ConsumeStream(uncompress, UINT32_MAX, outputData); + ASSERT_NS_SUCCEEDED(rv); + + ASSERT_EQ(inputData.Length(), outputData.Length()); + for (uint32_t i = 0; i < inputData.Length(); ++i) { + EXPECT_EQ(inputData[i], outputData.get()[i]) << "Byte " << i; + } +} + +static void TestUncompressCorrupt(const char* aCorruptData, + uint32_t aCorruptLength) { + nsCOMPtr source; + nsresult rv = NS_NewByteInputStream( + getter_AddRefs(source), mozilla::Span(aCorruptData, aCorruptLength), + NS_ASSIGNMENT_DEPEND); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr uncompress = new SnappyUncompressInputStream(source); + + nsAutoCString outputData; + rv = NS_ConsumeStream(uncompress, UINT32_MAX, outputData); + ASSERT_EQ(NS_ERROR_CORRUPTED_CONTENT, rv); +} + +} // namespace + +TEST(SnappyStream, Compress_32k) +{ TestCompress(32 * 1024); } + +TEST(SnappyStream, Compress_64k) +{ TestCompress(64 * 1024); } + +TEST(SnappyStream, Compress_128k) +{ TestCompress(128 * 1024); } + +TEST(SnappyStream, CompressUncompress_0) +{ TestCompressUncompress(0); } + +TEST(SnappyStream, CompressUncompress_1) +{ TestCompressUncompress(1); } + +TEST(SnappyStream, CompressUncompress_32) +{ TestCompressUncompress(32); } + +TEST(SnappyStream, CompressUncompress_1k) +{ TestCompressUncompress(1024); } + +TEST(SnappyStream, CompressUncompress_32k) +{ TestCompressUncompress(32 * 1024); } + +TEST(SnappyStream, CompressUncompress_64k) +{ TestCompressUncompress(64 * 1024); } + +TEST(SnappyStream, CompressUncompress_128k) +{ TestCompressUncompress(128 * 1024); } + +// Test buffers that are not exactly power-of-2 in length to try to +// exercise more edge cases. The number 13 is arbitrary. + +TEST(SnappyStream, CompressUncompress_256k_less_13) +{ TestCompressUncompress((256 * 1024) - 13); } + +TEST(SnappyStream, CompressUncompress_256k) +{ TestCompressUncompress(256 * 1024); } + +TEST(SnappyStream, CompressUncompress_256k_plus_13) +{ TestCompressUncompress((256 * 1024) + 13); } + +TEST(SnappyStream, UncompressCorruptStreamIdentifier) +{ + static const char data[] = "This is not a valid compressed stream"; + TestUncompressCorrupt(data, strlen(data)); +} + +TEST(SnappyStream, UncompressCorruptCompressedDataLength) +{ + static const char data[] = + "\xff\x06\x00\x00sNaPpY" // stream identifier + "\x00\x99\x00\x00This is not a valid compressed stream"; + static const uint32_t dataLength = (sizeof(data) / sizeof(const char)) - 1; + TestUncompressCorrupt(data, dataLength); +} + +TEST(SnappyStream, UncompressCorruptCompressedDataContent) +{ + static const char data[] = + "\xff\x06\x00\x00sNaPpY" // stream identifier + "\x00\x25\x00\x00This is not a valid compressed stream"; + static const uint32_t dataLength = (sizeof(data) / sizeof(const char)) - 1; + TestUncompressCorrupt(data, dataLength); +} diff --git a/xpcom/tests/gtest/TestStateWatching.cpp b/xpcom/tests/gtest/TestStateWatching.cpp new file mode 100644 index 0000000000..e4b48fb2b3 --- /dev/null +++ b/xpcom/tests/gtest/TestStateWatching.cpp @@ -0,0 +1,50 @@ +/* -*- 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 "gtest/gtest.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/StateWatching.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Unused.h" +#include "nsISupportsImpl.h" +#include "VideoUtils.h" + +namespace TestStateWatching { + +using namespace mozilla; + +struct Foo { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Foo) + void Notify() { mNotified = true; } + bool mNotified = false; + + private: + ~Foo() = default; +}; + +TEST(WatchManager, Shutdown) +{ + RefPtr queue = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestWatchManager Shutdown"); + + RefPtr p = new Foo; + WatchManager manager(p, queue); + Watchable notifier(false, "notifier"); + + Unused << queue->Dispatch(NS_NewRunnableFunction( + "TestStateWatching::WatchManager_Shutdown_Test::TestBody", [&]() { + manager.Watch(notifier, &Foo::Notify); + notifier = true; // Trigger the call to Foo::Notify(). + manager.Shutdown(); // Shutdown() should cancel the call. + })); + + queue->BeginShutdown(); + queue->AwaitShutdownAndIdle(); + EXPECT_FALSE(p->mNotified); +} + +} // namespace TestStateWatching diff --git a/xpcom/tests/gtest/TestStorageStream.cpp b/xpcom/tests/gtest/TestStorageStream.cpp new file mode 100644 index 0000000000..f92cb986ba --- /dev/null +++ b/xpcom/tests/gtest/TestStorageStream.cpp @@ -0,0 +1,130 @@ +/* -*- 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 +#include "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsCOMPtr.h" +#include "nsICloneableInputStream.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIStorageStream.h" +#include "nsTArray.h" + +namespace { + +void WriteData(nsIOutputStream* aOut, nsTArray& aData, uint32_t aNumBytes, + nsACString& aDataWritten) { + uint32_t n; + nsresult rv = aOut->Write(aData.Elements(), aNumBytes, &n); + EXPECT_NS_SUCCEEDED(rv); + aDataWritten.Append(aData.Elements(), aNumBytes); +} + +} // namespace + +TEST(StorageStreams, Main) +{ + // generate some test data we will write in 4k chunks to the stream + nsTArray kData; + testing::CreateData(4096, kData); + + // track how much data was written so we can compare at the end + nsAutoCString dataWritten; + + nsresult rv; + nsCOMPtr stor; + + rv = NS_NewStorageStream(kData.Length(), UINT32_MAX, getter_AddRefs(stor)); + EXPECT_NS_SUCCEEDED(rv); + + nsCOMPtr out; + rv = stor->GetOutputStream(0, getter_AddRefs(out)); + EXPECT_NS_SUCCEEDED(rv); + + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + + rv = out->Close(); + EXPECT_NS_SUCCEEDED(rv); + out = nullptr; + + nsCOMPtr in; + rv = stor->NewInputStream(0, getter_AddRefs(in)); + EXPECT_NS_SUCCEEDED(rv); + + nsCOMPtr cloneable = do_QueryInterface(in); + ASSERT_TRUE(cloneable != nullptr); + ASSERT_TRUE(cloneable->GetCloneable()); + + nsCOMPtr clone; + rv = cloneable->Clone(getter_AddRefs(clone)); + + testing::ConsumeAndValidateStream(in, dataWritten); + testing::ConsumeAndValidateStream(clone, dataWritten); + in = nullptr; + clone = nullptr; + + // now, write 3 more full 4k segments + 11 bytes, starting at 8192 + // total written equals 20491 bytes + + rv = stor->GetOutputStream(dataWritten.Length(), getter_AddRefs(out)); + EXPECT_NS_SUCCEEDED(rv); + + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, 11, dataWritten); + + rv = out->Close(); + EXPECT_NS_SUCCEEDED(rv); + out = nullptr; + + // now, read all + rv = stor->NewInputStream(0, getter_AddRefs(in)); + EXPECT_NS_SUCCEEDED(rv); + + testing::ConsumeAndValidateStream(in, dataWritten); + in = nullptr; +} + +TEST(StorageStreams, EarlyInputStream) +{ + // generate some test data we will write in 4k chunks to the stream + nsTArray kData; + testing::CreateData(4096, kData); + + // track how much data was written so we can compare at the end + nsAutoCString dataWritten; + + nsresult rv; + nsCOMPtr stor; + + rv = NS_NewStorageStream(kData.Length(), UINT32_MAX, getter_AddRefs(stor)); + EXPECT_NS_SUCCEEDED(rv); + + // Get input stream before writing data into the output stream + nsCOMPtr in; + rv = stor->NewInputStream(0, getter_AddRefs(in)); + EXPECT_NS_SUCCEEDED(rv); + + // Write data to output stream + nsCOMPtr out; + rv = stor->GetOutputStream(0, getter_AddRefs(out)); + EXPECT_NS_SUCCEEDED(rv); + + WriteData(out, kData, kData.Length(), dataWritten); + WriteData(out, kData, kData.Length(), dataWritten); + + rv = out->Close(); + EXPECT_NS_SUCCEEDED(rv); + out = nullptr; + + // Should be able to consume input stream + testing::ConsumeAndValidateStream(in, dataWritten); + in = nullptr; +} diff --git a/xpcom/tests/gtest/TestStringStream.cpp b/xpcom/tests/gtest/TestStringStream.cpp new file mode 100644 index 0000000000..8314c3e74c --- /dev/null +++ b/xpcom/tests/gtest/TestStringStream.cpp @@ -0,0 +1,100 @@ +/* -*- 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 "gtest/gtest.h" +#include "Helpers.h" +#include "mozilla/gtest/MozAssertions.h" +#include "nsICloneableInputStream.h" +#include "nsStringStream.h" +#include "nsTArray.h" +#include "nsIInputStream.h" +#include "nsCOMPtr.h" +#include "nsStreamUtils.h" +#include "mozilla/Span.h" +#include "nsISeekableStream.h" + +namespace { + +static void TestStringStream(uint32_t aNumBytes) { + nsTArray inputData; + testing::CreateData(aNumBytes, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr stream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString); + ASSERT_NS_SUCCEEDED(rv); + + testing::ConsumeAndValidateStream(stream, inputString); +} + +static void TestStringStreamClone(uint32_t aNumBytes) { + nsTArray inputData; + testing::CreateData(aNumBytes, inputData); + nsDependentCSubstring inputString(inputData.Elements(), inputData.Length()); + + nsCOMPtr stream; + nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr cloneable = do_QueryInterface(stream); + ASSERT_TRUE(cloneable != nullptr); + ASSERT_TRUE(cloneable->GetCloneable()); + + nsCOMPtr clone; + rv = cloneable->Clone(getter_AddRefs(clone)); + + testing::ConsumeAndValidateStream(stream, inputString); + + // Release the stream to verify that the clone's string survives correctly. + stream = nullptr; + + testing::ConsumeAndValidateStream(clone, inputString); +} + +} // namespace + +TEST(StringStream, Simple_4k) +{ TestStringStream(1024 * 4); } + +TEST(StringStream, Clone_4k) +{ TestStringStreamClone(1024 * 4); } + +static nsresult CloseStreamThenRead(nsIInputStream* aInStr, void* aClosure, + const char* aBuffer, uint32_t aOffset, + uint32_t aCount, uint32_t* aCountWritten) { + // Closing the stream will free the data + nsresult rv = aInStr->Close(); + if (NS_FAILED(rv)) { + return rv; + } + // This will likely be allocated in the same slot as what we have in aBuffer + char* newAlloc = moz_xstrdup("abcd"); + + char* toBuf = static_cast(aClosure); + memcpy(&toBuf[aOffset], aBuffer, aCount); + *aCountWritten = aCount; + free(newAlloc); + return NS_OK; +} + +TEST(StringStream, CancelInReadSegments) +{ + char* buffer = moz_xstrdup("test"); + nsCOMPtr stream; + nsresult rv = NS_NewByteInputStream( + getter_AddRefs(stream), mozilla::Span(buffer, 5), NS_ASSIGNMENT_ADOPT); + ASSERT_NS_SUCCEEDED(rv); + + char buf[100]; + uint32_t count = 0; + uint64_t available = 0; + rv = stream->Available(&available); + ASSERT_NS_SUCCEEDED(rv); + rv = stream->ReadSegments(CloseStreamThenRead, buf, available, &count); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(count == 5); + ASSERT_TRUE(!strcmp(buf, "test")); +} diff --git a/xpcom/tests/gtest/TestStrings.cpp b/xpcom/tests/gtest/TestStrings.cpp new file mode 100644 index 0000000000..7e0f986d29 --- /dev/null +++ b/xpcom/tests/gtest/TestStrings.cpp @@ -0,0 +1,2801 @@ +/* -*- 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 +#include +#include "nsASCIIMask.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsPrintfCString.h" +#include "nsString.h" +#include "nsStringBuffer.h" +#include "nsReadableUtils.h" +#include "nsCRTGlue.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TextUtils.h" +#include "mozilla/Unused.h" +#include "mozilla/Utf8.h" +#include "nsTArray.h" +#include "gtest/gtest.h" +#include "gtest/MozGTestBench.h" // For MOZ_GTEST_BENCH +#include "gtest/BlackBox.h" +#include "nsBidiUtils.h" +#include "js/String.h" + +#define CONVERSION_ITERATIONS 50000 + +#define CONVERSION_BENCH(name, func, src, dstType) \ + MOZ_GTEST_BENCH_F(Strings, name, [this] { \ + for (int i = 0; i < CONVERSION_ITERATIONS; i++) { \ + dstType dst; \ + func(*BlackBox(&src), *BlackBox(&dst)); \ + } \ + }); + +// Disable the C++ 2a warning. See bug #1509926 +#if defined(__clang__) && (__clang_major__ >= 6) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wc++2a-compat" +#endif + +namespace TestStrings { + +using mozilla::BlackBox; +using mozilla::fallible; +using mozilla::IsAscii; +using mozilla::IsUtf8; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::Some; +using mozilla::Span; + +#define TestExample1 \ + "Sed ut perspiciatis unde omnis iste natus error sit voluptatem " \ + "accusantium doloremque laudantium,\n totam rem aperiam, eaque ipsa quae " \ + "ab illo inventore veritatis et quasi\r architecto beatae vitae dicta sunt " \ + "explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur\n aut " \ + "odit aut fugit, sed quia consequuntur magni dolores eos qui ratione " \ + "voluptatem sequi nesciunt. Neque porro quisquam est, qui\r\n\r dolorem " \ + "ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non " \ + "numquam eius modi tempora incidunt ut labore et dolore magnam aliquam " \ + "quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem " \ + "ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi " \ + "consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate " \ + "velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum " \ + "fugiat quo voluptas nulla pariatur?" + +#define TestExample2 \ + "At vero eos et accusamus et iusto odio dignissimos ducimus\n\n qui " \ + "blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et " \ + "quas molestias excepturi sint occaecati cupiditate non provident, " \ + "similique sunt in culpa qui officia deserunt\r\r \n mollitia animi, id " \ + "est laborum et dolorum fuga. Et harum quidem rerum facilis est et " \ + "expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi " \ + "optio cumque nihil impedit quo minus id quod maxime placeat facere " \ + "possimus, omnis voluptas assumenda est, omnis dolor repellendus. " \ + "Temporibus autem quibusdam et aut officiis debitis aut rerum " \ + "necessitatibus saepe eveniet ut et voluptates repudiandae sint et " \ + "molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente " \ + "delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut " \ + "perferendis doloribus asperiores repellat." + +#define TestExample3 \ + " Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis ac tellus " \ + "eget velit viverra viverra id sit amet neque. Sed id consectetur mi, " \ + "vestibulum aliquet arcu. Curabitur sagittis accumsan convallis. Sed eu " \ + "condimentum ipsum, a laoreet tortor. Orci varius natoque penatibus et " \ + "magnis dis \r\r\n\n parturient montes, nascetur ridiculus mus. Sed non " \ + "tellus nec ante sodales placerat a nec risus. Cras vel bibendum sapien, " \ + "nec ullamcorper felis. Pellentesque congue eget nisi sit amet vehicula. " \ + "Morbi pulvinar turpis justo, in commodo dolor vulputate id. Curabitur in " \ + "dui urna. Vestibulum placerat dui in sem congue, ut faucibus nibh rutrum. " \ + "Duis mattis turpis facilisis ullamcorper tincidunt. Vestibulum pharetra " \ + "tortor at enim sagittis, dapibus consectetur ex blandit. Curabitur ac " \ + "fringilla quam. In ornare lectus ut ipsum mattis venenatis. Etiam in " \ + "mollis lectus, sed luctus risus.\nCras dapibus\f\t \n finibus justo sit " \ + "amet dictum. Aliquam non elit diam. Fusce magna nulla, bibendum in massa " \ + "a, commodo finibus lectus. Sed rutrum a augue id imperdiet. Aliquam " \ + "sagittis sodales felis, a tristique ligula. Aliquam erat volutpat. " \ + "Pellentesque habitant morbi tristique senectus et netus et malesuada " \ + "fames ac turpis egestas. Duis volutpat interdum lorem et congue. " \ + "Phasellus porttitor posuere justo eget euismod. Nam a condimentum turpis, " \ + "sit amet gravida lacus. Vestibulum dolor diam, lobortis ac metus et, " \ + "convallis dapibus tellus. Ut nec metus in velit malesuada tincidunt et " \ + "eget justo. Curabitur ut libero bibendum, porttitor diam vitae, aliquet " \ + "justo. " + +#define TestExample4 \ + " Donec feugiat volutpat massa. Cras ornare lacinia porta. Fusce in " \ + "feugiat nunc. Praesent non felis varius diam feugiat ultrices ultricies a " \ + "risus. Donec maximus nisi nisl, non consectetur nulla eleifend in. Nulla " \ + "in massa interdum, eleifend orci a, vestibulum est. Mauris aliquet, massa " \ + "et convallis mollis, felis augue vestibulum augue, in lobortis metus eros " \ + "a quam. Nam ac diam ornare, vestibulum elit sit amet, " \ + "consectetur ante. Praesent massa mauris, pulvinar sit amet sapien vel, " \ + "tempus gravida neque. Praesent id quam sit amet est maximus molestie eget " \ + "at turpis. Nunc sit amet orci id arcu dapibus fermentum non eu " \ + "erat.\f\tSuspendisse commodo nunc sem, eu congue eros condimentum vel. " \ + "Nullam sit amet posuere arcu. Nulla facilisi. Mauris dapibus iaculis " \ + "massa sed gravida. Nullam vitae urna at tortor feugiat auctor ut sit amet " \ + "dolor. Proin rutrum at nunc et faucibus. Quisque suscipit id nibh a " \ + "aliquet. Pellentesque habitant morbi tristique senectus et netus et " \ + "malesuada fames ac turpis egestas. Aliquam a dapibus erat, id imperdiet " \ + "mauris. Nulla blandit libero non magna dapibus tristique. Integer " \ + "hendrerit imperdiet lorem, quis facilisis lacus semper ut. Vestibulum " \ + "ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia " \ + "Curae Nullam dignissim elit in congue ultricies. Quisque erat odio, " \ + "maximus mollis laoreet id, iaculis at turpis. " + +#define TestExample5 \ + "Donec id risus urna. Nunc consequat lacinia urna id bibendum. Nulla " \ + "faucibus faucibus enim. Cras ex risus, ultrices id semper vitae, luctus " \ + "ut nulla. Sed vehicula tellus sed purus imperdiet efficitur. Suspendisse " \ + "feugiat\n\n\n imperdiet odio, sed porta lorem feugiat nec. Curabitur " \ + "laoreet massa venenatis\r\n risus ornare\r\n, vitae feugiat tortor " \ + "accumsan. Lorem ipsum dolor sit amet, consectetur adipiscing elit. " \ + "Maecenas id scelerisque mauris, eget facilisis erat. Ut nec pulvinar " \ + "risus, sed iaculis ante. Mauris tincidunt, risus et pretium elementum, " \ + "leo nisi consectetur ligula, tincidunt suscipit erat velit eget libero. " \ + "Sed ac est tempus, consequat dolor mattis, mattis mi. " + +// Originally ReadVPXFile in TestVPXDecoding.cpp +static void ReadFile(const char* aPath, nsACString& aBuffer) { + FILE* f = fopen(aPath, "rb"); + ASSERT_NE(f, (FILE*)nullptr); + + int r = fseek(f, 0, SEEK_END); + ASSERT_EQ(r, 0); + + long size = ftell(f); + ASSERT_NE(size, -1); + aBuffer.SetLength(size); + + r = fseek(f, 0, SEEK_SET); + ASSERT_EQ(r, 0); + + size_t got = fread(aBuffer.BeginWriting(), 1, size, f); + ASSERT_EQ(got, size_t(size)); + + r = fclose(f); + ASSERT_EQ(r, 0); +} + +class Strings : public ::testing::Test { + protected: + void SetUp() override { + // Intentionally AssignASCII and not AssignLiteral + // to simulate the usual heap case. + mExample1Utf8.AssignASCII(TestExample1); + mExample2Utf8.AssignASCII(TestExample2); + mExample3Utf8.AssignASCII(TestExample3); + mExample4Utf8.AssignASCII(TestExample4); + mExample5Utf8.AssignASCII(TestExample5); + + // Use span to make the resulting string as ordinary as possible + mAsciiOneUtf8.Append(Span(mExample3Utf8).To(1)); + mAsciiThreeUtf8.Append(Span(mExample3Utf8).To(3)); + mAsciiFifteenUtf8.Append(Span(mExample3Utf8).To(15)); + mAsciiHundredUtf8.Append(Span(mExample3Utf8).To(100)); + mAsciiThousandUtf8.Append(Span(mExample3Utf8).To(1000)); + + ReadFile("ar.txt", mArUtf8); + ReadFile("de.txt", mDeUtf8); + ReadFile("de-edit.txt", mDeEditUtf8); + ReadFile("ru.txt", mRuUtf8); + ReadFile("th.txt", mThUtf8); + ReadFile("ko.txt", mKoUtf8); + ReadFile("ja.txt", mJaUtf8); + ReadFile("tr.txt", mTrUtf8); + ReadFile("vi.txt", mViUtf8); + + CopyASCIItoUTF16(mExample1Utf8, mExample1Utf16); + CopyASCIItoUTF16(mExample2Utf8, mExample2Utf16); + CopyASCIItoUTF16(mExample3Utf8, mExample3Utf16); + CopyASCIItoUTF16(mExample4Utf8, mExample4Utf16); + CopyASCIItoUTF16(mExample5Utf8, mExample5Utf16); + + CopyASCIItoUTF16(mAsciiOneUtf8, mAsciiOneUtf16); + CopyASCIItoUTF16(mAsciiFifteenUtf8, mAsciiFifteenUtf16); + CopyASCIItoUTF16(mAsciiHundredUtf8, mAsciiHundredUtf16); + CopyASCIItoUTF16(mAsciiThousandUtf8, mAsciiThousandUtf16); + + CopyUTF8toUTF16(mArUtf8, mArUtf16); + CopyUTF8toUTF16(mDeUtf8, mDeUtf16); + CopyUTF8toUTF16(mDeEditUtf8, mDeEditUtf16); + CopyUTF8toUTF16(mRuUtf8, mRuUtf16); + CopyUTF8toUTF16(mThUtf8, mThUtf16); + CopyUTF8toUTF16(mJaUtf8, mJaUtf16); + CopyUTF8toUTF16(mKoUtf8, mKoUtf16); + CopyUTF8toUTF16(mTrUtf8, mTrUtf16); + CopyUTF8toUTF16(mViUtf8, mViUtf16); + + LossyCopyUTF16toASCII(mDeEditUtf16, mDeEditLatin1); + + // Use span to make the resulting string as ordinary as possible + mArOneUtf16.Append(Span(mArUtf16).To(1)); + mDeOneUtf16.Append(Span(mDeUtf16).To(1)); + mDeEditOneUtf16.Append(Span(mDeEditUtf16).To(1)); + mRuOneUtf16.Append(Span(mRuUtf16).To(1)); + mThOneUtf16.Append(Span(mThUtf16).To(1)); + mJaOneUtf16.Append(Span(mJaUtf16).To(1)); + mKoOneUtf16.Append(Span(mKoUtf16).To(1)); + mTrOneUtf16.Append(Span(mTrUtf16).To(1)); + mViOneUtf16.Append(Span(mViUtf16).To(1)); + + mDeEditOneLatin1.Append(Span(mDeEditLatin1).To(1)); + + mArThreeUtf16.Append(Span(mArUtf16).To(3)); + mDeThreeUtf16.Append(Span(mDeUtf16).To(3)); + mDeEditThreeUtf16.Append(Span(mDeEditUtf16).To(3)); + mRuThreeUtf16.Append(Span(mRuUtf16).To(3)); + mThThreeUtf16.Append(Span(mThUtf16).To(3)); + mJaThreeUtf16.Append(Span(mJaUtf16).To(3)); + mKoThreeUtf16.Append(Span(mKoUtf16).To(3)); + mTrThreeUtf16.Append(Span(mTrUtf16).To(3)); + mViThreeUtf16.Append(Span(mViUtf16).To(3)); + + mDeEditThreeLatin1.Append(Span(mDeEditLatin1).To(3)); + + mArFifteenUtf16.Append(Span(mArUtf16).To(15)); + mDeFifteenUtf16.Append(Span(mDeUtf16).To(15)); + mDeEditFifteenUtf16.Append(Span(mDeEditUtf16).To(15)); + mRuFifteenUtf16.Append(Span(mRuUtf16).To(15)); + mThFifteenUtf16.Append(Span(mThUtf16).To(15)); + mJaFifteenUtf16.Append(Span(mJaUtf16).To(15)); + mKoFifteenUtf16.Append(Span(mKoUtf16).To(15)); + mTrFifteenUtf16.Append(Span(mTrUtf16).To(15)); + mViFifteenUtf16.Append(Span(mViUtf16).To(15)); + + mDeEditFifteenLatin1.Append(Span(mDeEditLatin1).To(15)); + + mArHundredUtf16.Append(Span(mArUtf16).To(100)); + mDeHundredUtf16.Append(Span(mDeUtf16).To(100)); + mDeEditHundredUtf16.Append(Span(mDeEditUtf16).To(100)); + mRuHundredUtf16.Append(Span(mRuUtf16).To(100)); + mThHundredUtf16.Append(Span(mThUtf16).To(100)); + mJaHundredUtf16.Append(Span(mJaUtf16).To(100)); + mKoHundredUtf16.Append(Span(mKoUtf16).To(100)); + mTrHundredUtf16.Append(Span(mTrUtf16).To(100)); + mViHundredUtf16.Append(Span(mViUtf16).To(100)); + + mDeEditHundredLatin1.Append(Span(mDeEditLatin1).To(100)); + + mArThousandUtf16.Append(Span(mArUtf16).To(1000)); + mDeThousandUtf16.Append(Span(mDeUtf16).To(1000)); + mDeEditThousandUtf16.Append(Span(mDeEditUtf16).To(1000)); + mRuThousandUtf16.Append(Span(mRuUtf16).To(1000)); + mThThousandUtf16.Append(Span(mThUtf16).To(1000)); + mJaThousandUtf16.Append(Span(mJaUtf16).To(1000)); + mKoThousandUtf16.Append(Span(mKoUtf16).To(1000)); + mTrThousandUtf16.Append(Span(mTrUtf16).To(1000)); + mViThousandUtf16.Append(Span(mViUtf16).To(1000)); + + mDeEditThousandLatin1.Append(Span(mDeEditLatin1).To(1000)); + + CopyUTF16toUTF8(mArOneUtf16, mArOneUtf8); + CopyUTF16toUTF8(mDeOneUtf16, mDeOneUtf8); + CopyUTF16toUTF8(mDeEditOneUtf16, mDeEditOneUtf8); + CopyUTF16toUTF8(mRuOneUtf16, mRuOneUtf8); + CopyUTF16toUTF8(mThOneUtf16, mThOneUtf8); + CopyUTF16toUTF8(mJaOneUtf16, mJaOneUtf8); + CopyUTF16toUTF8(mKoOneUtf16, mKoOneUtf8); + CopyUTF16toUTF8(mTrOneUtf16, mTrOneUtf8); + CopyUTF16toUTF8(mViOneUtf16, mViOneUtf8); + + CopyUTF16toUTF8(mArThreeUtf16, mArThreeUtf8); + CopyUTF16toUTF8(mDeThreeUtf16, mDeThreeUtf8); + CopyUTF16toUTF8(mDeEditThreeUtf16, mDeEditThreeUtf8); + CopyUTF16toUTF8(mRuThreeUtf16, mRuThreeUtf8); + CopyUTF16toUTF8(mThThreeUtf16, mThThreeUtf8); + CopyUTF16toUTF8(mJaThreeUtf16, mJaThreeUtf8); + CopyUTF16toUTF8(mKoThreeUtf16, mKoThreeUtf8); + CopyUTF16toUTF8(mTrThreeUtf16, mTrThreeUtf8); + CopyUTF16toUTF8(mViThreeUtf16, mViThreeUtf8); + + CopyUTF16toUTF8(mArFifteenUtf16, mArFifteenUtf8); + CopyUTF16toUTF8(mDeFifteenUtf16, mDeFifteenUtf8); + CopyUTF16toUTF8(mDeEditFifteenUtf16, mDeEditFifteenUtf8); + CopyUTF16toUTF8(mRuFifteenUtf16, mRuFifteenUtf8); + CopyUTF16toUTF8(mThFifteenUtf16, mThFifteenUtf8); + CopyUTF16toUTF8(mJaFifteenUtf16, mJaFifteenUtf8); + CopyUTF16toUTF8(mKoFifteenUtf16, mKoFifteenUtf8); + CopyUTF16toUTF8(mTrFifteenUtf16, mTrFifteenUtf8); + CopyUTF16toUTF8(mViFifteenUtf16, mViFifteenUtf8); + + CopyUTF16toUTF8(mArHundredUtf16, mArHundredUtf8); + CopyUTF16toUTF8(mDeHundredUtf16, mDeHundredUtf8); + CopyUTF16toUTF8(mDeEditHundredUtf16, mDeEditHundredUtf8); + CopyUTF16toUTF8(mRuHundredUtf16, mRuHundredUtf8); + CopyUTF16toUTF8(mThHundredUtf16, mThHundredUtf8); + CopyUTF16toUTF8(mJaHundredUtf16, mJaHundredUtf8); + CopyUTF16toUTF8(mKoHundredUtf16, mKoHundredUtf8); + CopyUTF16toUTF8(mTrHundredUtf16, mTrHundredUtf8); + CopyUTF16toUTF8(mViHundredUtf16, mViHundredUtf8); + + CopyUTF16toUTF8(mArThousandUtf16, mArThousandUtf8); + CopyUTF16toUTF8(mDeThousandUtf16, mDeThousandUtf8); + CopyUTF16toUTF8(mDeEditThousandUtf16, mDeEditThousandUtf8); + CopyUTF16toUTF8(mRuThousandUtf16, mRuThousandUtf8); + CopyUTF16toUTF8(mThThousandUtf16, mThThousandUtf8); + CopyUTF16toUTF8(mJaThousandUtf16, mJaThousandUtf8); + CopyUTF16toUTF8(mKoThousandUtf16, mKoThousandUtf8); + CopyUTF16toUTF8(mTrThousandUtf16, mTrThousandUtf8); + CopyUTF16toUTF8(mViThousandUtf16, mViThousandUtf8); + } + + public: + nsCString mAsciiOneUtf8; + nsCString mAsciiThreeUtf8; + nsCString mAsciiFifteenUtf8; + nsCString mAsciiHundredUtf8; + nsCString mAsciiThousandUtf8; + nsCString mExample1Utf8; + nsCString mExample2Utf8; + nsCString mExample3Utf8; + nsCString mExample4Utf8; + nsCString mExample5Utf8; + nsCString mArUtf8; + nsCString mDeUtf8; + nsCString mDeEditUtf8; + nsCString mRuUtf8; + nsCString mThUtf8; + nsCString mJaUtf8; + nsCString mKoUtf8; + nsCString mTrUtf8; + nsCString mViUtf8; + + nsString mAsciiOneUtf16; + nsString mAsciiThreeUtf16; + nsString mAsciiFifteenUtf16; + nsString mAsciiHundredUtf16; + nsString mAsciiThousandUtf16; + nsString mExample1Utf16; + nsString mExample2Utf16; + nsString mExample3Utf16; + nsString mExample4Utf16; + nsString mExample5Utf16; + nsString mArUtf16; + nsString mDeUtf16; + nsString mDeEditUtf16; + nsString mRuUtf16; + nsString mThUtf16; + nsString mJaUtf16; + nsString mKoUtf16; + nsString mTrUtf16; + nsString mViUtf16; + + nsCString mDeEditLatin1; + + nsString mArOneUtf16; + nsString mDeOneUtf16; + nsString mDeEditOneUtf16; + nsString mRuOneUtf16; + nsString mThOneUtf16; + nsString mJaOneUtf16; + nsString mKoOneUtf16; + nsString mTrOneUtf16; + nsString mViOneUtf16; + + nsCString mDeEditOneLatin1; + + nsCString mArOneUtf8; + nsCString mDeOneUtf8; + nsCString mDeEditOneUtf8; + nsCString mRuOneUtf8; + nsCString mThOneUtf8; + nsCString mJaOneUtf8; + nsCString mKoOneUtf8; + nsCString mTrOneUtf8; + nsCString mViOneUtf8; + + nsString mArThreeUtf16; + nsString mDeThreeUtf16; + nsString mDeEditThreeUtf16; + nsString mRuThreeUtf16; + nsString mThThreeUtf16; + nsString mJaThreeUtf16; + nsString mKoThreeUtf16; + nsString mTrThreeUtf16; + nsString mViThreeUtf16; + + nsCString mDeEditThreeLatin1; + + nsCString mArThreeUtf8; + nsCString mDeThreeUtf8; + nsCString mDeEditThreeUtf8; + nsCString mRuThreeUtf8; + nsCString mThThreeUtf8; + nsCString mJaThreeUtf8; + nsCString mKoThreeUtf8; + nsCString mTrThreeUtf8; + nsCString mViThreeUtf8; + + nsString mArFifteenUtf16; + nsString mDeFifteenUtf16; + nsString mDeEditFifteenUtf16; + nsString mRuFifteenUtf16; + nsString mThFifteenUtf16; + nsString mJaFifteenUtf16; + nsString mKoFifteenUtf16; + nsString mTrFifteenUtf16; + nsString mViFifteenUtf16; + + nsCString mDeEditFifteenLatin1; + + nsCString mArFifteenUtf8; + nsCString mDeFifteenUtf8; + nsCString mDeEditFifteenUtf8; + nsCString mRuFifteenUtf8; + nsCString mThFifteenUtf8; + nsCString mJaFifteenUtf8; + nsCString mKoFifteenUtf8; + nsCString mTrFifteenUtf8; + nsCString mViFifteenUtf8; + + nsString mArHundredUtf16; + nsString mDeHundredUtf16; + nsString mDeEditHundredUtf16; + nsString mRuHundredUtf16; + nsString mThHundredUtf16; + nsString mJaHundredUtf16; + nsString mKoHundredUtf16; + nsString mTrHundredUtf16; + nsString mViHundredUtf16; + + nsCString mDeEditHundredLatin1; + + nsCString mArHundredUtf8; + nsCString mDeHundredUtf8; + nsCString mDeEditHundredUtf8; + nsCString mRuHundredUtf8; + nsCString mThHundredUtf8; + nsCString mJaHundredUtf8; + nsCString mKoHundredUtf8; + nsCString mTrHundredUtf8; + nsCString mViHundredUtf8; + + nsString mArThousandUtf16; + nsString mDeThousandUtf16; + nsString mDeEditThousandUtf16; + nsString mRuThousandUtf16; + nsString mThThousandUtf16; + nsString mJaThousandUtf16; + nsString mKoThousandUtf16; + nsString mTrThousandUtf16; + nsString mViThousandUtf16; + + nsCString mDeEditThousandLatin1; + + nsCString mArThousandUtf8; + nsCString mDeThousandUtf8; + nsCString mDeEditThousandUtf8; + nsCString mRuThousandUtf8; + nsCString mThThousandUtf8; + nsCString mJaThousandUtf8; + nsCString mKoThousandUtf8; + nsCString mTrThousandUtf8; + nsCString mViThousandUtf8; +}; + +static void test_assign_helper(const nsACString& in, nsACString& _retval) { + _retval = in; +} + +// Simple helper struct to test if conditionally enabled string functions are +// working. +template +struct EnableTest { + template > + bool IsChar16() { + return true; + } + + template > + bool IsChar16(int dummy = 42) { + return false; + } + + template > + bool IsChar() { + return false; + } + + template > + bool IsChar(int dummy = 42) { + return true; + } +}; + +TEST_F(Strings, IsChar) { + EnableTest charTest; + EXPECT_TRUE(charTest.IsChar()); + EXPECT_FALSE(charTest.IsChar16()); + + EnableTest char16Test; + EXPECT_TRUE(char16Test.IsChar16()); + EXPECT_FALSE(char16Test.IsChar()); + +#ifdef COMPILATION_FAILURE_TEST + nsAutoCString a_ctest; + nsAutoString a_test; + + a_ctest.AssignLiteral("hello"); + // This should cause a compilation failure. + a_ctest.AssignLiteral(u"hello"); + a_test.AssignLiteral(u"hello"); + a_test.AssignLiteral("hello"); +#endif +} + +TEST_F(Strings, DependentStrings) { + // A few tests that make sure copying nsTDependentStrings behaves properly. + using DataFlags = mozilla::detail::StringDataFlags; + + { + // Test copy ctor. + nsDependentCString tmp("foo"); + auto data = tmp.Data(); + nsDependentCString foo(tmp); + // Neither string should be using a shared buffer. + EXPECT_FALSE(tmp.GetDataFlags() & DataFlags::REFCOUNTED); + EXPECT_FALSE(foo.GetDataFlags() & DataFlags::REFCOUNTED); + // Both strings should be pointing to the original buffer. + EXPECT_EQ(data, tmp.Data()); + EXPECT_EQ(data, foo.Data()); + } + { + // Test move ctor. + nsDependentCString tmp("foo"); + auto data = tmp.Data(); + nsDependentCString foo(std::move(tmp)); + // Neither string should be using a shared buffer. + EXPECT_FALSE(tmp.GetDataFlags() & DataFlags::REFCOUNTED); + EXPECT_FALSE(foo.GetDataFlags() & DataFlags::REFCOUNTED); + // First string should be reset, the second should be pointing to the + // original buffer. + EXPECT_NE(data, tmp.Data()); + EXPECT_EQ(data, foo.Data()); + EXPECT_TRUE(tmp.IsEmpty()); + } + { + // Test copying to a nsCString. + nsDependentCString tmp("foo"); + auto data = tmp.Data(); + nsCString foo(tmp); + // Original string should not be shared, copy should be shared. + EXPECT_FALSE(tmp.GetDataFlags() & DataFlags::REFCOUNTED); + EXPECT_TRUE(foo.GetDataFlags() & DataFlags::REFCOUNTED); + // First string should remain the same, the second should be pointing to + // a new buffer. + EXPECT_EQ(data, tmp.Data()); + EXPECT_NE(data, foo.Data()); + } +} + +TEST_F(Strings, assign) { + nsCString result; + test_assign_helper("a"_ns + "b"_ns, result); + EXPECT_STREQ(result.get(), "ab"); +} + +TEST_F(Strings, assign_c) { + nsCString c; + c.Assign('c'); + EXPECT_STREQ(c.get(), "c"); +} + +TEST_F(Strings, test1) { + constexpr auto empty = u""_ns; + const nsAString& aStr = empty; + + nsAutoString buf(aStr); + + int32_t n = buf.FindChar(','); + EXPECT_EQ(n, kNotFound); + + n = buf.Length(); + + buf.Cut(0, n + 1); + n = buf.FindChar(','); + + EXPECT_EQ(n, kNotFound); +} + +TEST_F(Strings, test2) { + nsCString data("hello world"); + const nsACString& aStr = data; + + nsCString temp(aStr); + temp.Cut(0, 6); + + EXPECT_STREQ(temp.get(), "world"); +} + +TEST_F(Strings, find) { + nsCString src(""); + + int32_t i = src.Find("DOCTYPE", 2); + EXPECT_EQ(i, 2); + + i = src.Find("DOCTYPE"); + EXPECT_EQ(i, 2); +} + +TEST_F(Strings, lower_case_find) { + nsCString src(""); + + int32_t i = src.LowerCaseFindASCII("doctype", 2); + EXPECT_EQ(i, 2); + + i = src.LowerCaseFindASCII("doctype"); + EXPECT_EQ(i, 2); +} + +TEST_F(Strings, rfind) { + const char text[] = ""; + nsCString src(text); + int32_t i; + + i = src.RFind("bLaH"); + EXPECT_EQ(i, 20); + + i = src.RFind("blah"); + EXPECT_EQ(i, 10); + + i = src.RFind("BLAH"); + EXPECT_EQ(i, kNotFound); +} + +TEST_F(Strings, rfind_2) { + const char text[] = ""; + nsCString src(text); + int32_t i = src.RFind("TYPE"); + EXPECT_EQ(i, 5); +} + +TEST_F(Strings, rfind_3) { + const char text[] = "urn:mozilla:locale:en-US:necko"; + nsAutoCString value(text); + int32_t i = value.RFind(":"); + EXPECT_EQ(i, 24); +} + +TEST_F(Strings, rfind_4) { + nsCString value("a.msf"); + int32_t i = value.RFind(".msf"); + EXPECT_EQ(i, 1); +} + +TEST_F(Strings, findinreadable) { + const char text[] = + "jar:jar:file:///c:/software/mozilla/mozilla_2006_02_21.jar!/browser/" + "chrome/classic.jar!/"; + nsAutoCString value(text); + + nsACString::const_iterator begin, end; + value.BeginReading(begin); + value.EndReading(end); + nsACString::const_iterator delim_begin(begin), delim_end(end); + + // Search for last !/ at the end of the string + EXPECT_TRUE(FindInReadable("!/"_ns, delim_begin, delim_end)); + char* r = ToNewCString(Substring(delim_begin, delim_end)); + // Should match the first "!/" but not the last + EXPECT_NE(delim_end, end); + EXPECT_STREQ(r, "!/"); + free(r); + + delim_begin = begin; + delim_end = end; + + // Search for first jar: + EXPECT_TRUE(FindInReadable("jar:"_ns, delim_begin, delim_end)); + + r = ToNewCString(Substring(delim_begin, delim_end)); + // Should not match the first jar:, but the second one + EXPECT_EQ(delim_begin, begin); + EXPECT_STREQ(r, "jar:"); + free(r); + + // Search for jar: in a Substring + delim_begin = begin; + delim_begin++; + delim_end = end; + EXPECT_TRUE(FindInReadable("jar:"_ns, delim_begin, delim_end)); + + r = ToNewCString(Substring(delim_begin, delim_end)); + // Should not match the first jar:, but the second one + EXPECT_NE(delim_begin, begin); + EXPECT_STREQ(r, "jar:"); + free(r); + + // Should not find a match + EXPECT_FALSE(FindInReadable("gecko"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); + + // Should not find a match (search not beyond Substring) + delim_begin = begin; + for (int i = 0; i < 6; i++) delim_begin++; + delim_end = end; + EXPECT_FALSE(FindInReadable("jar:"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); + + // Should not find a match (search not beyond Substring) + delim_begin = begin; + delim_end = end; + for (int i = 0; i < 7; i++) delim_end--; + EXPECT_FALSE(FindInReadable("classic"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); +} + +TEST_F(Strings, rfindinreadable) { + const char text[] = + "jar:jar:file:///c:/software/mozilla/mozilla_2006_02_21.jar!/browser/" + "chrome/classic.jar!/"; + nsAutoCString value(text); + + nsACString::const_iterator begin, end; + value.BeginReading(begin); + value.EndReading(end); + nsACString::const_iterator delim_begin(begin), delim_end(end); + + // Search for last !/ at the end of the string + EXPECT_TRUE(RFindInReadable("!/"_ns, delim_begin, delim_end)); + char* r = ToNewCString(Substring(delim_begin, delim_end)); + // Should match the last "!/" + EXPECT_EQ(delim_end, end); + EXPECT_STREQ(r, "!/"); + free(r); + + delim_begin = begin; + delim_end = end; + + // Search for last jar: but not the first one... + EXPECT_TRUE(RFindInReadable("jar:"_ns, delim_begin, delim_end)); + + r = ToNewCString(Substring(delim_begin, delim_end)); + // Should not match the first jar:, but the second one + EXPECT_NE(delim_begin, begin); + EXPECT_STREQ(r, "jar:"); + free(r); + + // Search for jar: in a Substring + delim_begin = begin; + delim_end = begin; + for (int i = 0; i < 6; i++) delim_end++; + EXPECT_TRUE(RFindInReadable("jar:"_ns, delim_begin, delim_end)); + + r = ToNewCString(Substring(delim_begin, delim_end)); + // Should not match the first jar:, but the second one + EXPECT_EQ(delim_begin, begin); + EXPECT_STREQ(r, "jar:"); + free(r); + + // Should not find a match + delim_begin = begin; + delim_end = end; + EXPECT_FALSE(RFindInReadable("gecko"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); + + // Should not find a match (search not before Substring) + delim_begin = begin; + for (int i = 0; i < 6; i++) delim_begin++; + delim_end = end; + EXPECT_FALSE(RFindInReadable("jar:"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); + + // Should not find a match (search not beyond Substring) + delim_begin = begin; + delim_end = end; + for (int i = 0; i < 7; i++) delim_end--; + EXPECT_FALSE(RFindInReadable("classic"_ns, delim_begin, delim_end)); + + // When no match is found, range should be empty + EXPECT_EQ(delim_begin, delim_end); +} + +TEST_F(Strings, distance) { + const char text[] = "abc-xyz"; + nsCString s(text); + nsCString::const_iterator begin, end; + s.BeginReading(begin); + s.EndReading(end); + size_t d = Distance(begin, end); + EXPECT_EQ(d, sizeof(text) - 1); +} + +TEST_F(Strings, length) { + const char text[] = "abc-xyz"; + nsCString s(text); + size_t d = s.Length(); + EXPECT_EQ(d, sizeof(text) - 1); +} + +TEST_F(Strings, trim) { + const char text[] = " a\t $ "; + const char set[] = " \t$"; + + nsCString s(text); + s.Trim(set); + EXPECT_STREQ(s.get(), "a"); + + s.AssignLiteral("\t \t\t \t"); + s.Trim(set); + EXPECT_STREQ(s.get(), ""); + + s.AssignLiteral(" "); + s.Trim(set); + EXPECT_STREQ(s.get(), ""); + + s.AssignLiteral(" "); + s.Trim(set, false, true); + EXPECT_STREQ(s.get(), ""); + + s.AssignLiteral(" "); + s.Trim(set, true, false); + EXPECT_STREQ(s.get(), ""); +} + +TEST_F(Strings, replace_substr) { + const char text[] = "abc-ppp-qqq-ppp-xyz"; + nsCString s(text); + s.ReplaceSubstring("ppp", "www"); + EXPECT_STREQ(s.get(), "abc-www-qqq-www-xyz"); + + s.AssignLiteral("foobar"); + s.ReplaceSubstring("foo", "bar"); + s.ReplaceSubstring("bar", ""); + EXPECT_STREQ(s.get(), ""); + + s.AssignLiteral("foofoofoo"); + s.ReplaceSubstring("foo", "foo"); + EXPECT_STREQ(s.get(), "foofoofoo"); + + s.AssignLiteral("foofoofoo"); + s.ReplaceSubstring("of", "fo"); + EXPECT_STREQ(s.get(), "fofoofooo"); +} + +TEST_F(Strings, replace_substr_2) { + const char* newName = "user"; + nsString acctName; + acctName.AssignLiteral("forums.foo.com"); + nsAutoString newAcctName, oldVal, newVal; + CopyASCIItoUTF16(mozilla::MakeStringSpan(newName), newVal); + newAcctName.Assign(acctName); + + // here, oldVal is empty. we are testing that this function + // does not hang. see bug 235355. + newAcctName.ReplaceSubstring(oldVal, newVal); + + // we expect that newAcctName will be unchanged. + EXPECT_TRUE(newAcctName.Equals(acctName)); +} + +TEST_F(Strings, replace_substr_3) { + nsCString s; + s.AssignLiteral("abcabcabc"); + s.ReplaceSubstring("ca", "X"); + EXPECT_STREQ(s.get(), "abXbXbc"); + + s.AssignLiteral("abcabcabc"); + s.ReplaceSubstring("ca", "XYZ"); + EXPECT_STREQ(s.get(), "abXYZbXYZbc"); + + s.AssignLiteral("abcabcabc"); + s.ReplaceSubstring("ca", "XY"); + EXPECT_STREQ(s.get(), "abXYbXYbc"); + + s.AssignLiteral("abcabcabc"); + s.ReplaceSubstring("ca", "XYZ!"); + EXPECT_STREQ(s.get(), "abXYZ!bXYZ!bc"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "X"); + EXPECT_STREQ(s.get(), "aXaXaX"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZ!"); + EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XY"); + EXPECT_STREQ(s.get(), "aXYaXYaXY"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZABC"); + EXPECT_STREQ(s.get(), "aXYZABCaXYZABCaXYZABC"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZ"); + EXPECT_STREQ(s.get(), "aXYZaXYZaXYZ"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("bcd", "XYZ!"); + EXPECT_STREQ(s.get(), "aXYZ!aXYZ!aXYZ!"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("ab", "X"); + EXPECT_STREQ(s.get(), "XcdXcdXcd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("ab", "XYZABC"); + EXPECT_STREQ(s.get(), "XYZABCcdXYZABCcdXYZABCcd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("ab", "XY"); + EXPECT_STREQ(s.get(), "XYcdXYcdXYcd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("ab", "XYZ!"); + EXPECT_STREQ(s.get(), "XYZ!cdXYZ!cdXYZ!cd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("notfound", "X"); + EXPECT_STREQ(s.get(), "abcdabcdabcd"); + + s.AssignLiteral("abcdabcdabcd"); + s.ReplaceSubstring("notfound", "longlongstring"); + EXPECT_STREQ(s.get(), "abcdabcdabcd"); +} + +TEST_F(Strings, strip_ws) { + const char* texts[] = {"", " a $ ", "Some\fother\t thing\r\n", + "And \f\t\r\n even\nmore\r \f"}; + const char* results[] = {"", "a$", "Someotherthing", "Andevenmore"}; + for (size_t i = 0; i < sizeof(texts) / sizeof(texts[0]); i++) { + nsCString s(texts[i]); + s.StripWhitespace(); + EXPECT_STREQ(s.get(), results[i]); + } +} + +TEST_F(Strings, equals_ic) { + nsCString s; + EXPECT_FALSE(s.LowerCaseEqualsLiteral("view-source")); +} + +TEST_F(Strings, concat) { + nsCString bar("bar"); + const nsACString& barRef = bar; + + const nsPromiseFlatCString& result = + PromiseFlatCString("foo"_ns + ","_ns + barRef); + EXPECT_STREQ(result.get(), "foo,bar"); +} + +TEST_F(Strings, concat_2) { + nsCString fieldTextStr("xyz"); + nsCString text("text"); + const nsACString& aText = text; + + nsAutoCString result(fieldTextStr + aText); + + EXPECT_STREQ(result.get(), "xyztext"); +} + +TEST_F(Strings, concat_3) { + nsCString result; + nsCString ab("ab"), c("c"); + + result = ab + result + c; + EXPECT_STREQ(result.get(), "abc"); +} + +TEST_F(Strings, empty_assign) { + nsCString a; + a.AssignLiteral(""); + + a.AppendLiteral(""); + + nsCString b; + b.SetCapacity(0); +} + +TEST_F(Strings, set_length) { + const char kText[] = "Default Plugin"; + nsCString buf; + buf.SetCapacity(sizeof(kText) - 1); + buf.Assign(kText); + buf.SetLength(sizeof(kText) - 1); + EXPECT_STREQ(buf.get(), kText); +} + +TEST_F(Strings, substring) { + nsCString super("hello world"), sub("hello"); + + // this tests that |super| starts with |sub|, + + EXPECT_TRUE(sub.Equals(StringHead(super, sub.Length()))); + + // and verifies that |sub| does not start with |super|. + + EXPECT_FALSE(super.Equals(StringHead(sub, super.Length()))); +} + +#define test_append_expect(str, int, suffix, expect) \ + str.Truncate(); \ + str.AppendInt(suffix = int); \ + EXPECT_TRUE(str.EqualsLiteral(expect)); + +#define test_appends_expect(int, suffix, expect) \ + test_append_expect(str, int, suffix, expect) \ + test_append_expect(cstr, int, suffix, expect) + +#define test_appendbase(str, prefix, int, suffix, base) \ + str.Truncate(); \ + str.AppendInt(suffix = prefix##int##suffix, base); \ + EXPECT_TRUE(str.EqualsLiteral(#int)); + +#define test_appendbases(prefix, int, suffix, base) \ + test_appendbase(str, prefix, int, suffix, base) \ + test_appendbase(cstr, prefix, int, suffix, base) + +TEST_F(Strings, appendint) { + nsString str; + nsCString cstr; + int32_t L; + uint32_t UL; + int64_t LL; + uint64_t ULL; + test_appends_expect(INT32_MAX, L, "2147483647"); + test_appends_expect(INT32_MIN, L, "-2147483648"); + test_appends_expect(UINT32_MAX, UL, "4294967295"); + test_appends_expect(INT64_MAX, LL, "9223372036854775807"); + test_appends_expect(INT64_MIN, LL, "-9223372036854775808"); + test_appends_expect(UINT64_MAX, ULL, "18446744073709551615"); + test_appendbases(0, 17777777777, L, 8); + test_appendbases(0, 20000000000, L, 8); + test_appendbases(0, 37777777777, UL, 8); + test_appendbases(0, 777777777777777777777, LL, 8); + test_appendbases(0, 1000000000000000000000, LL, 8); + test_appendbases(0, 1777777777777777777777, ULL, 8); + test_appendbases(0x, 7fffffff, L, 16); + test_appendbases(0x, 80000000, L, 16); + test_appendbases(0x, ffffffff, UL, 16); + test_appendbases(0x, 7fffffffffffffff, LL, 16); + test_appendbases(0x, 8000000000000000, LL, 16); + test_appendbases(0x, ffffffffffffffff, ULL, 16); +} + +TEST_F(Strings, appendint64) { + nsCString str; + + int64_t max = INT64_MAX; + static const char max_expected[] = "9223372036854775807"; + int64_t min = INT64_MIN; + static const char min_expected[] = "-9223372036854775808"; + static const char min_expected_oct[] = "1000000000000000000000"; + int64_t maxint_plus1 = 1LL << 32; + static const char maxint_plus1_expected[] = "4294967296"; + static const char maxint_plus1_expected_x[] = "100000000"; + + str.AppendInt(max); + + EXPECT_TRUE(str.Equals(max_expected)); + + str.Truncate(); + str.AppendInt(min); + EXPECT_TRUE(str.Equals(min_expected)); + str.Truncate(); + str.AppendInt(min, 8); + EXPECT_TRUE(str.Equals(min_expected_oct)); + + str.Truncate(); + str.AppendInt(maxint_plus1); + EXPECT_TRUE(str.Equals(maxint_plus1_expected)); + str.Truncate(); + str.AppendInt(maxint_plus1, 16); + EXPECT_TRUE(str.Equals(maxint_plus1_expected_x)); +} + +TEST_F(Strings, inttotstring) { + EXPECT_EQ("42"_ns, IntToCString(42)); + EXPECT_EQ(u"42"_ns, IntToString(42)); + + EXPECT_EQ("2a"_ns, IntToCString(42, 16)); + EXPECT_EQ(u"2a"_ns, IntToString(42, 16)); +} + +TEST_F(Strings, appendfloat) { + nsCString str; + double bigdouble = 11223344556.66; + static const char double_expected[] = "11223344556.66"; + static const char float_expected[] = "0.01"; + + // AppendFloat is used to append doubles, therefore the precision must be + // large enough (see bug 327719) + str.AppendFloat(bigdouble); + EXPECT_TRUE(str.Equals(double_expected)); + + str.Truncate(); + // AppendFloat is used to append floats (bug 327719 #27) + str.AppendFloat(0.1f * 0.1f); + EXPECT_TRUE(str.Equals(float_expected)); +} + +TEST_F(Strings, findcharinset) { + nsCString buf("hello, how are you?"); + + int32_t index = buf.FindCharInSet(",?", 5); + EXPECT_EQ(index, 5); + + index = buf.FindCharInSet("helo", 0); + EXPECT_EQ(index, 0); + + index = buf.FindCharInSet("z?", 6); + EXPECT_EQ(index, (int32_t)buf.Length() - 1); +} + +TEST_F(Strings, rfindcharinset) { + nsCString buf("hello, how are you?"); + + int32_t index = buf.RFindCharInSet(",?", 5); + EXPECT_EQ(index, 5); + + index = buf.RFindCharInSet("helo", 0); + EXPECT_EQ(index, 0); + + index = buf.RFindCharInSet("z?", 6); + EXPECT_EQ(index, kNotFound); + + index = buf.RFindCharInSet("l", 5); + EXPECT_EQ(index, 3); + + buf.AssignLiteral("abcdefghijkabc"); + + index = buf.RFindCharInSet("ab"); + EXPECT_EQ(index, 12); + + index = buf.RFindCharInSet("ab", 11); + EXPECT_EQ(index, 11); + + index = buf.RFindCharInSet("ab", 10); + EXPECT_EQ(index, 1); + + index = buf.RFindCharInSet("ab", 0); + EXPECT_EQ(index, 0); + + index = buf.RFindCharInSet("cd", 1); + EXPECT_EQ(index, kNotFound); + + index = buf.RFindCharInSet("h"); + EXPECT_EQ(index, 7); +} + +TEST_F(Strings, stringbuffer) { + const char kData[] = "hello world"; + + RefPtr buf; + + buf = nsStringBuffer::Alloc(sizeof(kData)); + EXPECT_TRUE(!!buf); + + buf = nsStringBuffer::Alloc(sizeof(kData)); + EXPECT_TRUE(!!buf); + char* data = (char*)buf->Data(); + memcpy(data, kData, sizeof(kData)); + + nsCString str; + buf->ToString(sizeof(kData) - 1, str); + + nsStringBuffer* buf2; + buf2 = nsStringBuffer::FromString(str); + + EXPECT_EQ(buf, buf2); +} + +TEST_F(Strings, voided) { + const char kData[] = "hello world"; + + nsCString str; + EXPECT_TRUE(!str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); + + str.SetIsVoid(true); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); + + str.Assign(kData); + EXPECT_TRUE(!str.IsVoid()); + EXPECT_TRUE(!str.IsEmpty()); + EXPECT_STREQ(str.get(), kData); + + str.SetIsVoid(true); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); + + str.SetIsVoid(false); + EXPECT_TRUE(!str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); + + str.Assign(kData); + EXPECT_TRUE(!str.IsVoid()); + EXPECT_TRUE(!str.IsEmpty()); + EXPECT_STREQ(str.get(), kData); + + str.Adopt(nullptr); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + EXPECT_STREQ(str.get(), ""); +} + +TEST_F(Strings, voided_autostr) { + const char kData[] = "hello world"; + + nsAutoCString str; + EXPECT_FALSE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + + str.Assign(kData); + EXPECT_STREQ(str.get(), kData); + + str.SetIsVoid(true); + EXPECT_TRUE(str.IsVoid()); + EXPECT_TRUE(str.IsEmpty()); + + str.Assign(kData); + EXPECT_FALSE(str.IsVoid()); + EXPECT_FALSE(str.IsEmpty()); + EXPECT_STREQ(str.get(), kData); +} + +TEST_F(Strings, voided_assignment) { + nsCString a, b; + b.SetIsVoid(true); + a = b; + EXPECT_TRUE(a.IsVoid()); + EXPECT_EQ(a.get(), b.get()); +} + +TEST_F(Strings, empty_assignment) { + nsCString a, b; + a = b; + EXPECT_EQ(a.get(), b.get()); +} + +struct ToIntegerTest { + const char* str; + uint32_t radix; + int32_t result; + nsresult rv; +}; + +static const ToIntegerTest kToIntegerTests[] = { + {"123", 10, 123, NS_OK}, + {"7b", 16, 123, NS_OK}, + {"90194313659", 10, 0, NS_ERROR_ILLEGAL_VALUE}, + {nullptr, 0, 0, NS_OK}}; + +TEST_F(Strings, string_tointeger) { + nsresult rv; + for (const ToIntegerTest* t = kToIntegerTests; t->str; ++t) { + int32_t result = nsAutoCString(t->str).ToInteger(&rv, t->radix); + EXPECT_EQ(rv, t->rv); + EXPECT_EQ(result, t->result); + result = nsAutoCString(t->str).ToInteger(&rv, t->radix); + EXPECT_EQ(rv, t->rv); + EXPECT_EQ(result, t->result); + } +} + +static void test_parse_string_helper(const char* str, char separator, int len, + const char* s1, const char* s2) { + nsCString data(str); + nsTArray results; + ParseString(data, separator, results); + EXPECT_EQ(int(results.Length()), len); + const char* strings[] = {s1, s2}; + for (int i = 0; i < len; ++i) { + EXPECT_TRUE(results[i].Equals(strings[i])); + } +} + +static void test_parse_string_helper0(const char* str, char separator) { + test_parse_string_helper(str, separator, 0, nullptr, nullptr); +} + +static void test_parse_string_helper1(const char* str, char separator, + const char* s1) { + test_parse_string_helper(str, separator, 1, s1, nullptr); +} + +static void test_parse_string_helper2(const char* str, char separator, + const char* s1, const char* s2) { + test_parse_string_helper(str, separator, 2, s1, s2); +} + +TEST(String, parse_string) +{ + test_parse_string_helper1("foo, bar", '_', "foo, bar"); + test_parse_string_helper2("foo, bar", ',', "foo", " bar"); + test_parse_string_helper2("foo, bar ", ' ', "foo,", "bar"); + test_parse_string_helper2("foo,bar", 'o', "f", ",bar"); + test_parse_string_helper0("", '_'); + test_parse_string_helper0(" ", ' '); + test_parse_string_helper1(" foo", ' ', "foo"); + test_parse_string_helper1(" foo", ' ', "foo"); +} + +static void test_strip_chars_helper(const char16_t* str, const char16_t* strip, + const nsAString& result) { + nsAutoString data(str); + data.StripChars(strip); + EXPECT_TRUE(data.Equals(result)); +} + +TEST(String, strip_chars) +{ + test_strip_chars_helper(u"foo \r \nbar", u" \n\r", u"foobar"_ns); + test_strip_chars_helper(u"\r\nfoo\r\n", u" \n\r", u"foo"_ns); + test_strip_chars_helper(u"foo", u" \n\r", u"foo"_ns); + test_strip_chars_helper(u"foo", u"fo", u""_ns); + test_strip_chars_helper(u"foo", u"foo", u""_ns); + test_strip_chars_helper(u" foo", u" ", u"foo"_ns); +} + +TEST_F(Strings, append_with_capacity) { + nsAutoString s; + const char16_t* origPtr = s.BeginReading(); + s.SetCapacity(8000); + const char16_t* ptr = s.BeginReading(); + EXPECT_NE(origPtr, ptr); + for (int i = 0; i < 100; i++) { + s.Append(u'a'); + EXPECT_EQ(s.BeginReading(), ptr); + EXPECT_EQ(s.Length(), uint32_t(i + 1)); + } +} + +TEST_F(Strings, append_string_with_capacity) { + nsAutoString aa; + aa.Append(u'a'); + aa.Append(u'a'); + nsAutoString s; + const char16_t* origPtr = s.BeginReading(); + s.SetCapacity(8000); + const char16_t* ptr = s.BeginReading(); + EXPECT_NE(origPtr, ptr); + nsAutoString empty; + s.Append(empty); + EXPECT_EQ(s.BeginReading(), ptr); + for (int i = 0; i < 100; i++) { + s.Append(aa); + EXPECT_EQ(s.BeginReading(), ptr); + EXPECT_EQ(s.Length(), uint32_t(2 * (i + 1))); + } +} + +TEST_F(Strings, append_literal_with_capacity) { + nsAutoString s; + const char16_t* origPtr = s.BeginReading(); + s.SetCapacity(8000); + const char16_t* ptr = s.BeginReading(); + EXPECT_NE(origPtr, ptr); + s.AppendLiteral(u""); + EXPECT_EQ(s.BeginReading(), ptr); + for (int i = 0; i < 100; i++) { + s.AppendLiteral(u"aa"); + EXPECT_EQ(s.BeginReading(), ptr); + EXPECT_EQ(s.Length(), uint32_t(2 * (i + 1))); + } +} + +// The following test is intentionally not memory +// checking-clean. +#if !(defined(MOZ_HAVE_MEM_CHECKS) || defined(MOZ_MSAN)) +TEST_F(Strings, legacy_set_length_semantics) { + const char* foobar = "foobar"; + nsCString s; + s.SetCapacity(2048); + memcpy(s.BeginWriting(), foobar, strlen(foobar)); + s.SetLength(strlen(foobar)); + EXPECT_FALSE(s.EqualsASCII(foobar)); +} +#endif + +TEST_F(Strings, bulk_write) { + nsCString s; + const char* ptrTwoThousand; + { + auto handleOrErr = s.BulkWrite(500, 0, true); + EXPECT_TRUE(handleOrErr.isOk()); + + auto handle = handleOrErr.unwrap(); + + auto span = handle.AsSpan(); + for (auto&& c : span) { + c = 'a'; + } + mozilla::Unused << handle.RestartBulkWrite(2000, 500, false); + span = handle.AsSpan().From(500); + for (auto&& c : span) { + c = 'b'; + } + ptrTwoThousand = handle.Elements(); + handle.Finish(1000, true); + } + EXPECT_EQ(s.Length(), 1000U); + EXPECT_NE(s.BeginReading(), ptrTwoThousand); + EXPECT_EQ(s.BeginReading()[1000], '\0'); + for (uint32_t i = 0; i < 500; i++) { + EXPECT_EQ(s[i], 'a'); + } + for (uint32_t i = 500; i < 1000; i++) { + EXPECT_EQ(s[i], 'b'); + } +} + +TEST_F(Strings, bulk_write_fail) { + nsCString s; + { + auto handleOrErr = s.BulkWrite(500, 0, true); + EXPECT_TRUE(handleOrErr.isOk()); + } + EXPECT_EQ(s.Length(), 3U); + EXPECT_TRUE(s.Equals(u8"\uFFFD")); +} + +TEST_F(Strings, huge_capacity) { + nsString a, b, c, d, e, f, g, h, i, j, k, l, m, n, o; + nsCString n1, o1; + + // Ignore the result if the address space is less than 64-bit because + // some of the allocations above will exhaust the address space. + if (sizeof(void*) >= 8) { + EXPECT_TRUE(a.SetCapacity(1, fallible)); + EXPECT_FALSE(a.SetCapacity(uint32_t(-1) / 2, fallible)); + a.Truncate(); // free the allocated memory + + EXPECT_TRUE(b.SetCapacity(1, fallible)); + EXPECT_FALSE(b.SetCapacity(uint32_t(-1) / 2 - 1, fallible)); + b.Truncate(); + + EXPECT_TRUE(c.SetCapacity(1, fallible)); + EXPECT_FALSE(c.SetCapacity(uint32_t(-1) / 2, fallible)); + c.Truncate(); + + EXPECT_FALSE(d.SetCapacity(uint32_t(-1) / 2 - 1, fallible)); + EXPECT_FALSE(d.SetCapacity(uint32_t(-1) / 2, fallible)); + d.Truncate(); + + EXPECT_FALSE(e.SetCapacity(uint32_t(-1) / 4, fallible)); + EXPECT_FALSE(e.SetCapacity(uint32_t(-1) / 4 + 1, fallible)); + e.Truncate(); + + EXPECT_FALSE(f.SetCapacity(uint32_t(-1) / 2, fallible)); + f.Truncate(); + + EXPECT_FALSE(g.SetCapacity(uint32_t(-1) / 4 + 1000, fallible)); + EXPECT_FALSE(g.SetCapacity(uint32_t(-1) / 4 + 1001, fallible)); + g.Truncate(); + + EXPECT_FALSE(h.SetCapacity(uint32_t(-1) / 4 + 1, fallible)); + EXPECT_FALSE(h.SetCapacity(uint32_t(-1) / 2, fallible)); + h.Truncate(); + + EXPECT_TRUE(i.SetCapacity(1, fallible)); + EXPECT_TRUE(i.SetCapacity(uint32_t(-1) / 4 - 1000, fallible)); + EXPECT_FALSE(i.SetCapacity(uint32_t(-1) / 4 + 1, fallible)); + i.Truncate(); + + EXPECT_TRUE(j.SetCapacity(uint32_t(-1) / 4 - 1000, fallible)); + EXPECT_FALSE(j.SetCapacity(uint32_t(-1) / 4 + 1, fallible)); + j.Truncate(); + +// Disabled due to intermittent failures. +// https://bugzilla.mozilla.org/show_bug.cgi?id=1493458 +#if 0 + EXPECT_TRUE(k.SetCapacity(uint32_t(-1)/8 - 1000, fallible)); + EXPECT_TRUE(k.SetCapacity(uint32_t(-1)/4 - 1001, fallible)); + EXPECT_TRUE(k.SetCapacity(uint32_t(-1)/4 - 998, fallible)); + EXPECT_FALSE(k.SetCapacity(uint32_t(-1)/4 + 1, fallible)); + k.Truncate(); +#endif + + EXPECT_TRUE(l.SetCapacity(uint32_t(-1) / 8, fallible)); + EXPECT_TRUE(l.SetCapacity(uint32_t(-1) / 8 + 1, fallible)); + EXPECT_TRUE(l.SetCapacity(uint32_t(-1) / 8 + 2, fallible)); + l.Truncate(); + + EXPECT_TRUE(m.SetCapacity(uint32_t(-1) / 8 + 1000, fallible)); + EXPECT_TRUE(m.SetCapacity(uint32_t(-1) / 8 + 1001, fallible)); + m.Truncate(); + + EXPECT_TRUE(n.SetCapacity(uint32_t(-1) / 8 + 1, fallible)); + EXPECT_FALSE(n.SetCapacity(uint32_t(-1) / 4, fallible)); + n.Truncate(); + + n.Truncate(); + EXPECT_TRUE(n.SetCapacity((uint32_t(-1) / 2) / 2 - 1, fallible)); + n.Truncate(); + EXPECT_FALSE(n.SetCapacity((uint32_t(-1) / 2) / 2, fallible)); + n.Truncate(); + n1.Truncate(); + EXPECT_TRUE(n1.SetCapacity((uint32_t(-1) / 2) / 1 - 1, fallible)); + n1.Truncate(); + EXPECT_FALSE(n1.SetCapacity((uint32_t(-1) / 2) / 1, fallible)); + n1.Truncate(); + + // The longest possible JS string should fit within both a `nsString` and + // nsCString. + EXPECT_TRUE(o.SetCapacity(JS::MaxStringLength, fallible)); + o.Truncate(); + EXPECT_TRUE(o1.SetCapacity(JS::MaxStringLength, fallible)); + o1.Truncate(); + } +} + +static void test_tofloat_helper(const nsString& aStr, + mozilla::Maybe aExpected) { + nsresult result; + float value = aStr.ToFloat(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); + } else { + EXPECT_TRUE(NS_FAILED(result)); + } +} + +TEST_F(Strings, tofloat) { + test_tofloat_helper(u"42"_ns, Some(42.f)); + test_tofloat_helper(u"42.0"_ns, Some(42.f)); + test_tofloat_helper(u"-42"_ns, Some(-42.f)); + test_tofloat_helper(u"+42"_ns, Some(42)); + test_tofloat_helper(u"13.37"_ns, Some(13.37f)); + test_tofloat_helper(u"1.23456789"_ns, Some(1.23456789f)); + test_tofloat_helper(u"1.98765432123456"_ns, Some(1.98765432123456f)); + test_tofloat_helper(u"0"_ns, Some(0.f)); + test_tofloat_helper(u"1.e5"_ns, Some(100000)); + test_tofloat_helper(u""_ns, Nothing()); + test_tofloat_helper(u"42foo"_ns, Nothing()); + test_tofloat_helper(u"foo"_ns, Nothing()); + test_tofloat_helper(u"1.5e-"_ns, Nothing()); + + // Leading spaces are ignored + test_tofloat_helper(u" \t5"_ns, Some(5.f)); + + // Values which are too large generate an error + test_tofloat_helper(u"3.402823e38"_ns, Some(3.402823e+38)); + test_tofloat_helper(u"1e39"_ns, Nothing()); + test_tofloat_helper(u"-3.402823e38"_ns, Some(-3.402823e+38)); + test_tofloat_helper(u"-1e39"_ns, Nothing()); + + // Values which are too small round to zero + test_tofloat_helper(u"1.4013e-45"_ns, Some(1.4013e-45f)); + test_tofloat_helper(u"1e-46"_ns, Some(0.f)); + test_tofloat_helper(u"-1.4013e-45"_ns, Some(-1.4013e-45f)); + test_tofloat_helper(u"-1e-46"_ns, Some(-0.f)); +} + +static void test_tofloat_allow_trailing_chars_helper(const nsString& aStr, + Maybe aExpected) { + nsresult result; + float value = aStr.ToFloatAllowTrailingChars(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); + } else { + EXPECT_TRUE(NS_FAILED(result)); + } +} + +TEST_F(Strings, ToFloatAllowTrailingChars) { + test_tofloat_allow_trailing_chars_helper(u""_ns, Nothing()); + test_tofloat_allow_trailing_chars_helper(u"foo"_ns, Nothing()); + test_tofloat_allow_trailing_chars_helper(u"42foo"_ns, Some(42.f)); + test_tofloat_allow_trailing_chars_helper(u"42-5"_ns, Some(42.f)); + test_tofloat_allow_trailing_chars_helper(u"13.37.8"_ns, Some(13.37f)); + test_tofloat_allow_trailing_chars_helper(u"1.5e-"_ns, Some(1.5f)); +} + +static void test_todouble_helper(const nsString& aStr, + Maybe aExpected) { + nsresult result; + double value = aStr.ToDouble(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); + } else { + EXPECT_TRUE(NS_FAILED(result)); + } +} + +TEST_F(Strings, todouble) { + test_todouble_helper(u"42"_ns, Some(42)); + test_todouble_helper(u"42.0"_ns, Some(42)); + test_todouble_helper(u"-42"_ns, Some(-42)); + test_todouble_helper(u"+42"_ns, Some(42)); + test_todouble_helper(u"13.37"_ns, Some(13.37)); + test_todouble_helper(u"1.23456789"_ns, Some(1.23456789)); + test_todouble_helper(u"1.98765432123456"_ns, Some(1.98765432123456)); + test_todouble_helper(u"123456789.98765432123456"_ns, + Some(123456789.98765432123456)); + test_todouble_helper(u"0"_ns, Some(0)); + test_todouble_helper(u"1.e5"_ns, Some(100000)); + test_todouble_helper(u""_ns, Nothing()); + test_todouble_helper(u"42foo"_ns, Nothing()); + test_todouble_helper(u"foo"_ns, Nothing()); + test_todouble_helper(u"1.5e-"_ns, Nothing()); + + // Leading spaces are ignored + test_todouble_helper(u" \t5"_ns, Some(5.)); + + // Values which are too large generate an error + test_todouble_helper(u"1.797693e+308"_ns, Some(1.797693e+308)); + test_todouble_helper(u"1e309"_ns, Nothing()); + test_todouble_helper(u"-1.797693e+308"_ns, Some(-1.797693e+308)); + test_todouble_helper(u"-1e309"_ns, Nothing()); + + // Values which are too small round to zero + test_todouble_helper(u"4.940656e-324"_ns, Some(4.940656e-324)); + test_todouble_helper(u"1e-325"_ns, Some(0.)); + test_todouble_helper(u"-4.940656e-324"_ns, Some(-4.940656e-324)); + test_todouble_helper(u"-1e-325"_ns, Some(-0.)); +} + +static void test_todouble_allow_trailing_chars_helper(const nsString& aStr, + Maybe aExpected) { + nsresult result; + double value = aStr.ToDoubleAllowTrailingChars(&result); + if (aExpected) { + EXPECT_TRUE(NS_SUCCEEDED(result)); + EXPECT_EQ(value, *aExpected); + } else { + EXPECT_TRUE(NS_FAILED(result)); + } +} + +TEST_F(Strings, ToDoubleAllowTrailingChars) { + test_todouble_allow_trailing_chars_helper(u""_ns, Nothing()); + test_todouble_allow_trailing_chars_helper(u"foo"_ns, Nothing()); + test_todouble_allow_trailing_chars_helper(u"42foo"_ns, Some(42)); + test_todouble_allow_trailing_chars_helper(u"42-5"_ns, Some(42)); + test_todouble_allow_trailing_chars_helper(u"13.37.8"_ns, Some(13.37)); + test_todouble_allow_trailing_chars_helper(u"1.5e-"_ns, Some(1.5)); +} + +TEST_F(Strings, Split) { + nsCString one("one"), two("one;two"), three("one--three"), empty(""), + delimStart("-two"), delimEnd("one-"); + + nsString wide(u"hello world"); + + size_t counter = 0; + for (const nsACString& token : one.Split(',')) { + EXPECT_TRUE(token.EqualsLiteral("one")); + counter++; + } + EXPECT_EQ(counter, (size_t)1); + + counter = 0; + for (const nsACString& token : two.Split(';')) { + if (counter == 0) { + EXPECT_TRUE(token.EqualsLiteral("one")); + } else if (counter == 1) { + EXPECT_TRUE(token.EqualsLiteral("two")); + } + counter++; + } + EXPECT_EQ(counter, (size_t)2); + + counter = 0; + for (const nsACString& token : three.Split('-')) { + if (counter == 0) { + EXPECT_TRUE(token.EqualsLiteral("one")); + } else if (counter == 1) { + EXPECT_TRUE(token.EqualsLiteral("")); + } else if (counter == 2) { + EXPECT_TRUE(token.EqualsLiteral("three")); + } + counter++; + } + EXPECT_EQ(counter, (size_t)3); + + counter = 0; + for (const nsACString& token : empty.Split(',')) { + mozilla::Unused << token; + counter++; + } + EXPECT_EQ(counter, (size_t)0); + + counter = 0; + for (const nsACString& token : delimStart.Split('-')) { + if (counter == 0) { + EXPECT_TRUE(token.EqualsLiteral("")); + } else if (counter == 1) { + EXPECT_TRUE(token.EqualsLiteral("two")); + } + counter++; + } + EXPECT_EQ(counter, (size_t)2); + + counter = 0; + for (const nsACString& token : delimEnd.Split('-')) { + if (counter == 0) { + EXPECT_TRUE(token.EqualsLiteral("one")); + } else if (counter == 1) { + EXPECT_TRUE(token.EqualsLiteral("")); + } + counter++; + } + EXPECT_EQ(counter, (size_t)2); + + counter = 0; + for (const nsAString& token : wide.Split(' ')) { + if (counter == 0) { + EXPECT_TRUE(token.Equals(u"hello"_ns)); + } else if (counter == 1) { + EXPECT_TRUE(token.Equals(u"world"_ns)); + } + counter++; + } + EXPECT_EQ(counter, (size_t)2); +} + +TEST_F(Strings, Join) { + // Join a sequence of strings. + { + // 8-bit strings + EXPECT_EQ(""_ns, StringJoin(","_ns, std::array{})); + EXPECT_EQ("foo"_ns, StringJoin(","_ns, std::array{"foo"_ns})); + EXPECT_EQ("foo,bar"_ns, StringJoin(","_ns, std::array{"foo"_ns, "bar"_ns})); + + // 16-bit strings + EXPECT_EQ(u""_ns, StringJoin(u","_ns, std::array{})); + EXPECT_EQ(u"foo"_ns, StringJoin(u","_ns, std::array{u"foo"_ns})); + EXPECT_EQ(u"foo,bar"_ns, + StringJoin(u","_ns, std::array{u"foo"_ns, u"bar"_ns})); + } + + // Join a sequence of strings, appending. + { + // 8-bit string + { + nsAutoCString dst{"prefix:"_ns}; + StringJoinAppend(dst, ","_ns, std::array{"foo"_ns, "bar"_ns}); + EXPECT_EQ("prefix:foo,bar"_ns, dst); + } + + // 16-bit string + { + nsAutoString dst{u"prefix:"_ns}; + StringJoinAppend(dst, u","_ns, std::array{u"foo"_ns, u"bar"_ns}); + EXPECT_EQ(u"prefix:foo,bar"_ns, dst); + } + } +} + +TEST_F(Strings, JoinWithAppendingTransformation) { + const auto toCString = [](nsACString& dst, int val) { dst.AppendInt(val); }; + const auto toString = [](nsAString& dst, int val) { dst.AppendInt(val); }; + + // Join a sequence of elements transformed to a string. + { + // 8-bit strings + EXPECT_EQ(""_ns, StringJoin(","_ns, std::array{}, toCString)); + EXPECT_EQ("7"_ns, StringJoin(","_ns, std::array{7}, toCString)); + EXPECT_EQ("7,42"_ns, StringJoin(","_ns, std::array{7, 42}, toCString)); + + // 16-bit strings + EXPECT_EQ(u""_ns, StringJoin(u","_ns, std::array{}, toString)); + EXPECT_EQ(u"7"_ns, StringJoin(u","_ns, std::array{7}, toString)); + EXPECT_EQ(u"7,42"_ns, StringJoin(u","_ns, std::array{7, 42}, toString)); + } + + // Join a sequence of elements transformed to a string, appending. + { + // 8-bit string + { + nsAutoCString dst{"prefix:"_ns}; + StringJoinAppend(dst, ","_ns, std::array{7, 42}, toCString); + EXPECT_EQ("prefix:7,42"_ns, dst); + } + + // 16-bit string + { + nsAutoString dst{u"prefix:"_ns}; + StringJoinAppend(dst, u","_ns, std::array{7, 42}, toString); + EXPECT_EQ(u"prefix:7,42"_ns, dst); + } + } +} + +constexpr bool TestSomeChars(char c) { + return c == 'a' || c == 'c' || c == 'e' || c == '7' || c == 'G' || c == 'Z' || + c == '\b' || c == '?'; +} +TEST_F(Strings, ASCIIMask) { + const ASCIIMaskArray& maskCRLF = mozilla::ASCIIMask::MaskCRLF(); + EXPECT_TRUE(maskCRLF['\n'] && mozilla::ASCIIMask::IsMasked(maskCRLF, '\n')); + EXPECT_TRUE(maskCRLF['\r'] && mozilla::ASCIIMask::IsMasked(maskCRLF, '\r')); + EXPECT_FALSE(maskCRLF['g'] || mozilla::ASCIIMask::IsMasked(maskCRLF, 'g')); + EXPECT_FALSE(maskCRLF[' '] || mozilla::ASCIIMask::IsMasked(maskCRLF, ' ')); + EXPECT_FALSE(maskCRLF['\0'] || mozilla::ASCIIMask::IsMasked(maskCRLF, '\0')); + EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324)); + + const ASCIIMaskArray& mask0to9 = mozilla::ASCIIMask::Mask0to9(); + EXPECT_TRUE(mask0to9['9'] && mozilla::ASCIIMask::IsMasked(mask0to9, '9')); + EXPECT_TRUE(mask0to9['0'] && mozilla::ASCIIMask::IsMasked(mask0to9, '0')); + EXPECT_TRUE(mask0to9['4'] && mozilla::ASCIIMask::IsMasked(mask0to9, '4')); + EXPECT_FALSE(mask0to9['g'] || mozilla::ASCIIMask::IsMasked(mask0to9, 'g')); + EXPECT_FALSE(mask0to9[' '] || mozilla::ASCIIMask::IsMasked(mask0to9, ' ')); + EXPECT_FALSE(mask0to9['\n'] || mozilla::ASCIIMask::IsMasked(mask0to9, '\n')); + EXPECT_FALSE(mask0to9['\0'] || mozilla::ASCIIMask::IsMasked(mask0to9, '\0')); + EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324)); + + const ASCIIMaskArray& maskWS = mozilla::ASCIIMask::MaskWhitespace(); + EXPECT_TRUE(maskWS[' '] && mozilla::ASCIIMask::IsMasked(maskWS, ' ')); + EXPECT_TRUE(maskWS['\t'] && mozilla::ASCIIMask::IsMasked(maskWS, '\t')); + EXPECT_FALSE(maskWS['8'] || mozilla::ASCIIMask::IsMasked(maskWS, '8')); + EXPECT_FALSE(maskWS['\0'] || mozilla::ASCIIMask::IsMasked(maskWS, '\0')); + EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324)); + + constexpr ASCIIMaskArray maskSome = mozilla::CreateASCIIMask(TestSomeChars); + EXPECT_TRUE(maskSome['a'] && mozilla::ASCIIMask::IsMasked(maskSome, 'a')); + EXPECT_TRUE(maskSome['c'] && mozilla::ASCIIMask::IsMasked(maskSome, 'c')); + EXPECT_TRUE(maskSome['e'] && mozilla::ASCIIMask::IsMasked(maskSome, 'e')); + EXPECT_TRUE(maskSome['7'] && mozilla::ASCIIMask::IsMasked(maskSome, '7')); + EXPECT_TRUE(maskSome['G'] && mozilla::ASCIIMask::IsMasked(maskSome, 'G')); + EXPECT_TRUE(maskSome['Z'] && mozilla::ASCIIMask::IsMasked(maskSome, 'Z')); + EXPECT_TRUE(maskSome['\b'] && mozilla::ASCIIMask::IsMasked(maskSome, '\b')); + EXPECT_TRUE(maskSome['?'] && mozilla::ASCIIMask::IsMasked(maskSome, '?')); + EXPECT_FALSE(maskSome['8'] || mozilla::ASCIIMask::IsMasked(maskSome, '8')); + EXPECT_FALSE(maskSome['\0'] || mozilla::ASCIIMask::IsMasked(maskSome, '\0')); + EXPECT_FALSE(mozilla::ASCIIMask::IsMasked(maskCRLF, 14324)); +} + +template +void CompressWhitespaceHelper() { + T s; + s.AssignLiteral("abcabcabc"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abcabcabc")); + + s.AssignLiteral(" \n\rabcabcabc\r\n"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abcabcabc")); + + s.AssignLiteral(" \n\rabc abc abc\r\n"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abc abc abc")); + + s.AssignLiteral(" \n\rabc\r \nabc\n \rabc\r\n"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.CompressWhitespace(false, true); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.CompressWhitespace(true, false); + EXPECT_TRUE(s.EqualsLiteral("abc abc abc ")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.CompressWhitespace(false, false); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc ")); + + s.AssignLiteral(" \r\n "); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(" \r\n \t"); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral("\n \r\n \t"); + s.CompressWhitespace(false, false); + EXPECT_TRUE(s.EqualsLiteral(" ")); + + s.AssignLiteral("\n \r\n \t"); + s.CompressWhitespace(false, true); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral("\n \r\n \t"); + s.CompressWhitespace(true, false); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(""); + s.CompressWhitespace(false, false); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(""); + s.CompressWhitespace(false, true); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(""); + s.CompressWhitespace(true, false); + EXPECT_TRUE(s.EqualsLiteral("")); + + s.AssignLiteral(""); + s.CompressWhitespace(true, true); + EXPECT_TRUE(s.EqualsLiteral("")); +} + +TEST_F(Strings, CompressWhitespace) { CompressWhitespaceHelper(); } + +TEST_F(Strings, CompressWhitespaceW) { + CompressWhitespaceHelper(); + + nsString str, result; + str.AssignLiteral(u"\u263A is\r\n ;-)"); + result.AssignLiteral(u"\u263A is ;-)"); + str.CompressWhitespace(true, true); + EXPECT_TRUE(str == result); +} + +template +void StripCRLFHelper() { + T s; + s.AssignLiteral("abcabcabc"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral("abcabcabc")); + + s.AssignLiteral(" \n\rabcabcabc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abcabcabc")); + + s.AssignLiteral(" \n\rabc abc abc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \n\rabc\r \nabc\n \rabc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \n\rabc\r abc\n abc\r\n"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" abc abc abc")); + + s.AssignLiteral(" \r\n "); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" ")); + + s.AssignLiteral(" \r\n \t"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" \t")); + + s.AssignLiteral("\n \r\n \t"); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral(" \t")); + + s.AssignLiteral(""); + s.StripCRLF(); + EXPECT_TRUE(s.EqualsLiteral("")); +} + +TEST_F(Strings, StripCRLF) { StripCRLFHelper(); } + +TEST_F(Strings, StripCRLFW) { + StripCRLFHelper(); + + nsString str, result; + str.AssignLiteral(u"\u263A is\r\n ;-)"); + result.AssignLiteral(u"\u263A is ;-)"); + str.StripCRLF(); + EXPECT_TRUE(str == result); +} + +TEST_F(Strings, utf8_to_latin1_sharing) { + nsCString s; + s.Append('a'); + s.Append('b'); + s.Append('c'); + nsCString t; + LossyAppendUTF8toLatin1(s, t); + EXPECT_TRUE(t.EqualsLiteral("abc")); + EXPECT_EQ(s.BeginReading(), t.BeginReading()); + LossyCopyUTF8toLatin1(s, t); + EXPECT_TRUE(t.EqualsLiteral("abc")); + EXPECT_EQ(s.BeginReading(), t.BeginReading()); +} + +TEST_F(Strings, latin1_to_utf8_sharing) { + nsCString s; + s.Append('a'); + s.Append('b'); + s.Append('c'); + nsCString t; + AppendLatin1toUTF8(s, t); + EXPECT_TRUE(t.EqualsLiteral("abc")); + EXPECT_EQ(s.BeginReading(), t.BeginReading()); + CopyLatin1toUTF8(s, t); + EXPECT_TRUE(t.EqualsLiteral("abc")); + EXPECT_EQ(s.BeginReading(), t.BeginReading()); +} + +TEST_F(Strings, utf8_to_latin1) { + nsCString s; + s.AssignLiteral("\xC3\xA4"); + nsCString t; + LossyCopyUTF8toLatin1(s, t); + // EqualsLiteral requires ASCII + EXPECT_TRUE(t.Equals("\xE4")); +} + +TEST_F(Strings, latin1_to_utf8) { + nsCString s; + s.AssignLiteral("\xE4"); + nsCString t; + CopyLatin1toUTF8(s, t); + // EqualsLiteral requires ASCII + EXPECT_TRUE(t.Equals("\xC3\xA4")); +} + +TEST_F(Strings, ConvertToSpan) { + nsString string; + + // from const string + { + const auto& constStringRef = string; + + auto span = Span{constStringRef}; + static_assert(std::is_same_v>); + } + + // from non-const string + { + auto span = Span{string}; + static_assert(std::is_same_v>); + } + + // get mutable data + { + auto span = string.GetMutableData(); + static_assert(std::is_same_v>); + } + + nsCString cstring; + + // from const string + { + const auto& constCStringRef = cstring; + + auto span = Span{constCStringRef}; + static_assert(std::is_same_v>); + } + + // from non-const string + { + auto span = Span{cstring}; + static_assert(std::is_same_v>); + } + + // get mutable data + { + auto span = cstring.GetMutableData(); + static_assert(std::is_same_v>); + } +} + +template +void InsertSpanHelper() { + T str1, str2; + str1.AssignLiteral("hello world"); + str2.AssignLiteral("span "); + + T expect; + expect.AssignLiteral("hello span world"); + + Span span(str2); + str1.Insert(span, 6); + EXPECT_TRUE(str1.Equals(expect)); +} + +TEST_F(Strings, InsertSpan) { InsertSpanHelper(); } +TEST_F(Strings, InsertSpanW) { InsertSpanHelper(); } + +TEST_F(Strings, TokenizedRangeEmpty) { + // 8-bit strings + { + for (const auto& token : nsCCharSeparatedTokenizer(""_ns, ',').ToRange()) { + (void)token; + ADD_FAILURE(); + } + } + + // 16-bit strings + { + for (const auto& token : nsCharSeparatedTokenizer(u""_ns, ',').ToRange()) { + (void)token; + ADD_FAILURE(); + } + } +} + +TEST_F(Strings, TokenizedRangeWhitespaceOnly) { + // 8-bit strings + { + for (const auto& token : nsCCharSeparatedTokenizer(" "_ns, ',').ToRange()) { + (void)token; + ADD_FAILURE(); + } + } + + // 16-bit strings + { + for (const auto& token : nsCharSeparatedTokenizer(u" "_ns, ',').ToRange()) { + (void)token; + ADD_FAILURE(); + } + } +} + +TEST_F(Strings, TokenizedRangeNonEmpty) { + // 8-bit strings + { + nsTArray res; + for (const auto& token : + nsCCharSeparatedTokenizer("foo,bar"_ns, ',').ToRange()) { + res.EmplaceBack(token); + } + + EXPECT_EQ(res, (nsTArray{"foo"_ns, "bar"_ns})); + } + + // 16-bit strings + { + nsTArray res; + for (const auto& token : + nsCharSeparatedTokenizer(u"foo,bar"_ns, ',').ToRange()) { + res.EmplaceBack(token); + } + + EXPECT_EQ(res, (nsTArray{u"foo"_ns, u"bar"_ns})); + } +} + +// Macros for reducing verbosity of printf tests. +#define create_printf_strings(format, ...) \ + nsCString appendPrintfString; \ + appendPrintfString.AppendPrintf(format, __VA_ARGS__); \ + const nsCString appendVprintfString( \ + getAppendVprintfString(format, __VA_ARGS__)); \ + const nsPrintfCString printfString(format, __VA_ARGS__); \ + const nsVprintfCString vprintfString{getVprintfCString(format, __VA_ARGS__)}; + +// We don't check every possible combination as we assume equality is +// transitive. +#define verify_printf_strings(expected) \ + EXPECT_TRUE(appendPrintfString.EqualsASCII(expected)) \ + << "appendPrintfString != expected:" << appendPrintfString.get() \ + << " != " << (expected); \ + EXPECT_TRUE(appendPrintfString.Equals(appendVprintfString)) \ + << "appendPrintfString != appendVprintfString:" \ + << appendPrintfString.get() << " != " << appendVprintfString; \ + EXPECT_TRUE(appendPrintfString.Equals(printfString)) \ + << "appendPrintfString != printfString:" << appendPrintfString.get() \ + << " != " << printfString; \ + EXPECT_TRUE(appendPrintfString.Equals(vprintfString)) \ + << "appendPrintfString != vprintfString:" << appendPrintfString.get() \ + << " != " << vprintfString; + +TEST_F(Strings, printf) { + auto getAppendVprintfString = [](const char* aFormat, ...) { + // Helper to get a string with contents set via AppendVprint. + nsCString cString; + va_list ap; + va_start(ap, aFormat); + cString.AppendVprintf(aFormat, ap); + va_end(ap); + return cString; + }; + + auto getVprintfCString = [](const char* aFormat, ...) { + // Helper to get a nsVprintfCString. + va_list ap; + va_start(ap, aFormat); + const nsVprintfCString vprintfString(aFormat, ap); + va_end(ap); + return vprintfString; + }; + + { + const char* format = "Characters %c %%"; + const char* expectedOutput = "Characters B %"; + create_printf_strings(format, 'B'); + verify_printf_strings(expectedOutput); + } + { + const char* format = "Strings %s %s"; + const char* expectedOutput = "Strings foo bar"; + create_printf_strings(format, "foo", "bar"); + verify_printf_strings(expectedOutput); + } + { + const int signedThree = 3; + const unsigned int unsignedTen = 10; + const char* format = "Integers %i %.3d %.2u %o %x %X"; + const char* expectedOutput = "Integers 3 003 10 12 a A"; + create_printf_strings(format, signedThree, signedThree, unsignedTen, + unsignedTen, unsignedTen, unsignedTen); + verify_printf_strings(expectedOutput); + } + { + const char* format = "Floats %f %.0f %e %.2E"; + const char* expectedOutput = "Floats 1.500000 2 1.500000e+00 1.50E+00"; + create_printf_strings(format, 1.5, 1.5, 1.5, 1.5); + verify_printf_strings(expectedOutput); + } + { + const char* expectedOutput = "Just a string"; + const char* format = "%s"; + create_printf_strings(format, "Just a string"); + verify_printf_strings(expectedOutput); + } + { + const char* anotherString = "another string"; + const char* format = "Just a string and %s"; + const char* expectedOutput = "Just a string and another string"; + create_printf_strings(format, anotherString); + verify_printf_strings(expectedOutput); + } + { + // This case tickles an unexpected overload resolution in MSVC where a + // va_list overload will be selected if available. See bug 1673670 and + // 1673917 for more detail. + char anotherString[] = "another string"; + const char* format = "Just a string and %s"; + const char* expectedOutput = "Just a string and another string"; + // Calling with a non-const pointer triggers selection of va_list overload + // in MSVC at time of writing + create_printf_strings(format, (char*)anotherString); + verify_printf_strings(expectedOutput); + } +} + +// We don't need these macros following the printf test. +#undef verify_printf_strings +#undef create_printf_strings + +// Note the five calls in the loop, so divide by 100k +MOZ_GTEST_BENCH_F(Strings, PerfStripWhitespace, [this] { + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.StripWhitespace(); + test2.StripWhitespace(); + test3.StripWhitespace(); + test4.StripWhitespace(); + test5.StripWhitespace(); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfStripCharsWhitespace, [this] { + // This is the unoptimized (original) version of + // StripWhitespace using StripChars. + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.StripChars("\f\t\r\n "); + test2.StripChars("\f\t\r\n "); + test3.StripChars("\f\t\r\n "); + test4.StripChars("\f\t\r\n "); + test5.StripChars("\f\t\r\n "); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfCompressWhitespace, [this] { + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.CompressWhitespace(); + test2.CompressWhitespace(); + test3.CompressWhitespace(); + test4.CompressWhitespace(); + test5.CompressWhitespace(); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfStripCRLF, [this] { + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.StripCRLF(); + test2.StripCRLF(); + test3.StripCRLF(); + test4.StripCRLF(); + test5.StripCRLF(); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfStripCharsCRLF, [this] { + // This is the unoptimized (original) version of + // stripping \r\n using StripChars. + nsCString test1(mExample1Utf8); + nsCString test2(mExample2Utf8); + nsCString test3(mExample3Utf8); + nsCString test4(mExample4Utf8); + nsCString test5(mExample5Utf8); + for (int i = 0; i < 20000; i++) { + test1.StripChars("\r\n"); + test2.StripChars("\r\n"); + test3.StripChars("\r\n"); + test4.StripChars("\r\n"); + test5.StripChars("\r\n"); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8One, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsUtf8(*BlackBox(&mAsciiOneUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8Fifteen, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsUtf8(*BlackBox(&mAsciiFifteenUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8Hundred, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsUtf8(*BlackBox(&mAsciiHundredUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsUTF8Example3, [this] { + for (int i = 0; i < 100000; i++) { + bool b = IsUtf8(*BlackBox(&mExample3Utf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsASCII8One, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsAscii(*BlackBox(&mAsciiOneUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsASCIIFifteen, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsAscii(*BlackBox(&mAsciiFifteenUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsASCIIHundred, [this] { + for (int i = 0; i < 200000; i++) { + bool b = IsAscii(*BlackBox(&mAsciiHundredUtf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfIsASCIIExample3, [this] { + for (int i = 0; i < 100000; i++) { + bool b = IsAscii(*BlackBox(&mExample3Utf8)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsExample3, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mExample3Utf16)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsDE, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mDeUtf16)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsRU, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mRuUtf16)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsTH, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mThUtf16)); + BlackBox(&b); + } +}); + +MOZ_GTEST_BENCH_F(Strings, PerfHasRTLCharsJA, [this] { + for (int i = 0; i < 5000; i++) { + bool b = HasRTLChars(*BlackBox(&mJaUtf16)); + BlackBox(&b); + } +}); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIOne, LossyCopyUTF16toASCII, + mAsciiOneUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIThree, LossyCopyUTF16toASCII, + mAsciiThreeUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIFifteen, LossyCopyUTF16toASCII, + mAsciiFifteenUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIHundred, LossyCopyUTF16toASCII, + mAsciiHundredUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1ASCIIThousand, LossyCopyUTF16toASCII, + mAsciiThousandUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEOne, LossyCopyUTF16toASCII, mDeEditOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEThree, LossyCopyUTF16toASCII, + mDeEditThreeUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEFifteen, LossyCopyUTF16toASCII, + mDeEditFifteenUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEHundred, LossyCopyUTF16toASCII, + mDeEditHundredUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toLatin1DEThousand, LossyCopyUTF16toASCII, + mDeEditThousandUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiOne, CopyASCIItoUTF16, mAsciiOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiThree, CopyASCIItoUTF16, mAsciiThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiFifteen, CopyASCIItoUTF16, + mAsciiFifteenUtf8, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiHundred, CopyASCIItoUTF16, + mAsciiHundredUtf8, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16AsciiThousand, CopyASCIItoUTF16, + mAsciiThousandUtf8, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEOne, CopyASCIItoUTF16, mDeEditOneLatin1, + nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEThree, CopyASCIItoUTF16, mDeEditThreeLatin1, + nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEFifteen, CopyASCIItoUTF16, + mDeEditFifteenLatin1, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEHundred, CopyASCIItoUTF16, + mDeEditHundredLatin1, nsAutoString); + +CONVERSION_BENCH(PerfLatin1toUTF16DEThousand, CopyASCIItoUTF16, + mDeEditThousandLatin1, nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiOne, CopyUTF16toUTF8, mAsciiOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiThree, CopyUTF16toUTF8, mAsciiThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiFifteen, CopyUTF16toUTF8, + mAsciiFifteenUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiHundred, CopyUTF16toUTF8, + mAsciiHundredUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8AsciiThousand, CopyUTF16toUTF8, + mAsciiThousandUtf16, nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiOne, CopyUTF8toUTF16, mAsciiOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiThree, CopyUTF8toUTF16, mAsciiThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiFifteen, CopyUTF8toUTF16, + mAsciiFifteenUtf8, nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiHundred, CopyUTF8toUTF16, + mAsciiHundredUtf8, nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16AsciiThousand, CopyUTF8toUTF16, + mAsciiThousandUtf8, nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8AROne, CopyUTF16toUTF8, mArOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8ARThree, CopyUTF16toUTF8, mArThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8ARFifteen, CopyUTF16toUTF8, mArFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8ARHundred, CopyUTF16toUTF8, mArHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8ARThousand, CopyUTF16toUTF8, mArThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16AROne, CopyUTF8toUTF16, mArOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16ARThree, CopyUTF8toUTF16, mArThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16ARFifteen, CopyUTF8toUTF16, mArFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16ARHundred, CopyUTF8toUTF16, mArHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16ARThousand, CopyUTF8toUTF16, mArThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEOne, CopyUTF16toUTF8, mDeOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEThree, CopyUTF16toUTF8, mDeThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEFifteen, CopyUTF16toUTF8, mDeFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEHundred, CopyUTF16toUTF8, mDeHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8DEThousand, CopyUTF16toUTF8, mDeThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEOne, CopyUTF8toUTF16, mDeOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEThree, CopyUTF8toUTF16, mDeThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEFifteen, CopyUTF8toUTF16, mDeFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEHundred, CopyUTF8toUTF16, mDeHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16DEThousand, CopyUTF8toUTF16, mDeThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUOne, CopyUTF16toUTF8, mRuOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUThree, CopyUTF16toUTF8, mRuThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUFifteen, CopyUTF16toUTF8, mRuFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUHundred, CopyUTF16toUTF8, mRuHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8RUThousand, CopyUTF16toUTF8, mRuThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUOne, CopyUTF8toUTF16, mRuOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUThree, CopyUTF8toUTF16, mRuThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUFifteen, CopyUTF8toUTF16, mRuFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUHundred, CopyUTF8toUTF16, mRuHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16RUThousand, CopyUTF8toUTF16, mRuThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8THOne, CopyUTF16toUTF8, mThOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8THThree, CopyUTF16toUTF8, mThThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8THFifteen, CopyUTF16toUTF8, mThFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8THHundred, CopyUTF16toUTF8, mThHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8THThousand, CopyUTF16toUTF8, mThThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16THOne, CopyUTF8toUTF16, mThOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16THThree, CopyUTF8toUTF16, mThThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16THFifteen, CopyUTF8toUTF16, mThFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16THHundred, CopyUTF8toUTF16, mThHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16THThousand, CopyUTF8toUTF16, mThThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAOne, CopyUTF16toUTF8, mJaOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAThree, CopyUTF16toUTF8, mJaThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAFifteen, CopyUTF16toUTF8, mJaFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAHundred, CopyUTF16toUTF8, mJaHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8JAThousand, CopyUTF16toUTF8, mJaThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAOne, CopyUTF8toUTF16, mJaOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAThree, CopyUTF8toUTF16, mJaThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAFifteen, CopyUTF8toUTF16, mJaFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAHundred, CopyUTF8toUTF16, mJaHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16JAThousand, CopyUTF8toUTF16, mJaThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOOne, CopyUTF16toUTF8, mKoOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOThree, CopyUTF16toUTF8, mKoThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOFifteen, CopyUTF16toUTF8, mKoFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOHundred, CopyUTF16toUTF8, mKoHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8KOThousand, CopyUTF16toUTF8, mKoThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOOne, CopyUTF8toUTF16, mKoOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOThree, CopyUTF8toUTF16, mKoThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOFifteen, CopyUTF8toUTF16, mKoFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOHundred, CopyUTF8toUTF16, mKoHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16KOThousand, CopyUTF8toUTF16, mKoThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8TROne, CopyUTF16toUTF8, mTrOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8TRThree, CopyUTF16toUTF8, mTrThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8TRFifteen, CopyUTF16toUTF8, mTrFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8TRHundred, CopyUTF16toUTF8, mTrHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8TRThousand, CopyUTF16toUTF8, mTrThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16TROne, CopyUTF8toUTF16, mTrOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16TRThree, CopyUTF8toUTF16, mTrThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16TRFifteen, CopyUTF8toUTF16, mTrFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16TRHundred, CopyUTF8toUTF16, mTrHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16TRThousand, CopyUTF8toUTF16, mTrThousandUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIOne, CopyUTF16toUTF8, mViOneUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIThree, CopyUTF16toUTF8, mViThreeUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIFifteen, CopyUTF16toUTF8, mViFifteenUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIHundred, CopyUTF16toUTF8, mViHundredUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF16toUTF8VIThousand, CopyUTF16toUTF8, mViThousandUtf16, + nsAutoCString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIOne, CopyUTF8toUTF16, mViOneUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIThree, CopyUTF8toUTF16, mViThreeUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIFifteen, CopyUTF8toUTF16, mViFifteenUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIHundred, CopyUTF8toUTF16, mViHundredUtf8, + nsAutoString); + +CONVERSION_BENCH(PerfUTF8toUTF16VIThousand, CopyUTF8toUTF16, mViThousandUtf8, + nsAutoString); + +// Tests for usability of nsTLiteralString in constant expressions. +static_assert(u""_ns.IsEmpty()); + +constexpr auto testStringA = u"a"_ns; +static_assert(!testStringA.IsEmpty()); +static_assert(!testStringA.IsVoid()); +static_assert(testStringA.IsLiteral()); +static_assert(testStringA.IsTerminated()); +static_assert(testStringA.GetDataFlags() == + (nsLiteralString::DataFlags::LITERAL | + nsLiteralString::DataFlags::TERMINATED)); +static_assert(*static_cast(testStringA.Data()) == 'a'); +static_assert(1 == testStringA.Length()); +static_assert(testStringA.CharAt(0) == 'a'); +static_assert(testStringA[0] == 'a'); +static_assert(*testStringA.BeginReading() == 'a'); +static_assert(*testStringA.EndReading() == 0); +static_assert(testStringA.EndReading() - testStringA.BeginReading() == 1); + +} // namespace TestStrings + +#if defined(__clang__) && (__clang_major__ >= 6) +# pragma clang diagnostic pop +#endif diff --git a/xpcom/tests/gtest/TestSubstringTuple.cpp b/xpcom/tests/gtest/TestSubstringTuple.cpp new file mode 100644 index 0000000000..6dfd6bc154 --- /dev/null +++ b/xpcom/tests/gtest/TestSubstringTuple.cpp @@ -0,0 +1,55 @@ +/* -*- 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 "nsLiteralString.h" +#include "nsTSubstringTuple.h" +#include "gtest/gtest.h" + +namespace TestSubstringTuple { + +static const auto kFooLiteral = u"foo"_ns; + +static const auto kFoo = nsCString("foo"); +static const auto kBar = nsCString("bar"); +static const auto kBaz = nsCString("baz"); + +// The test must be done in a macro to ensure that tuple is always a temporary. +#define DO_SUBSTRING_TUPLE_TEST(tuple, dependentString, expectedLength, \ + expectedDependency) \ + const auto length = (tuple).Length(); \ + const auto isDependentOn = (tuple).IsDependentOn( \ + dependentString.BeginReading(), dependentString.EndReading()); \ + \ + EXPECT_EQ((expectedLength), length); \ + EXPECT_EQ((expectedDependency), isDependentOn); \ + \ + const auto [combinedIsDependentOn, combinedLength] = \ + (tuple).IsDependentOnWithLength(dependentString.BeginReading(), \ + dependentString.EndReading()); \ + \ + EXPECT_EQ(length, combinedLength); \ + EXPECT_EQ(isDependentOn, combinedIsDependentOn); + +TEST(SubstringTuple, IsDependentOnAndLength_NonDependent_Literal_ZeroLength) +{ DO_SUBSTRING_TUPLE_TEST(u""_ns + u""_ns, kFooLiteral, 0u, false); } + +TEST(SubstringTuple, IsDependentOnAndLength_NonDependent_Literal_NonZeroLength) +{ DO_SUBSTRING_TUPLE_TEST(u"bar"_ns + u"baz"_ns, kFooLiteral, 6u, false); } + +TEST(SubstringTuple, IsDependentOnAndLength_NonDependent_NonZeroLength) +{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz, kFoo, 6u, false); } + +TEST(SubstringTuple, + IsDependentOnAndLength_NonDependent_NonZeroLength_ThreeParts) +{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz + kBar, kFoo, 9u, false); } + +TEST(SubstringTuple, IsDependentOnAndLength_Dependent_NonZeroLength) +{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz, kBar, 6u, true); } + +TEST(SubstringTuple, IsDependentOnAndLength_Dependent_NonZeroLength_ThreeParts) +{ DO_SUBSTRING_TUPLE_TEST(kBar + kBaz + kFoo, kBar, 9u, true); } + +} // namespace TestSubstringTuple diff --git a/xpcom/tests/gtest/TestSynchronization.cpp b/xpcom/tests/gtest/TestSynchronization.cpp new file mode 100644 index 0000000000..c4a1f5c99e --- /dev/null +++ b/xpcom/tests/gtest/TestSynchronization.cpp @@ -0,0 +1,324 @@ +/* -*- 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/CondVar.h" +#include "mozilla/Monitor.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Mutex.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +static PRThread* spawn(void (*run)(void*), void* arg) { + return PR_CreateThread(PR_SYSTEM_THREAD, run, arg, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); +} + +//----------------------------------------------------------------------------- +// Sanity check: tests that can be done on a single thread +// +TEST(Synchronization, Sanity) +MOZ_NO_THREAD_SAFETY_ANALYSIS { + Mutex lock("sanity::lock"); + lock.Lock(); + lock.AssertCurrentThreadOwns(); + lock.Unlock(); + + { + MutexAutoLock autolock(lock); + lock.AssertCurrentThreadOwns(); + } + + lock.Lock(); + lock.AssertCurrentThreadOwns(); + { MutexAutoUnlock autounlock(lock); } + lock.AssertCurrentThreadOwns(); + lock.Unlock(); + + ReentrantMonitor mon("sanity::monitor"); + mon.Enter(); + mon.AssertCurrentThreadIn(); + mon.Enter(); + mon.AssertCurrentThreadIn(); + mon.Exit(); + mon.AssertCurrentThreadIn(); + mon.Exit(); + + { + ReentrantMonitorAutoEnter automon(mon); + mon.AssertCurrentThreadIn(); + } +} + +//----------------------------------------------------------------------------- +// Mutex contention tests +// +static Mutex* gLock1; + +static void MutexContention_thread(void* /*arg*/) { + for (int i = 0; i < 100000; ++i) { + gLock1->Lock(); + gLock1->AssertCurrentThreadOwns(); + gLock1->Unlock(); + } +} + +TEST(Synchronization, MutexContention) +{ + gLock1 = new Mutex("lock1"); + // PURPOSELY not checking for OOM. YAY! + + PRThread* t1 = spawn(MutexContention_thread, nullptr); + PRThread* t2 = spawn(MutexContention_thread, nullptr); + PRThread* t3 = spawn(MutexContention_thread, nullptr); + + PR_JoinThread(t1); + PR_JoinThread(t2); + PR_JoinThread(t3); + + delete gLock1; +} + +//----------------------------------------------------------------------------- +// Monitor tests +// +static Monitor* gMon1; + +static void MonitorContention_thread(void* /*arg*/) { + for (int i = 0; i < 100000; ++i) { + gMon1->Lock(); + gMon1->AssertCurrentThreadOwns(); + gMon1->Unlock(); + } +} + +TEST(Synchronization, MonitorContention) +{ + gMon1 = new Monitor("mon1"); + + PRThread* t1 = spawn(MonitorContention_thread, nullptr); + PRThread* t2 = spawn(MonitorContention_thread, nullptr); + PRThread* t3 = spawn(MonitorContention_thread, nullptr); + + PR_JoinThread(t1); + PR_JoinThread(t2); + PR_JoinThread(t3); + + delete gMon1; +} + +static ReentrantMonitor* gMon2; + +static void MonitorContention2_thread(void* /*arg*/) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + for (int i = 0; i < 100000; ++i) { + gMon2->Enter(); + gMon2->AssertCurrentThreadIn(); + { + gMon2->Enter(); + gMon2->AssertCurrentThreadIn(); + gMon2->Exit(); + } + gMon2->AssertCurrentThreadIn(); + gMon2->Exit(); + } +} + +TEST(Synchronization, MonitorContention2) +{ + gMon2 = new ReentrantMonitor("mon1"); + + PRThread* t1 = spawn(MonitorContention2_thread, nullptr); + PRThread* t2 = spawn(MonitorContention2_thread, nullptr); + PRThread* t3 = spawn(MonitorContention2_thread, nullptr); + + PR_JoinThread(t1); + PR_JoinThread(t2); + PR_JoinThread(t3); + + delete gMon2; +} + +static ReentrantMonitor* gMon3; +static int32_t gMonFirst; + +static void MonitorSyncSanity_thread(void* /*arg*/) + MOZ_NO_THREAD_SAFETY_ANALYSIS { + gMon3->Enter(); + gMon3->AssertCurrentThreadIn(); + if (gMonFirst) { + gMonFirst = 0; + gMon3->Wait(); + gMon3->Enter(); + } else { + gMon3->Notify(); + gMon3->Enter(); + } + gMon3->AssertCurrentThreadIn(); + gMon3->Exit(); + gMon3->AssertCurrentThreadIn(); + gMon3->Exit(); +} + +TEST(Synchronization, MonitorSyncSanity) +{ + gMon3 = new ReentrantMonitor("monitor::syncsanity"); + + for (int32_t i = 0; i < 10000; ++i) { + gMonFirst = 1; + PRThread* ping = spawn(MonitorSyncSanity_thread, nullptr); + PRThread* pong = spawn(MonitorSyncSanity_thread, nullptr); + PR_JoinThread(ping); + PR_JoinThread(pong); + } + + delete gMon3; +} + +//----------------------------------------------------------------------------- +// Condvar tests +// +static Mutex* gCvlock1; +static CondVar* gCv1; +static int32_t gCvFirst; + +static void CondVarSanity_thread(void* /*arg*/) { + gCvlock1->Lock(); + gCvlock1->AssertCurrentThreadOwns(); + if (gCvFirst) { + gCvFirst = 0; + gCv1->Wait(); + } else { + gCv1->Notify(); + } + gCvlock1->AssertCurrentThreadOwns(); + gCvlock1->Unlock(); +} + +TEST(Synchronization, CondVarSanity) +{ + gCvlock1 = new Mutex("cvlock1"); + gCv1 = new CondVar(*gCvlock1, "cvlock1"); + + for (int32_t i = 0; i < 10000; ++i) { + gCvFirst = 1; + PRThread* ping = spawn(CondVarSanity_thread, nullptr); + PRThread* pong = spawn(CondVarSanity_thread, nullptr); + PR_JoinThread(ping); + PR_JoinThread(pong); + } + + delete gCv1; + delete gCvlock1; +} + +//----------------------------------------------------------------------------- +// AutoLock tests +// +TEST(Synchronization, AutoLock) +{ + Mutex l1 MOZ_UNANNOTATED("autolock"); + MutexAutoLock autol1(l1); + + l1.AssertCurrentThreadOwns(); + + { + Mutex l2 MOZ_UNANNOTATED("autolock2"); + MutexAutoLock autol2(l2); + + l1.AssertCurrentThreadOwns(); + l2.AssertCurrentThreadOwns(); + } + + l1.AssertCurrentThreadOwns(); +} + +//----------------------------------------------------------------------------- +// AutoTryLock tests +// +// The thread owns assertions make mutex analysis throw spurious warnings +TEST(Synchronization, AutoTryLock) +MOZ_NO_THREAD_SAFETY_ANALYSIS { + Mutex l1 MOZ_UNANNOTATED("autotrylock"); + MutexAutoTryLock autol1(l1); + + EXPECT_TRUE(autol1); + l1.AssertCurrentThreadOwns(); + + MutexAutoTryLock autol2(l1); + + EXPECT_TRUE(autol1); + EXPECT_FALSE(autol2); + l1.AssertCurrentThreadOwns(); + + { + Mutex l2 MOZ_UNANNOTATED("autotrylock2"); + MutexAutoTryLock autol3(l2); + + EXPECT_TRUE(autol3); + l1.AssertCurrentThreadOwns(); + l2.AssertCurrentThreadOwns(); + } + + l1.AssertCurrentThreadOwns(); +} + +//----------------------------------------------------------------------------- +// AutoUnlock tests +// +TEST(Synchronization, AutoUnlock) +{ + Mutex l1 MOZ_UNANNOTATED("autounlock"); + Mutex l2 MOZ_UNANNOTATED("autounlock2"); + + l1.Lock(); + l1.AssertCurrentThreadOwns(); + + { + MutexAutoUnlock autol1(l1); + { + l2.Lock(); + l2.AssertCurrentThreadOwns(); + + MutexAutoUnlock autol2(l2); + } + l2.AssertCurrentThreadOwns(); + l2.Unlock(); + } + l1.AssertCurrentThreadOwns(); + + l1.Unlock(); +} + +//----------------------------------------------------------------------------- +// AutoMonitor tests +// +TEST(Synchronization, AutoMonitor) +MOZ_NO_THREAD_SAFETY_ANALYSIS { + ReentrantMonitor m1("automonitor"); + ReentrantMonitor m2("automonitor2"); + + m1.Enter(); + m1.AssertCurrentThreadIn(); + { + ReentrantMonitorAutoEnter autom1(m1); + m1.AssertCurrentThreadIn(); + + m2.Enter(); + m2.AssertCurrentThreadIn(); + { + ReentrantMonitorAutoEnter autom2(m2); + m1.AssertCurrentThreadIn(); + m2.AssertCurrentThreadIn(); + } + m2.AssertCurrentThreadIn(); + m2.Exit(); + + m1.AssertCurrentThreadIn(); + } + m1.AssertCurrentThreadIn(); + m1.Exit(); +} diff --git a/xpcom/tests/gtest/TestTArray.cpp b/xpcom/tests/gtest/TestTArray.cpp new file mode 100644 index 0000000000..54aea92dff --- /dev/null +++ b/xpcom/tests/gtest/TestTArray.cpp @@ -0,0 +1,1042 @@ +/* -*- 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 "nsTArray.h" +#include "gtest/gtest.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/RefPtr.h" +#include "nsTHashMap.h" + +using namespace mozilla; + +namespace TestTArray { + +struct Copyable { + Copyable() : mDestructionCounter(nullptr) {} + + ~Copyable() { + if (mDestructionCounter) { + (*mDestructionCounter)++; + } + } + + Copyable(const Copyable&) = default; + Copyable& operator=(const Copyable&) = default; + + uint32_t* mDestructionCounter; +}; + +struct Movable { + Movable() : mDestructionCounter(nullptr) {} + + ~Movable() { + if (mDestructionCounter) { + (*mDestructionCounter)++; + } + } + + Movable(Movable&& aOther) : mDestructionCounter(aOther.mDestructionCounter) { + aOther.mDestructionCounter = nullptr; + } + + uint32_t* mDestructionCounter; +}; + +} // namespace TestTArray + +template <> +struct nsTArray_RelocationStrategy { + using Type = nsTArray_RelocateUsingMoveConstructor; +}; + +template <> +struct nsTArray_RelocationStrategy { + using Type = nsTArray_RelocateUsingMoveConstructor; +}; + +namespace TestTArray { + +constexpr int dummyArrayData[] = {4, 1, 2, 8}; + +static const nsTArray& DummyArray() { + static nsTArray sArray; + if (sArray.IsEmpty()) { + sArray.AppendElements(dummyArrayData, ArrayLength(dummyArrayData)); + } + return sArray; +} + +// This returns an invalid nsTArray with a huge length in order to test that +// fallible operations actually fail. +#ifdef DEBUG +static const nsTArray& FakeHugeArray() { + static nsTArray sArray; + if (sArray.IsEmpty()) { + sArray.AppendElement(); + ((nsTArrayHeader*)sArray.DebugGetHeader())->mLength = UINT32_MAX; + } + return sArray; +} +#endif + +TEST(TArray, int_AppendElements_PlainArray) +{ + nsTArray array; + + int* ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData)); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + + ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData)); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); +} + +TEST(TArray, int_AppendElements_PlainArray_Fallible) +{ + nsTArray array; + + int* ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData), + fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + + ptr = array.AppendElements(dummyArrayData, ArrayLength(dummyArrayData), + fallible); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); +} + +TEST(TArray, int_AppendElements_TArray_Copy) +{ + nsTArray array; + + const nsTArray temp(DummyArray().Clone()); + int* ptr = array.AppendElements(temp); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_FALSE(temp.IsEmpty()); + + ptr = array.AppendElements(temp); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_FALSE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_TArray_Copy_Fallible) +{ + nsTArray array; + + const nsTArray temp(DummyArray().Clone()); + int* ptr = array.AppendElements(temp, fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_FALSE(temp.IsEmpty()); + + ptr = array.AppendElements(temp, fallible); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_FALSE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_TArray_Rvalue) +{ + nsTArray array; + + nsTArray temp(DummyArray().Clone()); + int* ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyArray().Clone(); + ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_TRUE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_TArray_Rvalue_Fallible) +{ + nsTArray array; + + nsTArray temp(DummyArray().Clone()); + int* ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyArray().Clone(); + ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_TRUE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_FallibleArray_Rvalue) +{ + nsTArray array; + + FallibleTArray temp; + ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible)); + int* ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_TRUE(temp.IsEmpty()); + + ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible)); + ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_TRUE(temp.IsEmpty()); +} + +TEST(TArray, int_AppendElements_FallibleArray_Rvalue_Fallible) +{ + nsTArray array; + + FallibleTArray temp; + ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible)); + int* ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_EQ(DummyArray(), array); + ASSERT_TRUE(temp.IsEmpty()); + + ASSERT_TRUE(temp.AppendElements(DummyArray(), fallible)); + ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[DummyArray().Length()], ptr); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); + ASSERT_TRUE(temp.IsEmpty()); +} + +TEST(TArray, AppendElementsSpan) +{ + nsTArray array; + + nsTArray temp(DummyArray().Clone()); + Span span = temp; + array.AppendElements(span); + ASSERT_EQ(DummyArray(), array); + + Span constSpan = temp; + array.AppendElements(constSpan); + nsTArray expected; + expected.AppendElements(DummyArray()); + expected.AppendElements(DummyArray()); + ASSERT_EQ(expected, array); +} + +TEST(TArray, int_AppendElement_NoElementArg) +{ + nsTArray array; + array.AppendElement(); + + ASSERT_EQ(1u, array.Length()); +} + +TEST(TArray, int_AppendElement_NoElementArg_Fallible) +{ + nsTArray array; + ASSERT_NE(nullptr, array.AppendElement(fallible)); + + ASSERT_EQ(1u, array.Length()); +} + +TEST(TArray, int_AppendElement_NoElementArg_Address) +{ + nsTArray array; + *array.AppendElement() = 42; + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(42, array[0]); +} + +TEST(TArray, int_AppendElement_NoElementArg_Fallible_Address) +{ + nsTArray array; + *array.AppendElement(fallible) = 42; + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(42, array[0]); +} + +TEST(TArray, int_AppendElement_ElementArg) +{ + nsTArray array; + array.AppendElement(42); + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(42, array[0]); +} + +TEST(TArray, int_AppendElement_ElementArg_Fallible) +{ + nsTArray array; + ASSERT_NE(nullptr, array.AppendElement(42, fallible)); + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(42, array[0]); +} + +constexpr size_t dummyMovableArrayLength = 4; +uint32_t dummyMovableArrayDestructorCounter; + +static nsTArray DummyMovableArray() { + nsTArray res; + res.SetLength(dummyMovableArrayLength); + for (size_t i = 0; i < dummyMovableArrayLength; ++i) { + res[i].mDestructionCounter = &dummyMovableArrayDestructorCounter; + } + return res; +} + +TEST(TArray, Movable_AppendElements_TArray_Rvalue) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray array; + + nsTArray temp(DummyMovableArray()); + Movable* ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[0], ptr); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyMovableArray(); + ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[dummyMovableArrayLength], ptr); + ASSERT_TRUE(temp.IsEmpty()); + } + ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElements_TArray_Rvalue_Fallible) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray array; + + nsTArray temp(DummyMovableArray()); + Movable* ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyMovableArray(); + ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[dummyMovableArrayLength], ptr); + ASSERT_TRUE(temp.IsEmpty()); + } + ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElements_FallibleArray_Rvalue) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray array; + + FallibleTArray temp(DummyMovableArray()); + Movable* ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[0], ptr); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyMovableArray(); + ptr = array.AppendElements(std::move(temp)); + ASSERT_EQ(&array[dummyMovableArrayLength], ptr); + ASSERT_TRUE(temp.IsEmpty()); + } + ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElements_FallibleArray_Rvalue_Fallible) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray array; + + FallibleTArray temp(DummyMovableArray()); + Movable* ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[0], ptr); + ASSERT_TRUE(temp.IsEmpty()); + + temp = DummyMovableArray(); + ptr = array.AppendElements(std::move(temp), fallible); + ASSERT_EQ(&array[dummyMovableArrayLength], ptr); + ASSERT_TRUE(temp.IsEmpty()); + } + ASSERT_EQ(2 * dummyMovableArrayLength, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElement_NoElementArg) +{ + nsTArray array; + array.AppendElement(); + + ASSERT_EQ(1u, array.Length()); +} + +TEST(TArray, Movable_AppendElement_NoElementArg_Fallible) +{ + nsTArray array; + ASSERT_NE(nullptr, array.AppendElement(fallible)); + + ASSERT_EQ(1u, array.Length()); +} + +TEST(TArray, Movable_AppendElement_NoElementArg_Address) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray array; + array.AppendElement()->mDestructionCounter = + &dummyMovableArrayDestructorCounter; + + ASSERT_EQ(1u, array.Length()); + } + ASSERT_EQ(1u, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElement_NoElementArg_Fallible_Address) +{ + dummyMovableArrayDestructorCounter = 0; + { + nsTArray array; + array.AppendElement(fallible)->mDestructionCounter = + &dummyMovableArrayDestructorCounter; + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(&dummyMovableArrayDestructorCounter, + array[0].mDestructionCounter); + } + ASSERT_EQ(1u, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElement_ElementArg) +{ + dummyMovableArrayDestructorCounter = 0; + Movable movable; + movable.mDestructionCounter = &dummyMovableArrayDestructorCounter; + { + nsTArray array; + array.AppendElement(std::move(movable)); + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(&dummyMovableArrayDestructorCounter, + array[0].mDestructionCounter); + } + ASSERT_EQ(1u, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, Movable_AppendElement_ElementArg_Fallible) +{ + dummyMovableArrayDestructorCounter = 0; + Movable movable; + movable.mDestructionCounter = &dummyMovableArrayDestructorCounter; + { + nsTArray array; + ASSERT_NE(nullptr, array.AppendElement(std::move(movable), fallible)); + + ASSERT_EQ(1u, array.Length()); + ASSERT_EQ(&dummyMovableArrayDestructorCounter, + array[0].mDestructionCounter); + } + ASSERT_EQ(1u, dummyMovableArrayDestructorCounter); +} + +TEST(TArray, int_Assign) +{ + nsTArray array; + array.Assign(DummyArray()); + ASSERT_EQ(DummyArray(), array); + + ASSERT_TRUE(array.Assign(DummyArray(), fallible)); + ASSERT_EQ(DummyArray(), array); + +#ifdef DEBUG + ASSERT_FALSE(array.Assign(FakeHugeArray(), fallible)); +#endif + + nsTArray array2; + array2.Assign(std::move(array)); + ASSERT_TRUE(array.IsEmpty()); + ASSERT_EQ(DummyArray(), array2); +} + +TEST(TArray, int_Assign_FromEmpty_ToNonEmpty) +{ + nsTArray array; + array.AppendElement(42); + + const nsTArray empty; + array.Assign(empty); + + ASSERT_TRUE(array.IsEmpty()); +} + +TEST(TArray, int_Assign_FromEmpty_ToNonEmpty_Fallible) +{ + nsTArray array; + array.AppendElement(42); + + const nsTArray empty; + ASSERT_TRUE(array.Assign(empty, fallible)); + + ASSERT_TRUE(array.IsEmpty()); +} + +TEST(TArray, int_AssignmentOperatorSelfAssignment) +{ + CopyableTArray array; + array = DummyArray(); + + array = *&array; + ASSERT_EQ(DummyArray(), array); + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wself-move" +#endif + array = std::move(array); // self-move + ASSERT_EQ(DummyArray(), array); +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +} + +TEST(TArray, Movable_CopyOverlappingForwards) +{ + const size_t rangeLength = 8; + const size_t initialLength = 2 * rangeLength; + uint32_t destructionCounters[initialLength]; + nsTArray array; + array.AppendElements(initialLength); + + for (uint32_t i = 0; i < initialLength; ++i) { + destructionCounters[i] = 0; + } + for (uint32_t i = 0; i < initialLength; ++i) { + array[i].mDestructionCounter = &destructionCounters[i]; + } + + const size_t removedLength = rangeLength / 2; + array.RemoveElementsAt(0, removedLength); + + for (uint32_t i = 0; i < removedLength; ++i) { + ASSERT_EQ(destructionCounters[i], 1u); + } + for (uint32_t i = removedLength; i < initialLength; ++i) { + ASSERT_EQ(destructionCounters[i], 0u); + } +} + +// The code to copy overlapping regions had a bug in that it wouldn't correctly +// destroy all over the source elements being copied. +TEST(TArray, Copyable_CopyOverlappingBackwards) +{ + const size_t rangeLength = 8; + const size_t initialLength = 2 * rangeLength; + uint32_t destructionCounters[initialLength]; + nsTArray array; + array.SetCapacity(3 * rangeLength); + array.AppendElements(initialLength); + // To tickle the bug, we need to copy a source region: + // + // ..XXXXX.. + // + // such that it overlaps the destination region: + // + // ....XXXXX + // + // so we are forced to copy back-to-front to ensure correct behavior. + // The easiest way to do that is to call InsertElementsAt, which will force + // the desired kind of shift. + for (uint32_t i = 0; i < initialLength; ++i) { + destructionCounters[i] = 0; + } + for (uint32_t i = 0; i < initialLength; ++i) { + array[i].mDestructionCounter = &destructionCounters[i]; + } + + array.InsertElementsAt(0, rangeLength); + + for (uint32_t i = 0; i < initialLength; ++i) { + ASSERT_EQ(destructionCounters[i], 1u); + } +} + +namespace { + +class E { + public: + E() : mA(-1), mB(-2) { constructCount++; } + E(int a, int b) : mA(a), mB(b) { constructCount++; } + E(E&& aRhs) : mA(aRhs.mA), mB(aRhs.mB) { + aRhs.mA = 0; + aRhs.mB = 0; + moveCount++; + } + + E& operator=(E&& aRhs) { + mA = aRhs.mA; + aRhs.mA = 0; + mB = aRhs.mB; + aRhs.mB = 0; + moveCount++; + return *this; + } + + int a() const { return mA; } + int b() const { return mB; } + + E(const E&) = delete; + E& operator=(const E&) = delete; + + static size_t constructCount; + static size_t moveCount; + + private: + int mA; + int mB; +}; + +size_t E::constructCount = 0; +size_t E::moveCount = 0; + +} // namespace + +TEST(TArray, Emplace) +{ + nsTArray array; + array.SetCapacity(20); + + ASSERT_EQ(array.Length(), 0u); + + for (int i = 0; i < 10; i++) { + E s(i, i * i); + array.AppendElement(std::move(s)); + } + + ASSERT_EQ(array.Length(), 10u); + ASSERT_EQ(E::constructCount, 10u); + ASSERT_EQ(E::moveCount, 10u); + + for (int i = 10; i < 20; i++) { + array.EmplaceBack(i, i * i); + } + + ASSERT_EQ(array.Length(), 20u); + ASSERT_EQ(E::constructCount, 20u); + ASSERT_EQ(E::moveCount, 10u); + + for (int i = 0; i < 20; i++) { + ASSERT_EQ(array[i].a(), i); + ASSERT_EQ(array[i].b(), i * i); + } + + array.EmplaceBack(); + + ASSERT_EQ(array.Length(), 21u); + ASSERT_EQ(E::constructCount, 21u); + ASSERT_EQ(E::moveCount, 10u); + + ASSERT_EQ(array[20].a(), -1); + ASSERT_EQ(array[20].b(), -2); +} + +TEST(TArray, UnorderedRemoveElements) +{ + // When removing an element from the end of the array, it can be removed in + // place, by destroying it and decrementing the length. + // + // [ 1, 2, 3 ] => [ 1, 2 ] + // ^ + { + nsTArray array{1, 2, 3}; + array.UnorderedRemoveElementAt(2); + + nsTArray goal{1, 2}; + ASSERT_EQ(array, goal); + } + + // When removing any other single element, it is removed by swapping it with + // the last element, and then decrementing the length as before. + // + // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 6, 3, 4, 5 ] + // ^ + { + nsTArray array{1, 2, 3, 4, 5, 6}; + array.UnorderedRemoveElementAt(1); + + nsTArray goal{1, 6, 3, 4, 5}; + ASSERT_EQ(array, goal); + } + + // This method also supports efficiently removing a range of elements. If they + // are at the end, then they can all be removed like in the one element case. + // + // [ 1, 2, 3, 4, 5, 6 ] => [ 1, 2 ] + // ^--------^ + { + nsTArray array{1, 2, 3, 4, 5, 6}; + array.UnorderedRemoveElementsAt(2, 4); + + nsTArray goal{1, 2}; + ASSERT_EQ(array, goal); + } + + // If more elements are removed than exist after the removed section, the + // remaining elements will be shifted down like in a normal removal. + // + // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 2, 7, 8 ] + // ^--------^ + { + nsTArray array{1, 2, 3, 4, 5, 6, 7, 8}; + array.UnorderedRemoveElementsAt(2, 4); + + nsTArray goal{1, 2, 7, 8}; + ASSERT_EQ(array, goal); + } + + // And if fewer elements are removed than exist after the removed section, + // elements will be moved from the end of the array to fill the vacated space. + // + // [ 1, 2, 3, 4, 5, 6, 7, 8 ] => [ 1, 7, 8, 4, 5, 6 ] + // ^--^ + { + nsTArray array{1, 2, 3, 4, 5, 6, 7, 8}; + array.UnorderedRemoveElementsAt(1, 2); + + nsTArray goal{1, 7, 8, 4, 5, 6}; + ASSERT_EQ(array, goal); + } + + // We should do the right thing if we drain the entire array. + { + nsTArray array{1, 2, 3, 4, 5}; + array.UnorderedRemoveElementsAt(0, 5); + + nsTArray goal{}; + ASSERT_EQ(array, goal); + } + + { + nsTArray array{1}; + array.UnorderedRemoveElementAt(0); + + nsTArray goal{}; + ASSERT_EQ(array, goal); + } + + // We should do the right thing if we remove the same number of elements that + // we have remaining. + { + nsTArray array{1, 2, 3, 4, 5, 6}; + array.UnorderedRemoveElementsAt(2, 2); + + nsTArray goal{1, 2, 5, 6}; + ASSERT_EQ(array, goal); + } + + { + nsTArray array{1, 2, 3}; + array.UnorderedRemoveElementAt(1); + + nsTArray goal{1, 3}; + ASSERT_EQ(array, goal); + } + + // We should be able to remove elements from the front without issue. + { + nsTArray array{1, 2, 3, 4, 5, 6}; + array.UnorderedRemoveElementsAt(0, 2); + + nsTArray goal{5, 6, 3, 4}; + ASSERT_EQ(array, goal); + } + + { + nsTArray array{1, 2, 3, 4}; + array.UnorderedRemoveElementAt(0); + + nsTArray goal{4, 2, 3}; + ASSERT_EQ(array, goal); + } +} + +TEST(TArray, RemoveFromEnd) +{ + { + nsTArray array{1, 2, 3, 4}; + ASSERT_EQ(array.PopLastElement(), 4); + array.RemoveLastElement(); + ASSERT_EQ(array.PopLastElement(), 2); + array.RemoveLastElement(); + ASSERT_TRUE(array.IsEmpty()); + } +} + +TEST(TArray, ConvertIteratorToConstIterator) +{ + nsTArray array{1, 2, 3, 4}; + + nsTArray::const_iterator it = array.begin(); + ASSERT_EQ(array.cbegin(), it); +} + +TEST(TArray, RemoveElementAt_ByIterator) +{ + nsTArray array{1, 2, 3, 4}; + const auto it = std::find(array.begin(), array.end(), 3); + const auto itAfter = array.RemoveElementAt(it); + + // Based on the implementation of the iterator, we could compare it and + // itAfter, but we should not rely on such implementation details. + + ASSERT_EQ(2, std::distance(array.cbegin(), itAfter)); + const nsTArray expected{1, 2, 4}; + ASSERT_EQ(expected, array); +} + +TEST(TArray, RemoveElementsRange_ByIterator) +{ + nsTArray array{1, 2, 3, 4}; + const auto it = std::find(array.begin(), array.end(), 3); + const auto itAfter = array.RemoveElementsRange(it, array.end()); + + // Based on the implementation of the iterator, we could compare it and + // itAfter, but we should not rely on such implementation details. + + ASSERT_EQ(2, std::distance(array.cbegin(), itAfter)); + const nsTArray expected{1, 2}; + ASSERT_EQ(expected, array); +} + +TEST(TArray, RemoveLastElements_None) +{ + const nsTArray original{1, 2, 3, 4}; + nsTArray array = original.Clone(); + array.RemoveLastElements(0); + + ASSERT_EQ(original, array); +} + +TEST(TArray, RemoveLastElements_Empty_None) +{ + nsTArray array; + array.RemoveLastElements(0); + + ASSERT_EQ(0u, array.Length()); +} + +TEST(TArray, RemoveLastElements_All) +{ + nsTArray array{1, 2, 3, 4}; + array.RemoveLastElements(4); + + ASSERT_EQ(0u, array.Length()); +} + +TEST(TArray, RemoveLastElements_One) +{ + nsTArray array{1, 2, 3, 4}; + array.RemoveLastElements(1); + + ASSERT_EQ((nsTArray{1, 2, 3}), array); +} + +static_assert(std::is_copy_assignable&>()))>::value, + "output iteraror must be copy-assignable"); +static_assert(std::is_copy_constructible&>()))>::value, + "output iterator must be copy-constructible"); + +TEST(TArray, MakeBackInserter) +{ + const std::vector src{1, 2, 3, 4}; + nsTArray dst; + + std::copy(src.begin(), src.end(), MakeBackInserter(dst)); + + const nsTArray expected{1, 2, 3, 4}; + ASSERT_EQ(expected, dst); +} + +TEST(TArray, MakeBackInserter_Move) +{ + uint32_t destructionCounter = 0; + + { + std::vector src(1); + src[0].mDestructionCounter = &destructionCounter; + + nsTArray dst; + + std::copy(std::make_move_iterator(src.begin()), + std::make_move_iterator(src.end()), MakeBackInserter(dst)); + + ASSERT_EQ(1u, dst.Length()); + ASSERT_EQ(0u, destructionCounter); + } + + ASSERT_EQ(1u, destructionCounter); +} + +TEST(TArray, ConvertToSpan) +{ + nsTArray arr = {1, 2, 3, 4, 5}; + + // from const + { + const auto& constArrRef = arr; + + auto span = Span{constArrRef}; + static_assert(std::is_same_v>); + } + + // from non-const + { + auto span = Span{arr}; + static_assert(std::is_same_v>); + } +} + +// This should compile: +struct RefCounted; + +class Foo { + ~Foo(); // Intentionally out of line + + nsTArray> mArray; + + const RefCounted* GetFirst() const { return mArray.SafeElementAt(0); } +}; + +TEST(TArray, StableSort) +{ + const nsTArray> expected = { + std::pair(1, 9), std::pair(1, 8), std::pair(1, 7), std::pair(2, 0), + std::pair(3, 0)}; + nsTArray> array = {std::pair(1, 9), std::pair(2, 0), + std::pair(1, 8), std::pair(3, 0), + std::pair(1, 7)}; + + array.StableSort([](std::pair left, std::pair right) { + return left.first - right.first; + }); + + EXPECT_EQ(expected, array); +} + +TEST(TArray, ToArray) +{ + const auto src = std::array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + nsTArray keys = ToArray(src); + keys.Sort(); + + EXPECT_EQ((nsTArray{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys); +} + +// Test this to make sure this properly uses ADL. +TEST(TArray, ToArray_HashMap) +{ + nsTHashMap src; + + for (uint32_t i = 0; i < 10; ++i) { + src.InsertOrUpdate(i, i); + } + + nsTArray keys = ToArray(src.Keys()); + keys.Sort(); + + EXPECT_EQ((nsTArray{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys); +} + +TEST(TArray, ToTArray) +{ + const auto src = std::array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + + auto keys = ToTArray>(src); + keys.Sort(); + + static_assert(std::is_same_v>); + + EXPECT_EQ((nsTArray{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), keys); +} + +TEST(TArray, RemoveElementsBy) +{ + // Removing elements returns the correct number of removed elements. + { + nsTArray array{8, 1, 1, 3, 3, 5, 2, 3}; + auto removed = array.RemoveElementsBy([](int i) { return i == 3; }); + EXPECT_EQ(removed, 3u); + + nsTArray goal{8, 1, 1, 5, 2}; + EXPECT_EQ(array, goal); + } + + // The check is called in order. + { + int index = 0; + nsTArray array{0, 1, 2, 3, 4, 5}; + auto removed = array.RemoveElementsBy([&](int i) { + EXPECT_EQ(index, i); + index++; + return i == 3; + }); + EXPECT_EQ(removed, 1u); + + nsTArray goal{0, 1, 2, 4, 5}; + EXPECT_EQ(array, goal); + } + + // Removing nothing works + { + nsTArray array{0, 1, 2, 3, 4}; + auto removed = array.RemoveElementsBy([](int) { return false; }); + EXPECT_EQ(removed, 0u); + + nsTArray goal{0, 1, 2, 3, 4}; + EXPECT_EQ(array, goal); + } + + // Removing everything works + { + nsTArray array{0, 1, 2, 3, 4}; + auto removed = array.RemoveElementsBy([](int) { return true; }); + EXPECT_EQ(removed, 5u); + + nsTArray goal{}; + EXPECT_EQ(array, goal); + } +} + +} // namespace TestTArray diff --git a/xpcom/tests/gtest/TestTArray2.cpp b/xpcom/tests/gtest/TestTArray2.cpp new file mode 100644 index 0000000000..bc64ef1e05 --- /dev/null +++ b/xpcom/tests/gtest/TestTArray2.cpp @@ -0,0 +1,1423 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/ArrayUtils.h" +#include "mozilla/Unused.h" + +#include +#include +#include +#include "nsTArray.h" +#include "nsString.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsXPCOM.h" +#include "nsIFile.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +using namespace mozilla; + +namespace TestTArray { + +// Define this so we can use test_basic_array in test_comptr_array +template +inline bool operator<(const nsCOMPtr& lhs, const nsCOMPtr& rhs) { + return lhs.get() < rhs.get(); +} + +//---- + +template +static bool test_basic_array(ElementType* data, size_t dataLen, + const ElementType& extra) { + CopyableTArray ary; + const nsTArray& cary = ary; + + ary.AppendElements(data, dataLen); + if (ary.Length() != dataLen) { + return false; + } + if (!(ary == ary)) { + return false; + } + size_t i; + for (i = 0; i < ary.Length(); ++i) { + if (ary[i] != data[i]) return false; + } + for (i = 0; i < ary.Length(); ++i) { + if (ary.SafeElementAt(i, extra) != data[i]) return false; + } + if (ary.SafeElementAt(ary.Length(), extra) != extra || + ary.SafeElementAt(ary.Length() * 10, extra) != extra) + return false; + // ensure sort results in ascending order + ary.Sort(); + size_t j = 0, k = ary.IndexOfFirstElementGt(extra); + if (k != 0 && ary[k - 1] == extra) return false; + for (i = 0; i < ary.Length(); ++i) { + k = ary.IndexOfFirstElementGt(ary[i]); + if (k == 0 || ary[k - 1] != ary[i]) return false; + if (k < j) return false; + j = k; + } + for (i = ary.Length(); --i;) { + if (ary[i] < ary[i - 1]) return false; + if (ary[i] == ary[i - 1]) ary.RemoveElementAt(i); + } + if (!(ary == ary)) { + return false; + } + for (i = 0; i < ary.Length(); ++i) { + if (ary.BinaryIndexOf(ary[i]) != i) return false; + } + if (ary.BinaryIndexOf(extra) != ary.NoIndex) return false; + size_t oldLen = ary.Length(); + ary.RemoveElement(data[dataLen / 2]); + if (ary.Length() != (oldLen - 1)) return false; + if (!(ary == ary)) return false; + + if (ary.ApplyIf( + extra, []() { return true; }, []() { return false; })) + return false; + if (ary.ApplyIf( + extra, [](size_t) { return true; }, []() { return false; })) + return false; + // On a non-const array, ApplyIf's first lambda may use either const or non- + // const element types. + if (ary.ApplyIf( + extra, [](ElementType&) { return true; }, []() { return false; })) + return false; + if (ary.ApplyIf( + extra, [](const ElementType&) { return true; }, + []() { return false; })) + return false; + if (ary.ApplyIf( + extra, [](size_t, ElementType&) { return true; }, + []() { return false; })) + return false; + if (ary.ApplyIf( + extra, [](size_t, const ElementType&) { return true; }, + []() { return false; })) + return false; + + if (cary.ApplyIf( + extra, []() { return true; }, []() { return false; })) + if (cary.ApplyIf( + extra, [](size_t) { return true; }, []() { return false; })) + // On a const array, ApplyIf's first lambda must only use const element + // types. + if (cary.ApplyIf( + extra, [](const ElementType&) { return true; }, + []() { return false; })) + if (cary.ApplyIf( + extra, [](size_t, const ElementType&) { return true; }, + []() { return false; })) + return false; + + size_t index = ary.Length() / 2; + ary.InsertElementAt(index, extra); + if (!(ary == ary)) return false; + if (ary[index] != extra) return false; + if (ary.IndexOf(extra) == ary.NoIndex) return false; + if (ary.LastIndexOf(extra) == ary.NoIndex) return false; + // ensure proper searching + if (ary.IndexOf(extra) > ary.LastIndexOf(extra)) return false; + if (ary.IndexOf(extra, index) != ary.LastIndexOf(extra, index)) return false; + if (!ary.ApplyIf( + extra, + [&](size_t i, const ElementType& e) { + return i == index && e == extra; + }, + []() { return false; })) + return false; + if (!cary.ApplyIf( + extra, + [&](size_t i, const ElementType& e) { + return i == index && e == extra; + }, + []() { return false; })) + return false; + + nsTArray copy(ary.Clone()); + if (!(ary == copy)) return false; + for (i = 0; i < copy.Length(); ++i) { + if (ary[i] != copy[i]) return false; + } + ary.AppendElements(copy); + size_t cap = ary.Capacity(); + ary.RemoveElementsAt(copy.Length(), copy.Length()); + ary.Compact(); + if (ary.Capacity() == cap) return false; + + ary.Clear(); + if (ary.IndexOf(extra) != ary.NoIndex) return false; + if (ary.LastIndexOf(extra) != ary.NoIndex) return false; + if (ary.ApplyIf( + extra, []() { return true; }, []() { return false; })) + return false; + if (cary.ApplyIf( + extra, []() { return true; }, []() { return false; })) + return false; + + ary.Clear(); + if (!ary.IsEmpty()) return false; + if (!(ary == nsTArray())) return false; + if (ary == copy) return false; + if (ary.SafeElementAt(0, extra) != extra || + ary.SafeElementAt(10, extra) != extra) + return false; + + ary = copy; + if (!(ary == copy)) return false; + for (i = 0; i < copy.Length(); ++i) { + if (ary[i] != copy[i]) return false; + } + + ary.InsertElementsAt(0, copy); + if (ary == copy) return false; + ary.RemoveElementsAt(0, copy.Length()); + for (i = 0; i < copy.Length(); ++i) { + if (ary[i] != copy[i]) return false; + } + + // These shouldn't crash! + nsTArray empty; + ary.AppendElements(reinterpret_cast(0), 0); + ary.AppendElements(empty); + + // See bug 324981 + ary.RemoveElement(extra); + ary.RemoveElement(extra); + + return true; +} + +TEST(TArray, test_int_array) +{ + int data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + ASSERT_TRUE(test_basic_array(data, ArrayLength(data), int(14))); +} + +TEST(TArray, test_int64_array) +{ + int64_t data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + ASSERT_TRUE(test_basic_array(data, ArrayLength(data), int64_t(14))); +} + +TEST(TArray, test_char_array) +{ + char data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + ASSERT_TRUE(test_basic_array(data, ArrayLength(data), char(14))); +} + +TEST(TArray, test_uint32_array) +{ + uint32_t data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + ASSERT_TRUE(test_basic_array(data, ArrayLength(data), uint32_t(14))); +} + +//---- + +class Object { + public: + Object() : mNum(0) {} + Object(const char* str, uint32_t num) : mStr(str), mNum(num) {} + Object(const Object& other) = default; + ~Object() = default; + + Object& operator=(const Object& other) = default; + + bool operator==(const Object& other) const { + return mStr == other.mStr && mNum == other.mNum; + } + + bool operator<(const Object& other) const { + // sort based on mStr only + return Compare(mStr, other.mStr) < 0; + } + + const char* Str() const { return mStr.get(); } + uint32_t Num() const { return mNum; } + + private: + nsCString mStr; + uint32_t mNum; +}; + +TEST(TArray, test_object_array) +{ + nsTArray objArray; + const char kdata[] = "hello world"; + size_t i; + for (i = 0; i < ArrayLength(kdata); ++i) { + char x[] = {kdata[i], '\0'}; + objArray.AppendElement(Object(x, i)); + } + for (i = 0; i < ArrayLength(kdata); ++i) { + ASSERT_EQ(objArray[i].Str()[0], kdata[i]); + ASSERT_EQ(objArray[i].Num(), i); + } + objArray.Sort(); + const char ksorted[] = "\0 dehllloorw"; + for (i = 0; i < ArrayLength(kdata) - 1; ++i) { + ASSERT_EQ(objArray[i].Str()[0], ksorted[i]); + } +} + +class Countable { + static int sCount; + + public: + Countable() { sCount++; } + + Countable(const Countable& aOther) { sCount++; } + + static int Count() { return sCount; } +}; + +class Moveable { + static int sCount; + + public: + Moveable() { sCount++; } + + Moveable(const Moveable& aOther) { sCount++; } + + Moveable(Moveable&& aOther) { + // Do not increment sCount + } + + static int Count() { return sCount; } +}; + +class MoveOnly_RelocateUsingMemutils { + public: + MoveOnly_RelocateUsingMemutils() = default; + + MoveOnly_RelocateUsingMemutils(const MoveOnly_RelocateUsingMemutils&) = + delete; + MoveOnly_RelocateUsingMemutils(MoveOnly_RelocateUsingMemutils&&) = default; + + MoveOnly_RelocateUsingMemutils& operator=( + const MoveOnly_RelocateUsingMemutils&) = delete; + MoveOnly_RelocateUsingMemutils& operator=(MoveOnly_RelocateUsingMemutils&&) = + default; +}; + +static_assert( + std::is_move_constructible_v>); +static_assert( + std::is_move_assignable_v>); +static_assert( + !std::is_copy_constructible_v>); +static_assert( + !std::is_copy_assignable_v>); + +class MoveOnly_RelocateUsingMoveConstructor { + public: + MoveOnly_RelocateUsingMoveConstructor() = default; + + MoveOnly_RelocateUsingMoveConstructor( + const MoveOnly_RelocateUsingMoveConstructor&) = delete; + MoveOnly_RelocateUsingMoveConstructor( + MoveOnly_RelocateUsingMoveConstructor&&) = default; + + MoveOnly_RelocateUsingMoveConstructor& operator=( + const MoveOnly_RelocateUsingMoveConstructor&) = delete; + MoveOnly_RelocateUsingMoveConstructor& operator=( + MoveOnly_RelocateUsingMoveConstructor&&) = default; +}; +} // namespace TestTArray + +MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR( + TestTArray::MoveOnly_RelocateUsingMoveConstructor) + +namespace TestTArray { +static_assert(std::is_move_constructible_v< + nsTArray>); +static_assert( + std::is_move_assignable_v>); +static_assert(!std::is_copy_constructible_v< + nsTArray>); +static_assert(!std::is_copy_assignable_v< + nsTArray>); +} // namespace TestTArray + +namespace TestTArray { + +/* static */ +int Countable::sCount = 0; +/* static */ +int Moveable::sCount = 0; + +static nsTArray returns_by_value() { + nsTArray result; + return result; +} + +TEST(TArray, test_return_by_value) +{ + nsTArray result = returns_by_value(); + ASSERT_TRUE(true); // This is just a compilation test. +} + +TEST(TArray, test_move_array) +{ + nsTArray countableArray; + uint32_t i; + for (i = 0; i < 4; ++i) { + countableArray.AppendElement(Countable()); + } + + ASSERT_EQ(Countable::Count(), 8); + + const nsTArray& constRefCountableArray = countableArray; + + ASSERT_EQ(Countable::Count(), 8); + + nsTArray copyCountableArray(constRefCountableArray.Clone()); + + ASSERT_EQ(Countable::Count(), 12); + + nsTArray&& moveRefCountableArray = std::move(countableArray); + moveRefCountableArray.Length(); // Make compilers happy. + + ASSERT_EQ(Countable::Count(), 12); + + nsTArray movedCountableArray(std::move(countableArray)); + + ASSERT_EQ(Countable::Count(), 12); + + // Test ctor + FallibleTArray differentAllocatorCountableArray( + std::move(copyCountableArray)); + // operator= + copyCountableArray = std::move(differentAllocatorCountableArray); + differentAllocatorCountableArray = std::move(copyCountableArray); + // And the other ctor + nsTArray copyCountableArray2( + std::move(differentAllocatorCountableArray)); + // with auto + AutoTArray autoCountableArray(std::move(copyCountableArray2)); + // operator= + copyCountableArray2 = std::move(autoCountableArray); + // Mix with FallibleTArray + FallibleTArray differentAllocatorCountableArray2( + std::move(copyCountableArray2)); + AutoTArray autoCountableArray2( + std::move(differentAllocatorCountableArray2)); + differentAllocatorCountableArray2 = std::move(autoCountableArray2); + + ASSERT_EQ(Countable::Count(), 12); + + nsTArray moveableArray; + for (i = 0; i < 4; ++i) { + moveableArray.AppendElement(Moveable()); + } + + ASSERT_EQ(Moveable::Count(), 4); + + const nsTArray& constRefMoveableArray = moveableArray; + + ASSERT_EQ(Moveable::Count(), 4); + + nsTArray copyMoveableArray(constRefMoveableArray.Clone()); + + ASSERT_EQ(Moveable::Count(), 8); + + nsTArray&& moveRefMoveableArray = std::move(moveableArray); + moveRefMoveableArray.Length(); // Make compilers happy. + + ASSERT_EQ(Moveable::Count(), 8); + + nsTArray movedMoveableArray(std::move(moveableArray)); + + ASSERT_EQ(Moveable::Count(), 8); + + // Test ctor + FallibleTArray differentAllocatorMoveableArray( + std::move(copyMoveableArray)); + // operator= + copyMoveableArray = std::move(differentAllocatorMoveableArray); + differentAllocatorMoveableArray = std::move(copyMoveableArray); + // And the other ctor + nsTArray copyMoveableArray2( + std::move(differentAllocatorMoveableArray)); + // with auto + AutoTArray autoMoveableArray(std::move(copyMoveableArray2)); + // operator= + copyMoveableArray2 = std::move(autoMoveableArray); + // Mix with FallibleTArray + FallibleTArray differentAllocatorMoveableArray2( + std::move(copyMoveableArray2)); + AutoTArray autoMoveableArray2( + std::move(differentAllocatorMoveableArray2)); + differentAllocatorMoveableArray2 = std::move(autoMoveableArray2); + + ASSERT_EQ(Moveable::Count(), 8); + + AutoTArray moveableAutoArray; + for (uint32_t i = 0; i < 4; ++i) { + moveableAutoArray.AppendElement(Moveable()); + } + + ASSERT_EQ(Moveable::Count(), 12); + + const AutoTArray& constRefMoveableAutoArray = moveableAutoArray; + + ASSERT_EQ(Moveable::Count(), 12); + + CopyableAutoTArray copyMoveableAutoArray( + constRefMoveableAutoArray); + + ASSERT_EQ(Moveable::Count(), 16); + + AutoTArray movedMoveableAutoArray(std::move(moveableAutoArray)); + + ASSERT_EQ(Moveable::Count(), 16); +} + +template +class TArray_MoveOnlyTest : public ::testing::Test {}; + +TYPED_TEST_SUITE_P(TArray_MoveOnlyTest); + +static constexpr size_t kMoveOnlyTestArrayLength = 4; + +template +static auto MakeMoveOnlyArray() { + ArrayType moveOnlyArray; + for (size_t i = 0; i < kMoveOnlyTestArrayLength; ++i) { + EXPECT_TRUE(moveOnlyArray.AppendElement(typename ArrayType::value_type(), + fallible)); + } + return moveOnlyArray; +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + nsTArray movedMoveOnlyArray(std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, movedMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + nsTArray movedMoveOnlyArray; + movedMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, movedMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_MoveReAssign) { + nsTArray movedMoveOnlyArray; + movedMoveOnlyArray = MakeMoveOnlyArray>(); + // Re-assign, to check that move-assign does not only work on an empty array. + movedMoveOnlyArray = MakeMoveOnlyArray>(); + + ASSERT_EQ(kMoveOnlyTestArrayLength, movedMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_to_FallibleTArray_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + FallibleTArray differentAllocatorMoveOnlyArray( + std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, nsTArray_to_FallibleTArray_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + FallibleTArray differentAllocatorMoveOnlyArray; + differentAllocatorMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, FallibleTArray_to_nsTArray_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + nsTArray differentAllocatorMoveOnlyArray(std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, FallibleTArray_to_nsTArray_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + nsTArray differentAllocatorMoveOnlyArray; + differentAllocatorMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, differentAllocatorMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, AutoTArray_AutoStorage_MoveConstruct) { + auto moveOnlyArray = + MakeMoveOnlyArray>(); + AutoTArray autoMoveOnlyArray( + std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, AutoTArray_AutoStorage_MoveAssign) { + auto moveOnlyArray = + MakeMoveOnlyArray>(); + AutoTArray autoMoveOnlyArray; + autoMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + nsTArray_to_AutoTArray_AutoStorage_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + AutoTArray autoMoveOnlyArray( + std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + nsTArray_to_AutoTArray_AutoStorage_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + AutoTArray autoMoveOnlyArray; + autoMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + nsTArray_to_AutoTArray_HeapStorage_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + AutoTArray autoMoveOnlyArray( + std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + nsTArray_to_AutoTArray_HeapStorage_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + AutoTArray autoMoveOnlyArray; + autoMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + FallibleTArray_to_AutoTArray_HeapStorage_MoveConstruct) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + AutoTArray autoMoveOnlyArray(std::move(moveOnlyArray)); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +TYPED_TEST_P(TArray_MoveOnlyTest, + FallibleTArray_to_AutoTArray_HeapStorage_MoveAssign) { + auto moveOnlyArray = MakeMoveOnlyArray>(); + AutoTArray autoMoveOnlyArray; + autoMoveOnlyArray = std::move(moveOnlyArray); + + ASSERT_EQ(0u, moveOnlyArray.Length()); + ASSERT_EQ(kMoveOnlyTestArrayLength, autoMoveOnlyArray.Length()); +} + +REGISTER_TYPED_TEST_SUITE_P( + TArray_MoveOnlyTest, nsTArray_MoveConstruct, nsTArray_MoveAssign, + nsTArray_MoveReAssign, nsTArray_to_FallibleTArray_MoveConstruct, + nsTArray_to_FallibleTArray_MoveAssign, + FallibleTArray_to_nsTArray_MoveConstruct, + FallibleTArray_to_nsTArray_MoveAssign, AutoTArray_AutoStorage_MoveConstruct, + AutoTArray_AutoStorage_MoveAssign, + nsTArray_to_AutoTArray_AutoStorage_MoveConstruct, + nsTArray_to_AutoTArray_AutoStorage_MoveAssign, + nsTArray_to_AutoTArray_HeapStorage_MoveConstruct, + nsTArray_to_AutoTArray_HeapStorage_MoveAssign, + FallibleTArray_to_AutoTArray_HeapStorage_MoveConstruct, + FallibleTArray_to_AutoTArray_HeapStorage_MoveAssign); + +using BothMoveOnlyTypes = + ::testing::Types; +INSTANTIATE_TYPED_TEST_SUITE_P(InstantiationOf, TArray_MoveOnlyTest, + BothMoveOnlyTypes); + +//---- + +TEST(TArray, test_string_array) +{ + nsTArray strArray; + const char kdata[] = "hello world"; + size_t i; + for (i = 0; i < ArrayLength(kdata); ++i) { + nsCString str; + str.Assign(kdata[i]); + strArray.AppendElement(str); + } + for (i = 0; i < ArrayLength(kdata); ++i) { + ASSERT_EQ(strArray[i].CharAt(0), kdata[i]); + } + + const char kextra[] = "foo bar"; + size_t oldLen = strArray.Length(); + strArray.AppendElement(kextra); + strArray.RemoveElement(kextra); + ASSERT_EQ(oldLen, strArray.Length()); + + ASSERT_EQ(strArray.IndexOf("e"), size_t(1)); + ASSERT_TRUE(strArray.ApplyIf( + "e", [](size_t i, nsCString& s) { return i == 1 && s == "e"; }, + []() { return false; })); + + strArray.Sort(); + const char ksorted[] = "\0 dehllloorw"; + for (i = ArrayLength(kdata); i--;) { + ASSERT_EQ(strArray[i].CharAt(0), ksorted[i]); + if (i > 0 && strArray[i] == strArray[i - 1]) strArray.RemoveElementAt(i); + } + for (i = 0; i < strArray.Length(); ++i) { + ASSERT_EQ(strArray.BinaryIndexOf(strArray[i]), i); + } + auto no_index = strArray.NoIndex; // Fixes gtest compilation error + ASSERT_EQ(strArray.BinaryIndexOf(""_ns), no_index); + + nsCString rawArray[MOZ_ARRAY_LENGTH(kdata) - 1]; + for (i = 0; i < ArrayLength(rawArray); ++i) + rawArray[i].Assign(kdata + i); // substrings of kdata + + ASSERT_TRUE( + test_basic_array(rawArray, ArrayLength(rawArray), nsCString("foopy"))); +} + +//---- + +typedef nsCOMPtr FilePointer; + +class nsFileNameComparator { + public: + bool Equals(const FilePointer& a, const char* b) const { + nsAutoCString name; + a->GetNativeLeafName(name); + return name.Equals(b); + } +}; + +TEST(TArray, test_comptr_array) +{ + FilePointer tmpDir; + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir)); + ASSERT_TRUE(tmpDir); + const char* kNames[] = {"foo.txt", "bar.html", "baz.gif"}; + nsTArray fileArray; + size_t i; + for (i = 0; i < ArrayLength(kNames); ++i) { + FilePointer f; + tmpDir->Clone(getter_AddRefs(f)); + ASSERT_TRUE(f); + ASSERT_NS_SUCCEEDED(f->AppendNative(nsDependentCString(kNames[i]))); + fileArray.AppendElement(f); + } + + ASSERT_EQ(fileArray.IndexOf(kNames[1], 0, nsFileNameComparator()), size_t(1)); + ASSERT_TRUE(fileArray.ApplyIf( + kNames[1], 0, nsFileNameComparator(), [](size_t i) { return i == 1; }, + []() { return false; })); + + // It's unclear what 'operator<' means for nsCOMPtr, but whatever... + ASSERT_TRUE( + test_basic_array(fileArray.Elements(), fileArray.Length(), tmpDir)); +} + +//---- + +class RefcountedObject { + public: + RefcountedObject() : rc(0) {} + void AddRef() { ++rc; } + void Release() { + if (--rc == 0) delete this; + } + ~RefcountedObject() = default; + + private: + int32_t rc; +}; + +TEST(TArray, test_refptr_array) +{ + nsTArray> objArray; + + RefcountedObject* a = new RefcountedObject(); + a->AddRef(); + RefcountedObject* b = new RefcountedObject(); + b->AddRef(); + RefcountedObject* c = new RefcountedObject(); + c->AddRef(); + + objArray.AppendElement(a); + objArray.AppendElement(b); + objArray.AppendElement(c); + + ASSERT_EQ(objArray.IndexOf(b), size_t(1)); + ASSERT_TRUE(objArray.ApplyIf( + b, + [&](size_t i, RefPtr& r) { return i == 1 && r == b; }, + []() { return false; })); + + a->Release(); + b->Release(); + c->Release(); +} + +//---- + +TEST(TArray, test_ptrarray) +{ + nsTArray ary; + ASSERT_EQ(ary.SafeElementAt(0), nullptr); + ASSERT_EQ(ary.SafeElementAt(1000), nullptr); + + uint32_t a = 10; + ary.AppendElement(&a); + ASSERT_EQ(*ary[0], a); + ASSERT_EQ(*ary.SafeElementAt(0), a); + + nsTArray cary; + ASSERT_EQ(cary.SafeElementAt(0), nullptr); + ASSERT_EQ(cary.SafeElementAt(1000), nullptr); + + const uint32_t b = 14; + cary.AppendElement(&a); + cary.AppendElement(&b); + ASSERT_EQ(*cary[0], a); + ASSERT_EQ(*cary[1], b); + ASSERT_EQ(*cary.SafeElementAt(0), a); + ASSERT_EQ(*cary.SafeElementAt(1), b); +} + +//---- + +// This test relies too heavily on the existence of DebugGetHeader to be +// useful in non-debug builds. +#ifdef DEBUG +TEST(TArray, test_autoarray) +{ + uint32_t data[] = {4, 6, 8, 2, 4, 1, 5, 7, 3}; + AutoTArray array; + + void* hdr = array.DebugGetHeader(); + ASSERT_NE(hdr, nsTArray().DebugGetHeader()); + ASSERT_NE(hdr, + (AutoTArray().DebugGetHeader())); + + array.AppendElement(1u); + ASSERT_EQ(hdr, array.DebugGetHeader()); + + array.RemoveElement(1u); + array.AppendElements(data, ArrayLength(data)); + ASSERT_EQ(hdr, array.DebugGetHeader()); + + array.AppendElement(2u); + ASSERT_NE(hdr, array.DebugGetHeader()); + + array.Clear(); + array.Compact(); + ASSERT_EQ(hdr, array.DebugGetHeader()); + array.AppendElements(data, ArrayLength(data)); + ASSERT_EQ(hdr, array.DebugGetHeader()); + + nsTArray array2; + void* emptyHdr = array2.DebugGetHeader(); + array.SwapElements(array2); + ASSERT_NE(emptyHdr, array.DebugGetHeader()); + ASSERT_NE(hdr, array2.DebugGetHeader()); + size_t i; + for (i = 0; i < ArrayLength(data); ++i) { + ASSERT_EQ(array2[i], data[i]); + } + ASSERT_TRUE(array.IsEmpty()); + + array.Compact(); + array.AppendElements(data, ArrayLength(data)); + uint32_t data3[] = {5, 7, 11}; + AutoTArray array3; + array3.AppendElements(data3, ArrayLength(data3)); + array.SwapElements(array3); + for (i = 0; i < ArrayLength(data); ++i) { + ASSERT_EQ(array3[i], data[i]); + } + for (i = 0; i < ArrayLength(data3); ++i) { + ASSERT_EQ(array[i], data3[i]); + } +} +#endif + +//---- + +// IndexOf used to potentially scan beyond the end of the array. Test for +// this incorrect behavior by adding a value (5), removing it, then seeing +// if IndexOf finds it. +TEST(TArray, test_indexof) +{ + nsTArray array; + array.AppendElement(0); + // add and remove the 5 + array.AppendElement(5); + array.RemoveElementAt(1); + // we should not find the 5! + auto no_index = array.NoIndex; // Fixes gtest compilation error. + ASSERT_EQ(array.IndexOf(5, 1), no_index); + ASSERT_FALSE(array.ApplyIf( + 5, 1, []() { return true; }, []() { return false; })); +} + +//---- + +template +static bool is_heap(const Array& ary, size_t len) { + size_t index = 1; + while (index < len) { + if (ary[index] > ary[(index - 1) >> 1]) return false; + index++; + } + return true; +} + +//---- + +// An array |arr| is using its auto buffer if |&arr < arr.Elements()| and +// |arr.Elements() - &arr| is small. + +#define IS_USING_AUTO(arr) \ + ((uintptr_t) & (arr) < (uintptr_t)arr.Elements() && \ + ((ptrdiff_t)arr.Elements() - (ptrdiff_t)&arr) <= 16) + +#define CHECK_IS_USING_AUTO(arr) \ + do { \ + ASSERT_TRUE(IS_USING_AUTO(arr)); \ + } while (0) + +#define CHECK_NOT_USING_AUTO(arr) \ + do { \ + ASSERT_FALSE(IS_USING_AUTO(arr)); \ + } while (0) + +#define CHECK_USES_SHARED_EMPTY_HDR(arr) \ + do { \ + nsTArray _empty; \ + ASSERT_EQ(_empty.Elements(), arr.Elements()); \ + } while (0) + +#define CHECK_EQ_INT(actual, expected) \ + do { \ + ASSERT_EQ((actual), (expected)); \ + } while (0) + +#define CHECK_ARRAY(arr, data) \ + do { \ + CHECK_EQ_INT((arr).Length(), (size_t)ArrayLength(data)); \ + for (size_t _i = 0; _i < ArrayLength(data); _i++) { \ + CHECK_EQ_INT((arr)[_i], (data)[_i]); \ + } \ + } while (0) + +TEST(TArray, test_swap) +{ + // Test nsTArray::SwapElements. Unfortunately there are many cases. + int data1[] = {8, 6, 7, 5}; + int data2[] = {3, 0, 9}; + + // Swap two auto arrays. + { + AutoTArray a; + AutoTArray b; + + a.AppendElements(data1, ArrayLength(data1)); + b.AppendElements(data2, ArrayLength(data2)); + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + CHECK_ARRAY(a, data2); + CHECK_ARRAY(b, data1); + } + + // Swap two auto arrays -- one whose data lives on the heap, the other whose + // data lives on the stack -- which each fits into the other's auto storage. + { + AutoTArray a; + AutoTArray b; + + a.AppendElements(data1, ArrayLength(data1)); + a.RemoveElementAt(3); + b.AppendElements(data2, ArrayLength(data2)); + + // Here and elsewhere, we assert that if we start with an auto array + // capable of storing N elements, we store N+1 elements into the array, and + // then we remove one element, that array is still not using its auto + // buffer. + // + // This isn't at all required by the TArray API. It would be fine if, when + // we shrink back to N elements, the TArray frees its heap storage and goes + // back to using its stack storage. But we assert here as a check that the + // test does what we expect. If the TArray implementation changes, just + // change the failing assertions. + CHECK_NOT_USING_AUTO(a); + + // This check had better not change, though. + CHECK_IS_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(b); + CHECK_ARRAY(a, data2); + int expectedB[] = {8, 6, 7}; + CHECK_ARRAY(b, expectedB); + } + + // Swap two auto arrays which are using heap storage such that one fits into + // the other's auto storage, but the other needs to stay on the heap. + { + AutoTArray a; + AutoTArray b; + a.AppendElements(data1, ArrayLength(data1)); + a.RemoveElementAt(3); + + b.AppendElements(data2, ArrayLength(data2)); + b.RemoveElementAt(2); + + CHECK_NOT_USING_AUTO(a); + CHECK_NOT_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_NOT_USING_AUTO(b); + + int expected1[] = {3, 0}; + int expected2[] = {8, 6, 7}; + + CHECK_ARRAY(a, expected1); + CHECK_ARRAY(b, expected2); + } + + // Swap two arrays, neither of which fits into the other's auto-storage. + { + AutoTArray a; + AutoTArray b; + + a.AppendElements(data1, ArrayLength(data1)); + b.AppendElements(data2, ArrayLength(data2)); + + a.SwapElements(b); + + CHECK_ARRAY(a, data2); + CHECK_ARRAY(b, data1); + } + + // Swap an empty nsTArray with a non-empty AutoTArray. + { + nsTArray a; + AutoTArray b; + + b.AppendElements(data2, ArrayLength(data2)); + CHECK_IS_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_ARRAY(a, data2); + CHECK_EQ_INT(b.Length(), size_t(0)); + CHECK_IS_USING_AUTO(b); + } + + // Swap two big auto arrays. + { + const unsigned size = 8192; + AutoTArray a; + AutoTArray b; + + for (unsigned i = 0; i < size; i++) { + a.AppendElement(i); + b.AppendElement(i + 1); + } + + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + + CHECK_EQ_INT(a.Length(), size_t(size)); + CHECK_EQ_INT(b.Length(), size_t(size)); + + for (unsigned i = 0; i < size; i++) { + CHECK_EQ_INT(a[i], i + 1); + CHECK_EQ_INT(b[i], i); + } + } + + // Swap two arrays and make sure that their capacities don't increase + // unnecessarily. + { + nsTArray a; + nsTArray b; + b.AppendElements(data2, ArrayLength(data2)); + + CHECK_EQ_INT(a.Capacity(), size_t(0)); + size_t bCapacity = b.Capacity(); + + a.SwapElements(b); + + // Make sure that we didn't increase the capacity of either array. + CHECK_ARRAY(a, data2); + CHECK_EQ_INT(b.Length(), size_t(0)); + CHECK_EQ_INT(b.Capacity(), size_t(0)); + CHECK_EQ_INT(a.Capacity(), bCapacity); + } + + // Swap an auto array with a TArray, then clear the auto array and make sure + // it doesn't forget the fact that it has an auto buffer. + { + nsTArray a; + AutoTArray b; + + a.AppendElements(data1, ArrayLength(data1)); + + a.SwapElements(b); + + CHECK_EQ_INT(a.Length(), size_t(0)); + CHECK_ARRAY(b, data1); + + b.Clear(); + + CHECK_USES_SHARED_EMPTY_HDR(a); + CHECK_IS_USING_AUTO(b); + } + + // Same thing as the previous test, but with more auto arrays. + { + AutoTArray a; + AutoTArray b; + + a.AppendElements(data1, ArrayLength(data1)); + + a.SwapElements(b); + + CHECK_EQ_INT(a.Length(), size_t(0)); + CHECK_ARRAY(b, data1); + + b.Clear(); + + CHECK_IS_USING_AUTO(a); + CHECK_IS_USING_AUTO(b); + } + + // Swap an empty nsTArray and an empty AutoTArray. + { + AutoTArray a; + nsTArray b; + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(a); + CHECK_NOT_USING_AUTO(b); + CHECK_EQ_INT(a.Length(), size_t(0)); + CHECK_EQ_INT(b.Length(), size_t(0)); + } + + // Swap empty auto array with non-empty AutoTArray using malloc'ed storage. + // I promise, all these tests have a point. + { + AutoTArray a; + AutoTArray b; + + a.AppendElements(data1, ArrayLength(data1)); + + a.SwapElements(b); + + CHECK_IS_USING_AUTO(a); + CHECK_NOT_USING_AUTO(b); + CHECK_ARRAY(b, data1); + CHECK_EQ_INT(a.Length(), size_t(0)); + } + + // Test fallible SwapElements of nsTArray. + { + nsTArray a; + nsTArray b; + + a.AppendElements(data1, ArrayLength(data1)); + + ASSERT_TRUE(a.SwapElements(b, fallible)); + + CHECK_ARRAY(b, data1); + CHECK_EQ_INT(a.Length(), size_t(0)); + } + + // Test fallible SwapElements of FallibleTArray. + { + FallibleTArray a; + FallibleTArray b; + + ASSERT_TRUE(a.AppendElements(data1, ArrayLength(data1), fallible)); + + ASSERT_TRUE(a.SwapElements(b, fallible)); + + CHECK_ARRAY(b, data1); + CHECK_EQ_INT(a.Length(), size_t(0)); + } + + // Test fallible SwapElements of FallibleTArray with large AutoTArray. + { + FallibleTArray a; + AutoTArray b; + + ASSERT_TRUE(a.AppendElements(data1, ArrayLength(data1), fallible)); + + ASSERT_TRUE(a.SwapElements(b, fallible)); + + CHECK_IS_USING_AUTO(b); + CHECK_ARRAY(b, data1); + CHECK_EQ_INT(a.Length(), size_t(0)); + } +} + +// Bug 1171296: Disabled on andoid due to crashes. +#if !defined(ANDROID) +TEST(TArray, test_fallible) +{ + // Test that FallibleTArray works properly; that is, it never OOMs, but + // instead eventually returns false. + // + // This test is only meaningful on 32-bit systems. On a 64-bit system, we + // might never OOM. + if (sizeof(void*) > 4) { + ASSERT_TRUE(true); + return; + } + + // Allocate a bunch of 128MB arrays. Larger allocations will fail on some + // platforms without actually hitting OOM. + // + // 36 * 128MB > 4GB, so we should definitely OOM by the 36th array. + const unsigned numArrays = 36; + FallibleTArray arrays[numArrays]; + bool oomed = false; + for (size_t i = 0; i < numArrays; i++) { + // SetCapacity allocates the requested capacity + a header, and we want to + // avoid allocating more than 128MB overall because of the size padding it + // will cause, which depends on allocator behavior, so use 128MB - an + // arbitrary size larger than the array header, so that chances are good + // that allocations will always be 128MB. + bool success = arrays[i].SetCapacity(128 * 1024 * 1024 - 1024, fallible); + if (!success) { + // We got our OOM. Check that it didn't come too early. + oomed = true; +# ifdef XP_WIN + // 32-bit Windows sometimes OOMs on the 6th, 7th, or 8th. To keep the + // test green, choose the lower of those: the important thing here is + // that some allocations fail and some succeed. We're not too + // concerned about how many iterations it takes. + const size_t kOOMIterations = 6; +# else + const size_t kOOMIterations = 8; +# endif + ASSERT_GE(i, kOOMIterations) + << "Got OOM on iteration " << i << ". Too early!"; + } + } + + ASSERT_TRUE(oomed) + << "Didn't OOM or crash? nsTArray::SetCapacity" + "must be lying."; +} +#endif + +TEST(TArray, test_conversion_operator) +{ + FallibleTArray f; + const FallibleTArray fconst; + + nsTArray t; + const nsTArray tconst; + AutoTArray tauto; + const AutoTArray tautoconst; + +#define CHECK_ARRAY_CAST(type) \ + do { \ + const type& z1 = f; \ + ASSERT_EQ((void*)&z1, (void*)&f); \ + const type& z2 = fconst; \ + ASSERT_EQ((void*)&z2, (void*)&fconst); \ + const type& z9 = t; \ + ASSERT_EQ((void*)&z9, (void*)&t); \ + const type& z10 = tconst; \ + ASSERT_EQ((void*)&z10, (void*)&tconst); \ + const type& z11 = tauto; \ + ASSERT_EQ((void*)&z11, (void*)&tauto); \ + const type& z12 = tautoconst; \ + ASSERT_EQ((void*)&z12, (void*)&tautoconst); \ + } while (0) + + CHECK_ARRAY_CAST(FallibleTArray); + CHECK_ARRAY_CAST(nsTArray); + +#undef CHECK_ARRAY_CAST +} + +template +struct BufAccessor : public T { + void* GetHdr() { return T::mHdr; } +}; + +TEST(TArray, test_SetLengthAndRetainStorage_no_ctor) +{ + // 1050 because sizeof(int)*1050 is more than a page typically. + const int N = 1050; + FallibleTArray f; + + nsTArray t; + AutoTArray tauto; + +#define LPAREN ( +#define RPAREN ) +#define FOR_EACH(pre, post) \ + do { \ + pre f post; \ + pre t post; \ + pre tauto post; \ + } while (0) + + // Setup test arrays. + FOR_EACH(; Unused <<, .SetLength(N, fallible)); + for (int n = 0; n < N; ++n) { + FOR_EACH(;, [n] = n); + } + + void* initial_Hdrs[] = { + static_cast>&>(f).GetHdr(), + static_cast>&>(t).GetHdr(), + static_cast>&>(tauto).GetHdr(), nullptr}; + + // SetLengthAndRetainStorage(n), should NOT overwrite memory when T hasn't + // a default constructor. + FOR_EACH(;, .SetLengthAndRetainStorage(8)); + FOR_EACH(;, .SetLengthAndRetainStorage(12)); + for (int n = 0; n < 12; ++n) { + ASSERT_EQ(f[n], n); + ASSERT_EQ(t[n], n); + ASSERT_EQ(tauto[n], n); + } + FOR_EACH(;, .SetLengthAndRetainStorage(0)); + FOR_EACH(;, .SetLengthAndRetainStorage(N)); + for (int n = 0; n < N; ++n) { + ASSERT_EQ(f[n], n); + ASSERT_EQ(t[n], n); + ASSERT_EQ(tauto[n], n); + } + + void* current_Hdrs[] = { + static_cast>&>(f).GetHdr(), + static_cast>&>(t).GetHdr(), + static_cast>&>(tauto).GetHdr(), nullptr}; + + // SetLengthAndRetainStorage(n) should NOT have reallocated the internal + // memory. + ASSERT_EQ(sizeof(initial_Hdrs), sizeof(current_Hdrs)); + for (size_t n = 0; n < sizeof(current_Hdrs) / sizeof(current_Hdrs[0]); ++n) { + ASSERT_EQ(current_Hdrs[n], initial_Hdrs[n]); + } + +#undef FOR_EACH +#undef LPAREN +#undef RPAREN +} + +template +bool TestCompareMethods(const Comparator& aComp) { + nsTArray ary({57, 4, 16, 17, 3, 5, 96, 12}); + + ary.Sort(aComp); + + const int sorted[] = {3, 4, 5, 12, 16, 17, 57, 96}; + for (size_t i = 0; i < MOZ_ARRAY_LENGTH(sorted); i++) { + if (sorted[i] != ary[i]) { + return false; + } + } + + if (!ary.ContainsSorted(5, aComp)) { + return false; + } + if (ary.ContainsSorted(42, aComp)) { + return false; + } + + if (ary.BinaryIndexOf(16, aComp) != 4) { + return false; + } + + return true; +} + +struct IntComparator { + bool Equals(int aLeft, int aRight) const { return aLeft == aRight; } + + bool LessThan(int aLeft, int aRight) const { return aLeft < aRight; } +}; + +TEST(TArray, test_comparator_objects) +{ + ASSERT_TRUE(TestCompareMethods(IntComparator())); + ASSERT_TRUE( + TestCompareMethods([](int aLeft, int aRight) { return aLeft - aRight; })); +} + +struct Big { + uint64_t size[40] = {}; +}; + +TEST(TArray, test_AutoTArray_SwapElements) +{ + AutoTArray oneArray; + AutoTArray another; + + for (size_t i = 0; i < 8; ++i) { + oneArray.AppendElement(Big()); + } + oneArray[0].size[10] = 1; + for (size_t i = 0; i < 9; ++i) { + another.AppendElement(Big()); + } + oneArray.SwapElements(another); + + ASSERT_EQ(oneArray.Length(), 9u); + ASSERT_EQ(another.Length(), 8u); + + ASSERT_EQ(oneArray[0].size[10], 0u); + ASSERT_EQ(another[0].size[10], 1u); +} + +} // namespace TestTArray diff --git a/xpcom/tests/gtest/TestTaskQueue.cpp b/xpcom/tests/gtest/TestTaskQueue.cpp new file mode 100644 index 0000000000..bc0e78b608 --- /dev/null +++ b/xpcom/tests/gtest/TestTaskQueue.cpp @@ -0,0 +1,215 @@ +/* -*- 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 +#include "gtest/gtest.h" +#include "mozilla/SharedThreadPool.h" +#include "mozilla/SyncRunnable.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Unused.h" +#include "nsITargetShutdownTask.h" +#include "VideoUtils.h" + +namespace TestTaskQueue { + +using namespace mozilla; + +TEST(TaskQueue, EventOrder) +{ + RefPtr tq1 = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestTaskQueue tq1", true); + RefPtr tq2 = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestTaskQueue tq2", true); + RefPtr tq3 = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestTaskQueue tq3", true); + + bool errored = false; + int counter = 0; + int sync = 0; + Monitor monitor MOZ_UNANNOTATED("TaskQueue::EventOrder::monitor"); + + // We expect task1 happens before task3. + for (int i = 0; i < 10000; ++i) { + Unused << tq1->Dispatch( + NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + [&]() { + Unused << tq2->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + []() { // task0 + })); + Unused << tq3->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + [&]() { // task1 + EXPECT_EQ(1, ++counter); + errored = counter != 1; + MonitorAutoLock mon(monitor); + ++sync; + mon.Notify(); + })); + Unused << tq2->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + [&]() { // task2 + Unused << tq3->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TaskQueue_EventOrder_Test::TestBody", + [&]() { // task3 + EXPECT_EQ(0, --counter); + errored = counter != 0; + MonitorAutoLock mon(monitor); + ++sync; + mon.Notify(); + })); + })); + }), + AbstractThread::TailDispatch); + + // Ensure task1 and task3 are done before next loop. + MonitorAutoLock mon(monitor); + while (sync != 2) { + mon.Wait(); + } + sync = 0; + + if (errored) { + break; + } + } + + tq1->BeginShutdown(); + tq1->AwaitShutdownAndIdle(); + tq2->BeginShutdown(); + tq2->AwaitShutdownAndIdle(); + tq3->BeginShutdown(); + tq3->AwaitShutdownAndIdle(); +} + +TEST(TaskQueue, GetCurrentSerialEventTarget) +{ + RefPtr tq1 = + TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR), + "TestTaskQueue GetCurrentSerialEventTarget", false); + Unused << tq1->Dispatch(NS_NewRunnableFunction( + "TestTaskQueue::TestCurrentSerialEventTarget::TestBody", [tq1]() { + nsCOMPtr thread = GetCurrentSerialEventTarget(); + EXPECT_EQ(thread, tq1); + })); + tq1->BeginShutdown(); + tq1->AwaitShutdownAndIdle(); +} + +namespace { + +class TestShutdownTask final : public nsITargetShutdownTask { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit TestShutdownTask(std::function aCallback) + : mCallback(std::move(aCallback)) {} + + void TargetShutdown() override { + if (mCallback) { + mCallback(); + } + } + + private: + ~TestShutdownTask() = default; + std::function mCallback; +}; + +NS_IMPL_ISUPPORTS(TestShutdownTask, nsITargetShutdownTask) + +} // namespace + +TEST(TaskQueue, ShutdownTask) +{ + auto shutdownTaskRun = std::make_shared(); + auto runnableFromShutdownRun = std::make_shared(); + + RefPtr tq = TaskQueue::Create( + GetMediaThreadPool(MediaThreadType::SUPERVISOR), "Testing TaskQueue"); + + nsCOMPtr shutdownTask = new TestShutdownTask([=] { + EXPECT_TRUE(tq->IsOnCurrentThread()); + + ASSERT_FALSE(*shutdownTaskRun); + *shutdownTaskRun = true; + + nsCOMPtr dummyTask = new TestShutdownTask([] {}); + nsresult rv = tq->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + MOZ_ALWAYS_SUCCEEDS( + tq->Dispatch(NS_NewRunnableFunction("afterShutdownTask", [=] { + EXPECT_TRUE(tq->IsOnCurrentThread()); + + nsCOMPtr dummyTask = + new TestShutdownTask([] {}); + nsresult rv = tq->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + ASSERT_FALSE(*runnableFromShutdownRun); + *runnableFromShutdownRun = true; + }))); + }); + MOZ_ALWAYS_SUCCEEDS(tq->RegisterShutdownTask(shutdownTask)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + RefPtr syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(tq)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + tq->BeginShutdown(); + tq->AwaitShutdownAndIdle(); + + ASSERT_TRUE(*shutdownTaskRun); + ASSERT_TRUE(*runnableFromShutdownRun); +} + +TEST(TaskQueue, UnregisteredShutdownTask) +{ + RefPtr tq = TaskQueue::Create( + GetMediaThreadPool(MediaThreadType::SUPERVISOR), "Testing TaskQueue"); + + nsCOMPtr shutdownTask = + new TestShutdownTask([=] { MOZ_CRASH("should not be run"); }); + + MOZ_ALWAYS_SUCCEEDS(tq->RegisterShutdownTask(shutdownTask)); + + RefPtr syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(tq)); + + MOZ_ALWAYS_SUCCEEDS(tq->UnregisterShutdownTask(shutdownTask)); + + tq->BeginShutdown(); + tq->AwaitShutdownAndIdle(); +} + +TEST(AbstractThread, GetCurrentSerialEventTarget) +{ + RefPtr mainThread = AbstractThread::GetCurrent(); + EXPECT_EQ(mainThread, AbstractThread::MainThread()); + Unused << mainThread->Dispatch(NS_NewRunnableFunction( + "TestAbstractThread::TestCurrentSerialEventTarget::TestBody", + [mainThread]() { + nsCOMPtr thread = GetCurrentSerialEventTarget(); + EXPECT_EQ(thread, mainThread); + })); + + // Spin the event loop. + NS_ProcessPendingEvents(nullptr); +} + +} // namespace TestTaskQueue diff --git a/xpcom/tests/gtest/TestTextFormatter.cpp b/xpcom/tests/gtest/TestTextFormatter.cpp new file mode 100644 index 0000000000..9e3d99a056 --- /dev/null +++ b/xpcom/tests/gtest/TestTextFormatter.cpp @@ -0,0 +1,237 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsTextFormatter.h" +#include "nsString.h" +#include "gtest/gtest.h" + +TEST(TextFormatter, Tests) +{ + nsAutoString fmt(u"%3$s %4$S %1$d %2$d %2$d %3$s"_ns); + char utf8[] = "Hello"; + char16_t ucs2[] = {'W', 'o', 'r', 'l', 'd', + 0x4e00, 0xAc00, 0xFF45, 0x0103, 0x00}; + int d = 3; + + char16_t buf[256]; + nsTextFormatter::snprintf(buf, 256, fmt.get(), d, 333, utf8, ucs2); + nsAutoString out(buf); + + const char16_t* uout = out.get(); + const char16_t expected[] = { + 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64, + 0x4E00, 0xAC00, 0xFF45, 0x0103, 0x20, 0x33, 0x20, 0x33, 0x33, 0x33, 0x20, + 0x33, 0x33, 0x33, 0x20, 0x48, 0x65, 0x6C, 0x6C, 0x6F}; + + for (uint32_t i = 0; i < out.Length(); i++) { + ASSERT_EQ(uout[i], expected[i]); + } + + // Test that an unrecognized escape is passed through. + nsString out2; + nsTextFormatter::ssprintf(out2, u"%1m!", 23); + EXPECT_STREQ("%1m!", NS_ConvertUTF16toUTF8(out2).get()); + + // Treat NULL the same in both %s cases. + nsTextFormatter::ssprintf(out2, u"%s %S", (char*)nullptr, (char16_t*)nullptr); + EXPECT_STREQ("(null) (null)", NS_ConvertUTF16toUTF8(out2).get()); + + nsTextFormatter::ssprintf(out2, u"%lld", INT64_MIN); + EXPECT_STREQ("-9223372036854775808", NS_ConvertUTF16toUTF8(out2).get()); + + // Regression test for bug 1401821. + nsTextFormatter::ssprintf(out2, u"%*.f", 0, 23.2); + EXPECT_STREQ("23", NS_ConvertUTF16toUTF8(out2).get()); +} + +/* + * Check misordered parameters + */ + +TEST(TextFormatterOrdering, orders) +{ + nsString out; + + // plain list + nsTextFormatter::ssprintf(out, u"%S %S %S", u"1", u"2", u"3"); + EXPECT_STREQ("1 2 3", NS_ConvertUTF16toUTF8(out).get()); + + // ordered list + nsTextFormatter::ssprintf(out, u"%2$S %3$S %1$S", u"1", u"2", u"3"); + EXPECT_STREQ("2 3 1", NS_ConvertUTF16toUTF8(out).get()); + + // Mixed ordered list and non-ordered does not work. This shouldn't + // crash (hence the calls to ssprintf) but should fail for for + // snprintf. + nsTextFormatter::ssprintf(out, u"%2S %S %1$S", u"1", u"2", u"3"); + nsTextFormatter::ssprintf(out, u"%S %2$S", u"1", u"2"); + char16_t buffer[1024]; // plenty big + EXPECT_EQ(nsTextFormatter::snprintf(buffer, sizeof(buffer), u"%2S %S %1$S", + u"1", u"2", u"3"), + uint32_t(-1)); + EXPECT_EQ( + nsTextFormatter::snprintf(buffer, sizeof(buffer), u"%S %2$S", u"1", u"2"), + uint32_t(-1)); + + // Referencing an extra param returns empty strings in release. +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u" %2$S ", u"1"); + EXPECT_STREQ(" ", NS_ConvertUTF16toUTF8(out).get()); +#endif + + // Double referencing existing argument works + nsTextFormatter::ssprintf(out, u"%1$S %1$S", u"1"); + EXPECT_STREQ("1 1", NS_ConvertUTF16toUTF8(out).get()); + + // Dropping trailing argument works + nsTextFormatter::ssprintf(out, u" %1$S ", u"1", u"2"); + EXPECT_STREQ(" 1 ", NS_ConvertUTF16toUTF8(out).get()); + + // Dropping leading arguments works + nsTextFormatter::ssprintf(out, u" %2$S ", u"1", u"2"); + EXPECT_STREQ(" 2 ", NS_ConvertUTF16toUTF8(out).get()); + + // Dropping middle arguments works + nsTextFormatter::ssprintf(out, u" %3$S %1$S ", u"1", u"2", u"3"); + EXPECT_STREQ(" 3 1 ", NS_ConvertUTF16toUTF8(out).get()); +} + +/* + * Tests to validate that horrible things don't happen if the passed-in + * variable and the formatter don't match. + */ +TEST(TextFormatterTestMismatch, format_d) +{ + nsString out; + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%d", int(-1)); + EXPECT_STREQ("-1", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%d", uint32_t(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%d", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%d", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%d", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif +} + +TEST(TextFormatterTestMismatch, format_u) +{ + nsString out; + nsTextFormatter::ssprintf(out, u"%u", int(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%u", uint32_t(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%u", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%u", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%u", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif +} + +TEST(TextFormatterTestMismatch, format_x) +{ + nsString out; + nsTextFormatter::ssprintf(out, u"%x", int32_t(-1)); + EXPECT_STREQ("ffffffff", NS_ConvertUTF16toUTF8(out).get()); + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%x", uint32_t(-1)); + EXPECT_STREQ("ffffffff", NS_ConvertUTF16toUTF8(out).get()); +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%x", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%x", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%x", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif +} + +TEST(TextFormatterTestMismatch, format_s) +{ + nsString out; +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%s", int(-1)); + EXPECT_STREQ("-1", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%s", uint32_t(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%s", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); +#endif + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%s", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%s", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif +} + +TEST(TextFormatterTestMismatch, format_S) +{ + nsString out; +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%S", int32_t(-1)); + EXPECT_STREQ("-1", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%S", uint32_t(-1)); + EXPECT_STREQ("4294967295", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%S", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%S", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%S", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +} + +TEST(TextFormatterTestMismatch, format_c) +{ + nsString out; + nsTextFormatter::ssprintf(out, u"%c", int32_t(-1)); + EXPECT_EQ(1u, out.Length()); + EXPECT_EQ((uint16_t)-1, out.CharAt(0)); // not useful for humans :-/ + nsTextFormatter::ssprintf(out, u"%c", uint32_t(-1)); + EXPECT_EQ(1u, out.Length()); + EXPECT_EQ((uint16_t)-1, out.CharAt(0)); // not useful for humans :-/ +#ifndef DEBUG + nsTextFormatter::ssprintf(out, u"%c", float(3.5)); + EXPECT_STREQ("3.5", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%c", "foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); + nsTextFormatter::ssprintf(out, u"%c", u"foo"); + EXPECT_STREQ("foo", NS_ConvertUTF16toUTF8(out).get()); +#endif + + // just for completeness, this is our format, and works + nsTextFormatter::ssprintf(out, u"%c", 'c'); + EXPECT_EQ(1u, out.Length()); + EXPECT_EQ(u'c', out.CharAt(0)); + nsTextFormatter::ssprintf(out, u"%c", u'c'); + EXPECT_EQ(1u, out.Length()); + EXPECT_EQ(u'c', out.CharAt(0)); +} + +TEST(TextFormatterTestResults, Tests) +{ + char16_t buf[10]; + + EXPECT_EQ( + nsTextFormatter::snprintf(buf, 10, u"%s", "more than 10 characters"), 9u); + EXPECT_EQ(buf[9], '\0'); + EXPECT_STREQ("more than", NS_ConvertUTF16toUTF8(&buf[0]).get()); + + nsString out; + nsTextFormatter::ssprintf(out, u"%s", "more than 10 characters"); + // The \0 isn't written here. + EXPECT_EQ(out.Length(), 23u); +} diff --git a/xpcom/tests/gtest/TestThreadManager.cpp b/xpcom/tests/gtest/TestThreadManager.cpp new file mode 100644 index 0000000000..41279e104c --- /dev/null +++ b/xpcom/tests/gtest/TestThreadManager.cpp @@ -0,0 +1,147 @@ +/* -*- 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 "nsIThreadManager.h" +#include "nsCOMPtr.h" +#include "nsIThread.h" +#include "nsXPCOM.h" +#include "nsThreadUtils.h" +#include "nsServiceManagerUtils.h" +#include "mozilla/Atomics.h" +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +using mozilla::Atomic; +using mozilla::Runnable; + +class WaitCondition final : public nsINestedEventLoopCondition { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + WaitCondition(Atomic& aCounter, uint32_t aMaxCount) + : mCounter(aCounter), mMaxCount(aMaxCount) {} + + NS_IMETHODIMP IsDone(bool* aDone) override { + *aDone = (mCounter == mMaxCount); + return NS_OK; + } + + private: + ~WaitCondition() = default; + + Atomic& mCounter; + const uint32_t mMaxCount; +}; + +NS_IMPL_ISUPPORTS(WaitCondition, nsINestedEventLoopCondition) + +class SpinRunnable final : public Runnable { + public: + explicit SpinRunnable(nsINestedEventLoopCondition* aCondition) + : Runnable("SpinRunnable"), mCondition(aCondition), mResult(NS_OK) {} + + NS_IMETHODIMP Run() { + nsCOMPtr threadMan = + do_GetService("@mozilla.org/thread-manager;1"); + + mResult = threadMan->SpinEventLoopUntil( + "xpcom:TestThreadManager.cpp:SpinRunnable->Run()"_ns, mCondition); + return NS_OK; + } + + nsresult SpinLoopResult() { return mResult; } + + private: + ~SpinRunnable() = default; + + nsCOMPtr mCondition; + Atomic mResult; +}; + +class CountRunnable final : public Runnable { + public: + explicit CountRunnable(Atomic& aCounter) + : Runnable("CountRunnable"), mCounter(aCounter) {} + + NS_IMETHODIMP Run() { + mCounter++; + return NS_OK; + } + + private: + Atomic& mCounter; +}; + +TEST(ThreadManager, SpinEventLoopUntilSuccess) +{ + const uint32_t kRunnablesToDispatch = 100; + nsresult rv; + mozilla::Atomic count(0); + + nsCOMPtr condition = + new WaitCondition(count, kRunnablesToDispatch); + RefPtr spinner = new SpinRunnable(condition); + nsCOMPtr thread; + rv = NS_NewNamedThread("SpinEventLoop", getter_AddRefs(thread), spinner); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr counter = new CountRunnable(count); + for (uint32_t i = 0; i < kRunnablesToDispatch; ++i) { + rv = thread->Dispatch(counter, NS_DISPATCH_NORMAL); + ASSERT_NS_SUCCEEDED(rv); + } + + rv = thread->Shutdown(); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_NS_SUCCEEDED(spinner->SpinLoopResult()); +} + +class ErrorCondition final : public nsINestedEventLoopCondition { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + ErrorCondition(Atomic& aCounter, uint32_t aMaxCount) + : mCounter(aCounter), mMaxCount(aMaxCount) {} + + NS_IMETHODIMP IsDone(bool* aDone) override { + if (mCounter == mMaxCount) { + return NS_ERROR_ILLEGAL_VALUE; + } + return NS_OK; + } + + private: + ~ErrorCondition() = default; + + Atomic& mCounter; + const uint32_t mMaxCount; +}; + +NS_IMPL_ISUPPORTS(ErrorCondition, nsINestedEventLoopCondition) + +TEST(ThreadManager, SpinEventLoopUntilError) +{ + const uint32_t kRunnablesToDispatch = 100; + nsresult rv; + mozilla::Atomic count(0); + + nsCOMPtr condition = + new ErrorCondition(count, kRunnablesToDispatch); + RefPtr spinner = new SpinRunnable(condition); + nsCOMPtr thread; + rv = NS_NewNamedThread("SpinEventLoop", getter_AddRefs(thread), spinner); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr counter = new CountRunnable(count); + for (uint32_t i = 0; i < kRunnablesToDispatch; ++i) { + rv = thread->Dispatch(counter, NS_DISPATCH_NORMAL); + ASSERT_NS_SUCCEEDED(rv); + } + + rv = thread->Shutdown(); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_NS_FAILED(spinner->SpinLoopResult()); +} diff --git a/xpcom/tests/gtest/TestThreadMetrics.cpp b/xpcom/tests/gtest/TestThreadMetrics.cpp new file mode 100644 index 0000000000..1a8e94b932 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadMetrics.cpp @@ -0,0 +1,320 @@ +/* -*- 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 "gtest/gtest.h" +#include "gmock/gmock.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Preferences.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/Document.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/TaskCategory.h" +#include "mozilla/PerformanceCounter.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsTArray.h" +#include "nsThread.h" + +using namespace mozilla; +using mozilla::Runnable; +using mozilla::dom::DocGroup; +using mozilla::dom::Document; + +/* A struct that describes a runnable to run and, optionally, a + * docgroup to dispatch it to. + */ +struct RunnableDescriptor { + MOZ_IMPLICIT RunnableDescriptor(nsIRunnable* aRunnable, + DocGroup* aDocGroup = nullptr) + : mRunnable(aRunnable), mDocGroup(aDocGroup) {} + + RunnableDescriptor(RunnableDescriptor&& aDescriptor) + : mRunnable(std::move(aDescriptor.mRunnable)), + mDocGroup(std::move(aDescriptor.mDocGroup)) {} + + nsCOMPtr mRunnable; + RefPtr mDocGroup; +}; + +/* Timed runnable which simulates some execution time + * and can run some nested runnables. + */ +class TimedRunnable final : public Runnable { + public: + explicit TimedRunnable(uint32_t aExecutionTime1, uint32_t aExecutionTime2) + : Runnable("TimedRunnable"), + mExecutionTime1(aExecutionTime1), + mExecutionTime2(aExecutionTime2) {} + + NS_IMETHODIMP Run() { + Sleep(mExecutionTime1); + for (uint32_t index = 0; index < mNestedRunnables.Length(); ++index) { + if (index != 0) { + Sleep(mExecutionTime1); + } + (void)DispatchNestedRunnable(mNestedRunnables[index].mRunnable, + mNestedRunnables[index].mDocGroup); + } + Sleep(mExecutionTime2); + return NS_OK; + } + + void AddNestedRunnable(RunnableDescriptor aDescriptor) { + mNestedRunnables.AppendElement(std::move(aDescriptor)); + } + + void Sleep(uint32_t aMilliseconds) { + TimeStamp start = TimeStamp::Now(); + PR_Sleep(PR_MillisecondsToInterval(aMilliseconds + 5)); + TimeStamp stop = TimeStamp::Now(); + mTotalSlept += (stop - start).ToMicroseconds(); + } + + // Total sleep time, in microseconds. + uint64_t TotalSlept() const { return mTotalSlept; } + + static void DispatchNestedRunnable(nsIRunnable* aRunnable, + DocGroup* aDocGroup) { + // Dispatch another runnable so nsThread::ProcessNextEvent is called + // recursively + nsCOMPtr thread = do_GetMainThread(); + if (aDocGroup) { + (void)DispatchWithDocgroup(aRunnable, aDocGroup); + } else { + thread->Dispatch(aRunnable, NS_DISPATCH_NORMAL); + } + (void)NS_ProcessNextEvent(thread, false); + } + + static nsresult DispatchWithDocgroup(nsIRunnable* aRunnable, + DocGroup* aDocGroup) { + nsCOMPtr runnable = aRunnable; + runnable = new SchedulerGroup::Runnable(runnable.forget(), + aDocGroup->GetPerformanceCounter()); + return aDocGroup->Dispatch(TaskCategory::Other, runnable.forget()); + } + + private: + uint32_t mExecutionTime1; + uint32_t mExecutionTime2; + // When we sleep, the actual time we sleep might not match how long + // we asked to sleep for. Record how much we actually slept. + uint64_t mTotalSlept = 0; + nsTArray mNestedRunnables; +}; + +/* test class used for all metrics tests + * + * - sets up the enable_scheduler_timing pref + * - provides a function to dispatch runnables and spin the loop + */ + +class ThreadMetrics : public ::testing::Test { + public: + explicit ThreadMetrics() = default; + + protected: + virtual void SetUp() { + // FIXME: This is horribly sketchy and relies a ton on BrowsingContextGroup + // not doing anything too fancy or asserting invariants it expects to be + // held. We should probably try to rework this test or remove it completely + // at some point when we can get away with it. Changes to BCGs frequently + // cause this test to start failing as it doesn't behave like normal. + + // building the DocGroup structure + RefPtr group = + dom::BrowsingContextGroup::Create(); + MOZ_ALWAYS_SUCCEEDS(NS_NewHTMLDocument(getter_AddRefs(mDocument), true)); + MOZ_ALWAYS_SUCCEEDS(NS_NewHTMLDocument(getter_AddRefs(mDocument2), true)); + mDocGroup = group->AddDocument("key"_ns, mDocument); + mDocGroup2 = group->AddDocument("key2"_ns, mDocument2); + mCounter = mDocGroup->GetPerformanceCounter(); + mCounter2 = mDocGroup2->GetPerformanceCounter(); + mThreadMgr = do_GetService("@mozilla.org/thread-manager;1"); + mOther = DispatchCategory(TaskCategory::Other).GetValue(); + mDispatchCount = (uint32_t)TaskCategory::Other + 1; + } + + virtual void TearDown() { + // and remove the document from the doc group + mDocGroup->RemoveDocument(mDocument); + mDocGroup2->RemoveDocument(mDocument2); + mDocGroup = nullptr; + mDocGroup2 = nullptr; + mDocument = nullptr; + mDocument2 = nullptr; + ProcessAllEvents(); + } + + // this is used to get rid of transient events + void initScheduler() { ProcessAllEvents(); } + + nsresult Dispatch(nsIRunnable* aRunnable) { + ProcessAllEvents(); + return TimedRunnable::DispatchWithDocgroup(aRunnable, mDocGroup); + } + + void ProcessAllEvents() { mThreadMgr->SpinEventLoopUntilEmpty(); } + + uint32_t mOther; + bool mOldPref; + RefPtr mDocument; + RefPtr mDocument2; + RefPtr mDocGroup; + RefPtr mDocGroup2; + RefPtr mCounter; + RefPtr mCounter2; + nsCOMPtr mThreadMgr; + uint32_t mDispatchCount; +}; + +TEST_F(ThreadMetrics, CollectMetrics) { + nsresult rv; + initScheduler(); + + // Dispatching a runnable that will last for +50ms + RefPtr runnable = new TimedRunnable(25, 25); + rv = Dispatch(runnable); + ASSERT_NS_SUCCEEDED(rv); + + // Flush the queue + ProcessAllEvents(); + + // Let's look at the task category "other" counter + ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u); + + // other counters should stay empty + for (uint32_t i = 0; i < mDispatchCount; i++) { + if (i != mOther) { + ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u); + } + } + + // Did we get incremented in the docgroup ? + uint64_t duration = mCounter->GetExecutionDuration(); + ASSERT_GE(duration, runnable->TotalSlept()); +} + +TEST_F(ThreadMetrics, CollectRecursiveMetrics) { + nsresult rv; + + initScheduler(); + + // Dispatching a runnable that will last for +50ms + // and run another one recursively that lasts for 400ms + RefPtr runnable = new TimedRunnable(25, 25); + nsCOMPtr nested = new TimedRunnable(400, 0); + runnable->AddNestedRunnable({nested}); + rv = Dispatch(runnable); + ASSERT_NS_SUCCEEDED(rv); + + // Flush the queue + ProcessAllEvents(); + + // let's look at the counters + ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u); + + // other counters should stay empty + for (uint32_t i = 0; i < mDispatchCount; i++) { + if (i != mOther) { + ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u); + } + } + + // did we get incremented in the docgroup ? + uint64_t duration = mCounter->GetExecutionDuration(); + ASSERT_GE(duration, runnable->TotalSlept()); + + // let's make sure we don't count the time spent in recursive calls + ASSERT_LT(duration, runnable->TotalSlept() + 200000u); +} + +TEST_F(ThreadMetrics, CollectMultipleRecursiveMetrics) { + nsresult rv; + + initScheduler(); + + // Dispatching a runnable that will last for +75ms + // and run another two recursively that last for 400ms each. + RefPtr runnable = new TimedRunnable(25, 25); + for (auto i : {1, 2}) { + Unused << i; + nsCOMPtr nested = new TimedRunnable(400, 0); + runnable->AddNestedRunnable({nested}); + } + + rv = Dispatch(runnable); + ASSERT_NS_SUCCEEDED(rv); + + // Flush the queue + ProcessAllEvents(); + + // let's look at the counters + ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u); + + // other counters should stay empty + for (uint32_t i = 0; i < mDispatchCount; i++) { + if (i != mOther) { + ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u); + } + } + + // did we get incremented in the docgroup ? + uint64_t duration = mCounter->GetExecutionDuration(); + ASSERT_GE(duration, runnable->TotalSlept()); + + // let's make sure we don't count the time spent in recursive calls + ASSERT_LT(duration, runnable->TotalSlept() + 200000u); +} + +TEST_F(ThreadMetrics, CollectMultipleRecursiveMetricsWithTwoDocgroups) { + nsresult rv; + + initScheduler(); + + // Dispatching a runnable that will last for +75ms + // and run another two recursively that last for 400ms each. The + // first nested runnable will have a docgroup, but the second will + // not, to test that the time for first nested event is accounted + // correctly. + RefPtr runnable = new TimedRunnable(25, 25); + RefPtr nested1 = new TimedRunnable(400, 0); + runnable->AddNestedRunnable({nested1, mDocGroup2}); + nsCOMPtr nested2 = new TimedRunnable(400, 0); + runnable->AddNestedRunnable({nested2}); + + rv = Dispatch(runnable); + ASSERT_NS_SUCCEEDED(rv); + + // Flush the queue + ProcessAllEvents(); + + // let's look at the counters + ASSERT_EQ(mCounter->GetDispatchCounter()[mOther], 1u); + + // other counters should stay empty + for (uint32_t i = 0; i < mDispatchCount; i++) { + if (i != mOther) { + ASSERT_EQ(mCounter->GetDispatchCounter()[i], 0u); + } + } + + uint64_t duration = mCounter2->GetExecutionDuration(); + // Make sure this we incremented the timings for the first nested + // runnable correctly. + ASSERT_GE(duration, nested1->TotalSlept()); + ASSERT_LT(duration, nested1->TotalSlept() + 20000u); + + // And now for the outer runnable. + duration = mCounter->GetExecutionDuration(); + ASSERT_GE(duration, runnable->TotalSlept()); + + // let's make sure we don't count the time spent in recursive calls + ASSERT_LT(duration, runnable->TotalSlept() + 200000u); +} diff --git a/xpcom/tests/gtest/TestThreadPool.cpp b/xpcom/tests/gtest/TestThreadPool.cpp new file mode 100644 index 0000000000..0dd0f51537 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadPool.cpp @@ -0,0 +1,211 @@ +/* -*- 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 +#include +#include "nsXPCOM.h" +#include "nsXPCOMCIDInternal.h" +#include "nsThreadPool.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/Monitor.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +class TestTask final : public Runnable { + public: + TestTask(int i, Atomic& aCounter) + : Runnable("TestThreadPool::Task"), mIndex(i), mCounter(aCounter) {} + + NS_IMETHOD Run() override { + printf("###(%d) running from thread: %p\n", mIndex, + (void*)PR_GetCurrentThread()); + int r = (int)((float)rand() * 200 / float(RAND_MAX)); + PR_Sleep(PR_MillisecondsToInterval(r)); + printf("###(%d) exiting from thread: %p\n", mIndex, + (void*)PR_GetCurrentThread()); + ++mCounter; + return NS_OK; + } + + private: + ~TestTask() = default; + + int mIndex; + Atomic& mCounter; +}; + +TEST(ThreadPool, Main) +{ + nsCOMPtr pool = new nsThreadPool(); + + Atomic count(0); + + for (int i = 0; i < 100; ++i) { + nsCOMPtr task = new TestTask(i, count); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + pool->Shutdown(); + EXPECT_EQ(count, 100); +} + +TEST(ThreadPool, Parallelism) +{ + nsCOMPtr pool = new nsThreadPool(); + + // Dispatch and sleep to ensure we have an idle thread + nsCOMPtr r0 = new Runnable("TestRunnable"); + NS_DispatchAndSpinEventLoopUntilComplete("ThreadPool::Parallelism"_ns, pool, + do_AddRef(r0)); + PR_Sleep(PR_SecondsToInterval(2)); + + class Runnable1 : public Runnable { + public: + Runnable1(Monitor& aMonitor, bool& aDone) + : mozilla::Runnable("Runnable1"), mMonitor(aMonitor), mDone(aDone) {} + + NS_IMETHOD Run() override { + MonitorAutoLock mon(mMonitor); + if (!mDone) { + // Wait for a reasonable timeout since we don't want to block gtests + // forever should any regression happen. + mon.Wait(TimeDuration::FromSeconds(300)); + } + EXPECT_TRUE(mDone); + return NS_OK; + } + + private: + Monitor& mMonitor; + bool& mDone; + }; + + class Runnable2 : public Runnable { + public: + Runnable2(Monitor& aMonitor, bool& aDone) + : mozilla::Runnable("Runnable2"), mMonitor(aMonitor), mDone(aDone) {} + + NS_IMETHOD Run() override { + MonitorAutoLock mon(mMonitor); + mDone = true; + mon.NotifyAll(); + return NS_OK; + } + + private: + Monitor& mMonitor; + bool& mDone; + }; + + // Dispatch 2 events in a row. Since we are still within the thread limit, + // We should wake up the idle thread and spawn a new thread so these 2 events + // can run in parallel. We will time out if r1 and r2 run in sequence for r1 + // won't finish until r2 finishes. + Monitor mon MOZ_UNANNOTATED("ThreadPool::Parallelism"); + bool done = false; + nsCOMPtr r1 = new Runnable1(mon, done); + nsCOMPtr r2 = new Runnable2(mon, done); + pool->Dispatch(r1, NS_DISPATCH_NORMAL); + pool->Dispatch(r2, NS_DISPATCH_NORMAL); + + pool->Shutdown(); +} + +TEST(ThreadPool, ShutdownWithTimeout) +{ + nsCOMPtr pool = new nsThreadPool(); + + Atomic allThreadsCount(0); + for (int i = 0; i < 4; ++i) { + nsCOMPtr task = new TestTask(i, allThreadsCount); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + // Wait for a max of 350 ms. All threads should be done by then. + pool->ShutdownWithTimeout(350); + EXPECT_EQ(allThreadsCount, 4); + + Atomic infiniteLoopCount(0); + Atomic shutdownInfiniteLoop(false); + Atomic shutdownAck(false); + pool = new nsThreadPool(); + for (int i = 0; i < 3; ++i) { + nsCOMPtr task = new TestTask(i, infiniteLoopCount); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + pool->Dispatch(NS_NewRunnableFunction( + "infinite-loop", + [&shutdownInfiniteLoop, &shutdownAck]() { + printf("### running from thread that never ends: %p\n", + (void*)PR_GetCurrentThread()); + while (!shutdownInfiniteLoop) { + PR_Sleep(PR_MillisecondsToInterval(100)); + } + shutdownAck = true; + }), + NS_DISPATCH_NORMAL); + + pool->ShutdownWithTimeout(1000); + EXPECT_EQ(infiniteLoopCount, 3); + + shutdownInfiniteLoop = true; + while (!shutdownAck) { + /* nothing */ + } +} + +TEST(ThreadPool, ShutdownWithTimeoutThenSleep) +{ + Atomic count(0); + nsCOMPtr pool = new nsThreadPool(); + + for (int i = 0; i < 3; ++i) { + nsCOMPtr task = new TestTask(i, count); + EXPECT_TRUE(task); + + pool->Dispatch(task, NS_DISPATCH_NORMAL); + } + + pool->Dispatch( + NS_NewRunnableFunction( + "sleep-for-400-ms", + [&count]() { + printf("### running from thread that sleeps for 400ms: %p\n", + (void*)PR_GetCurrentThread()); + PR_Sleep(PR_MillisecondsToInterval(400)); + ++count; + printf("### thread awoke from long sleep: %p\n", + (void*)PR_GetCurrentThread()); + }), + NS_DISPATCH_NORMAL); + + // Wait for a max of 350 ms. The thread should still be sleeping, and will + // be leaked. + pool->ShutdownWithTimeout(350); + // We can't be exact here; the thread we're running on might have gotten + // suspended and the sleeping thread, above, might have finished. + EXPECT_GE(count, 3); + + // Sleep for a bit, and wait for the last thread to finish up. + PR_Sleep(PR_MillisecondsToInterval(200)); + + // Process events so the shutdown ack is received + NS_ProcessPendingEvents(NS_GetCurrentThread()); + + EXPECT_EQ(count, 4); +} diff --git a/xpcom/tests/gtest/TestThreadPoolListener.cpp b/xpcom/tests/gtest/TestThreadPoolListener.cpp new file mode 100644 index 0000000000..2ca4fa26f1 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadPoolListener.cpp @@ -0,0 +1,205 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIThread.h" + +#include "nsComponentManagerUtils.h" +#include "nsThreadPool.h" +#include "nsThreadUtils.h" +#include "nsXPCOMCIDInternal.h" +#include "pratom.h" +#include "prinrval.h" +#include "prmon.h" +#include "prthread.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/gtest/MozAssertions.h" + +#include "mozilla/ReentrantMonitor.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +#define NUMBER_OF_THREADS 4 + +// One hour... because test boxes can be slow! +#define IDLE_THREAD_TIMEOUT 3600000 + +namespace TestThreadPoolListener { +static nsIThread** gCreatedThreadList = nullptr; +static nsIThread** gShutDownThreadList = nullptr; + +static ReentrantMonitor* gReentrantMonitor = nullptr; + +static bool gAllRunnablesPosted = false; +static bool gAllThreadsCreated = false; +static bool gAllThreadsShutDown = false; + +class Listener final : public nsIThreadPoolListener { + ~Listener() = default; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITHREADPOOLLISTENER +}; + +NS_IMPL_ISUPPORTS(Listener, nsIThreadPoolListener) + +NS_IMETHODIMP +Listener::OnThreadCreated() { + nsCOMPtr current(do_GetCurrentThread()); + EXPECT_TRUE(current) << "Couldn't get current thread!"; + + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + while (!gAllRunnablesPosted) { + mon.Wait(); + } + + for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) { + nsIThread* thread = gCreatedThreadList[i]; + EXPECT_NE(thread, current) << "Saw the same thread twice!"; + + if (!thread) { + gCreatedThreadList[i] = current; + if (i == (NUMBER_OF_THREADS - 1)) { + gAllThreadsCreated = true; + mon.NotifyAll(); + } + return NS_OK; + } + } + + EXPECT_TRUE(false) << "Too many threads!"; + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +Listener::OnThreadShuttingDown() { + nsCOMPtr current(do_GetCurrentThread()); + EXPECT_TRUE(current) << "Couldn't get current thread!"; + + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) { + nsIThread* thread = gShutDownThreadList[i]; + EXPECT_NE(thread, current) << "Saw the same thread twice!"; + + if (!thread) { + gShutDownThreadList[i] = current; + if (i == (NUMBER_OF_THREADS - 1)) { + gAllThreadsShutDown = true; + mon.NotifyAll(); + } + return NS_OK; + } + } + + EXPECT_TRUE(false) << "Too many threads!"; + return NS_ERROR_FAILURE; +} + +class AutoCreateAndDestroyReentrantMonitor { + public: + explicit AutoCreateAndDestroyReentrantMonitor( + ReentrantMonitor** aReentrantMonitorPtr) + : mReentrantMonitorPtr(aReentrantMonitorPtr) { + *aReentrantMonitorPtr = + new ReentrantMonitor("TestThreadPoolListener::AutoMon"); + MOZ_RELEASE_ASSERT(*aReentrantMonitorPtr, "Out of memory!"); + } + + ~AutoCreateAndDestroyReentrantMonitor() { + delete *mReentrantMonitorPtr; + *mReentrantMonitorPtr = nullptr; + } + + private: + ReentrantMonitor** mReentrantMonitorPtr; +}; + +TEST(ThreadPoolListener, Test) +{ + nsIThread* createdThreadList[NUMBER_OF_THREADS] = {nullptr}; + gCreatedThreadList = createdThreadList; + + nsIThread* shutDownThreadList[NUMBER_OF_THREADS] = {nullptr}; + gShutDownThreadList = shutDownThreadList; + + AutoCreateAndDestroyReentrantMonitor newMon(&gReentrantMonitor); + ASSERT_TRUE(gReentrantMonitor); + + nsresult rv; + + nsCOMPtr pool = new nsThreadPool(); + + rv = pool->SetThreadLimit(NUMBER_OF_THREADS); + ASSERT_NS_SUCCEEDED(rv); + + rv = pool->SetIdleThreadLimit(NUMBER_OF_THREADS); + ASSERT_NS_SUCCEEDED(rv); + + rv = pool->SetIdleThreadTimeout(IDLE_THREAD_TIMEOUT); + ASSERT_NS_SUCCEEDED(rv); + + nsCOMPtr listener = new Listener(); + ASSERT_TRUE(listener); + + rv = pool->SetListener(listener); + ASSERT_NS_SUCCEEDED(rv); + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + + for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) { + nsCOMPtr runnable = new Runnable("TestRunnable"); + ASSERT_TRUE(runnable); + + rv = pool->Dispatch(runnable, NS_DISPATCH_NORMAL); + ASSERT_NS_SUCCEEDED(rv); + } + + gAllRunnablesPosted = true; + mon.NotifyAll(); + } + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + while (!gAllThreadsCreated) { + mon.Wait(); + } + } + + rv = pool->Shutdown(); + ASSERT_NS_SUCCEEDED(rv); + + { + ReentrantMonitorAutoEnter mon(*gReentrantMonitor); + while (!gAllThreadsShutDown) { + mon.Wait(); + } + } + + for (uint32_t i = 0; i < NUMBER_OF_THREADS; i++) { + nsIThread* created = gCreatedThreadList[i]; + ASSERT_TRUE(created); + + bool match = false; + for (uint32_t j = 0; j < NUMBER_OF_THREADS; j++) { + nsIThread* destroyed = gShutDownThreadList[j]; + ASSERT_TRUE(destroyed); + + if (destroyed == created) { + match = true; + break; + } + } + + ASSERT_TRUE(match); + } +} + +} // namespace TestThreadPoolListener diff --git a/xpcom/tests/gtest/TestThreadUtils.cpp b/xpcom/tests/gtest/TestThreadUtils.cpp new file mode 100644 index 0000000000..2b9ff97192 --- /dev/null +++ b/xpcom/tests/gtest/TestThreadUtils.cpp @@ -0,0 +1,2226 @@ +/* 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 + +#include "nsComponentManagerUtils.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "mozilla/IdleTaskRunner.h" +#include "mozilla/RefCounted.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/UniquePtr.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +enum { + TEST_CALL_VOID_ARG_VOID_RETURN, + TEST_CALL_VOID_ARG_VOID_RETURN_CONST, + TEST_CALL_VOID_ARG_NONVOID_RETURN, + TEST_CALL_NONVOID_ARG_VOID_RETURN, + TEST_CALL_NONVOID_ARG_NONVOID_RETURN, + TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT, + TEST_CALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT, +#ifdef HAVE_STDCALL + TEST_STDCALL_VOID_ARG_VOID_RETURN, + TEST_STDCALL_VOID_ARG_NONVOID_RETURN, + TEST_STDCALL_NONVOID_ARG_VOID_RETURN, + TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN, + TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT, +#endif + TEST_CALL_NEWTHREAD_SUICIDAL, + MAX_TESTS +}; + +bool gRunnableExecuted[MAX_TESTS]; + +class nsFoo : public nsISupports { + NS_DECL_ISUPPORTS + nsresult DoFoo(bool* aBool) { + *aBool = true; + return NS_OK; + } + + private: + virtual ~nsFoo() = default; +}; + +NS_IMPL_ISUPPORTS0(nsFoo) + +class TestSuicide : public mozilla::Runnable { + public: + TestSuicide() : mozilla::Runnable("TestSuicide") {} + NS_IMETHOD Run() override { + // Runs first time on thread "Suicide", then dies on MainThread + if (!NS_IsMainThread()) { + mThread = do_GetCurrentThread(); + NS_DispatchToMainThread(this); + return NS_OK; + } + MOZ_RELEASE_ASSERT(mThread); + mThread->Shutdown(); + gRunnableExecuted[TEST_CALL_NEWTHREAD_SUICIDAL] = true; + return NS_OK; + } + + private: + nsCOMPtr mThread; +}; + +class nsBar : public nsISupports { + virtual ~nsBar() = default; + + public: + NS_DECL_ISUPPORTS + void DoBar1(void) { + gRunnableExecuted[TEST_CALL_VOID_ARG_VOID_RETURN] = true; + } + void DoBar1Const(void) const { + gRunnableExecuted[TEST_CALL_VOID_ARG_VOID_RETURN_CONST] = true; + } + nsresult DoBar2(void) { + gRunnableExecuted[TEST_CALL_VOID_ARG_NONVOID_RETURN] = true; + return NS_OK; + } + void DoBar3(nsFoo* aFoo) { + aFoo->DoFoo(&gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN]); + } + nsresult DoBar4(nsFoo* aFoo) { + return aFoo->DoFoo( + &gRunnableExecuted[TEST_CALL_NONVOID_ARG_NONVOID_RETURN]); + } + void DoBar5(nsFoo* aFoo) { + if (aFoo) + gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true; + } + nsresult DoBar6(char* aFoo) { + if (strlen(aFoo)) + gRunnableExecuted[TEST_CALL_NONVOID_ARG_NONVOID_RETURN_EXPLICIT] = true; + return NS_OK; + } +#ifdef HAVE_STDCALL + void __stdcall DoBar1std(void) { + gRunnableExecuted[TEST_STDCALL_VOID_ARG_VOID_RETURN] = true; + } + nsresult __stdcall DoBar2std(void) { + gRunnableExecuted[TEST_STDCALL_VOID_ARG_NONVOID_RETURN] = true; + return NS_OK; + } + void __stdcall DoBar3std(nsFoo* aFoo) { + aFoo->DoFoo(&gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_VOID_RETURN]); + } + nsresult __stdcall DoBar4std(nsFoo* aFoo) { + return aFoo->DoFoo( + &gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_NONVOID_RETURN]); + } + void __stdcall DoBar5std(nsFoo* aFoo) { + if (aFoo) + gRunnableExecuted[TEST_STDCALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true; + } + nsresult __stdcall DoBar6std(char* aFoo) { + if (strlen(aFoo)) + gRunnableExecuted[TEST_CALL_NONVOID_ARG_VOID_RETURN_EXPLICIT] = true; + return NS_OK; + } +#endif +}; + +NS_IMPL_ISUPPORTS0(nsBar) + +struct TestCopyWithNoMove { + explicit TestCopyWithNoMove(int* aCopyCounter) : mCopyCounter(aCopyCounter) {} + TestCopyWithNoMove(const TestCopyWithNoMove& a) + : mCopyCounter(a.mCopyCounter) { + *mCopyCounter += 1; + }; + // No 'move' declaration, allows passing object by rvalue copy. + // Destructor nulls member variable... + ~TestCopyWithNoMove() { mCopyCounter = nullptr; } + // ... so we can check that the object is called when still alive. + void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); } + int* mCopyCounter; +}; +struct TestCopyWithDeletedMove { + explicit TestCopyWithDeletedMove(int* aCopyCounter) + : mCopyCounter(aCopyCounter) {} + TestCopyWithDeletedMove(const TestCopyWithDeletedMove& a) + : mCopyCounter(a.mCopyCounter) { + *mCopyCounter += 1; + }; + // Deleted move prevents passing by rvalue (even if copy would work) + TestCopyWithDeletedMove(TestCopyWithDeletedMove&&) = delete; + ~TestCopyWithDeletedMove() { mCopyCounter = nullptr; } + void operator()() { MOZ_RELEASE_ASSERT(mCopyCounter); } + int* mCopyCounter; +}; +struct TestMove { + explicit TestMove(int* aMoveCounter) : mMoveCounter(aMoveCounter) {} + TestMove(const TestMove&) = delete; + TestMove(TestMove&& a) : mMoveCounter(a.mMoveCounter) { + a.mMoveCounter = nullptr; + *mMoveCounter += 1; + } + ~TestMove() { mMoveCounter = nullptr; } + void operator()() { MOZ_RELEASE_ASSERT(mMoveCounter); } + int* mMoveCounter; +}; +struct TestCopyMove { + TestCopyMove(int* aCopyCounter, int* aMoveCounter) + : mCopyCounter(aCopyCounter), mMoveCounter(aMoveCounter) {} + TestCopyMove(const TestCopyMove& a) + : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { + *mCopyCounter += 1; + }; + TestCopyMove(TestCopyMove&& a) + : mCopyCounter(a.mCopyCounter), mMoveCounter(a.mMoveCounter) { + a.mMoveCounter = nullptr; + *mMoveCounter += 1; + } + ~TestCopyMove() { + mCopyCounter = nullptr; + mMoveCounter = nullptr; + } + void operator()() { + MOZ_RELEASE_ASSERT(mCopyCounter); + MOZ_RELEASE_ASSERT(mMoveCounter); + } + int* mCopyCounter; + int* mMoveCounter; +}; + +struct TestRefCounted : RefCounted { + MOZ_DECLARE_REFCOUNTED_TYPENAME(TestRefCounted); +}; + +static void Expect(const char* aContext, int aCounter, int aMaxExpected) { + EXPECT_LE(aCounter, aMaxExpected) << aContext; +} + +static void ExpectRunnableName(Runnable* aRunnable, const char* aExpectedName) { +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + nsAutoCString name; + EXPECT_TRUE(NS_SUCCEEDED(aRunnable->GetName(name))) << "Runnable::GetName()"; + EXPECT_TRUE(name.EqualsASCII(aExpectedName)) << "Verify Runnable name"; +#endif +} + +struct BasicRunnableFactory { + static constexpr bool SupportsCopyWithDeletedMove = true; + + template + static auto Create(const char* aName, Function&& aFunc) { + return NS_NewRunnableFunction(aName, std::forward(aFunc)); + } +}; + +struct CancelableRunnableFactory { + static constexpr bool SupportsCopyWithDeletedMove = false; + + template + static auto Create(const char* aName, Function&& aFunc) { + return NS_NewCancelableRunnableFunction(aName, + std::forward(aFunc)); + } +}; + +template +static void TestRunnableFactory(bool aNamed) { + // Test RunnableFactory with copyable-only function object. + { + int copyCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyWithNoMove tracker(©Counter); + trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker) + : RunnableFactory::Create( + "TestNewRunnableFunction", tracker); + // Original 'tracker' is destroyed here. + } + // Verify that the runnable contains a non-destroyed function object. + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and no move) function, " + "copies", + copyCounter, 1); + } + { + int copyCounter = 0; + { + nsCOMPtr trackedRunnable; + { + // Passing as rvalue, but using copy. + // (TestCopyWithDeletedMove wouldn't allow this.) + trackedRunnable = + aNamed ? RunnableFactory::Create("unused", + TestCopyWithNoMove(©Counter)) + : RunnableFactory::Create("TestNewRunnableFunction", + TestCopyWithNoMove(©Counter)); + } + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and no move) function " + "rvalue, copies", + copyCounter, 1); + } + if constexpr (RunnableFactory::SupportsCopyWithDeletedMove) { + int copyCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyWithDeletedMove tracker(©Counter); + trackedRunnable = aNamed ? RunnableFactory::Create("unused", tracker) + : RunnableFactory::Create( + "TestNewRunnableFunction", tracker); + } + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and deleted move) " + "function, copies", + copyCounter, 1); + } + + // Test RunnableFactory with movable-only function object. + { + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestMove tracker(&moveCounter); + trackedRunnable = + aNamed ? RunnableFactory::Create("unused", std::move(tracker)) + : RunnableFactory::Create("TestNewRunnableFunction", + std::move(tracker)); + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with movable-only function, moves", moveCounter, 1); + } + { + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + trackedRunnable = + aNamed ? RunnableFactory::Create("unused", TestMove(&moveCounter)) + : RunnableFactory::Create("TestNewRunnableFunction", + TestMove(&moveCounter)); + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with movable-only function rvalue, moves", + moveCounter, 1); + } + + // Test RunnableFactory with copyable&movable function object. + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyMove tracker(©Counter, &moveCounter); + trackedRunnable = + aNamed ? RunnableFactory::Create("unused", std::move(tracker)) + : RunnableFactory::Create("TestNewRunnableFunction", + std::move(tracker)); + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with copyable&movable function, copies", + copyCounter, 0); + Expect("RunnableFactory with copyable&movable function, moves", moveCounter, + 1); + } + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + trackedRunnable = + aNamed ? RunnableFactory::Create( + "unused", TestCopyMove(©Counter, &moveCounter)) + : RunnableFactory::Create( + "TestNewRunnableFunction", + TestCopyMove(©Counter, &moveCounter)); + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with copyable&movable function rvalue, copies", + copyCounter, 0); + Expect("RunnableFactory with copyable&movable function rvalue, moves", + moveCounter, 1); + } + + // Test RunnableFactory with copyable-only lambda capture. + { + int copyCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyWithNoMove tracker(©Counter); + // Expect 2 copies (here -> local lambda -> runnable lambda). + trackedRunnable = + aNamed + ? RunnableFactory::Create("unused", + [tracker]() mutable { tracker(); }) + : RunnableFactory::Create("TestNewRunnableFunction", + [tracker]() mutable { tracker(); }); + } + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and no move) capture, " + "copies", + copyCounter, 2); + } + { + int copyCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyWithDeletedMove tracker(©Counter); + // Expect 2 copies (here -> local lambda -> runnable lambda). + trackedRunnable = + aNamed + ? RunnableFactory::Create("unused", + [tracker]() mutable { tracker(); }) + : RunnableFactory::Create("TestNewRunnableFunction", + [tracker]() mutable { tracker(); }); + } + trackedRunnable->Run(); + } + Expect( + "RunnableFactory with copyable-only (and deleted move) capture, " + "copies", + copyCounter, 2); + } + + // Note: Not possible to use move-only captures. + // (Until we can use C++14 generalized lambda captures) + + // Test RunnableFactory with copyable&movable lambda capture. + { + int copyCounter = 0; + int moveCounter = 0; + { + nsCOMPtr trackedRunnable; + { + TestCopyMove tracker(©Counter, &moveCounter); + trackedRunnable = + aNamed + ? RunnableFactory::Create("unused", + [tracker]() mutable { tracker(); }) + : RunnableFactory::Create("TestNewRunnableFunction", + [tracker]() mutable { tracker(); }); + // Expect 1 copy (here -> local lambda) and 1 move (local -> runnable + // lambda). + } + trackedRunnable->Run(); + } + Expect("RunnableFactory with copyable&movable capture, copies", copyCounter, + 1); + Expect("RunnableFactory with copyable&movable capture, moves", moveCounter, + 1); + } +} + +TEST(ThreadUtils, NewRunnableFunction) +{ TestRunnableFactory(/*aNamed*/ false); } + +TEST(ThreadUtils, NewNamedRunnableFunction) +{ + // The named overload shall behave identical to the non-named counterpart. + TestRunnableFactory(/*aNamed*/ true); + + // Test naming. + { + const char* expectedName = "NamedRunnable"; + RefPtr NamedRunnable = + NS_NewRunnableFunction(expectedName, [] {}); + ExpectRunnableName(NamedRunnable, expectedName); + } +} + +TEST(ThreadUtils, NewCancelableRunnableFunction) +{ TestRunnableFactory(/*aNamed*/ false); } + +TEST(ThreadUtils, NewNamedCancelableRunnableFunction) +{ + // The named overload shall behave identical to the non-named counterpart. + TestRunnableFactory(/*aNamed*/ true); + + // Test naming. + { + const char* expectedName = "NamedRunnable"; + RefPtr NamedRunnable = + NS_NewCancelableRunnableFunction(expectedName, [] {}); + ExpectRunnableName(NamedRunnable, expectedName); + } + + // Test release on cancelation. + { + auto foo = MakeRefPtr(); + bool ran = false; + + RefPtr func = + NS_NewCancelableRunnableFunction("unused", [foo, &ran] { ran = true; }); + + EXPECT_EQ(foo->refCount(), 2u); + func->Cancel(); + + EXPECT_EQ(foo->refCount(), 1u); + EXPECT_FALSE(ran); + } + + // Test no-op after cancelation. + { + auto foo = MakeRefPtr(); + bool ran = false; + + RefPtr func = + NS_NewCancelableRunnableFunction("unused", [foo, &ran] { ran = true; }); + + EXPECT_EQ(foo->refCount(), 2u); + func->Cancel(); + func->Run(); + + EXPECT_FALSE(ran); + } +} + +static void TestNewRunnableMethod(bool aNamed) { + memset(gRunnableExecuted, false, MAX_TESTS * sizeof(bool)); + // Scope the smart ptrs so that the runnables need to hold on to whatever they + // need + { + RefPtr foo = new nsFoo(); + RefPtr bar = new nsBar(); + RefPtr constBar = bar; + + // This pointer will be freed at the end of the block + // Do not dereference this pointer in the runnable method! + RefPtr rawFoo = new nsFoo(); + + // Read only string. Dereferencing in runnable method to check this works. + char* message = (char*)"Test message"; + + { + auto bar = MakeRefPtr(); + + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", std::move(bar), &nsBar::DoBar1) + : NewRunnableMethod("nsBar::DoBar1", std::move(bar), + &nsBar::DoBar1)); + } + + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar1) + : NewRunnableMethod("nsBar::DoBar1", bar, &nsBar::DoBar1)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", constBar, &nsBar::DoBar1Const) + : NewRunnableMethod("nsBar::DoBar1Const", constBar, + &nsBar::DoBar1Const)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar2) + : NewRunnableMethod("nsBar::DoBar2", bar, &nsBar::DoBar2)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod>("unused", bar, &nsBar::DoBar3, + foo) + : NewRunnableMethod>("nsBar::DoBar3", bar, + &nsBar::DoBar3, foo)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod>("unused", bar, &nsBar::DoBar4, + foo) + : NewRunnableMethod>("nsBar::DoBar4", bar, + &nsBar::DoBar4, foo)); + NS_DispatchToMainThread( + aNamed + ? NewRunnableMethod("unused", bar, &nsBar::DoBar5, rawFoo) + : NewRunnableMethod("nsBar::DoBar5", bar, &nsBar::DoBar5, + rawFoo)); + NS_DispatchToMainThread( + aNamed + ? NewRunnableMethod("unused", bar, &nsBar::DoBar6, message) + : NewRunnableMethod("nsBar::DoBar6", bar, &nsBar::DoBar6, + message)); +#ifdef HAVE_STDCALL + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar1std) + : NewRunnableMethod(bar, &nsBar::DoBar1std)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar2std) + : NewRunnableMethod(bar, &nsBar::DoBar2std)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod>("unused", bar, + &nsBar::DoBar3std, foo) + : NewRunnableMethod>(bar, &nsBar::DoBar3std, foo)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod>("unused", bar, + &nsBar::DoBar4std, foo) + : NewRunnableMethod>(bar, &nsBar::DoBar4std, foo)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar5std, + rawFoo) + : NewRunnableMethod(bar, &nsBar::DoBar5std, rawFoo)); + NS_DispatchToMainThread( + aNamed ? NewRunnableMethod("unused", bar, &nsBar::DoBar6std, + message) + : NewRunnableMethod(bar, &nsBar::DoBar6std, message)); +#endif + } + + // Spin the event loop + NS_ProcessPendingEvents(nullptr); + + // Now test a suicidal event in NS_New(Named)Thread + nsCOMPtr thread; + NS_NewNamedThread("SuicideThread", getter_AddRefs(thread), new TestSuicide()); + ASSERT_TRUE(thread); + + while (!gRunnableExecuted[TEST_CALL_NEWTHREAD_SUICIDAL]) { + NS_ProcessPendingEvents(nullptr); + } + + for (uint32_t i = 0; i < MAX_TESTS; i++) { + EXPECT_TRUE(gRunnableExecuted[i]) << "Error in test " << i; + } +} + +TEST(ThreadUtils, RunnableMethod) +{ TestNewRunnableMethod(/* aNamed */ false); } + +TEST(ThreadUtils, NamedRunnableMethod) +{ + // The named overloads shall behave identical to the non-named counterparts. + TestNewRunnableMethod(/* aNamed */ true); + + // Test naming. + { + RefPtr foo = new nsFoo(); + const char* expectedName = "NamedRunnable"; + bool unused; + RefPtr NamedRunnable = + NewRunnableMethod(expectedName, foo, &nsFoo::DoFoo, &unused); + ExpectRunnableName(NamedRunnable, expectedName); + } +} + +class IdleObjectWithoutSetDeadline final { + public: + NS_INLINE_DECL_REFCOUNTING(IdleObjectWithoutSetDeadline) + IdleObjectWithoutSetDeadline() : mRunnableExecuted(false) {} + void Method() { mRunnableExecuted = true; } + bool mRunnableExecuted; + + private: + ~IdleObjectWithoutSetDeadline() = default; +}; + +class IdleObjectParentWithSetDeadline { + public: + IdleObjectParentWithSetDeadline() : mSetDeadlineCalled(false) {} + void SetDeadline(TimeStamp aDeadline) { mSetDeadlineCalled = true; } + bool mSetDeadlineCalled; +}; + +class IdleObjectInheritedSetDeadline final + : public IdleObjectParentWithSetDeadline { + public: + NS_INLINE_DECL_REFCOUNTING(IdleObjectInheritedSetDeadline) + IdleObjectInheritedSetDeadline() : mRunnableExecuted(false) {} + void Method() { mRunnableExecuted = true; } + bool mRunnableExecuted; + + private: + ~IdleObjectInheritedSetDeadline() = default; +}; + +class IdleObject final { + public: + NS_INLINE_DECL_REFCOUNTING(IdleObject) + IdleObject() { + for (uint32_t index = 0; index < ArrayLength(mRunnableExecuted); ++index) { + mRunnableExecuted[index] = false; + mSetIdleDeadlineCalled = false; + } + } + void SetDeadline(TimeStamp aTimeStamp) { mSetIdleDeadlineCalled = true; } + + void CheckExecutedMethods(const char* aKey, uint32_t aNumExecuted) { + uint32_t index; + for (index = 0; index < aNumExecuted; ++index) { + ASSERT_TRUE(mRunnableExecuted[index]) + << aKey << ": Method" << index << " should've executed"; + } + + for (; index < ArrayLength(mRunnableExecuted); ++index) { + ASSERT_FALSE(mRunnableExecuted[index]) + << aKey << ": Method" << index << " shouldn't have executed"; + } + } + + void Method0() { + CheckExecutedMethods("Method0", 0); + mRunnableExecuted[0] = true; + mSetIdleDeadlineCalled = false; + } + + void Method1() { + CheckExecutedMethods("Method1", 1); + ASSERT_TRUE(mSetIdleDeadlineCalled); + mRunnableExecuted[1] = true; + mSetIdleDeadlineCalled = false; + } + + void Method2() { + CheckExecutedMethods("Method2", 2); + ASSERT_TRUE(mSetIdleDeadlineCalled); + mRunnableExecuted[2] = true; + mSetIdleDeadlineCalled = false; + NS_DispatchToCurrentThread( + NewRunnableMethod("IdleObject::Method3", this, &IdleObject::Method3)); + } + + void Method3() { + CheckExecutedMethods("Method3", 3); + + NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer), Method4, this, 10, + nsITimer::TYPE_ONE_SHOT, "IdleObject::Method3"); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethodWithTimer("IdleObject::Method5", this, + &IdleObject::Method5), + 50, EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewRunnableMethod("IdleObject::Method6", this, &IdleObject::Method6), + 100, EventQueuePriority::Idle); + + PR_Sleep(PR_MillisecondsToInterval(200)); + mRunnableExecuted[3] = true; + mSetIdleDeadlineCalled = false; + } + + static void Method4(nsITimer* aTimer, void* aClosure) { + RefPtr self = static_cast(aClosure); + self->CheckExecutedMethods("Method4", 4); + self->mRunnableExecuted[4] = true; + self->mSetIdleDeadlineCalled = false; + } + + void Method5() { + CheckExecutedMethods("Method5", 5); + ASSERT_TRUE(mSetIdleDeadlineCalled); + mRunnableExecuted[5] = true; + mSetIdleDeadlineCalled = false; + } + + void Method6() { + CheckExecutedMethods("Method6", 6); + mRunnableExecuted[6] = true; + mSetIdleDeadlineCalled = false; + } + + void Method7() { + CheckExecutedMethods("Method7", 7); + ASSERT_TRUE(mSetIdleDeadlineCalled); + mRunnableExecuted[7] = true; + mSetIdleDeadlineCalled = false; + } + + private: + nsCOMPtr mTimer; + bool mRunnableExecuted[8]; + bool mSetIdleDeadlineCalled; + ~IdleObject() = default; +}; + +// Disable test due to frequent failures +#if 0 +// because test fails on multiple platforms +TEST(ThreadUtils, IdleRunnableMethod) +{ + { + RefPtr idle = new IdleObject(); + RefPtr idleNoSetDeadline = + new IdleObjectWithoutSetDeadline(); + RefPtr idleInheritedSetDeadline = + new IdleObjectInheritedSetDeadline(); + + NS_DispatchToCurrentThread( + NewRunnableMethod("IdleObject::Method0", idle, &IdleObject::Method0)); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod("IdleObject::Method1", idle, + &IdleObject::Method1), + EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethodWithTimer("IdleObject::Method2", idle, + &IdleObject::Method2), + 60000, EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod("IdleObject::Method7", idle, + &IdleObject::Method7), + EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod( + "IdleObject::CheckExecutedMethods", idle, + &IdleObject::CheckExecutedMethods, "final", 8), + EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod("IdleObjectWithoutSetDeadline::Method", + idleNoSetDeadline, + &IdleObjectWithoutSetDeadline::Method), + EventQueuePriority::Idle); + NS_DispatchToCurrentThreadQueue( + NewIdleRunnableMethod("IdleObjectInheritedSetDeadline::Method", + idleInheritedSetDeadline, + &IdleObjectInheritedSetDeadline::Method), + EventQueuePriority::Idle); + + NS_ProcessPendingEvents(nullptr); + + ASSERT_TRUE(idleNoSetDeadline->mRunnableExecuted); + ASSERT_TRUE(idleInheritedSetDeadline->mRunnableExecuted); + ASSERT_TRUE(idleInheritedSetDeadline->mSetDeadlineCalled); + } +} +#endif + +TEST(ThreadUtils, IdleTaskRunner) +{ + using namespace mozilla; + + // Repeating. + int cnt1 = 0; + RefPtr runner1 = IdleTaskRunner::Create( + [&cnt1](TimeStamp) { + cnt1++; + return true; + }, + "runner1", 0, TimeDuration::FromMilliseconds(10), + TimeDuration::FromMilliseconds(3), true, nullptr); + + // Non-repeating but callback always return false so it's still repeating. + int cnt2 = 0; + RefPtr runner2 = IdleTaskRunner::Create( + [&cnt2](TimeStamp) { + cnt2++; + return false; + }, + "runner2", 0, TimeDuration::FromMilliseconds(10), + TimeDuration::FromMilliseconds(3), false, nullptr); + + // Repeating until cnt3 >= 2 by returning 'true' in MayStopProcessing + // callback. The strategy is to stop repeating as early as possible so that we + // are more probable to catch the bug if it didn't stop as expected. + int cnt3 = 0; + RefPtr runner3 = IdleTaskRunner::Create( + [&cnt3](TimeStamp) { + cnt3++; + return true; + }, + "runner3", 0, TimeDuration::FromMilliseconds(10), + TimeDuration::FromMilliseconds(3), true, [&cnt3] { return cnt3 >= 2; }); + + // Non-repeating can callback return true so the callback will + // be only run once. + int cnt4 = 0; + RefPtr runner4 = IdleTaskRunner::Create( + [&cnt4](TimeStamp) { + cnt4++; + return true; + }, + "runner4", 0, TimeDuration::FromMilliseconds(10), + TimeDuration::FromMilliseconds(3), false, nullptr); + + // Firstly we wait until the two repeating tasks reach their limits. + MOZ_ALWAYS_TRUE( + SpinEventLoopUntil("xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt1"_ns, + [&]() { return cnt1 >= 100; })); + MOZ_ALWAYS_TRUE( + SpinEventLoopUntil("xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt2"_ns, + [&]() { return cnt2 >= 100; })); + + // At any point ==> 0 <= cnt3 <= 2 since MayStopProcessing() would return + // true when cnt3 >= 2. + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt3"_ns, [&]() { + if (cnt3 > 2) { + EXPECT_TRUE(false) << "MaybeContinueProcess() doesn't work."; + return true; // Stop on failure. + } + return cnt3 == 2; // Stop finish if we have reached its max value. + })); + + // At any point ==> 0 <= cnt4 <= 1 since this is a non-repeating + // idle runner. + MOZ_ALWAYS_TRUE(SpinEventLoopUntil( + "xpcom:TEST(ThreadUtils, IdleTaskRunner) cnt4"_ns, [&]() { + // At any point: 0 <= cnt4 <= 1 + if (cnt4 > 1) { + EXPECT_TRUE(false) << "The 'mRepeating' flag doesn't work."; + return true; // Stop on failure. + } + return cnt4 == 1; + })); + + // The repeating timers require an explicit Cancel() call. + runner1->Cancel(); + runner2->Cancel(); +} + +// {9e70a320-be02-11d1-8031-006008159b5a} +#define NS_IFOO_IID \ + { \ + 0x9e70a320, 0xbe02, 0x11d1, { \ + 0x80, 0x31, 0x00, 0x60, 0x08, 0x15, 0x9b, 0x5a \ + } \ + } + +TEST(ThreadUtils, TypeTraits) +{ + static_assert(!mozilla::IsRefcountedSmartPointer::value, + "IsRefcountedSmartPointer should be false"); + static_assert(mozilla::IsRefcountedSmartPointer>::value, + "IsRefcountedSmartPointer> should be true"); + static_assert(mozilla::IsRefcountedSmartPointer>::value, + "IsRefcountedSmartPointer> should be true"); + static_assert( + mozilla::IsRefcountedSmartPointer>::value, + "IsRefcountedSmartPointer> should be true"); + static_assert( + mozilla::IsRefcountedSmartPointer>::value, + "IsRefcountedSmartPointer> should be true"); + static_assert(mozilla::IsRefcountedSmartPointer>::value, + "IsRefcountedSmartPointer> should be true"); + static_assert(mozilla::IsRefcountedSmartPointer>::value, + "IsRefcountedSmartPointer> should be true"); + static_assert( + mozilla::IsRefcountedSmartPointer>::value, + "IsRefcountedSmartPointer> should be true"); + static_assert( + mozilla::IsRefcountedSmartPointer>::value, + "IsRefcountedSmartPointer> should be true"); + + static_assert(std::is_same_v::Type>, + "RemoveSmartPointer::Type should be int"); + static_assert(std::is_same_v::Type>, + "RemoveSmartPointer::Type should be int*"); + static_assert( + std::is_same_v, + mozilla::RemoveSmartPointer>::Type>, + "RemoveSmartPointer>::Type should be UniquePtr"); + static_assert( + std::is_same_v>::Type>, + "RemoveSmartPointer>::Type should be int"); + static_assert( + std::is_same_v>::Type>, + "RemoveSmartPointer>::Type should be int"); + static_assert( + std::is_same_v>::Type>, + "RemoveSmartPointer>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveSmartPointer>::Type>, + "RemoveSmartPointer>::Type should be int"); + static_assert( + std::is_same_v>::Type>, + "RemoveSmartPointer>::Type should be int"); + static_assert( + std::is_same_v>::Type>, + "RemoveSmartPointer>::Type should be int"); + static_assert( + std::is_same_v>::Type>, + "RemoveSmartPointer>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveSmartPointer>::Type>, + "RemoveSmartPointer>::Type should be int"); + + static_assert( + std::is_same_v::Type>, + "RemoveRawOrSmartPointer::Type should be int"); + static_assert( + std::is_same_v, + mozilla::RemoveRawOrSmartPointer>::Type>, + "RemoveRawOrSmartPointer>::Type should be UniquePtr"); + static_assert( + std::is_same_v::Type>, + "RemoveRawOrSmartPointer::Type should be int"); + static_assert( + std::is_same_v::Type>, + "RemoveRawOrSmartPointer::Type should be const int"); + static_assert( + std::is_same_v::Type>, + "RemoveRawOrSmartPointer::Type should be volatile int"); + static_assert( + std::is_same_v::Type>, + "RemoveRawOrSmartPointer::Type should be const " + "volatile int"); + static_assert( + std::is_same_v>::Type>, + "RemoveRawOrSmartPointer>::Type should be int"); + static_assert( + std::is_same_v>::Type>, + "RemoveRawOrSmartPointer>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveRawOrSmartPointer>::Type>, + "RemoveRawOrSmartPointer>::Type should be int"); + static_assert( + std::is_same_v>::Type>, + "RemoveRawOrSmartPointer>::Type should be " + "int"); + static_assert( + std::is_same_v>::Type>, + "RemoveRawOrSmartPointer>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveRawOrSmartPointer>::Type>, + "RemoveRawOrSmartPointer>::Type should be int"); + static_assert( + std::is_same_v< + int, mozilla::RemoveRawOrSmartPointer>::Type>, + "RemoveRawOrSmartPointer>::Type should be int"); + static_assert( + std::is_same_v>::Type>, + "RemoveRawOrSmartPointer>::Type should be " + "int"); +} + +namespace TestThreadUtils { + +static bool gDebug = false; +static int gAlive, gZombies; +static int gAllConstructions, gConstructions, gCopyConstructions, + gMoveConstructions, gDestructions, gAssignments, gMoves; +struct Spy { + static void ClearActions() { + gAllConstructions = gConstructions = gCopyConstructions = + gMoveConstructions = gDestructions = gAssignments = gMoves = 0; + } + static void ClearAll() { + ClearActions(); + gAlive = 0; + } + + explicit Spy(int aID) : mID(aID) { + ++gAlive; + ++gAllConstructions; + ++gConstructions; + if (gDebug) { + printf("Spy[%d@%p]()\n", mID, this); + } + } + Spy(const Spy& o) : mID(o.mID) { + ++gAlive; + ++gAllConstructions; + ++gCopyConstructions; + if (gDebug) { + printf("Spy[%d@%p](&[%d@%p])\n", mID, this, o.mID, &o); + } + } + Spy(Spy&& o) : mID(o.mID) { + o.mID = -o.mID; + ++gZombies; + ++gAllConstructions; + ++gMoveConstructions; + if (gDebug) { + printf("Spy[%d@%p](&&[%d->%d@%p])\n", mID, this, -o.mID, o.mID, &o); + } + } + ~Spy() { + if (mID >= 0) { + --gAlive; + } else { + --gZombies; + } + ++gDestructions; + if (gDebug) { + printf("~Spy[%d@%p]()\n", mID, this); + } + mID = 0; + } + Spy& operator=(const Spy& o) { + ++gAssignments; + if (gDebug) { + printf("Spy[%d->%d@%p] = &[%d@%p]\n", mID, o.mID, this, o.mID, &o); + } + mID = o.mID; + return *this; + }; + Spy& operator=(Spy&& o) { + --gAlive; + ++gZombies; + ++gMoves; + if (gDebug) { + printf("Spy[%d->%d@%p] = &&[%d->%d@%p]\n", mID, o.mID, this, o.mID, + -o.mID, &o); + } + mID = o.mID; + o.mID = -o.mID; + return *this; + }; + + int mID; // ID given at construction, or negation if was moved from; 0 when + // destroyed. +}; + +struct ISpyWithISupports : public nsISupports { + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + NS_IMETHOD_(nsrefcnt) RefCnt() = 0; + NS_IMETHOD_(int32_t) ID() = 0; +}; +NS_DEFINE_STATIC_IID_ACCESSOR(ISpyWithISupports, NS_IFOO_IID) +struct SpyWithISupports : public ISpyWithISupports, public Spy { + private: + virtual ~SpyWithISupports() = default; + + public: + explicit SpyWithISupports(int aID) : Spy(aID){}; + NS_DECL_ISUPPORTS + NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return mID; } +}; +NS_IMPL_ISUPPORTS(SpyWithISupports, ISpyWithISupports) + +class IThreadUtilsObject : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFOO_IID) + + NS_IMETHOD_(nsrefcnt) RefCnt() = 0; + NS_IMETHOD_(int32_t) ID() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(IThreadUtilsObject, NS_IFOO_IID) + +struct ThreadUtilsObjectNonRefCountedBase { + virtual void MethodFromNonRefCountedBase() {} +}; + +struct ThreadUtilsObject : public IThreadUtilsObject, + public ThreadUtilsObjectNonRefCountedBase { + // nsISupports implementation + NS_DECL_ISUPPORTS + + // IThreadUtilsObject implementation + NS_IMETHOD_(nsrefcnt) RefCnt() override { return mRefCnt; } + NS_IMETHOD_(int32_t) ID() override { return 0; } + + int mCount; // Number of calls + arguments processed. + int mA0, mA1, mA2, mA3; + Spy mSpy; + const Spy* mSpyPtr; + ThreadUtilsObject() + : mCount(0), mA0(0), mA1(0), mA2(0), mA3(0), mSpy(1), mSpyPtr(nullptr) {} + + private: + virtual ~ThreadUtilsObject() = default; + + public: + void Test0() { mCount += 1; } + void Test1i(int a0) { + mCount += 2; + mA0 = a0; + } + void Test2i(int a0, int a1) { + mCount += 3; + mA0 = a0; + mA1 = a1; + } + void Test3i(int a0, int a1, int a2) { + mCount += 4; + mA0 = a0; + mA1 = a1; + mA2 = a2; + } + void Test4i(int a0, int a1, int a2, int a3) { + mCount += 5; + mA0 = a0; + mA1 = a1; + mA2 = a2; + mA3 = a3; + } + void Test1pi(int* ap) { + mCount += 2; + mA0 = ap ? *ap : -1; + } + void Test1pci(const int* ap) { + mCount += 2; + mA0 = ap ? *ap : -1; + } + void Test1ri(int& ar) { + mCount += 2; + mA0 = ar; + } + void Test1rri(int&& arr) { + mCount += 2; + mA0 = arr; + } + void Test1upi(mozilla::UniquePtr aup) { + mCount += 2; + mA0 = aup ? *aup : -1; + } + void Test1rupi(mozilla::UniquePtr& aup) { + mCount += 2; + mA0 = aup ? *aup : -1; + } + void Test1rrupi(mozilla::UniquePtr&& aup) { + mCount += 2; + mA0 = aup ? *aup : -1; + } + + void Test1s(Spy) { mCount += 2; } + void Test1ps(Spy*) { mCount += 2; } + void Test1rs(Spy&) { mCount += 2; } + void Test1rrs(Spy&&) { mCount += 2; } + void Test1ups(mozilla::UniquePtr) { mCount += 2; } + void Test1rups(mozilla::UniquePtr&) { mCount += 2; } + void Test1rrups(mozilla::UniquePtr&&) { mCount += 2; } + + // Possible parameter passing styles: + void TestByValue(Spy s) { + if (gDebug) { + printf("TestByValue(Spy[%d@%p])\n", s.mID, &s); + } + mSpy = s; + }; + void TestByConstLRef(const Spy& s) { + if (gDebug) { + printf("TestByConstLRef(Spy[%d@%p]&)\n", s.mID, &s); + } + mSpy = s; + }; + void TestByRRef(Spy&& s) { + if (gDebug) { + printf("TestByRRef(Spy[%d@%p]&&)\n", s.mID, &s); + } + mSpy = std::move(s); + }; + void TestByLRef(Spy& s) { + if (gDebug) { + printf("TestByLRef(Spy[%d@%p]&)\n", s.mID, &s); + } + mSpy = s; + mSpyPtr = &s; + }; + void TestByPointer(Spy* p) { + if (p) { + if (gDebug) { + printf("TestByPointer(&Spy[%d@%p])\n", p->mID, p); + } + mSpy = *p; + } else { + if (gDebug) { + printf("TestByPointer(nullptr)\n"); + } + } + mSpyPtr = p; + }; + void TestByPointerToConst(const Spy* p) { + if (p) { + if (gDebug) { + printf("TestByPointerToConst(&Spy[%d@%p])\n", p->mID, p); + } + mSpy = *p; + } else { + if (gDebug) { + printf("TestByPointerToConst(nullptr)\n"); + } + } + mSpyPtr = p; + }; +}; + +NS_IMPL_ISUPPORTS(ThreadUtilsObject, IThreadUtilsObject) + +class ThreadUtilsRefCountedFinal final { + public: + ThreadUtilsRefCountedFinal() : m_refCount(0) {} + ~ThreadUtilsRefCountedFinal() = default; + // 'AddRef' and 'Release' methods with different return types, to verify + // that the return type doesn't influence storage selection. + long AddRef(void) { return ++m_refCount; } + void Release(void) { --m_refCount; } + + private: + long m_refCount; +}; + +class ThreadUtilsRefCountedBase { + public: + ThreadUtilsRefCountedBase() : m_refCount(0) {} + virtual ~ThreadUtilsRefCountedBase() = default; + // 'AddRef' and 'Release' methods with different return types, to verify + // that the return type doesn't influence storage selection. + virtual void AddRef(void) { ++m_refCount; } + virtual MozExternalRefCountType Release(void) { return --m_refCount; } + + private: + MozExternalRefCountType m_refCount; +}; + +class ThreadUtilsRefCountedDerived : public ThreadUtilsRefCountedBase {}; + +class ThreadUtilsNonRefCounted {}; + +} // namespace TestThreadUtils + +TEST(ThreadUtils, main) +{ + using namespace TestThreadUtils; + + static_assert(!IsParameterStorageClass::value, + "'int' should not be recognized as Storage Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreCopyPassByValue should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreCopyPassByConstLRef should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreCopyPassByLRef should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreCopyPassByRRef should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreRefPassByLRef should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreConstRefPassByConstLRef should be recognized as Storage " + "Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreRefPtrPassByPtr should be recognized as Storage Class"); + static_assert(IsParameterStorageClass>::value, + "StorePtrPassByPtr should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreConstPtrPassByConstPtr should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreCopyPassByConstPtr should be recognized as Storage Class"); + static_assert( + IsParameterStorageClass>::value, + "StoreCopyPassByPtr should be recognized as Storage Class"); + + RefPtr rpt(new ThreadUtilsObject); + int count = 0; + + // Test legacy functions. + + nsCOMPtr r1 = + NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test0", rpt, + &ThreadUtilsObject::Test0); + r1->Run(); + EXPECT_EQ(count += 1, rpt->mCount); + + r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1i", rpt, + &ThreadUtilsObject::Test1i, 11); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(11, rpt->mA0); + + // Test calling a method from a non-ref-counted base. + + r1 = NewRunnableMethod( + "TestThreadUtils::ThreadUtilsObjectNonRefCountedBase::" + "MethodFromNonRefCountedBase", + rpt, &ThreadUtilsObject::MethodFromNonRefCountedBase); + r1->Run(); + EXPECT_EQ(count, rpt->mCount); + + // Test variadic function with simple POD arguments. + + r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test0", rpt, + &ThreadUtilsObject::Test0); + r1->Run(); + EXPECT_EQ(count += 1, rpt->mCount); + + static_assert(std::is_same_v<::detail::ParameterStorage::Type, + StoreCopyPassByConstLRef>, + "detail::ParameterStorage::Type should be " + "StoreCopyPassByConstLRef"); + static_assert(std::is_same_v< + ::detail::ParameterStorage>::Type, + StoreCopyPassByValue>, + "detail::ParameterStorage>::Type " + "should be StoreCopyPassByValue"); + + r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1i", rpt, + &ThreadUtilsObject::Test1i, 12); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(12, rpt->mA0); + + r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test2i", + rpt, &ThreadUtilsObject::Test2i, 21, 22); + r1->Run(); + EXPECT_EQ(count += 3, rpt->mCount); + EXPECT_EQ(21, rpt->mA0); + EXPECT_EQ(22, rpt->mA1); + + r1 = NewRunnableMethod( + "TestThreadUtils::ThreadUtilsObject::Test3i", rpt, + &ThreadUtilsObject::Test3i, 31, 32, 33); + r1->Run(); + EXPECT_EQ(count += 4, rpt->mCount); + EXPECT_EQ(31, rpt->mA0); + EXPECT_EQ(32, rpt->mA1); + EXPECT_EQ(33, rpt->mA2); + + r1 = NewRunnableMethod( + "TestThreadUtils::ThreadUtilsObject::Test4i", rpt, + &ThreadUtilsObject::Test4i, 41, 42, 43, 44); + r1->Run(); + EXPECT_EQ(count += 5, rpt->mCount); + EXPECT_EQ(41, rpt->mA0); + EXPECT_EQ(42, rpt->mA1); + EXPECT_EQ(43, rpt->mA2); + EXPECT_EQ(44, rpt->mA3); + + // More interesting types of arguments. + + // Passing a short to make sure forwarding works with an inexact type match. + short int si = 11; + r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1i", rpt, + &ThreadUtilsObject::Test1i, si); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(si, rpt->mA0); + + // Raw pointer, possible cv-qualified. + static_assert( + std::is_same_v<::detail::ParameterStorage::Type, + StorePtrPassByPtr>, + "detail::ParameterStorage::Type should be StorePtrPassByPtr"); + static_assert(std::is_same_v<::detail::ParameterStorage::Type, + StorePtrPassByPtr>, + "detail::ParameterStorage::Type should be " + "StorePtrPassByPtr"); + static_assert(std::is_same_v<::detail::ParameterStorage::Type, + StorePtrPassByPtr>, + "detail::ParameterStorage::Type should be " + "StorePtrPassByPtr"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type, + StorePtrPassByPtr>, + "detail::ParameterStorage::Type should be " + "StorePtrPassByPtr"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::stored_type, int*>, + "detail::ParameterStorage::Type::stored_type should be int*"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::passed_type, int*>, + "detail::ParameterStorage::Type::passed_type should be int*"); + { + int i = 12; + r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1pi", + rpt, &ThreadUtilsObject::Test1pi, &i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to const. + static_assert(std::is_same_v<::detail::ParameterStorage::Type, + StoreConstPtrPassByConstPtr>, + "detail::ParameterStorage::Type should be " + "StoreConstPtrPassByConstPtr"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type, + StoreConstPtrPassByConstPtr>, + "detail::ParameterStorage::Type should be " + "StoreConstPtrPassByConstPtr"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type, + StoreConstPtrPassByConstPtr>, + "detail::ParameterStorage::Type should be " + "StoreConstPtrPassByConstPtr"); + static_assert(std::is_same_v< + ::detail::ParameterStorage::Type, + StoreConstPtrPassByConstPtr>, + "detail::ParameterStorage::Type " + "should be StoreConstPtrPassByConstPtr"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::stored_type, + const int*>, + "detail::ParameterStorage::Type::stored_type should be const " + "int*"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::passed_type, + const int*>, + "detail::ParameterStorage::Type::passed_type should be const " + "int*"); + { + int i = 1201; + r1 = NewRunnableMethod( + "TestThreadUtils::ThreadUtilsObject::Test1pci", rpt, + &ThreadUtilsObject::Test1pci, &i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to copy. + static_assert(std::is_same_v::stored_type, int>, + "StoreCopyPassByPtr::stored_type should be int"); + static_assert(std::is_same_v::passed_type, int*>, + "StoreCopyPassByPtr::passed_type should be int*"); + { + int i = 1202; + r1 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::Test1pi", rpt, + &ThreadUtilsObject::Test1pi, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Raw pointer to const copy. + static_assert(std::is_same_v::stored_type, int>, + "StoreCopyPassByConstPtr::stored_type should be int"); + static_assert( + std::is_same_v::passed_type, const int*>, + "StoreCopyPassByConstPtr::passed_type should be const int*"); + { + int i = 1203; + r1 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::Test1pci", rpt, + &ThreadUtilsObject::Test1pci, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // nsRefPtr to pointer. + static_assert( + std::is_same_v<::detail::ParameterStorage< + StoreRefPtrPassByPtr>::Type, + StoreRefPtrPassByPtr>, + "ParameterStorage>::Type should " + "be StoreRefPtrPassByPtr"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type, + StoreRefPtrPassByPtr>, + "ParameterStorage::Type should be " + "StoreRefPtrPassByPtr"); + static_assert( + std::is_same_v::stored_type, + RefPtr>, + "StoreRefPtrPassByPtr::stored_type should be " + "RefPtr"); + static_assert( + std::is_same_v::passed_type, + SpyWithISupports*>, + "StoreRefPtrPassByPtr::passed_type should be " + "SpyWithISupports*"); + // (more nsRefPtr tests below) + + // nsRefPtr for ref-countable classes that do not derive from ISupports. + static_assert(::detail::HasRefCountMethods::value, + "ThreadUtilsRefCountedFinal has AddRef() and Release()"); + static_assert( + std::is_same_v< + ::detail::ParameterStorage::Type, + StoreRefPtrPassByPtr>, + "ParameterStorage::Type should be " + "StoreRefPtrPassByPtr"); + static_assert(::detail::HasRefCountMethods::value, + "ThreadUtilsRefCountedBase has AddRef() and Release()"); + static_assert( + std::is_same_v< + ::detail::ParameterStorage::Type, + StoreRefPtrPassByPtr>, + "ParameterStorage::Type should be " + "StoreRefPtrPassByPtr"); + static_assert( + ::detail::HasRefCountMethods::value, + "ThreadUtilsRefCountedDerived has AddRef() and Release()"); + static_assert( + std::is_same_v< + ::detail::ParameterStorage::Type, + StoreRefPtrPassByPtr>, + "ParameterStorage::Type should be " + "StoreRefPtrPassByPtr"); + + static_assert(!::detail::HasRefCountMethods::value, + "ThreadUtilsNonRefCounted doesn't have AddRef() and Release()"); + static_assert(!std::is_same_v< + ::detail::ParameterStorage::Type, + StoreRefPtrPassByPtr>, + "ParameterStorage::Type should NOT " + "be StoreRefPtrPassByPtr"); + + // Lvalue reference. + static_assert( + std::is_same_v<::detail::ParameterStorage::Type, + StoreRefPassByLRef>, + "ParameterStorage::Type should be StoreRefPassByLRef"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::stored_type, + StoreRefPassByLRef::stored_type>, + "ParameterStorage::Type::stored_type should be " + "StoreRefPassByLRef::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::stored_type, int&>, + "ParameterStorage::Type::stored_type should be int&"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::passed_type, int&>, + "ParameterStorage::Type::passed_type should be int&"); + { + int i = 13; + r1 = NewRunnableMethod("TestThreadUtils::ThreadUtilsObject::Test1ri", + rpt, &ThreadUtilsObject::Test1ri, i); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(i, rpt->mA0); + } + + // Rvalue reference -- Actually storing a copy and then moving it. + static_assert( + std::is_same_v<::detail::ParameterStorage::Type, + StoreCopyPassByRRef>, + "ParameterStorage::Type should be StoreCopyPassByRRef"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::stored_type, + StoreCopyPassByRRef::stored_type>, + "ParameterStorage::Type::stored_type should be " + "StoreCopyPassByRRef::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::stored_type, int>, + "ParameterStorage::Type::stored_type should be int"); + static_assert( + std::is_same_v<::detail::ParameterStorage::Type::passed_type, + int&&>, + "ParameterStorage::Type::passed_type should be int&&"); + { + int i = 14; + r1 = NewRunnableMethod( + "TestThreadUtils::ThreadUtilsObject::Test1rri", rpt, + &ThreadUtilsObject::Test1rri, std::move(i)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(14, rpt->mA0); + + // Null unique pointer, by semi-implicit store&move with "T&&" syntax. + static_assert(std::is_same_v< + ::detail::ParameterStorage&&>::Type, + StoreCopyPassByRRef>>, + "ParameterStorage&&>::Type should be " + "StoreCopyPassByRRef>"); + static_assert( + std::is_same_v<::detail::ParameterStorage< + mozilla::UniquePtr&&>::Type::stored_type, + StoreCopyPassByRRef>::stored_type>, + "ParameterStorage&&>::Type::stored_type should be " + "StoreCopyPassByRRef>::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage< + mozilla::UniquePtr&&>::Type::stored_type, + mozilla::UniquePtr>, + "ParameterStorage&&>::Type::stored_type should be " + "UniquePtr"); + static_assert( + std::is_same_v<::detail::ParameterStorage< + mozilla::UniquePtr&&>::Type::passed_type, + mozilla::UniquePtr&&>, + "ParameterStorage&&>::Type::passed_type should be " + "UniquePtr&&"); + { + mozilla::UniquePtr upi; + r1 = NewRunnableMethod&&>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, std::move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(-1, rpt->mA0); + rpt->mA0 = 0; + + // Null unique pointer, by explicit store&move with "StoreCopyPassByRRef" + // syntax. + static_assert( + std::is_same_v<::detail::ParameterStorage>>::Type::stored_type, + StoreCopyPassByRRef>::stored_type>, + "ParameterStorage>>::Type::stored_" + "type should be StoreCopyPassByRRef>::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage>>::Type::stored_type, + StoreCopyPassByRRef>::stored_type>, + "ParameterStorage>>::Type::stored_" + "type should be StoreCopyPassByRRef>::stored_type"); + static_assert( + std::is_same_v<::detail::ParameterStorage>>::Type::stored_type, + mozilla::UniquePtr>, + "ParameterStorage>>::Type::stored_" + "type should be UniquePtr"); + static_assert( + std::is_same_v<::detail::ParameterStorage>>::Type::passed_type, + mozilla::UniquePtr&&>, + "ParameterStorage>>::Type::passed_" + "type should be UniquePtr&&"); + { + mozilla::UniquePtr upi; + r1 = NewRunnableMethod>>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, std::move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(-1, rpt->mA0); + + // Unique pointer as xvalue. + { + mozilla::UniquePtr upi = mozilla::MakeUnique(1); + r1 = NewRunnableMethod&&>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, std::move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(1, rpt->mA0); + + { + mozilla::UniquePtr upi = mozilla::MakeUnique(1); + r1 = NewRunnableMethod>>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, std::move(upi)); + } + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(1, rpt->mA0); + + // Unique pointer as prvalue. + r1 = NewRunnableMethod&&>( + "TestThreadUtils::ThreadUtilsObject::Test1upi", rpt, + &ThreadUtilsObject::Test1upi, mozilla::MakeUnique(2)); + r1->Run(); + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(2, rpt->mA0); + + // Unique pointer as lvalue to lref. + { + mozilla::UniquePtr upi; + r1 = NewRunnableMethod&>( + "TestThreadUtils::ThreadUtilsObject::Test1rupi", rpt, + &ThreadUtilsObject::Test1rupi, upi); + // Passed as lref, so Run() must be called while local upi is still alive! + r1->Run(); + } + EXPECT_EQ(count += 2, rpt->mCount); + EXPECT_EQ(-1, rpt->mA0); + + // Verify copy/move assumptions. + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from lvalue, pass by value\n", __LINE__); + } + { // Block around nsCOMPtr lifetime. + nsCOMPtr r2; + { // Block around Spy lifetime. + if (gDebug) { + printf("%d - Spy s(10)\n", __LINE__); + } + Spy s(10); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { + printf( + "%d - r2 = " + "NewRunnableMethod>(&TestByValue, s)\n", + __LINE__); + } + r2 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt, + &ThreadUtilsObject::TestByValue, s); + EXPECT_EQ(2, gAlive); + EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with Spy s(10)\n", __LINE__); + } + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r2->Run(); + EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call. + EXPECT_EQ(10, rpt->mSpy.mID); + EXPECT_LE(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from prvalue, pass by value\n", __LINE__); + } + { + if (gDebug) { + printf( + "%d - r3 = " + "NewRunnableMethod>(&TestByValue, " + "Spy(11))\n", + __LINE__); + } + nsCOMPtr r3 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt, + &ThreadUtilsObject::TestByValue, Spy(11)); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gConstructions); + EXPECT_LE(1, gMoveConstructions); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r3->Run(); + EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call. + EXPECT_EQ(11, rpt->mSpy.mID); + EXPECT_LE(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + { // Store copy from xvalue, pass by value. + nsCOMPtr r4; + { + Spy s(12); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + r4 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::TestByValue", rpt, + &ThreadUtilsObject::TestByValue, std::move(s)); + EXPECT_LE(1, gMoveConstructions); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gZombies); + Spy::ClearActions(); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(0, gZombies); + Spy::ClearActions(); + r4->Run(); + EXPECT_LE(1, gCopyConstructions); // Another copy-construction in call. + EXPECT_EQ(12, rpt->mSpy.mID); + EXPECT_LE(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + // Won't test xvalues anymore, prvalues are enough to verify all rvalues. + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from lvalue, pass by const lvalue ref\n", + __LINE__); + } + { // Block around nsCOMPtr lifetime. + nsCOMPtr r5; + { // Block around Spy lifetime. + if (gDebug) { + printf("%d - Spy s(20)\n", __LINE__); + } + Spy s(20); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { + printf( + "%d - r5 = " + "NewRunnableMethod>(&TestByConstLRef," + " s)\n", + __LINE__); + } + r5 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::TestByConstLRef", rpt, + &ThreadUtilsObject::TestByConstLRef, s); + EXPECT_EQ(2, gAlive); + EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with Spy s(20)\n", __LINE__); + } + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r5->Run(); + EXPECT_EQ(0, gCopyConstructions); // No copies in call. + EXPECT_EQ(20, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from prvalue, pass by const lvalue ref\n", + __LINE__); + } + { + if (gDebug) { + printf( + "%d - r6 = " + "NewRunnableMethod>(&TestByConstLRef, " + "Spy(21))\n", + __LINE__); + } + nsCOMPtr r6 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::TestByConstLRef", rpt, + &ThreadUtilsObject::TestByConstLRef, Spy(21)); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gConstructions); + EXPECT_LE(1, gMoveConstructions); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r6->Run(); + EXPECT_EQ(0, gCopyConstructions); // No copies in call. + EXPECT_EQ(21, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from lvalue, pass by rvalue ref\n", __LINE__); + } + { // Block around nsCOMPtr lifetime. + nsCOMPtr r7; + { // Block around Spy lifetime. + if (gDebug) { + printf("%d - Spy s(30)\n", __LINE__); + } + Spy s(30); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { + printf( + "%d - r7 = " + "NewRunnableMethod>(&TestByRRef, s)\n", + __LINE__); + } + r7 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::TestByRRef", rpt, + &ThreadUtilsObject::TestByRRef, s); + EXPECT_EQ(2, gAlive); + EXPECT_LE(1, gCopyConstructions); // At least 1 copy-construction. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with Spy s(30)\n", __LINE__); + } + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r7->Run(); + EXPECT_LE(1, gMoves); // Move in call. + EXPECT_EQ(30, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(0, gAlive); // Spy inside Test is not counted. + EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store copy from prvalue, pass by rvalue ref\n", + __LINE__); + } + { + if (gDebug) { + printf( + "%d - r8 = NewRunnableMethod>(&TestByRRef, " + "Spy(31))\n", + __LINE__); + } + nsCOMPtr r8 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::TestByRRef", rpt, + &ThreadUtilsObject::TestByRRef, Spy(31)); + EXPECT_EQ(1, gAlive); + EXPECT_EQ(1, gConstructions); + EXPECT_LE(1, gMoveConstructions); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r8->Run(); + EXPECT_LE(1, gMoves); // Move in call. + EXPECT_EQ(31, rpt->mSpy.mID); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(0, gAlive); // Spy inside Test is not counted. + EXPECT_EQ(1, gZombies); // Our local spy should now be a zombie. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store lvalue ref, pass lvalue ref\n", __LINE__); + } + { + if (gDebug) { + printf("%d - Spy s(40)\n", __LINE__); + } + Spy s(40); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - r9 = NewRunnableMethod(&TestByLRef, s)\n", __LINE__); + } + nsCOMPtr r9 = NewRunnableMethod( + "TestThreadUtils::ThreadUtilsObject::TestByLRef", rpt, + &ThreadUtilsObject::TestByLRef, s); + EXPECT_EQ(0, gAllConstructions); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r9->Run(); + EXPECT_LE(1, gAssignments); // Assignment from reference in call. + EXPECT_EQ(40, rpt->mSpy.mID); + EXPECT_EQ(&s, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store nsRefPtr, pass by pointer\n", __LINE__); + } + { // Block around nsCOMPtr lifetime. + nsCOMPtr r10; + SpyWithISupports* ptr = 0; + { // Block around RefPtr lifetime. + if (gDebug) { + printf("%d - RefPtr s(new SpyWithISupports(45))\n", + __LINE__); + } + RefPtr s(new SpyWithISupports(45)); + ptr = s.get(); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + if (gDebug) { + printf( + "%d - r10 = " + "NewRunnableMethod>(&TestByRRef, " + "s.get())\n", + __LINE__); + } + r10 = NewRunnableMethod>( + "TestThreadUtils::ThreadUtilsObject::TestByPointer", rpt, + &ThreadUtilsObject::TestByPointer, s.get()); + EXPECT_LE(0, gAllConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with RefPtr s\n", __LINE__); + } + } + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r10->Run(); + EXPECT_LE(1, gAssignments); // Assignment from pointee in call. + EXPECT_EQ(45, rpt->mSpy.mID); + EXPECT_EQ(ptr, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store pointer to lvalue, pass by pointer\n", __LINE__); + } + { + if (gDebug) { + printf("%d - Spy s(55)\n", __LINE__); + } + Spy s(55); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - r11 = NewRunnableMethod(&TestByPointer, s)\n", + __LINE__); + } + nsCOMPtr r11 = NewRunnableMethod( + "TestThreadUtils::ThreadUtilsObject::TestByPointer", rpt, + &ThreadUtilsObject::TestByPointer, &s); + EXPECT_EQ(0, gAllConstructions); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r11->Run(); + EXPECT_LE(1, gAssignments); // Assignment from pointee in call. + EXPECT_EQ(55, rpt->mSpy.mID); + EXPECT_EQ(&s, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); + + Spy::ClearAll(); + if (gDebug) { + printf("%d - Test: Store pointer to const lvalue, pass by pointer\n", + __LINE__); + } + { + if (gDebug) { + printf("%d - Spy s(60)\n", __LINE__); + } + Spy s(60); + EXPECT_EQ(1, gConstructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - r12 = NewRunnableMethod(&TestByPointer, s)\n", + __LINE__); + } + nsCOMPtr r12 = NewRunnableMethod( + "TestThreadUtils::ThreadUtilsObject::TestByPointerToConst", rpt, + &ThreadUtilsObject::TestByPointerToConst, &s); + EXPECT_EQ(0, gAllConstructions); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); + Spy::ClearActions(); + if (gDebug) { + printf("%d - Run()\n", __LINE__); + } + r12->Run(); + EXPECT_LE(1, gAssignments); // Assignment from pointee in call. + EXPECT_EQ(60, rpt->mSpy.mID); + EXPECT_EQ(&s, rpt->mSpyPtr); + EXPECT_EQ(0, gDestructions); + EXPECT_EQ(1, gAlive); // Spy inside Test is not counted. + Spy::ClearActions(); + if (gDebug) { + printf("%d - End block with r\n", __LINE__); + } + } + if (gDebug) { + printf("%d - After end block with r\n", __LINE__); + } + EXPECT_EQ(1, gDestructions); + EXPECT_EQ(0, gAlive); +} diff --git a/xpcom/tests/gtest/TestThreads.cpp b/xpcom/tests/gtest/TestThreads.cpp new file mode 100644 index 0000000000..c4b1217031 --- /dev/null +++ b/xpcom/tests/gtest/TestThreads.cpp @@ -0,0 +1,415 @@ +/* -*- 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 "nsThreadUtils.h" +#include +#include +#include +#include "nspr.h" +#include "nsCOMPtr.h" +#include "nsITargetShutdownTask.h" +#include "nsIThread.h" +#include "nsXPCOM.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Monitor.h" +#include "mozilla/SyncRunnable.h" +#include "gtest/gtest.h" + +#ifdef XP_WIN +# include +# include +#endif + +using namespace mozilla; + +class nsRunner final : public Runnable { + ~nsRunner() = default; + + public: + NS_IMETHOD Run() override { + nsCOMPtr thread; + nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread)); + EXPECT_NS_SUCCEEDED(rv); + printf("running %d on thread %p\n", mNum, (void*)thread.get()); + + // if we don't do something slow, we'll never see the other + // worker threads run + PR_Sleep(PR_MillisecondsToInterval(100)); + + return rv; + } + + explicit nsRunner(int num) : Runnable("nsRunner"), mNum(num) {} + + protected: + int mNum; +}; + +TEST(Threads, Main) +{ + nsresult rv; + + nsCOMPtr event = new nsRunner(0); + EXPECT_TRUE(event); + + nsCOMPtr runner; + rv = NS_NewNamedThread("TestThreadsMain", getter_AddRefs(runner), event); + EXPECT_NS_SUCCEEDED(rv); + + nsCOMPtr thread; + rv = NS_GetCurrentThread(getter_AddRefs(thread)); + EXPECT_NS_SUCCEEDED(rv); + + rv = runner->Shutdown(); // wait for the runner to die before quitting + EXPECT_NS_SUCCEEDED(rv); + + PR_Sleep( + PR_MillisecondsToInterval(100)); // hopefully the runner will quit here +} + +class nsStressRunner final : public Runnable { + public: + NS_IMETHOD Run() override { + EXPECT_FALSE(mWasRun); + mWasRun = true; + PR_Sleep(1); + if (!PR_AtomicDecrement(&gNum)) { + printf(" last thread was %d\n", mNum); + } + return NS_OK; + } + + explicit nsStressRunner(int num) + : Runnable("nsStressRunner"), mNum(num), mWasRun(false) { + PR_AtomicIncrement(&gNum); + } + + static int32_t GetGlobalCount() { return gNum; } + + private: + ~nsStressRunner() { EXPECT_TRUE(mWasRun); } + + protected: + static int32_t gNum; + int32_t mNum; + bool mWasRun; +}; + +int32_t nsStressRunner::gNum = 0; + +TEST(Threads, Stress) +{ +#if defined(XP_WIN) && defined(MOZ_ASAN) // Easily hits OOM + const int loops = 250; +#else + const int loops = 1000; +#endif + + const int threads = 50; + + for (int i = 0; i < loops; i++) { + printf("Loop %d of %d\n", i + 1, loops); + + int k; + nsIThread** array = new nsIThread*[threads]; + + EXPECT_EQ(nsStressRunner::GetGlobalCount(), 0); + + for (k = 0; k < threads; k++) { + nsCOMPtr t; + nsresult rv = NS_NewNamedThread("StressRunner", getter_AddRefs(t), + new nsStressRunner(k)); + EXPECT_NS_SUCCEEDED(rv); + NS_ADDREF(array[k] = t); + } + + for (k = threads - 1; k >= 0; k--) { + array[k]->Shutdown(); + NS_RELEASE(array[k]); + } + delete[] array; + } +} + +mozilla::Monitor* gAsyncShutdownReadyMonitor; +mozilla::Monitor* gBeginAsyncShutdownMonitor; + +class AsyncShutdownPreparer : public Runnable { + public: + NS_IMETHOD Run() override { + EXPECT_FALSE(mWasRun); + mWasRun = true; + + mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor); + lock.Notify(); + + return NS_OK; + } + + explicit AsyncShutdownPreparer() + : Runnable("AsyncShutdownPreparer"), mWasRun(false) {} + + private: + virtual ~AsyncShutdownPreparer() { EXPECT_TRUE(mWasRun); } + + protected: + bool mWasRun; +}; + +class AsyncShutdownWaiter : public Runnable { + public: + NS_IMETHOD Run() override { + EXPECT_FALSE(mWasRun); + mWasRun = true; + + nsCOMPtr t; + nsresult rv; + + { + mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor); + + rv = NS_NewNamedThread("AsyncShutdownPr", getter_AddRefs(t), + new AsyncShutdownPreparer()); + EXPECT_NS_SUCCEEDED(rv); + + lock.Wait(); + } + + rv = t->AsyncShutdown(); + EXPECT_NS_SUCCEEDED(rv); + + return NS_OK; + } + + explicit AsyncShutdownWaiter() + : Runnable("AsyncShutdownWaiter"), mWasRun(false) {} + + private: + virtual ~AsyncShutdownWaiter() { EXPECT_TRUE(mWasRun); } + + protected: + bool mWasRun; +}; + +class SameThreadSentinel : public Runnable { + public: + NS_IMETHOD Run() override { + mozilla::MonitorAutoLock lock(*gBeginAsyncShutdownMonitor); + lock.Notify(); + return NS_OK; + } + + SameThreadSentinel() : Runnable("SameThreadSentinel") {} + + private: + virtual ~SameThreadSentinel() = default; +}; + +TEST(Threads, AsyncShutdown) +{ + gAsyncShutdownReadyMonitor = new mozilla::Monitor("gAsyncShutdownReady"); + gBeginAsyncShutdownMonitor = new mozilla::Monitor("gBeginAsyncShutdown"); + + nsCOMPtr t; + nsresult rv; + + { + mozilla::MonitorAutoLock lock(*gAsyncShutdownReadyMonitor); + + rv = NS_NewNamedThread("AsyncShutdownWt", getter_AddRefs(t), + new AsyncShutdownWaiter()); + EXPECT_NS_SUCCEEDED(rv); + + lock.Wait(); + } + + NS_DispatchToCurrentThread(new SameThreadSentinel()); + rv = t->Shutdown(); + EXPECT_NS_SUCCEEDED(rv); + + delete gAsyncShutdownReadyMonitor; + delete gBeginAsyncShutdownMonitor; +} + +static void threadProc(void* arg) { + // printf(" running thread %d\n", (int) arg); + PR_Sleep(1); + EXPECT_EQ(PR_JOINABLE_THREAD, PR_GetThreadState(PR_GetCurrentThread())); +} + +TEST(Threads, StressNSPR) +{ +#if defined(XP_WIN) && defined(MOZ_ASAN) // Easily hits OOM + const int loops = 250; +#else + const int loops = 1000; +#endif + + const int threads = 50; + + for (int i = 0; i < loops; i++) { + printf("Loop %d of %d\n", i + 1, loops); + + intptr_t k; + PRThread** array = new PRThread*[threads]; + + for (k = 0; k < threads; k++) { + array[k] = PR_CreateThread(PR_USER_THREAD, threadProc, (void*)k, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, 0); + EXPECT_TRUE(array[k]); + } + + for (k = 0; k < threads; k++) { + EXPECT_EQ(PR_JOINABLE_THREAD, PR_GetThreadState(array[k])); + } + + for (k = threads - 1; k >= 0; k--) { + PR_JoinThread(array[k]); + } + delete[] array; + } +} + +TEST(Threads, GetCurrentSerialEventTarget) +{ + nsCOMPtr thread; + nsresult rv = + NS_NewNamedThread("Testing Thread", getter_AddRefs(thread), + NS_NewRunnableFunction("Testing Thread::check", []() { + nsCOMPtr serialEventTarget = + GetCurrentSerialEventTarget(); + nsCOMPtr thread = NS_GetCurrentThread(); + EXPECT_EQ(thread.get(), serialEventTarget.get()); + })); + MOZ_ALWAYS_SUCCEEDS(rv); + thread->Shutdown(); +} + +namespace { + +class TestShutdownTask final : public nsITargetShutdownTask { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit TestShutdownTask(std::function aCallback) + : mCallback(std::move(aCallback)) {} + + void TargetShutdown() override { + if (mCallback) { + mCallback(); + } + } + + private: + ~TestShutdownTask() = default; + std::function mCallback; +}; + +NS_IMPL_ISUPPORTS(TestShutdownTask, nsITargetShutdownTask) + +} // namespace + +TEST(Threads, ShutdownTask) +{ + auto shutdownTaskRun = std::make_shared(); + auto runnableFromShutdownRun = std::make_shared(); + + nsCOMPtr thread; + nsresult rv = NS_NewNamedThread("Testing Thread", getter_AddRefs(thread)); + MOZ_ALWAYS_SUCCEEDS(rv); + + nsCOMPtr shutdownTask = new TestShutdownTask([=] { + EXPECT_TRUE(thread->IsOnCurrentThread()); + + ASSERT_FALSE(*shutdownTaskRun); + *shutdownTaskRun = true; + + nsCOMPtr dummyTask = new TestShutdownTask([] {}); + nsresult rv = thread->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + MOZ_ALWAYS_SUCCEEDS( + thread->Dispatch(NS_NewRunnableFunction("afterShutdownTask", [=] { + EXPECT_TRUE(thread->IsOnCurrentThread()); + + nsCOMPtr dummyTask = + new TestShutdownTask([] {}); + nsresult rv = thread->RegisterShutdownTask(dummyTask); + EXPECT_TRUE(rv == NS_ERROR_UNEXPECTED); + + ASSERT_FALSE(*runnableFromShutdownRun); + *runnableFromShutdownRun = true; + }))); + }); + MOZ_ALWAYS_SUCCEEDS(thread->RegisterShutdownTask(shutdownTask)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + RefPtr syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(thread)); + + ASSERT_FALSE(*shutdownTaskRun); + ASSERT_FALSE(*runnableFromShutdownRun); + + thread->Shutdown(); + + ASSERT_TRUE(*shutdownTaskRun); + ASSERT_TRUE(*runnableFromShutdownRun); +} + +TEST(Threads, UnregisteredShutdownTask) +{ + nsCOMPtr thread; + nsresult rv = NS_NewNamedThread("Testing Thread", getter_AddRefs(thread)); + MOZ_ALWAYS_SUCCEEDS(rv); + + nsCOMPtr shutdownTask = + new TestShutdownTask([=] { MOZ_CRASH("should not be run"); }); + + MOZ_ALWAYS_SUCCEEDS(thread->RegisterShutdownTask(shutdownTask)); + + RefPtr syncWithThread = + new mozilla::SyncRunnable(NS_NewRunnableFunction("dummy", [] {})); + MOZ_ALWAYS_SUCCEEDS(syncWithThread->DispatchToThread(thread)); + + MOZ_ALWAYS_SUCCEEDS(thread->UnregisterShutdownTask(shutdownTask)); + + thread->Shutdown(); +} + +#if (defined(XP_WIN) || !defined(DEBUG)) && !defined(XP_MACOSX) +TEST(Threads, OptionsIsUiThread) +{ + // On Windows, test that the isUiThread flag results in a GUI thread. + // In non-Windows non-debug builds, test that the isUiThread flag is ignored. + + nsCOMPtr thread; + nsIThreadManager::ThreadCreationOptions options; + options.isUiThread = true; + MOZ_ALWAYS_SUCCEEDS(NS_NewNamedThread( + "Testing Thread", getter_AddRefs(thread), nullptr, options)); + + bool isGuiThread = false; + auto syncRunnable = + MakeRefPtr(NS_NewRunnableFunction(__func__, [&] { +# ifdef XP_WIN + isGuiThread = ::IsGUIThread(false); +# endif + })); + MOZ_ALWAYS_SUCCEEDS(syncRunnable->DispatchToThread(thread)); + + bool expectGuiThread = false; +# ifdef XP_WIN + expectGuiThread = true; +# endif + EXPECT_EQ(expectGuiThread, isGuiThread); + + thread->Shutdown(); +} +#endif diff --git a/xpcom/tests/gtest/TestThreads_mac.mm b/xpcom/tests/gtest/TestThreads_mac.mm new file mode 100644 index 0000000000..c221452684 --- /dev/null +++ b/xpcom/tests/gtest/TestThreads_mac.mm @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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 https://mozilla.org/MPL/2.0/. */ + +#include + +#include "gtest/gtest.h" +#include "mozilla/SyncRunnable.h" +#include "nsIThread.h" +#include "nsIThreadManager.h" + +using namespace mozilla; + +// Tests whether an nsThread ran a block in its NSRunLoop. +// ThreadCreationOptions.isUiThread gets set to aIsUiThread. +// Returns true if a block ran on the NSRunLoop. +bool UiThreadRunsRunLoop(bool aIsUiThread) { + nsCOMPtr thread; + nsIThreadManager::ThreadCreationOptions options; + options.isUiThread = aIsUiThread; + MOZ_ALWAYS_SUCCEEDS( + NS_NewNamedThread("Testing Thread", getter_AddRefs(thread), nullptr, options)); + + __block bool blockRanInRunLoop = false; + { + // We scope this so `loop` doesn't live past `thread-Shutdown()` since this file is compiled + // without ARC. + NSRunLoop* loop = nil; + auto syncRunnable = MakeRefPtr(NS_NewRunnableFunction(__func__, [&] { + // If the thread doesn't already have an NSRunLoop, one will be created. + loop = NSRunLoop.currentRunLoop; + })); + MOZ_ALWAYS_SUCCEEDS(syncRunnable->DispatchToThread(thread)); + + [loop performBlock:^void() { + blockRanInRunLoop = true; + }]; + } + + thread->Shutdown(); + return blockRanInRunLoop; +} + +TEST(ThreadsMac, OptionsIsUiThread) +{ + const bool isUiThread = true; + const bool isNoUiThread = false; + + EXPECT_TRUE(UiThreadRunsRunLoop(isUiThread)); + EXPECT_FALSE(UiThreadRunsRunLoop(isNoUiThread)); +} diff --git a/xpcom/tests/gtest/TestThrottledEventQueue.cpp b/xpcom/tests/gtest/TestThrottledEventQueue.cpp new file mode 100644 index 0000000000..64f34ff14f --- /dev/null +++ b/xpcom/tests/gtest/TestThrottledEventQueue.cpp @@ -0,0 +1,613 @@ +/* -*- 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 +#include +#include +#include + +#include "MainThreadUtils.h" +#include "gtest/gtest.h" +#include "mozilla/Attributes.h" +#include "mozilla/CondVar.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ThrottledEventQueue.h" +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsIRunnable.h" +#include "nsISerialEventTarget.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" +#include "prinrval.h" + +using mozilla::CondVar; +using mozilla::MakeRefPtr; +using mozilla::Mutex; +using mozilla::MutexAutoLock; +using mozilla::ThrottledEventQueue; +using std::function; +using std::string; + +namespace TestThrottledEventQueue { + +// A simple queue of runnables, to serve as the base target of +// ThrottledEventQueues in tests. +// +// This is much simpler than mozilla::TaskQueue, and so better for unit tests. +// It's about the same as mozilla::EventQueue, but that doesn't implement +// nsIEventTarget, so it can't be the base target of a ThrottledEventQueue. +struct RunnableQueue : nsISerialEventTarget { + std::queue> runnables; + + bool IsEmpty() { return runnables.empty(); } + size_t Length() { return runnables.size(); } + + [[nodiscard]] nsresult Run() { + while (!runnables.empty()) { + auto runnable = std::move(runnables.front()); + runnables.pop(); + nsresult rv = runnable->Run(); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; + } + + // nsIEventTarget methods + + [[nodiscard]] NS_IMETHODIMP Dispatch(already_AddRefed aRunnable, + uint32_t aFlags) override { + MOZ_ALWAYS_TRUE(aFlags == nsIEventTarget::DISPATCH_NORMAL); + runnables.push(aRunnable); + return NS_OK; + } + + [[nodiscard]] NS_IMETHODIMP DispatchFromScript(nsIRunnable* aRunnable, + uint32_t aFlags) override { + RefPtr r = aRunnable; + return Dispatch(r.forget(), aFlags); + } + + NS_IMETHOD_(bool) + IsOnCurrentThreadInfallible(void) override { return NS_IsMainThread(); } + + [[nodiscard]] NS_IMETHOD IsOnCurrentThread(bool* retval) override { + *retval = IsOnCurrentThreadInfallible(); + return NS_OK; + } + + [[nodiscard]] NS_IMETHODIMP DelayedDispatch( + already_AddRefed aEvent, uint32_t aDelay) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask*) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // nsISupports methods + + NS_DECL_THREADSAFE_ISUPPORTS + + private: + virtual ~RunnableQueue() = default; +}; + +NS_IMPL_ISUPPORTS(RunnableQueue, nsIEventTarget, nsISerialEventTarget) + +static void Enqueue(nsIEventTarget* target, function&& aCallable) { + nsresult rv = target->Dispatch( + NS_NewRunnableFunction("TEQ GTest", std::move(aCallable))); + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(rv)); +} + +} // namespace TestThrottledEventQueue + +using namespace TestThrottledEventQueue; + +TEST(ThrottledEventQueue, RunnableQueue) +{ + string log; + + RefPtr queue = MakeRefPtr(); + Enqueue(queue, [&]() { log += 'a'; }); + Enqueue(queue, [&]() { log += 'b'; }); + Enqueue(queue, [&]() { log += 'c'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(queue->Run()); + ASSERT_EQ(log, "abc"); +} + +TEST(ThrottledEventQueue, SimpleDispatch) +{ + string log; + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 1"); + + Enqueue(throttled, [&]() { log += 'a'; }); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, MixedDispatch) +{ + string log; + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 2"); + + // A ThrottledEventQueue limits its impact on the base target by only queuing + // its next event on the base once the prior event has been run. What it + // actually queues on the base is a sort of proxy event called an + // "executor": the base running the executor draws an event from the + // ThrottledEventQueue and runs that. If the ThrottledEventQueue has further + // events, it re-queues the executor on the base, effectively "going to the + // back of the line". + + // Queue an event on the ThrottledEventQueue. This also queues the "executor" + // event on the base. + Enqueue(throttled, [&]() { log += 'a'; }); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 1U); + + // Add a second event to the throttled queue. The executor is already queued. + Enqueue(throttled, [&]() { log += 'b'; }); + ASSERT_EQ(throttled->Length(), 2U); + ASSERT_EQ(base->Length(), 1U); + + // Add an event directly to the base, after the executor. + Enqueue(base, [&]() { log += 'c'; }); + ASSERT_EQ(throttled->Length(), 2U); + ASSERT_EQ(base->Length(), 2U); + + // Run the base target. This runs: + // - the executor, which runs the first event from the ThrottledEventQueue, + // and re-enqueues itself + // - the event queued directly on the base + // - the executor again, which runs the second event from the + // ThrottledEventQueue. + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "acb"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, EnqueueFromRun) +{ + string log; + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 3"); + + // When an event from the throttled queue dispatches a new event directly to + // the base target, it is queued after the executor, so the next event from + // the throttled queue will run before it. + Enqueue(base, [&]() { log += 'a'; }); + Enqueue(throttled, [&]() { + log += 'b'; + Enqueue(base, [&]() { log += 'c'; }); + }); + Enqueue(throttled, [&]() { log += 'd'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "abdc"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, RunFromRun) +{ + string log; + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 4"); + + // Running the event queue from within an event (i.e., a nested event loop) + // does not stall the ThrottledEventQueue. + Enqueue(throttled, [&]() { + log += '('; + // This should run subsequent events from throttled. + ASSERT_NS_SUCCEEDED(base->Run()); + log += ')'; + }); + + Enqueue(throttled, [&]() { log += 'a'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "(a)"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, DropWhileRunning) +{ + string log; + + auto base = MakeRefPtr(); + + // If we drop the event queue while it still has events, they still run. + { + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 5"); + Enqueue(throttled, [&]() { log += 'a'; }); + } + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); +} + +TEST(ThrottledEventQueue, AwaitIdle) +{ + Mutex mutex MOZ_UNANNOTATED("TEQ AwaitIdle"); + CondVar cond(mutex, "TEQ AwaitIdle"); + + string dequeue_await; // mutex + bool threadFinished = false; // mutex & cond + bool runnableFinished = false; // main thread only + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 6"); + + // Put an event in the queue so the AwaitIdle might block. + Enqueue(throttled, [&]() { runnableFinished = true; }); + + // Create a separate thread that waits for the queue to become idle, and + // then takes observable action. + nsCOMPtr await = NS_NewRunnableFunction("TEQ AwaitIdle", [&]() { + throttled->AwaitIdle(); + MutexAutoLock lock(mutex); + dequeue_await += " await"; + threadFinished = true; + cond.Notify(); + }); + + nsCOMPtr thread; + nsresult rv = + NS_NewNamedThread("TEQ AwaitIdle", getter_AddRefs(thread), await); + ASSERT_NS_SUCCEEDED(rv); + + // We can't guarantee that the thread has reached the AwaitIdle call, but we + // can get pretty close. Either way, it shouldn't affect the behavior of the + // test. + PR_Sleep(PR_MillisecondsToInterval(100)); + + // Drain the queue. + { + MutexAutoLock lock(mutex); + ASSERT_EQ(dequeue_await, ""); + dequeue_await += "dequeue"; + ASSERT_FALSE(threadFinished); + } + ASSERT_FALSE(runnableFinished); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_TRUE(runnableFinished); + + // Wait for the thread to finish. + { + MutexAutoLock lock(mutex); + while (!threadFinished) cond.Wait(); + ASSERT_EQ(dequeue_await, "dequeue await"); + } + + ASSERT_NS_SUCCEEDED(thread->Shutdown()); +} + +TEST(ThrottledEventQueue, AwaitIdleMixed) +{ + // Create a separate thread that waits for the queue to become idle, and + // then takes observable action. + nsCOMPtr thread; + ASSERT_TRUE(NS_SUCCEEDED( + NS_NewNamedThread("AwaitIdleMixed", getter_AddRefs(thread)))); + + Mutex mutex MOZ_UNANNOTATED("AwaitIdleMixed"); + CondVar cond(mutex, "AwaitIdleMixed"); + + // The following are protected by mutex and cond, above. + string log; + bool threadStarted = false; + bool threadFinished = false; + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 7"); + + Enqueue(throttled, [&]() { + MutexAutoLock lock(mutex); + log += 'a'; + }); + + Enqueue(throttled, [&]() { + MutexAutoLock lock(mutex); + log += 'b'; + }); + + nsCOMPtr await = NS_NewRunnableFunction("AwaitIdleMixed", [&]() { + { + MutexAutoLock lock(mutex); + + // Note that we are about to begin awaiting. When the main thread sees + // this notification, it will begin draining the queue. + log += '('; + threadStarted = true; + cond.Notify(); + } + + // Wait for the main thread to drain the TEQ. + throttled->AwaitIdle(); + + { + MutexAutoLock lock(mutex); + + // Note that we have finished awaiting. + log += ')'; + threadFinished = true; + cond.Notify(); + } + }); + + { + MutexAutoLock lock(mutex); + ASSERT_EQ(log, ""); + } + + ASSERT_NS_SUCCEEDED(thread->Dispatch(await.forget())); + + // Wait for the thread to be ready to await. We can't be sure it will actually + // be blocking before we get around to draining the event queue, but that's + // the nature of the API; this test should work even if we drain the queue + // before it awaits. + { + MutexAutoLock lock(mutex); + while (!threadStarted) cond.Wait(); + ASSERT_EQ(log, "("); + } + + // Let the queue drain. + ASSERT_NS_SUCCEEDED(base->Run()); + + { + MutexAutoLock lock(mutex); + // The first runnable must always finish before AwaitIdle returns. But the + // TEQ notifies the condition variable as soon as it dequeues the last + // runnable, without waiting for that runnable to complete. So the thread + // and the last runnable could run in either order. Or, we might beat the + // thread to the mutex. + // + // (The only combination excluded here is "(a)": the 'b' runnable should + // definitely have run.) + ASSERT_TRUE(log == "(ab" || log == "(a)b" || log == "(ab)"); + while (!threadFinished) cond.Wait(); + ASSERT_TRUE(log == "(a)b" || log == "(ab)"); + } + + ASSERT_NS_SUCCEEDED(thread->Shutdown()); +} + +TEST(ThrottledEventQueue, SimplePauseResume) +{ + string log; + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 8"); + + ASSERT_FALSE(throttled->IsPaused()); + + Enqueue(throttled, [&]() { log += 'a'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); + + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + ASSERT_TRUE(throttled->IsPaused()); + + Enqueue(throttled, [&]() { log += 'b'; }); + + ASSERT_EQ(log, "a"); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); + + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_FALSE(throttled->IsPaused()); + + ASSERT_EQ(log, "a"); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "ab"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, MixedPauseResume) +{ + string log; + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 9"); + + ASSERT_FALSE(throttled->IsPaused()); + + Enqueue(base, [&]() { log += 'A'; }); + Enqueue(throttled, [&]() { + log += 'b'; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(throttled->SetIsPaused(true))); + }); + Enqueue(throttled, [&]() { log += 'c'; }); + Enqueue(base, [&]() { log += 'D'; }); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + // Since the 'b' event paused the throttled queue, 'c' should not have run. + // but 'D' was enqueued directly on the base, and should have run. + ASSERT_EQ(log, "AbD"); + ASSERT_TRUE(base->IsEmpty()); + ASSERT_FALSE(throttled->IsEmpty()); + ASSERT_TRUE(throttled->IsPaused()); + + Enqueue(base, [&]() { log += 'E'; }); + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + Enqueue(base, [&]() { log += 'F'; }); + ASSERT_FALSE(throttled->IsPaused()); + + ASSERT_NS_SUCCEEDED(base->Run()); + // Since we've unpaused, 'c' should be able to run now. The executor should + // have been enqueued between 'E' and 'F'. + ASSERT_EQ(log, "AbDEcF"); + + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); +} + +TEST(ThrottledEventQueue, AwaitIdlePaused) +{ + Mutex mutex MOZ_UNANNOTATED("AwaitIdlePaused"); + CondVar cond(mutex, "AwaitIdlePaused"); + + string dequeue_await; // mutex + bool threadFinished = false; // mutex & cond + bool runnableFinished = false; // main thread only + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 10"); + + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + + // Put an event in the queue so the AwaitIdle might block. Since throttled is + // paused, this should not enqueue an executor in the base target. + Enqueue(throttled, [&]() { runnableFinished = true; }); + ASSERT_TRUE(base->IsEmpty()); + + // Create a separate thread that waits for the queue to become idle, and + // then takes observable action. + nsCOMPtr await = + NS_NewRunnableFunction("AwaitIdlePaused", [&]() { + throttled->AwaitIdle(); + MutexAutoLock lock(mutex); + dequeue_await += " await"; + threadFinished = true; + cond.Notify(); + }); + + nsCOMPtr thread; + nsresult rv = + NS_NewNamedThread("AwaitIdlePaused", getter_AddRefs(thread), await); + ASSERT_NS_SUCCEEDED(rv); + + // We can't guarantee that the thread has reached the AwaitIdle call, but we + // can get pretty close. Either way, it shouldn't affect the behavior of the + // test. + PR_Sleep(PR_MillisecondsToInterval(100)); + + // The AwaitIdle call should be blocked, even though there is no executor, + // because throttled is paused. + { + MutexAutoLock lock(mutex); + ASSERT_EQ(dequeue_await, ""); + dequeue_await += "dequeue"; + ASSERT_FALSE(threadFinished); + } + + // A paused TEQ contributes no events to its base target. (This is covered by + // other tests...) + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_TRUE(base->IsEmpty()); + ASSERT_FALSE(throttled->IsEmpty()); + + // Resume and drain the queue. + ASSERT_FALSE(runnableFinished); + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_TRUE(base->IsEmpty()); + ASSERT_TRUE(throttled->IsEmpty()); + ASSERT_TRUE(runnableFinished); + + // Wait for the thread to finish. + { + MutexAutoLock lock(mutex); + while (!threadFinished) cond.Wait(); + ASSERT_EQ(dequeue_await, "dequeue await"); + } + + ASSERT_NS_SUCCEEDED(thread->Shutdown()); +} + +TEST(ThrottledEventQueue, ExecutorTransitions) +{ + string log; + + auto base = MakeRefPtr(); + RefPtr throttled = + ThrottledEventQueue::Create(base, "test queue 11"); + + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + + // Since we're paused, queueing an event on throttled shouldn't queue the + // executor on the base target. + Enqueue(throttled, [&]() { log += 'a'; }); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 0U); + + // Resuming throttled should create the executor, since throttled is not + // empty. + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 1U); + + // Pausing can't remove the executor from the base target since we've already + // queued it there, but it can ensure that it doesn't do anything. + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, ""); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 0U); + + // As before, resuming must create the executor, since throttled is not empty. + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_EQ(throttled->Length(), 1U); + ASSERT_EQ(base->Length(), 1U); + + ASSERT_EQ(log, ""); + ASSERT_NS_SUCCEEDED(base->Run()); + ASSERT_EQ(log, "a"); + ASSERT_EQ(throttled->Length(), 0U); + ASSERT_EQ(base->Length(), 0U); + + // Since throttled is empty, pausing and resuming now should not enqueue an + // executor. + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(true)); + ASSERT_NS_SUCCEEDED(throttled->SetIsPaused(false)); + ASSERT_EQ(throttled->Length(), 0U); + ASSERT_EQ(base->Length(), 0U); +} diff --git a/xpcom/tests/gtest/TestTimeStamp.cpp b/xpcom/tests/gtest/TestTimeStamp.cpp new file mode 100644 index 0000000000..acd8ff575c --- /dev/null +++ b/xpcom/tests/gtest/TestTimeStamp.cpp @@ -0,0 +1,70 @@ +/* -*- 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/TimeStamp.h" + +#include "prinrval.h" +#include "prthread.h" + +#include "gtest/gtest.h" + +using mozilla::TimeDuration; +using mozilla::TimeStamp; + +TEST(TimeStamp, Main) +{ + TimeDuration td; + EXPECT_TRUE(td.ToSeconds() == 0.0); + EXPECT_TRUE(TimeDuration::FromSeconds(5).ToSeconds() == 5.0); + EXPECT_TRUE(TimeDuration::FromMilliseconds(5000).ToSeconds() == 5.0); + EXPECT_TRUE(TimeDuration::FromSeconds(1) < TimeDuration::FromSeconds(2)); + EXPECT_FALSE(TimeDuration::FromSeconds(1) < TimeDuration::FromSeconds(1)); + EXPECT_TRUE(TimeDuration::FromSeconds(2) > TimeDuration::FromSeconds(1)); + EXPECT_FALSE(TimeDuration::FromSeconds(1) > TimeDuration::FromSeconds(1)); + EXPECT_TRUE(TimeDuration::FromSeconds(1) <= TimeDuration::FromSeconds(2)); + EXPECT_TRUE(TimeDuration::FromSeconds(1) <= TimeDuration::FromSeconds(1)); + EXPECT_FALSE(TimeDuration::FromSeconds(2) <= TimeDuration::FromSeconds(1)); + EXPECT_TRUE(TimeDuration::FromSeconds(2) >= TimeDuration::FromSeconds(1)); + EXPECT_TRUE(TimeDuration::FromSeconds(1) >= TimeDuration::FromSeconds(1)); + EXPECT_FALSE(TimeDuration::FromSeconds(1) >= TimeDuration::FromSeconds(2)); + + TimeStamp ts; + EXPECT_TRUE(ts.IsNull()); + + ts = TimeStamp::Now(); + EXPECT_TRUE(!ts.IsNull()); + EXPECT_TRUE((ts - ts).ToSeconds() == 0.0); + + PR_Sleep(PR_SecondsToInterval(2)); + + TimeStamp ts2(TimeStamp::Now()); + EXPECT_TRUE(ts2 > ts); + EXPECT_FALSE(ts > ts); + EXPECT_TRUE(ts < ts2); + EXPECT_FALSE(ts < ts); + EXPECT_TRUE(ts <= ts2); + EXPECT_TRUE(ts <= ts); + EXPECT_FALSE(ts2 <= ts); + EXPECT_TRUE(ts2 >= ts); + EXPECT_TRUE(ts2 >= ts); + EXPECT_FALSE(ts >= ts2); + + // We can't be sure exactly how long PR_Sleep slept for. It should have + // slept for at least one second. We might have slept a lot longer due + // to process scheduling, but hopefully not more than 10 seconds. + td = ts2 - ts; + EXPECT_TRUE(td.ToSeconds() > 1.0); + EXPECT_TRUE(td.ToSeconds() < 20.0); + td = ts - ts2; + EXPECT_TRUE(td.ToSeconds() < -1.0); + EXPECT_TRUE(td.ToSeconds() > -20.0); + + double resolution = TimeDuration::Resolution().ToSecondsSigDigits(); + printf(" (platform timer resolution is ~%g s)\n", resolution); + EXPECT_TRUE(1e-10 < resolution); + // Don't upper-bound sanity check ... although NSPR reports 1ms + // resolution, it might be lying, so we shouldn't compare with it +} diff --git a/xpcom/tests/gtest/TestTimers.cpp b/xpcom/tests/gtest/TestTimers.cpp new file mode 100644 index 0000000000..a080d13c01 --- /dev/null +++ b/xpcom/tests/gtest/TestTimers.cpp @@ -0,0 +1,924 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsIThread.h" +#include "nsITimer.h" + +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIObserverService.h" +#include "nsThreadUtils.h" +#include "prinrval.h" +#include "prmon.h" +#include "prthread.h" +#include "mozilla/Attributes.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/Services.h" + +#include "mozilla/Monitor.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPrefs_timer.h" + +#include +#include + +#include "gtest/gtest.h" + +using namespace mozilla; + +typedef nsresult (*TestFuncPtr)(); + +class AutoTestThread { + public: + AutoTestThread() { + nsCOMPtr newThread; + nsresult rv = + NS_NewNamedThread("AutoTestThread", getter_AddRefs(newThread)); + if (NS_FAILED(rv)) return; + + newThread.swap(mThread); + } + + ~AutoTestThread() { mThread->Shutdown(); } + + operator nsIThread*() const { return mThread; } + + nsIThread* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { + return mThread; + } + + private: + nsCOMPtr mThread; +}; + +class AutoCreateAndDestroyReentrantMonitor { + public: + AutoCreateAndDestroyReentrantMonitor() { + mReentrantMonitor = new ReentrantMonitor("TestTimers::AutoMon"); + MOZ_RELEASE_ASSERT(mReentrantMonitor, "Out of memory!"); + } + + ~AutoCreateAndDestroyReentrantMonitor() { delete mReentrantMonitor; } + + operator ReentrantMonitor*() const { return mReentrantMonitor; } + + private: + ReentrantMonitor* mReentrantMonitor; +}; + +class TimerCallback final : public nsITimerCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + TimerCallback(nsIThread** aThreadPtr, ReentrantMonitor* aReentrantMonitor) + : mThreadPtr(aThreadPtr), mReentrantMonitor(aReentrantMonitor) {} + + NS_IMETHOD Notify(nsITimer* aTimer) override { + MOZ_RELEASE_ASSERT(mThreadPtr, "Callback was not supposed to be called!"); + nsCOMPtr current(do_GetCurrentThread()); + + ReentrantMonitorAutoEnter mon(*mReentrantMonitor); + + MOZ_RELEASE_ASSERT(!*mThreadPtr, "Timer called back more than once!"); + *mThreadPtr = current; + + mon.Notify(); + + return NS_OK; + } + + private: + ~TimerCallback() = default; + + nsIThread** mThreadPtr; + ReentrantMonitor* mReentrantMonitor; +}; + +NS_IMPL_ISUPPORTS(TimerCallback, nsITimerCallback) + +class TimerHelper { + public: + explicit TimerHelper(nsIEventTarget* aTarget) + : mStart(TimeStamp::Now()), + mTimer(NS_NewTimer(aTarget)), + mMonitor(__func__), + mTarget(aTarget) {} + + ~TimerHelper() { Cancel(); } + + static void ClosureCallback(nsITimer*, void* aClosure) { + reinterpret_cast(aClosure)->Notify(); + } + + // We do not use nsITimerCallback, because that results in a circular + // reference. One of the properties we want from TimerHelper is for the + // timer to be canceled when it goes out of scope. + void Notify() { + MonitorAutoLock lock(mMonitor); + EXPECT_TRUE(mTarget->IsOnCurrentThread()); + TimeDuration elapsed = TimeStamp::Now() - mStart; + mStart = TimeStamp::Now(); + mLastDelay = Some(elapsed.ToMilliseconds()); + if (mBlockTime) { + PR_Sleep(mBlockTime); + } + mMonitor.Notify(); + } + + nsresult SetTimer(uint32_t aDelay, uint8_t aType) { + Cancel(); + MonitorAutoLock lock(mMonitor); + mStart = TimeStamp::Now(); + return mTimer->InitWithNamedFuncCallback( + ClosureCallback, this, aDelay, aType, "TimerHelper::ClosureCallback"); + } + + Maybe Wait(uint32_t aLimitMs) { + return WaitAndBlockCallback(aLimitMs, 0); + } + + // Waits for callback, and if it occurs within the limit, causes the callback + // to block for the specified time. Useful for testing cases where the + // callback takes a long time to return. + Maybe WaitAndBlockCallback(uint32_t aLimitMs, uint32_t aBlockTime) { + MonitorAutoLock lock(mMonitor); + mBlockTime = aBlockTime; + TimeStamp start = TimeStamp::Now(); + while (!mLastDelay.isSome()) { + mMonitor.Wait(TimeDuration::FromMilliseconds(aLimitMs)); + TimeDuration elapsed = TimeStamp::Now() - start; + uint32_t elapsedMs = static_cast(elapsed.ToMilliseconds()); + if (elapsedMs >= aLimitMs) { + break; + } + aLimitMs -= elapsedMs; + start = TimeStamp::Now(); + } + mBlockTime = 0; + return std::move(mLastDelay); + } + + void Cancel() { + NS_DispatchAndSpinEventLoopUntilComplete( + "~TimerHelper timer cancel"_ns, mTarget, + NS_NewRunnableFunction("~TimerHelper timer cancel", [this] { + MonitorAutoLock lock(mMonitor); + mTimer->Cancel(); + })); + } + + private: + TimeStamp mStart; + RefPtr mTimer; + mutable Monitor mMonitor MOZ_UNANNOTATED; + uint32_t mBlockTime = 0; + Maybe mLastDelay; + RefPtr mTarget; +}; + +class SimpleTimerTest : public ::testing::Test { + public: + std::unique_ptr MakeTimer(uint32_t aDelay, uint8_t aType) { + std::unique_ptr timer(new TimerHelper(mThread)); + timer->SetTimer(aDelay, aType); + return timer; + } + + void PauseTimerThread() { + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + observerService->NotifyObservers(nullptr, "sleep_notification", nullptr); + } + + void ResumeTimerThread() { + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + observerService->NotifyObservers(nullptr, "wake_notification", nullptr); + } + + protected: + AutoTestThread mThread; +}; + +#ifdef XP_MACOSX +// For some reason, our OS X testers fire timed condition waits _extremely_ +// late (as much as 200ms). +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1726915 +const unsigned kSlowdownFactor = 50; +#elif XP_WIN +// Windows also needs some extra leniency, but not nearly as much as our OS X +// testers +// See https://bugzilla.mozilla.org/show_bug.cgi?id=1729035 +const unsigned kSlowdownFactor = 5; +#else +const unsigned kSlowdownFactor = 1; +#endif + +TEST_F(SimpleTimerTest, OneShot) { + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT); + auto res = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(res.isSome()); + ASSERT_LT(*res, 110U * kSlowdownFactor); + ASSERT_GT(*res, 95U * kSlowdownFactor); +} + +TEST_F(SimpleTimerTest, TimerWithStoppedTarget) { + mThread->Shutdown(); + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT); + auto res = timer->Wait(110 * kSlowdownFactor); + ASSERT_FALSE(res.isSome()); +} + +TEST_F(SimpleTimerTest, SlackRepeating) { + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_REPEATING_SLACK); + auto delay = + timer->WaitAndBlockCallback(110 * kSlowdownFactor, 50 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + // REPEATING_SLACK timers re-schedule with the full duration when the timer + // callback completes + + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 160U * kSlowdownFactor); + ASSERT_GT(*delay, 145U * kSlowdownFactor); +} + +TEST_F(SimpleTimerTest, RepeatingPrecise) { + auto timer = MakeTimer(100 * kSlowdownFactor, + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); + auto delay = + timer->WaitAndBlockCallback(110 * kSlowdownFactor, 50 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + // Delays smaller than the timer's period do not effect the period. + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + // Delays larger than the timer's period should result in the skipping of + // firings, but the cadence should remain the same. + delay = + timer->WaitAndBlockCallback(110 * kSlowdownFactor, 150 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 210U * kSlowdownFactor); + ASSERT_GT(*delay, 195U * kSlowdownFactor); +} + +// gtest on 32bit Win7 debug build is unstable and somehow this test +// makes it even worse. +#if !defined(XP_WIN) || !defined(DEBUG) || defined(HAVE_64BIT_BUILD) + +class FindExpirationTimeState final { + public: + // We'll offset the timers 10 seconds into the future to assure that they + // won't fire + const uint32_t kTimerOffset = 10 * 1000; + // And we'll set the timers spaced by 5 seconds. + const uint32_t kTimerInterval = 5 * 1000; + // We'll use 20 timers + const uint32_t kNumTimers = 20; + + TimeStamp mBefore; + TimeStamp mMiddle; + + std::list> mTimers; + + ~FindExpirationTimeState() { + while (!mTimers.empty()) { + nsCOMPtr t = mTimers.front().get(); + mTimers.pop_front(); + t->Cancel(); + } + } + + // Create timers, with aNumLowPriority low priority timers first in the queue + void InitTimers(uint32_t aNumLowPriority, uint32_t aType) { + // aType is just for readability. + MOZ_ASSERT(aType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + InitTimers(aNumLowPriority, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, nullptr); + } + + // Create timers, with aNumDifferentTarget timers with target aTarget first in + // the queue + void InitTimers(uint32_t aNumDifferentTarget, nsIEventTarget* aTarget) { + InitTimers(aNumDifferentTarget, nsITimer::TYPE_ONE_SHOT, aTarget); + } + + void InitTimers(uint32_t aNumDifferingTimers, uint32_t aType, + nsIEventTarget* aTarget) { + do { + TimeStamp clearUntil = + TimeStamp::Now() + TimeDuration::FromMilliseconds( + kTimerOffset + kNumTimers * kTimerInterval); + + // NS_GetTimerDeadlineHintOnCurrentThread returns clearUntil if there are + // no pending timers before clearUntil. + // Passing 0 ensures that we examine the next timer to fire, regardless + // of its thread target. This is important, because lots of the checks + // we perform in the test get confused by timers targeted at other + // threads. + TimeStamp t = NS_GetTimerDeadlineHintOnCurrentThread(clearUntil, 0); + if (t >= clearUntil) { + break; + } + + // Clear whatever random timers there might be pending. + uint32_t waitTime = 10; + if (t > TimeStamp::Now()) { + waitTime = uint32_t((t - TimeStamp::Now()).ToMilliseconds()); + } + PR_Sleep(PR_MillisecondsToInterval(waitTime)); + } while (true); + + mBefore = TimeStamp::Now(); + mMiddle = mBefore + TimeDuration::FromMilliseconds( + kTimerOffset + kTimerInterval * kNumTimers / 2); + for (uint32_t i = 0; i < kNumTimers; ++i) { + nsCOMPtr timer = NS_NewTimer(); + ASSERT_TRUE(timer); + + if (i < aNumDifferingTimers) { + if (aTarget) { + timer->SetTarget(aTarget); + } + + timer->InitWithNamedFuncCallback( + &UnusedCallbackFunc, nullptr, kTimerOffset + kTimerInterval * i, + aType, "FindExpirationTimeState::InitTimers"); + } else { + timer->InitWithNamedFuncCallback( + &UnusedCallbackFunc, nullptr, kTimerOffset + kTimerInterval * i, + nsITimer::TYPE_ONE_SHOT, "FindExpirationTimeState::InitTimers"); + } + mTimers.push_front(timer.get()); + } + } + + static void UnusedCallbackFunc(nsITimer* aTimer, void* aClosure) { + FAIL() << "Timer shouldn't fire."; + } +}; + +TEST_F(SimpleTimerTest, FindExpirationTime) { + { + FindExpirationTimeState state; + // 0 low priority timers + state.InitTimers(0, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + } + + { + FindExpirationTimeState state; + // 5 low priority timers + state.InitTimers(5, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + } + + { + FindExpirationTimeState state; + // 15 low priority timers + state.InitTimers(15, nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, middle) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, middle) << "Found time should be equal to default"; + } + + { + AutoTestThread testThread; + FindExpirationTimeState state; + // 5 other targets + state.InitTimers(5, static_cast(testThread)); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + } + + { + AutoTestThread testThread; + FindExpirationTimeState state; + // 15 other targets + state.InitTimers(15, static_cast(testThread)); + TimeStamp before = state.mBefore; + TimeStamp middle = state.mMiddle; + + TimeStamp t; + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(before, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, before) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 0); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_LT(t, middle) << "Found time should be less than default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 10); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, middle) << "Found time should be equal to default"; + + t = NS_GetTimerDeadlineHintOnCurrentThread(middle, 20); + EXPECT_TRUE(t) << "We should find a time"; + EXPECT_EQ(t, middle) << "Found time should be equal to default"; + } +} + +#endif + +// Do these _after_ FindExpirationTime; apparently pausing the timer thread +// schedules minute-long timers, which FindExpirationTime waits out before +// starting. +TEST_F(SimpleTimerTest, SleepWakeOneShot) { + if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { + return; + } + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_ONE_SHOT); + PauseTimerThread(); + auto delay = timer->Wait(200 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); +} + +TEST_F(SimpleTimerTest, SleepWakeRepeatingSlack) { + if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { + return; + } + auto timer = MakeTimer(100 * kSlowdownFactor, nsITimer::TYPE_REPEATING_SLACK); + PauseTimerThread(); + auto delay = timer->Wait(200 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); + + // Timer thread slept for ~200ms, longer than the duration of the timer, so + // it should fire pretty much immediately. + delay = timer->Wait(10 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 210 * kSlowdownFactor); + ASSERT_GT(*delay, 199 * kSlowdownFactor); + + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + PauseTimerThread(); + delay = timer->Wait(50 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); + + // Timer thread only slept for ~50 ms, shorter than the duration of the + // timer, so there should be no effect on the timing. + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); +} + +TEST_F(SimpleTimerTest, SleepWakeRepeatingPrecise) { + if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { + return; + } + auto timer = MakeTimer(100 * kSlowdownFactor, + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); + PauseTimerThread(); + auto delay = timer->Wait(350 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); + + // Timer thread slept longer than the duration of the timer, so it should + // fire pretty much immediately. + delay = timer->Wait(10 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 360U * kSlowdownFactor); + ASSERT_GT(*delay, 349U * kSlowdownFactor); + + // After that, we should get back on our original cadence + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 60U * kSlowdownFactor); + ASSERT_GT(*delay, 45U * kSlowdownFactor); + + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); + + PauseTimerThread(); + delay = timer->Wait(50 * kSlowdownFactor); + ResumeTimerThread(); + ASSERT_FALSE(delay.isSome()); + + // Timer thread only slept for ~50 ms, shorter than the duration of the + // timer, so there should be no effect on the timing. + delay = timer->Wait(110 * kSlowdownFactor); + ASSERT_TRUE(delay.isSome()); + ASSERT_LT(*delay, 110U * kSlowdownFactor); + ASSERT_GT(*delay, 95U * kSlowdownFactor); +} + +#define FUZZ_MAX_TIMEOUT 9 +class FuzzTestThreadState final : public nsITimerCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit FuzzTestThreadState(nsIThread* thread) + : mThread(thread), mStopped(false) {} + + class StartRunnable final : public mozilla::Runnable { + public: + explicit StartRunnable(FuzzTestThreadState* threadState) + : mozilla::Runnable("FuzzTestThreadState::StartRunnable"), + mThreadState(threadState) {} + + NS_IMETHOD Run() override { + mThreadState->ScheduleOrCancelTimers(); + return NS_OK; + } + + private: + RefPtr mThreadState; + }; + + void Start() { + nsCOMPtr runnable = new StartRunnable(this); + nsresult rv = mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to dispatch StartRunnable."); + } + + void Stop() { mStopped = true; } + + NS_IMETHOD Notify(nsITimer* aTimer) override { + bool onCorrectThread; + nsresult rv = mThread->IsOnCurrentThread(&onCorrectThread); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to perform thread check."); + MOZ_RELEASE_ASSERT(onCorrectThread, "Notify invoked on wrong thread."); + + uint32_t delay; + rv = aTimer->GetDelay(&delay); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "GetDelay failed."); + + MOZ_RELEASE_ASSERT(delay <= FUZZ_MAX_TIMEOUT, + "Delay was an invalid value for this test."); + + uint32_t type; + rv = aTimer->GetType(&type); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to get timer type."); + MOZ_RELEASE_ASSERT(type <= nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP); + + if (type == nsITimer::TYPE_ONE_SHOT) { + MOZ_RELEASE_ASSERT(!mOneShotTimersByDelay[delay].empty(), + "Unexpected one-shot timer."); + + MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[delay].front().get() == aTimer, + "One-shot timers have been reordered."); + + mOneShotTimersByDelay[delay].pop_front(); + --mTimersOutstanding; + } else if (mStopped) { + CancelRepeatingTimer(aTimer); + } + + ScheduleOrCancelTimers(); + RescheduleSomeTimers(); + return NS_OK; + } + + bool HasTimersOutstanding() const { return !!mTimersOutstanding; } + + private: + ~FuzzTestThreadState() { + for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { + MOZ_RELEASE_ASSERT(mOneShotTimersByDelay[i].empty(), + "Timers remain at end of test."); + } + } + + uint32_t GetRandomType() const { + return rand() % (nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP + 1); + } + + size_t CountOneShotTimers() const { + size_t count = 0; + for (size_t i = 0; i <= FUZZ_MAX_TIMEOUT; ++i) { + count += mOneShotTimersByDelay[i].size(); + } + return count; + } + + void ScheduleOrCancelTimers() { + if (mStopped) { + return; + } + + const size_t numTimersDesired = (rand() % 100) + 100; + MOZ_RELEASE_ASSERT(numTimersDesired >= 100); + MOZ_RELEASE_ASSERT(numTimersDesired < 200); + int adjustment = numTimersDesired - mTimersOutstanding; + + while (adjustment > 0) { + CreateRandomTimer(); + --adjustment; + } + + while (adjustment < 0) { + CancelRandomTimer(); + ++adjustment; + } + + MOZ_RELEASE_ASSERT(numTimersDesired == mTimersOutstanding); + } + + void RescheduleSomeTimers() { + if (mStopped) { + return; + } + + static const size_t kNumRescheduled = 40; + + // Reschedule some timers with a Cancel first. + for (size_t i = 0; i < kNumRescheduled; ++i) { + InitRandomTimer(CancelRandomTimer().get()); + } + // Reschedule some timers without a Cancel first. + for (size_t i = 0; i < kNumRescheduled; ++i) { + InitRandomTimer(RemoveRandomTimer().get()); + } + } + + void CreateRandomTimer() { + nsCOMPtr timer = + NS_NewTimer(static_cast(mThread.get())); + MOZ_RELEASE_ASSERT(timer, "Failed to create timer."); + + InitRandomTimer(timer.get()); + } + + nsCOMPtr CancelRandomTimer() { + nsCOMPtr timer(RemoveRandomTimer()); + timer->Cancel(); + return timer; + } + + nsCOMPtr RemoveRandomTimer() { + MOZ_RELEASE_ASSERT(mTimersOutstanding); + + if ((GetRandomType() == nsITimer::TYPE_ONE_SHOT && CountOneShotTimers()) || + mRepeatingTimers.empty()) { + uint32_t delayToRemove = rand() % (FUZZ_MAX_TIMEOUT + 1); + while (mOneShotTimersByDelay[delayToRemove].empty()) { + // ++delayToRemove mod FUZZ_MAX_TIMEOUT + 1 + delayToRemove = (delayToRemove + 1) % (FUZZ_MAX_TIMEOUT + 1); + } + + uint32_t indexToRemove = + rand() % mOneShotTimersByDelay[delayToRemove].size(); + + for (auto it = mOneShotTimersByDelay[delayToRemove].begin(); + it != mOneShotTimersByDelay[delayToRemove].end(); ++it) { + if (indexToRemove) { + --indexToRemove; + continue; + } + + nsCOMPtr removed = *it; + mOneShotTimersByDelay[delayToRemove].erase(it); + --mTimersOutstanding; + return removed; + } + } else { + size_t indexToRemove = rand() % mRepeatingTimers.size(); + nsCOMPtr removed(mRepeatingTimers[indexToRemove]); + mRepeatingTimers.erase(mRepeatingTimers.begin() + indexToRemove); + --mTimersOutstanding; + return removed; + } + + MOZ_CRASH("Unable to remove a timer"); + } + + void InitRandomTimer(nsITimer* aTimer) { + // Between 0 and FUZZ_MAX_TIMEOUT + uint32_t delay = rand() % (FUZZ_MAX_TIMEOUT + 1); + uint32_t type = GetRandomType(); + nsresult rv = aTimer->InitWithCallback(this, delay, type); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv), "Failed to set timer."); + + if (type == nsITimer::TYPE_ONE_SHOT) { + mOneShotTimersByDelay[delay].push_back(aTimer); + } else { + mRepeatingTimers.push_back(aTimer); + } + ++mTimersOutstanding; + } + + void CancelRepeatingTimer(nsITimer* aTimer) { + for (auto it = mRepeatingTimers.begin(); it != mRepeatingTimers.end(); + ++it) { + if (it->get() == aTimer) { + mRepeatingTimers.erase(it); + aTimer->Cancel(); + --mTimersOutstanding; + return; + } + } + } + + nsCOMPtr mThread; + // Scheduled timers, indexed by delay between 0-9 ms, in lists + // with most recently scheduled last. + std::list> mOneShotTimersByDelay[FUZZ_MAX_TIMEOUT + 1]; + std::vector> mRepeatingTimers; + Atomic mStopped; + Atomic mTimersOutstanding; +}; + +NS_IMPL_ISUPPORTS(FuzzTestThreadState, nsITimerCallback) + +TEST(Timers, FuzzTestTimers) +{ + static const size_t kNumThreads(10); + AutoTestThread threads[kNumThreads]; + RefPtr threadStates[kNumThreads]; + + for (size_t i = 0; i < kNumThreads; ++i) { + threadStates[i] = new FuzzTestThreadState(&*threads[i]); + threadStates[i]->Start(); + } + + PR_Sleep(PR_MillisecondsToInterval(20000)); + + for (size_t i = 0; i < kNumThreads; ++i) { + threadStates[i]->Stop(); + } + + // Wait at most 10 seconds for all outstanding timers to pop + PRIntervalTime start = PR_IntervalNow(); + for (auto& threadState : threadStates) { + while (threadState->HasTimersOutstanding()) { + uint32_t elapsedMs = PR_IntervalToMilliseconds(PR_IntervalNow() - start); + ASSERT_LE(elapsedMs, uint32_t(10000)) + << "Timed out waiting for all timers to pop"; + PR_Sleep(PR_MillisecondsToInterval(10)); + } + } +} + +TEST(Timers, ClosureCallback) +{ + AutoCreateAndDestroyReentrantMonitor newMon; + ASSERT_TRUE(newMon); + + AutoTestThread testThread; + ASSERT_TRUE(testThread); + + nsIThread* notifiedThread = nullptr; + + nsCOMPtr timer; + nsresult rv = NS_NewTimerWithCallback( + getter_AddRefs(timer), + [&](nsITimer*) { + nsCOMPtr current(do_GetCurrentThread()); + + ReentrantMonitorAutoEnter mon(*newMon); + ASSERT_FALSE(notifiedThread); + notifiedThread = current; + mon.Notify(); + }, + 50, nsITimer::TYPE_ONE_SHOT, "(test) Timers.ClosureCallback", testThread); + ASSERT_NS_SUCCEEDED(rv); + + ReentrantMonitorAutoEnter mon(*newMon); + while (!notifiedThread) { + mon.Wait(); + } + ASSERT_EQ(notifiedThread, testThread); +} + +static void SetTime(nsITimer* aTimer, void* aClosure) { + *static_cast(aClosure) = TimeStamp::Now(); +} + +TEST(Timers, HighResFuncCallback) +{ + TimeStamp first; + TimeStamp second; + TimeStamp third; + nsCOMPtr t1 = NS_NewTimer(GetCurrentSerialEventTarget()); + nsCOMPtr t2 = NS_NewTimer(GetCurrentSerialEventTarget()); + nsCOMPtr t3 = NS_NewTimer(GetCurrentSerialEventTarget()); + + // Reverse order, since if the timers are not high-res we'd end up + // out-of-order. + MOZ_ALWAYS_SUCCEEDS(t3->InitHighResolutionWithNamedFuncCallback( + &SetTime, &third, TimeDuration::FromMicroseconds(300), + nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::third")); + MOZ_ALWAYS_SUCCEEDS(t2->InitHighResolutionWithNamedFuncCallback( + &SetTime, &second, TimeDuration::FromMicroseconds(200), + nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::second")); + MOZ_ALWAYS_SUCCEEDS(t1->InitHighResolutionWithNamedFuncCallback( + &SetTime, &first, TimeDuration::FromMicroseconds(100), + nsITimer::TYPE_ONE_SHOT, "TestTimers::HighResFuncCallback::first")); + + SpinEventLoopUntil( + "TestTimers::HighResFuncCallback"_ns, + [&] { return !first.IsNull() && !second.IsNull() && !third.IsNull(); }); +} diff --git a/xpcom/tests/gtest/TestTokenizer.cpp b/xpcom/tests/gtest/TestTokenizer.cpp new file mode 100644 index 0000000000..1457b82fff --- /dev/null +++ b/xpcom/tests/gtest/TestTokenizer.cpp @@ -0,0 +1,1447 @@ +/* -*- 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/Tokenizer.h" +#include "mozilla/IncrementalTokenizer.h" +#include "mozilla/Unused.h" +#include "gtest/gtest.h" + +using namespace mozilla; + +template +static bool IsOperator(Char const c) { + return c == '+' || c == '*'; +} + +static bool HttpHeaderCharacter(char const c) { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || (c == '_') || (c == '-'); +} + +TEST(Tokenizer, HTTPResponse) +{ + Tokenizer::Token t; + + // Real life test, HTTP response + + Tokenizer p( + nsLiteralCString("HTTP/1.0 304 Not modified\r\n" + "ETag: hallo\r\n" + "Content-Length: 16\r\n" + "\r\n" + "This is the body")); + + EXPECT_TRUE(p.CheckWord("HTTP")); + EXPECT_TRUE(p.CheckChar('/')); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 1); + EXPECT_TRUE(p.CheckChar('.')); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 0); + p.SkipWhites(); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 304); + p.SkipWhites(); + + p.Record(); + while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL) { + ; + } + EXPECT_FALSE(p.HasFailed()); + nsAutoCString h; + p.Claim(h); + EXPECT_TRUE(h == "Not modified"); + + p.Record(); + while (p.CheckChar(HttpHeaderCharacter)) { + ; + } + p.Claim(h, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(h == "ETag"); + p.SkipWhites(); + EXPECT_TRUE(p.CheckChar(':')); + p.SkipWhites(); + p.Record(); + while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOL) { + ; + } + EXPECT_FALSE(p.HasFailed()); + p.Claim(h); + EXPECT_TRUE(h == "hallo"); + + p.Record(); + while (p.CheckChar(HttpHeaderCharacter)) { + ; + } + p.Claim(h, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(h == "Content-Length"); + p.SkipWhites(); + EXPECT_TRUE(p.CheckChar(':')); + p.SkipWhites(); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + EXPECT_TRUE(t.AsInteger() == 16); + EXPECT_TRUE(p.CheckEOL()); + + EXPECT_TRUE(p.CheckEOL()); + + p.Record(); + while (p.Next(t) && t.Type() != Tokenizer::TOKEN_EOF) { + ; + } + nsAutoCString b; + p.Claim(b); + EXPECT_TRUE(b == "This is the body"); +} + +TEST(Tokenizer, Main) +{ + Tokenizer::Token t; + + // Synthetic code-specific test + + Tokenizer p("test123 ,15 \t*\r\n%xx,-15\r\r"_ns); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_WORD); + EXPECT_TRUE(t.AsString() == "test123"); + + Tokenizer::Token u; + EXPECT_FALSE(p.Check(u)); + + EXPECT_FALSE(p.CheckChar('!')); + + EXPECT_FALSE(p.Check(Tokenizer::Token::Number(123))); + + EXPECT_TRUE(p.CheckWhite()); + + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_FALSE(p.CheckChar(IsOperator)); + + EXPECT_TRUE(p.CheckWhite()); + + p.SkipWhites(); + + EXPECT_FALSE(p.CheckWhite()); + + p.Rollback(); + + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + + p.Record(Tokenizer::EXCLUDE_LAST); + + EXPECT_TRUE(p.CheckChar(IsOperator)); + + p.Rollback(); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '*'); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '%'); + + nsAutoCString claim; + p.Claim(claim, Tokenizer::EXCLUDE_LAST); + EXPECT_TRUE(claim == "*\r\n"); + p.Claim(claim, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(claim == "*\r\n%"); + + p.Rollback(); + EXPECT_TRUE(p.CheckChar('%')); + + p.Record(Tokenizer::INCLUDE_LAST); + + EXPECT_FALSE(p.CheckWord("xy")); + + EXPECT_TRUE(p.CheckWord("xx")); + + p.Claim(claim, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(claim == "%xx"); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == ','); + + EXPECT_TRUE(p.CheckChar('-')); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.CheckEOF()); + + EXPECT_FALSE(p.CheckEOF()); +} + +TEST(Tokenizer, Main16) +{ + Tokenizer16::Token t; + + // Synthetic code-specific test + + Tokenizer16 p(u"test123 ,15 \t*\r\n%xx,-15\r\r"_ns); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_WORD); + EXPECT_TRUE(t.AsString() == u"test123"_ns); + + Tokenizer16::Token u; + EXPECT_FALSE(p.Check(u)); + + EXPECT_FALSE(p.CheckChar('!')); + + EXPECT_FALSE(p.Check(Tokenizer16::Token::Number(123))); + + EXPECT_TRUE(p.CheckWhite()); + + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(Tokenizer16::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Check(Tokenizer16::Token::Number(15))); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_FALSE(p.CheckChar(IsOperator)); + + EXPECT_TRUE(p.CheckWhite()); + + p.SkipWhites(); + + EXPECT_FALSE(p.CheckWhite()); + + p.Rollback(); + + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + + p.Record(Tokenizer16::EXCLUDE_LAST); + + EXPECT_TRUE(p.CheckChar(IsOperator)); + + p.Rollback(); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '*'); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == '%'); + + nsAutoString claim; + p.Claim(claim, Tokenizer16::EXCLUDE_LAST); + EXPECT_TRUE(claim == u"*\r\n"_ns); + p.Claim(claim, Tokenizer16::INCLUDE_LAST); + EXPECT_TRUE(claim == u"*\r\n%"_ns); + + p.Rollback(); + EXPECT_TRUE(p.CheckChar('%')); + + p.Record(Tokenizer16::INCLUDE_LAST); + + EXPECT_FALSE(p.CheckWord(u"xy"_ns)); + + EXPECT_TRUE(p.CheckWord(u"xx"_ns)); + + p.Claim(claim, Tokenizer16::INCLUDE_LAST); + EXPECT_TRUE(claim == u"%xx"_ns); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_CHAR); + EXPECT_TRUE(t.AsChar() == ','); + + EXPECT_TRUE(p.CheckChar('-')); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_INTEGER); + EXPECT_TRUE(t.AsInteger() == 15); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOL); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer16::TOKEN_EOF); + + EXPECT_FALSE(p.Next(t)); + + p.Rollback(); + EXPECT_TRUE(p.CheckEOF()); + + EXPECT_FALSE(p.CheckEOF()); +} + +TEST(Tokenizer, SingleWord) +{ + // Single word with numbers in it test + + Tokenizer p("test123"_ns); + + EXPECT_TRUE(p.CheckWord("test123")); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, EndingAfterNumber) +{ + // An end handling after a number + + Tokenizer p("123"_ns); + + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(123))); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, BadInteger) +{ + Tokenizer::Token t; + + // A bad integer test + + Tokenizer p("189234891274981758617846178651647620587135"_ns); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_ERROR); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, CheckExpectedTokenValue) +{ + Tokenizer::Token t; + + // Check expected token value test + + Tokenizer p("blue velvet"_ns); + + EXPECT_FALSE(p.Check(Tokenizer::TOKEN_INTEGER, t)); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t)); + EXPECT_TRUE(t.AsString() == "blue"); + + EXPECT_FALSE(p.Check(Tokenizer::TOKEN_WORD, t)); + + EXPECT_TRUE(p.CheckWhite()); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t)); + EXPECT_TRUE(t.AsString() == "velvet"); + + EXPECT_TRUE(p.CheckEOF()); + + EXPECT_FALSE(p.Next(t)); +} + +TEST(Tokenizer, HasFailed) +{ + Tokenizer::Token t; + + // HasFailed test + + Tokenizer p1("a b"_ns); + + while (p1.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR) { + ; + } + EXPECT_TRUE(p1.HasFailed()); + + Tokenizer p2("a b ?!c"_ns); + + EXPECT_FALSE(p2.CheckChar('c')); + EXPECT_TRUE(p2.HasFailed()); + EXPECT_TRUE(p2.CheckChar(HttpHeaderCharacter)); + EXPECT_FALSE(p2.HasFailed()); + p2.SkipWhites(); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_FALSE(p2.CheckChar('c')); + EXPECT_TRUE(p2.HasFailed()); + EXPECT_TRUE(p2.Next(t)); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_TRUE(p2.Next(t)); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_FALSE(p2.CheckChar('c')); + EXPECT_TRUE(p2.HasFailed()); + EXPECT_TRUE(p2.Check(Tokenizer::TOKEN_CHAR, t)); + EXPECT_FALSE(p2.HasFailed()); + EXPECT_FALSE(p2.CheckChar('#')); + EXPECT_TRUE(p2.HasFailed()); + t = Tokenizer::Token::Char('!'); + EXPECT_TRUE(p2.Check(t)); + EXPECT_FALSE(p2.HasFailed()); + + while (p2.Next(t) && t.Type() != Tokenizer::TOKEN_CHAR) { + ; + } + EXPECT_TRUE(p2.HasFailed()); +} + +TEST(Tokenizer, Construction) +{ + { + nsCString a("test"); + Tokenizer p1(a); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + nsAutoCString a("test"); + Tokenizer p1(a); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + static const char _a[] = "test"; + nsDependentCString a(_a); + Tokenizer p1(a); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + static const char* _a = "test"; + nsDependentCString a(_a); + Tokenizer p1(a); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + Tokenizer p1(nsDependentCString("test")); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + Tokenizer p1("test"_ns); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } + + { + Tokenizer p1("test"); + EXPECT_TRUE(p1.CheckWord("test")); + EXPECT_TRUE(p1.CheckEOF()); + } +} + +TEST(Tokenizer, Customization) +{ + Tokenizer p1("test-custom*words and\tdefault-whites"_ns, nullptr, "-*"); + EXPECT_TRUE(p1.CheckWord("test-custom*words")); + EXPECT_TRUE(p1.CheckWhite()); + EXPECT_TRUE(p1.CheckWord("and")); + EXPECT_TRUE(p1.CheckWhite()); + EXPECT_TRUE(p1.CheckWord("default-whites")); + + Tokenizer p2("test, custom,whites"_ns, ", "); + EXPECT_TRUE(p2.CheckWord("test")); + EXPECT_TRUE(p2.CheckWhite()); + EXPECT_TRUE(p2.CheckWhite()); + EXPECT_TRUE(p2.CheckWord("custom")); + EXPECT_TRUE(p2.CheckWhite()); + EXPECT_TRUE(p2.CheckWord("whites")); + + Tokenizer p3("test, custom, whites-and#word-chars"_ns, ",", "-#"); + EXPECT_TRUE(p3.CheckWord("test")); + EXPECT_TRUE(p3.CheckWhite()); + EXPECT_FALSE(p3.CheckWhite()); + EXPECT_TRUE(p3.CheckChar(' ')); + EXPECT_TRUE(p3.CheckWord("custom")); + EXPECT_TRUE(p3.CheckWhite()); + EXPECT_FALSE(p3.CheckWhite()); + EXPECT_TRUE(p3.CheckChar(' ')); + EXPECT_TRUE(p3.CheckWord("whites-and#word-chars")); +} + +TEST(Tokenizer, ShortcutChecks) +{ + Tokenizer p("test1 test2,123"); + + nsAutoCString test1; + nsDependentCSubstring test2; + char comma; + uint32_t integer; + + EXPECT_TRUE(p.ReadWord(test1)); + EXPECT_TRUE(test1 == "test1"); + p.SkipWhites(); + EXPECT_TRUE(p.ReadWord(test2)); + EXPECT_TRUE(test2 == "test2"); + EXPECT_TRUE(p.ReadChar(&comma)); + EXPECT_TRUE(comma == ','); + EXPECT_TRUE(p.ReadInteger(&integer)); + EXPECT_TRUE(integer == 123); + EXPECT_TRUE(p.CheckEOF()); +} + +static bool ABChar(const char aChar) { return aChar == 'a' || aChar == 'b'; } + +TEST(Tokenizer, ReadCharClassified) +{ + Tokenizer p("abc"); + + char c; + EXPECT_TRUE(p.ReadChar(ABChar, &c)); + EXPECT_TRUE(c == 'a'); + EXPECT_TRUE(p.ReadChar(ABChar, &c)); + EXPECT_TRUE(c == 'b'); + EXPECT_FALSE(p.ReadChar(ABChar, &c)); + nsDependentCSubstring w; + EXPECT_TRUE(p.ReadWord(w)); + EXPECT_TRUE(w == "c"); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, ClaimSubstring) +{ + Tokenizer p(" abc "); + + EXPECT_TRUE(p.CheckWhite()); + + p.Record(); + EXPECT_TRUE(p.CheckWord("abc")); + nsDependentCSubstring v; + p.Claim(v, Tokenizer::INCLUDE_LAST); + EXPECT_TRUE(v == "abc"); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, Fragment) +{ + const char str[] = "ab;cd:10 "; + Tokenizer p(str); + nsDependentCSubstring f; + + Tokenizer::Token t1, t2; + + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_WORD); + EXPECT_TRUE(t1.Fragment() == "ab"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[0]); + + p.Rollback(); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t2)); + EXPECT_TRUE(t2.Fragment() == "ab"); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[0]); + + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t1.Fragment() == ";"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[2]); + + p.Rollback(); + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_CHAR, t2)); + EXPECT_TRUE(t2.Fragment() == ";"); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[2]); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WORD, t2)); + EXPECT_TRUE(t2.Fragment() == "cd"); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[3]); + + p.Rollback(); + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_WORD); + EXPECT_TRUE(t1.Fragment() == "cd"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[3]); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_CHAR, t2)); + EXPECT_TRUE(t2.Fragment() == ":"); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[5]); + + p.Rollback(); + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_CHAR); + EXPECT_TRUE(t1.Fragment() == ":"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[5]); + + EXPECT_TRUE(p.Next(t1)); + EXPECT_TRUE(t1.Type() == Tokenizer::TOKEN_INTEGER); + EXPECT_TRUE(t1.Fragment() == "10"); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[6]); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_WS, t2)); + EXPECT_TRUE(t2.Fragment() == " "); + EXPECT_TRUE(t2.Fragment().BeginReading() == &str[8]); + + EXPECT_TRUE(p.Check(Tokenizer::TOKEN_EOF, t1)); + EXPECT_TRUE(t1.Fragment() == ""); + EXPECT_TRUE(t1.Fragment().BeginReading() == &str[9]); +} + +TEST(Tokenizer, SkipWhites) +{ + Tokenizer p("Text1 \nText2 \nText3\n Text4\n "); + + EXPECT_TRUE(p.CheckWord("Text1")); + p.SkipWhites(); + EXPECT_TRUE(p.CheckEOL()); + + EXPECT_TRUE(p.CheckWord("Text2")); + p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE); + + EXPECT_TRUE(p.CheckWord("Text3")); + p.SkipWhites(); + EXPECT_TRUE(p.CheckEOL()); + p.SkipWhites(); + + EXPECT_TRUE(p.CheckWord("Text4")); + p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, SkipCustomWhites) +{ + Tokenizer p("Text1 \n\r\t.Text2 \n\r\t.", " \n\r\t."); + + EXPECT_TRUE(p.CheckWord("Text1")); + p.SkipWhites(); + EXPECT_TRUE(p.CheckWord("Text2")); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckWhite()); + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, IntegerReading) +{ +#define INT_6_BITS 64U +#define INT_30_BITS 1073741824UL +#define INT_32_BITS 4294967295UL +#define INT_50_BITS 1125899906842624ULL +#define STR_INT_MORE_THAN_64_BITS "922337203685477580899" + + { + Tokenizer p(MOZ_STRINGIFY(INT_6_BITS)); + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + EXPECT_TRUE(p.ReadInteger(&u8)); + EXPECT_TRUE(u8 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&u16)); + EXPECT_TRUE(u16 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&u32)); + EXPECT_TRUE(u32 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&u64)); + EXPECT_TRUE(u64 == INT_6_BITS); + + p.Rollback(); + + int8_t s8; + int16_t s16; + int32_t s32; + int64_t s64; + EXPECT_TRUE(p.ReadInteger(&s8)); + EXPECT_TRUE(s8 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&s16)); + EXPECT_TRUE(s16 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&s32)); + EXPECT_TRUE(s32 == INT_6_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&s64)); + EXPECT_TRUE(s64 == INT_6_BITS); + + EXPECT_TRUE(p.CheckWord("U")); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p(MOZ_STRINGIFY(INT_30_BITS)); + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + EXPECT_FALSE(p.ReadInteger(&u8)); + EXPECT_FALSE(p.ReadInteger(&u16)); + EXPECT_TRUE(p.ReadInteger(&u32)); + EXPECT_TRUE(u32 == INT_30_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&u64)); + EXPECT_TRUE(u64 == INT_30_BITS); + + p.Rollback(); + + int8_t s8; + int16_t s16; + int32_t s32; + int64_t s64; + EXPECT_FALSE(p.ReadInteger(&s8)); + EXPECT_FALSE(p.ReadInteger(&s16)); + EXPECT_TRUE(p.ReadInteger(&s32)); + EXPECT_TRUE(s32 == INT_30_BITS); + p.Rollback(); + EXPECT_TRUE(p.ReadInteger(&s64)); + EXPECT_TRUE(s64 == INT_30_BITS); + EXPECT_TRUE(p.CheckWord("UL")); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p(MOZ_STRINGIFY(INT_32_BITS)); + uint32_t u32; + int32_t s32; + EXPECT_FALSE(p.ReadInteger(&s32)); + EXPECT_TRUE(p.ReadInteger(&u32)); + EXPECT_TRUE(u32 == INT_32_BITS); + EXPECT_TRUE(p.CheckWord("UL")); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p(MOZ_STRINGIFY(INT_50_BITS)); + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + EXPECT_FALSE(p.ReadInteger(&u8)); + EXPECT_FALSE(p.ReadInteger(&u16)); + EXPECT_FALSE(p.ReadInteger(&u32)); + EXPECT_TRUE(p.ReadInteger(&u64)); + EXPECT_TRUE(u64 == INT_50_BITS); + EXPECT_TRUE(p.CheckWord("ULL")); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p(STR_INT_MORE_THAN_64_BITS); + int64_t i; + EXPECT_FALSE(p.ReadInteger(&i)); + uint64_t u; + EXPECT_FALSE(p.ReadInteger(&u)); + EXPECT_FALSE(p.CheckEOF()); + } +} + +TEST(Tokenizer, ReadUntil) +{ + Tokenizer p("Hello;test 4,"); + nsDependentCSubstring f; + EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(';'), f)); + EXPECT_TRUE(f == "Hello"); + p.Rollback(); + + EXPECT_TRUE( + p.ReadUntil(Tokenizer::Token::Char(';'), f, Tokenizer::INCLUDE_LAST)); + EXPECT_TRUE(f == "Hello;"); + p.Rollback(); + + EXPECT_FALSE(p.ReadUntil(Tokenizer::Token::Char('!'), f)); + EXPECT_TRUE(f == "Hello;test 4,"); + p.Rollback(); + + EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word("test"_ns), f)); + EXPECT_TRUE(f == "Hello;"); + p.Rollback(); + + EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Word("test"_ns), f, + Tokenizer::INCLUDE_LAST)); + EXPECT_TRUE(f == "Hello;test"); + EXPECT_TRUE(p.ReadUntil(Tokenizer::Token::Char(','), f)); + EXPECT_TRUE(f == " 4"); +} + +TEST(Tokenizer, SkipUntil) +{ + { + Tokenizer p("test1,test2,,,test3"); + + p.SkipUntil(Tokenizer::Token::Char(',')); + EXPECT_TRUE(p.CheckChar(',')); + EXPECT_TRUE(p.CheckWord("test2")); + + p.SkipUntil(Tokenizer::Token::Char(',')); // must not move + EXPECT_TRUE(p.CheckChar(',')); // check the first comma of the ',,,' string + + p.Rollback(); // moves cursor back to the first comma of the ',,,' string + + p.SkipUntil( + Tokenizer::Token::Char(',')); // must not move, we are on the ',' char + EXPECT_TRUE(p.CheckChar(',')); + EXPECT_TRUE(p.CheckChar(',')); + EXPECT_TRUE(p.CheckChar(',')); + EXPECT_TRUE(p.CheckWord("test3")); + p.Rollback(); + + p.SkipUntil(Tokenizer::Token::Char(',')); + EXPECT_TRUE(p.CheckEOF()); + } + + { + Tokenizer p("test0,test1,test2"); + + p.SkipUntil(Tokenizer::Token::Char(',')); + EXPECT_TRUE(p.CheckChar(',')); + + p.SkipUntil(Tokenizer::Token::Char(',')); + p.Rollback(); + + EXPECT_TRUE(p.CheckWord("test1")); + EXPECT_TRUE(p.CheckChar(',')); + + p.SkipUntil(Tokenizer::Token::Char(',')); + p.Rollback(); + + EXPECT_TRUE(p.CheckWord("test2")); + EXPECT_TRUE(p.CheckEOF()); + } +} + +TEST(Tokenizer, Custom) +{ + Tokenizer p( + "aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2"); + + Tokenizer::Token c1 = + p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE); + Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE); + + // It's expected to NOT FIND the custom token if it's not on an edge + // between other recognizable tokens. + EXPECT_TRUE(p.CheckWord("aaaaaacustom")); + EXPECT_TRUE(p.CheckChar('-')); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1))); + EXPECT_TRUE(p.CheckEOL()); + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(c1)); + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(c1)); + EXPECT_TRUE(p.CheckChar(',')); + + p.EnableCustomToken(c1, false); + EXPECT_TRUE(p.CheckWord("Custom")); + EXPECT_TRUE(p.CheckChar('-')); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(1))); + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(0))); + EXPECT_TRUE(p.Check(c2)); + EXPECT_TRUE(p.CheckWord("xxxx")); + EXPECT_TRUE(p.CheckChar(',')); + + EXPECT_TRUE(p.CheckWord("CUSTOM")); + EXPECT_TRUE(p.CheckChar('-')); + EXPECT_TRUE(p.Check(Tokenizer::Token::Number(2))); + + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, CustomRaw) +{ + Tokenizer p( + "aaaaaacustom-1\r,custom-1,Custom-1,Custom-1,00custom-2xxxx,CUSTOM-2"); + + Tokenizer::Token c1 = + p.AddCustomToken("custom-1", Tokenizer::CASE_INSENSITIVE); + Tokenizer::Token c2 = p.AddCustomToken("custom-2", Tokenizer::CASE_SENSITIVE); + + // In this mode it's expected to find all custom tokens among any kind of + // input. + p.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + + Tokenizer::Token t; + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral("aaaaaa")); + + EXPECT_TRUE(p.Check(c1)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral("\r,")); + + EXPECT_TRUE(p.Check(c1)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral(",")); + + EXPECT_TRUE(p.Check(c1)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral(",")); + + EXPECT_TRUE(p.Check(c1)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral(",00")); + + EXPECT_TRUE(p.Check(c2)); + + EXPECT_TRUE(p.Next(t)); + EXPECT_TRUE(t.Type() == Tokenizer::TOKEN_RAW); + EXPECT_TRUE(t.Fragment().EqualsLiteral("xxxx,CUSTOM-2")); + + EXPECT_TRUE(p.CheckEOF()); +} + +TEST(Tokenizer, Incremental) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(Token::Word("test1"_ns))); + break; + case 2: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 3: + EXPECT_TRUE(t.Equals(Token::Word("test2"_ns))); + break; + case 4: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 5: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 6: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 7: + EXPECT_TRUE(t.Equals(Token::Word("test3"_ns))); + break; + case 8: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }); + + constexpr auto input = "test1,test2,,,test3"_ns; + const auto* cur = input.BeginReading(); + const auto* end = input.EndReading(); + for (; cur < end; ++cur) { + i.FeedInput(nsDependentCSubstring(cur, 1)); + } + + EXPECT_TRUE(test == 6); + i.FinishInput(); + EXPECT_TRUE(test == 8); +} + +TEST(Tokenizer, IncrementalRollback) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(Token::Word("test1"_ns))); + break; + case 2: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 3: + EXPECT_TRUE(t.Equals(Token::Word("test2"_ns))); + i.Rollback(); // so that we get the token again + break; + case 4: + EXPECT_TRUE(t.Equals(Token::Word("test2"_ns))); + break; + case 5: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 6: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 7: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 8: + EXPECT_TRUE(t.Equals(Token::Word("test3"_ns))); + break; + case 9: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }); + + constexpr auto input = "test1,test2,,,test3"_ns; + const auto* cur = input.BeginReading(); + const auto* end = input.EndReading(); + for (; cur < end; ++cur) { + i.FeedInput(nsDependentCSubstring(cur, 1)); + } + + EXPECT_TRUE(test == 7); + i.FinishInput(); + EXPECT_TRUE(test == 9); +} + +TEST(Tokenizer, IncrementalNeedMoreInput) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + Token t2; + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(Token::Word("a"_ns))); + break; + case 2: + case 3: + case 4: + case 5: + EXPECT_TRUE(t.Equals(Token::Whitespace())); + if (i.Next(t2)) { + EXPECT_TRUE(test == 5); + EXPECT_TRUE(t2.Equals(Token::Word("bb"_ns))); + } else { + EXPECT_TRUE(test < 5); + i.NeedMoreInput(); + } + break; + case 6: + EXPECT_TRUE(t.Equals(Token::Char(','))); + break; + case 7: + EXPECT_TRUE(t.Equals(Token::Word("c"_ns))); + return NS_ERROR_FAILURE; + default: + EXPECT_TRUE(false); + break; + } + + return NS_OK; + }); + + constexpr auto input = "a bb,c"_ns; + const auto* cur = input.BeginReading(); + const auto* end = input.EndReading(); + + nsresult rv; + for (; cur < end; ++cur) { + rv = i.FeedInput(nsDependentCSubstring(cur, 1)); + if (NS_FAILED(rv)) { + break; + } + } + + EXPECT_TRUE(rv == NS_OK); + EXPECT_TRUE(test == 6); + + rv = i.FinishInput(); + EXPECT_TRUE(rv == NS_ERROR_FAILURE); + EXPECT_TRUE(test == 7); +} + +TEST(Tokenizer, IncrementalCustom) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(custom)); + break; + case 2: + EXPECT_TRUE(t.Equals(Token::Word("bla"_ns))); + break; + case 3: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }, + nullptr, "-"); + + custom = i.AddCustomToken("some-test", Tokenizer::CASE_SENSITIVE); + i.FeedInput("some-"_ns); + EXPECT_TRUE(test == 0); + i.FeedInput("tes"_ns); + EXPECT_TRUE(test == 0); + i.FeedInput("tbla"_ns); + EXPECT_TRUE(test == 1); + i.FinishInput(); + EXPECT_TRUE(test == 3); +} + +TEST(Tokenizer, IncrementalCustomRaw) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Fragment().EqualsLiteral("test1,")); + break; + case 2: + EXPECT_TRUE(t.Equals(custom)); + break; + case 3: + EXPECT_TRUE(t.Fragment().EqualsLiteral("!,,test3")); + i.Rollback(); + i.SetTokenizingMode(Tokenizer::Mode::FULL); + break; + case 4: + EXPECT_TRUE(t.Equals(Token::Char('!'))); + i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + break; + case 5: + EXPECT_TRUE(t.Fragment().EqualsLiteral(",,test3")); + break; + case 6: + EXPECT_TRUE(t.Equals(custom)); + break; + case 7: + EXPECT_TRUE(t.Fragment().EqualsLiteral("tes")); + break; + case 8: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }); + + custom = i.AddCustomToken("test2", Tokenizer::CASE_SENSITIVE); + i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + + constexpr auto input = "test1,test2!,,test3test2tes"_ns; + const auto* cur = input.BeginReading(); + const auto* end = input.EndReading(); + for (; cur < end; ++cur) { + i.FeedInput(nsDependentCSubstring(cur, 1)); + } + + EXPECT_TRUE(test == 6); + i.FinishInput(); + EXPECT_TRUE(test == 8); +} + +TEST(Tokenizer, IncrementalCustomRemove) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Equals(custom)); + i.RemoveCustomToken(custom); + break; + case 2: + EXPECT_FALSE(t.Equals(custom)); + break; + case 3: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + return NS_OK; + }); + + custom = i.AddCustomToken("custom1", Tokenizer::CASE_SENSITIVE); + + constexpr auto input = "custom1custom1"_ns; + i.FeedInput(input); + EXPECT_TRUE(test == 1); + i.FinishInput(); + EXPECT_TRUE(test == 3); +} + +TEST(Tokenizer, IncrementalBuffering1) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + nsDependentCSubstring observedFragment; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Fragment().EqualsLiteral("012")); + break; + case 2: + EXPECT_TRUE(t.Fragment().EqualsLiteral("3456789")); + break; + case 3: + EXPECT_TRUE(t.Equals(custom)); + break; + case 4: + EXPECT_TRUE(t.Fragment().EqualsLiteral("qwe")); + break; + case 5: + EXPECT_TRUE(t.Fragment().EqualsLiteral("rt")); + break; + case 6: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + + observedFragment.Rebind(t.Fragment().BeginReading(), + t.Fragment().Length()); + return NS_OK; + }, + nullptr, nullptr, 3); + + custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE); + // This externally unused token is added only to check the internal algorithm + // does work correctly as expected when there are two different length tokens. + Unused << i.AddCustomToken("bb", Tokenizer::CASE_SENSITIVE); + i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + + i.FeedInput("01234"_ns); + EXPECT_TRUE(test == 1); + EXPECT_TRUE(observedFragment.EqualsLiteral("012")); + + i.FeedInput("5"_ns); + EXPECT_TRUE(test == 1); + i.FeedInput("6789aa"_ns); + EXPECT_TRUE(test == 2); + EXPECT_TRUE(observedFragment.EqualsLiteral("3456789")); + + i.FeedInput("aqwert"_ns); + EXPECT_TRUE(test == 4); + EXPECT_TRUE(observedFragment.EqualsLiteral("qwe")); + + i.FinishInput(); + EXPECT_TRUE(test == 6); +} + +TEST(Tokenizer, IncrementalBuffering2) +{ + using Token = IncrementalTokenizer::Token; + + int test = 0; + Token custom; + IncrementalTokenizer i( + [&](Token const& t, IncrementalTokenizer& i) -> nsresult { + switch (++test) { + case 1: + EXPECT_TRUE(t.Fragment().EqualsLiteral("01")); + break; + case 2: + EXPECT_TRUE(t.Fragment().EqualsLiteral("234567")); + break; + case 3: + EXPECT_TRUE(t.Fragment().EqualsLiteral("89")); + break; + case 4: + EXPECT_TRUE(t.Equals(custom)); + break; + case 5: + EXPECT_TRUE(t.Fragment().EqualsLiteral("qwert")); + break; + case 6: + EXPECT_TRUE(t.Equals(Token::EndOfFile())); + break; + } + return NS_OK; + }, + nullptr, nullptr, 3); + + custom = i.AddCustomToken("aaa", Tokenizer::CASE_SENSITIVE); + // This externally unused token is added only to check the internal algorithm + // does work correctly as expected when there are two different length tokens. + Unused << i.AddCustomToken("bbbbb", Tokenizer::CASE_SENSITIVE); + i.SetTokenizingMode(Tokenizer::Mode::CUSTOM_ONLY); + + i.FeedInput("01234"_ns); + EXPECT_TRUE(test == 0); + i.FeedInput("5"_ns); + EXPECT_TRUE(test == 1); + i.FeedInput("6789aa"_ns); + EXPECT_TRUE(test == 2); + i.FeedInput("aqwert"_ns); + EXPECT_TRUE(test == 4); + i.FinishInput(); + EXPECT_TRUE(test == 6); +} + +TEST(Tokenizer, RecordAndReadUntil) +{ + Tokenizer t("aaaa,bbbb"); + t.SkipWhites(); + nsDependentCSubstring subject; + + EXPECT_TRUE(t.ReadUntil(mozilla::Tokenizer::Token::Char(','), subject)); + EXPECT_FALSE(t.CheckChar(',')); + EXPECT_TRUE(subject.Length() == 4); + EXPECT_TRUE(subject == "aaaa"); + + EXPECT_FALSE(t.ReadUntil(mozilla::Tokenizer::Token::Char(','), subject)); + EXPECT_TRUE(subject.Length() == 4); + EXPECT_TRUE(subject == "bbbb"); + + EXPECT_FALSE(t.ReadUntil(mozilla::Tokenizer::Token::Char(','), subject)); + EXPECT_TRUE(subject.Length() == 0); + + EXPECT_TRUE(t.CheckEOF()); +} + +TEST(Tokenizer, ReadIntegers) +{ + // Make sure that adding dash (the 'minus' sign) as an additional char + // doesn't break reading negative numbers. + Tokenizer t("100,-100,200,-200,4294967295,-4294967295,-2147483647", nullptr, + "-"); + + uint32_t unsigned_value32; + int32_t signed_value32; + int64_t signed_value64; + + // "100," + EXPECT_TRUE(t.ReadInteger(&unsigned_value32)); + EXPECT_TRUE(unsigned_value32 == 100); + EXPECT_TRUE(t.CheckChar(',')); + + // "-100," + EXPECT_FALSE(t.ReadInteger(&unsigned_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadSignedInteger(&signed_value32)); + EXPECT_TRUE(signed_value32 == -100); + EXPECT_TRUE(t.CheckChar(',')); + + // "200," + EXPECT_TRUE(t.ReadSignedInteger(&signed_value32)); + EXPECT_TRUE(signed_value32 == 200); + EXPECT_TRUE(t.CheckChar(',')); + + // "-200," + EXPECT_TRUE(t.ReadSignedInteger(&signed_value32)); + EXPECT_TRUE(signed_value32 == -200); + EXPECT_TRUE(t.CheckChar(',')); + + // "4294967295," + EXPECT_FALSE(t.ReadSignedInteger(&signed_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadInteger(&unsigned_value32)); + EXPECT_TRUE(unsigned_value32 == 4294967295UL); + EXPECT_TRUE(t.CheckChar(',')); + + // "-4294967295," + EXPECT_FALSE(t.ReadSignedInteger(&signed_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_FALSE(t.ReadInteger(&unsigned_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadSignedInteger(&signed_value64)); + EXPECT_TRUE(signed_value64 == -4294967295LL); + EXPECT_TRUE(t.CheckChar(',')); + + // "-2147483647" + EXPECT_FALSE(t.ReadInteger(&unsigned_value32)); + EXPECT_FALSE(t.CheckChar(',')); + + EXPECT_TRUE(t.ReadSignedInteger(&signed_value32)); + EXPECT_TRUE(signed_value32 == -2147483647L); + EXPECT_TRUE(t.CheckEOF()); +} + +TEST(Tokenizer, CheckPhrase) +{ + Tokenizer t("foo bar baz"); + + EXPECT_TRUE(t.CheckPhrase("foo ")); + + EXPECT_FALSE(t.CheckPhrase("barr")); + EXPECT_FALSE(t.CheckPhrase("BAR BAZ")); + EXPECT_FALSE(t.CheckPhrase("bar baz ")); + EXPECT_FALSE(t.CheckPhrase("b")); + EXPECT_FALSE(t.CheckPhrase("ba")); + EXPECT_FALSE(t.CheckPhrase("??")); + + EXPECT_TRUE(t.CheckPhrase("bar baz")); + + t.Rollback(); + EXPECT_TRUE(t.CheckPhrase("bar")); + EXPECT_TRUE(t.CheckPhrase(" baz")); + + t.Rollback(); + EXPECT_FALSE(t.CheckPhrase("\tbaz")); + EXPECT_TRUE(t.CheckPhrase(" baz")); + EXPECT_TRUE(t.CheckEOF()); +} diff --git a/xpcom/tests/gtest/TestUTF.cpp b/xpcom/tests/gtest/TestUTF.cpp new file mode 100644 index 0000000000..cb574aa855 --- /dev/null +++ b/xpcom/tests/gtest/TestUTF.cpp @@ -0,0 +1,264 @@ +/* -*- 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/ArrayUtils.h" + +#include +#include +#include "nsString.h" +#include "nsStringBuffer.h" +#include "nsReadableUtils.h" +#include "UTFStrings.h" +#include "nsUnicharUtils.h" +#include "mozilla/HashFunctions.h" +#include "nsUTF8Utils.h" + +#include "gtest/gtest.h" + +using namespace mozilla; + +namespace TestUTF { + +TEST(UTF, Valid) +{ + for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) { + nsDependentCString str8(ValidStrings[i].m8); + nsDependentString str16(ValidStrings[i].m16); + + EXPECT_TRUE(NS_ConvertUTF16toUTF8(str16).Equals(str8)); + + EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16)); + + nsCString tmp8("string "); + AppendUTF16toUTF8(str16, tmp8); + EXPECT_TRUE(tmp8.Equals("string "_ns + str8)); + + nsString tmp16(u"string "_ns); + AppendUTF8toUTF16(str8, tmp16); + EXPECT_TRUE(tmp16.Equals(u"string "_ns + str16)); + + EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0); + } +} + +TEST(UTF, Invalid16) +{ + for (unsigned int i = 0; i < ArrayLength(Invalid16Strings); ++i) { + nsDependentString str16(Invalid16Strings[i].m16); + nsDependentCString str8(Invalid16Strings[i].m8); + + EXPECT_TRUE(NS_ConvertUTF16toUTF8(str16).Equals(str8)); + + nsCString tmp8("string "); + AppendUTF16toUTF8(str16, tmp8); + EXPECT_TRUE(tmp8.Equals("string "_ns + str8)); + + EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0); + } +} + +TEST(UTF, Invalid8) +{ + for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) { + nsDependentString str16(Invalid8Strings[i].m16); + nsDependentCString str8(Invalid8Strings[i].m8); + + EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16)); + + nsString tmp16(u"string "_ns); + AppendUTF8toUTF16(str8, tmp16); + EXPECT_TRUE(tmp16.Equals(u"string "_ns + str16)); + + EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0); + } +} + +TEST(UTF, Malformed8) +{ + for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) { + nsDependentString str16(Malformed8Strings[i].m16); + nsDependentCString str8(Malformed8Strings[i].m8); + + EXPECT_TRUE(NS_ConvertUTF8toUTF16(str8).Equals(str16)); + + nsString tmp16(u"string "_ns); + AppendUTF8toUTF16(str8, tmp16); + EXPECT_TRUE(tmp16.Equals(u"string "_ns + str16)); + + EXPECT_EQ(CompareUTF8toUTF16(str8, str16), 0); + } +} + +TEST(UTF, Hash16) +{ + for (unsigned int i = 0; i < ArrayLength(ValidStrings); ++i) { + nsDependentCString str8(ValidStrings[i].m8); + bool err; + EXPECT_EQ(HashString(ValidStrings[i].m16), + HashUTF8AsUTF16(str8.get(), str8.Length(), &err)); + EXPECT_FALSE(err); + } + + for (unsigned int i = 0; i < ArrayLength(Invalid8Strings); ++i) { + nsDependentCString str8(Invalid8Strings[i].m8); + bool err; + EXPECT_EQ(HashUTF8AsUTF16(str8.get(), str8.Length(), &err), 0u); + EXPECT_TRUE(err); + } + + for (unsigned int i = 0; i < ArrayLength(Malformed8Strings); ++i) { + nsDependentCString str8(Malformed8Strings[i].m8); + bool err; + EXPECT_EQ(HashUTF8AsUTF16(str8.get(), str8.Length(), &err), 0u); + EXPECT_TRUE(err); + } +} + +/** + * This tests the handling of a non-ascii character at various locations in a + * UTF-16 string that is being converted to UTF-8. + */ +static void NonASCII16_helper(const size_t aStrSize) { + const size_t kTestSize = aStrSize; + const size_t kMaxASCII = 0x80; + const char16_t kUTF16Char = 0xC9; + const char kUTF8Surrogates[] = {char(0xC3), char(0x89)}; + + // Generate a string containing only ASCII characters. + nsString asciiString; + asciiString.SetLength(kTestSize); + nsCString asciiCString; + asciiCString.SetLength(kTestSize); + + auto str_buff = asciiString.BeginWriting(); + auto cstr_buff = asciiCString.BeginWriting(); + for (size_t i = 0; i < kTestSize; i++) { + str_buff[i] = i % kMaxASCII; + cstr_buff[i] = i % kMaxASCII; + } + + // Now go through and test conversion when exactly one character will + // result in a multibyte sequence. + for (size_t i = 0; i < kTestSize; i++) { + // Setup the UTF-16 string. + nsString unicodeString(asciiString); + auto buff = unicodeString.BeginWriting(); + buff[i] = kUTF16Char; + + // Do the conversion, make sure the length increased by 1. + nsCString dest; + AppendUTF16toUTF8(unicodeString, dest); + EXPECT_EQ(dest.Length(), unicodeString.Length() + 1); + + // Build up the expected UTF-8 string. + nsCString expected; + + // First add the leading ASCII chars. + expected.Append(asciiCString.BeginReading(), i); + + // Now append the UTF-8 pair we expect the UTF-16 unicode char to + // be converted to. + for (auto& c : kUTF8Surrogates) { + expected.Append(c); + } + + // And finish with the trailing ASCII chars. + expected.Append(asciiCString.BeginReading() + i + 1, kTestSize - i - 1); + + EXPECT_STREQ(dest.BeginReading(), expected.BeginReading()); + } +} + +TEST(UTF, NonASCII16) +{ + // Test with various string sizes to catch any special casing. + NonASCII16_helper(1); + NonASCII16_helper(8); + NonASCII16_helper(16); + NonASCII16_helper(32); + NonASCII16_helper(512); +} + +TEST(UTF, UTF8CharEnumerator) +{ + const char* p = + "\x61\xC0\xC2\xC2\x80\xE0\x80\x80\xE0\xA0\x80\xE1\x80\x80\xED\xBF\xBF\xED" + "\x9F\xBF\xEE\x80\x80\xEE\x80\xFF\xF0\x90\x80\x80\xF0\x80\x80\x80\xF1\x80" + "\x80\x80\xF4\x8F\xBF\xF4\x8F\xBF\xBF\xF4\xBF\xBF\xBF"; + const char* end = p + 49; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x0061U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x0080U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x0800U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x1000U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xD7FFU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xE000U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x10000U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x40000U); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0x10FFFFU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + p = "\xC2\xB6"; + end = p + 1; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + p = "\xE2\x98\x83"; + end = p + 2; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + p = "\xF0\x9F\x92\xA9"; + end = p + 2; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + p = "\xF0\x9F\x92\xA9"; + end = p + 3; + EXPECT_EQ(UTF8CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); +} + +TEST(UTF, UTF16CharEnumerator) +{ + const char16_t* p = u"\u0061\U0001F4A9"; + const char16_t* end = p + 3; + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0x0061U); + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0x1F4A9U); + EXPECT_EQ(p, end); + const char16_t loneHigh = 0xD83D; + p = &loneHigh; + end = p + 1; + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + const char16_t loneLow = 0xDCA9; + p = &loneLow; + end = p + 1; + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(p, end); + const char16_t loneHighStr[] = {0xD83D, 0x0061}; + p = loneHighStr; + end = p + 2; + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0xFFFDU); + EXPECT_EQ(UTF16CharEnumerator::NextChar(&p, end), 0x0061U); + EXPECT_EQ(p, end); +} + +} // namespace TestUTF diff --git a/xpcom/tests/gtest/TestVariant.cpp b/xpcom/tests/gtest/TestVariant.cpp new file mode 100644 index 0000000000..8aec8840c6 --- /dev/null +++ b/xpcom/tests/gtest/TestVariant.cpp @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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 https://mozilla.org/MPL/2.0/. */ + +#include +#include "nsVariant.h" +#include "gtest/gtest.h" + +TEST(Variant, DoubleNaN) +{ + nsDiscriminatedUnion du; + du.SetFromDouble(NAN); + + uint8_t ui8; + EXPECT_EQ(du.ConvertToInt8(&ui8), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + int16_t i16; + EXPECT_EQ(du.ConvertToInt16(&i16), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + int32_t i32; + EXPECT_EQ(du.ConvertToInt32(&i32), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + int64_t i64; + EXPECT_EQ(du.ConvertToInt64(&i64), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + EXPECT_EQ(du.ConvertToUint8(&ui8), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + uint16_t ui16; + EXPECT_EQ(du.ConvertToUint16(&ui16), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + uint32_t ui32; + EXPECT_EQ(du.ConvertToUint32(&ui32), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + uint64_t ui64; + EXPECT_EQ(du.ConvertToUint64(&ui64), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + float f = 0.0f; + EXPECT_EQ(du.ConvertToFloat(&f), NS_OK); + EXPECT_TRUE(isnan(f)); + + double d = 0.0; + EXPECT_EQ(du.ConvertToDouble(&d), NS_OK); + EXPECT_TRUE(isnan(d)); + + bool b = true; + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); + + char c; + EXPECT_EQ(du.ConvertToChar(&c), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + char16_t c16; + EXPECT_EQ(du.ConvertToWChar(&c16), NS_ERROR_LOSS_OF_SIGNIFICANT_DATA); + + nsID id = {}; + EXPECT_EQ(du.ConvertToID(&id), NS_ERROR_CANNOT_CONVERT_DATA); + + nsAutoString string; + EXPECT_EQ(du.ConvertToAString(string), NS_OK); + EXPECT_EQ(string, u"NaN"_ns); + + nsAutoCString utf8string; + EXPECT_EQ(du.ConvertToAUTF8String(utf8string), NS_OK); + EXPECT_EQ(utf8string, "NaN"_ns); + + nsAutoCString autocstring; + EXPECT_EQ(du.ConvertToACString(autocstring), NS_OK); + EXPECT_EQ(autocstring, "NaN"_ns); + + char* chars = nullptr; + EXPECT_EQ(du.ConvertToString(&chars), NS_OK); + EXPECT_STREQ(chars, "NaN"); + free(chars); + + char16_t* wchars = nullptr; + EXPECT_EQ(du.ConvertToWString(&wchars), NS_OK); + { + // gtest doesn't seem to support EXPECT_STREQ on char16_t, so convert + // to Gecko strings to do the comparison. + nsDependentString wstring(wchars); + EXPECT_EQ(wstring, u"NaN"_ns); + } + free(wchars); + + chars = nullptr; + uint32_t size = 0; + EXPECT_EQ(du.ConvertToStringWithSize(&size, &chars), NS_OK); + EXPECT_STREQ(chars, "NaN"); + EXPECT_EQ(size, 3u); + free(chars); + + wchars = nullptr; + size = 0; + EXPECT_EQ(du.ConvertToWStringWithSize(&size, &wchars), NS_OK); + { + // gtest doesn't seem to support EXPECT_STREQ on char16_t, so convert + // to Gecko strings to do the comparison. + nsDependentString wstring(wchars); + EXPECT_EQ(wstring, u"NaN"_ns); + EXPECT_EQ(size, 3u); + } + free(wchars); + + nsISupports* isupports; + EXPECT_EQ(du.ConvertToISupports(&isupports), NS_ERROR_CANNOT_CONVERT_DATA); + + nsIID* idp; + void* iface; + EXPECT_EQ(du.ConvertToInterface(&idp, &iface), NS_ERROR_CANNOT_CONVERT_DATA); + + uint16_t type; + nsIID iid; + uint32_t count; + void* array; + EXPECT_EQ(du.ConvertToArray(&type, &iid, &count, &array), + NS_ERROR_CANNOT_CONVERT_DATA); +} + +TEST(Variant, Bool) +{ + nsDiscriminatedUnion du; + bool b = false; + + du.SetFromInt64(12); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_TRUE(b); + + b = true; + du.SetFromInt64(0); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); + + b = true; + du.SetFromDouble(1.25); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_TRUE(b); + + b = true; + du.SetFromDouble(0.0); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); + + b = true; + du.SetFromDouble(-0.0); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); + + // This is also checked in the previous test, but I'm including it + // here for completeness. + b = true; + du.SetFromDouble(NAN); + EXPECT_EQ(du.ConvertToBool(&b), NS_OK); + EXPECT_FALSE(b); +} diff --git a/xpcom/tests/gtest/UTFStrings.h b/xpcom/tests/gtest/UTFStrings.h new file mode 100644 index 0000000000..9613da32a1 --- /dev/null +++ b/xpcom/tests/gtest/UTFStrings.h @@ -0,0 +1,130 @@ +/* -*- 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/. */ + +#ifndef utfstrings_h__ +#define utfstrings_h__ + +struct UTFStringsStringPair { + char16_t m16[16]; + char m8[16]; +}; + +static const UTFStringsStringPair ValidStrings[] = { + {{'a', 'b', 'c', 'd'}, {'a', 'b', 'c', 'd'}}, + {{'1', '2', '3', '4'}, {'1', '2', '3', '4'}}, + {{0x7F, 'A', 0x80, 'B', 0x101, 0x200}, + {0x7F, 'A', char(0xC2), char(0x80), 'B', char(0xC4), char(0x81), + char(0xC8), char(0x80)}}, + {{0x7FF, 0x800, 0x1000}, + {char(0xDF), char(0xBF), char(0xE0), char(0xA0), char(0x80), char(0xE1), + char(0x80), char(0x80)}}, + {{0xD7FF, 0xE000, 0xF00F, 'A', 0xFFF0}, + {char(0xED), char(0x9F), char(0xBF), char(0xEE), char(0x80), char(0x80), + char(0xEF), char(0x80), char(0x8F), 'A', char(0xEF), char(0xBF), + char(0xB0)}}, + {{0xFFF7, 0xFFFC, 0xFFFD, 0xFFFD}, + {char(0xEF), char(0xBF), char(0xB7), char(0xEF), char(0xBF), char(0xBC), + char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD)}}, + {{0xD800, 0xDC00, 0xD800, 0xDCFF}, + {char(0xF0), char(0x90), char(0x80), char(0x80), char(0xF0), char(0x90), + char(0x83), char(0xBF)}}, + {{0xDBFF, 0xDFFF, 0xDBB7, 0xDCBA}, + {char(0xF4), char(0x8F), char(0xBF), char(0xBF), char(0xF3), char(0xBD), + char(0xB2), char(0xBA)}}, + {{0xFFFD, 0xFFFF}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBF)}}, + {{0xFFFD, 0xFFFE, 0xFFFF}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBE), + char(0xEF), char(0xBF), char(0xBF)}}, +}; + +static const UTFStringsStringPair Invalid16Strings[] = { + {{'a', 'b', 0xD800}, {'a', 'b', char(0xEF), char(0xBF), char(0xBD)}}, + {{0xD8FF, 'b'}, {char(0xEF), char(0xBF), char(0xBD), 'b'}}, + {{0xD821}, {char(0xEF), char(0xBF), char(0xBD)}}, + {{0xDC21}, {char(0xEF), char(0xBF), char(0xBD)}}, + {{0xDC00, 0xD800, 'b'}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD), + 'b'}}, + {{'b', 0xDC00, 0xD800}, + {'b', char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), + char(0xBD)}}, + {{0xDC00, 0xD800}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD)}}, + {{0xDC00, 0xD800, 0xDC00, 0xD800}, + {char(0xEF), char(0xBF), char(0xBD), char(0xF0), char(0x90), char(0x80), + char(0x80), char(0xEF), char(0xBF), char(0xBD)}}, + {{0xDC00, 0xD800, 0xD800, 0xDC00}, + {char(0xEF), char(0xBF), char(0xBD), char(0xEF), char(0xBF), char(0xBD), + char(0xF0), char(0x90), char(0x80), char(0x80)}}, +}; + +static const UTFStringsStringPair Invalid8Strings[] = { + {{'a', 0xFFFD, 0xFFFD, 'b'}, {'a', char(0xC0), char(0x80), 'b'}}, + {{0xFFFD, 0xFFFD, 0x80}, {char(0xC1), char(0xBF), char(0xC2), char(0x80)}}, + {{0xFFFD, 0xFFFD}, {char(0xC1), char(0xBF)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 'x', 0x0800}, + {char(0xE0), char(0x80), char(0x80), 'x', char(0xE0), char(0xA0), + char(0x80)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'x', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD}, + {char(0xF0), char(0x80), char(0x80), char(0x80), 'x', char(0xF0), + char(0x80), char(0x8F), char(0x80)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD}, + {char(0xF4), char(0x90), char(0x80), char(0x80), char(0xF7), char(0xBF), + char(0xBF), char(0xBF)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'x', 0xD800, 0xDC00, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD}, + {char(0xF0), char(0x8F), char(0xBF), char(0xBF), 'x', char(0xF0), + char(0x90), char(0x80), char(0x80), char(0xF0), char(0x8F), char(0xBF), + char(0xBF)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'x', 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD}, + {char(0xF8), char(0x80), char(0x80), char(0x80), char(0x80), 'x', + char(0xF8), char(0x88), char(0x80), char(0x80), char(0x80)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD}, + {char(0xFB), char(0xBF), char(0xBF), char(0xBF), char(0xBF), char(0xFC), + char(0xA0), char(0x80), char(0x80), char(0x80), char(0x80)}}, + {{0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, + 0xFFFD, 0xFFFD, 0xFFFD}, + {char(0xFC), char(0x80), char(0x80), char(0x80), char(0x80), char(0x80), + char(0xFD), char(0xBF), char(0xBF), char(0xBF), char(0xBF), char(0xBF)}}, +}; + +static const UTFStringsStringPair Malformed8Strings[] = { + {{0xFFFD}, {char(0x80)}}, + {{'a', 0xFFFD, 'c'}, {'a', char(0xC8), 'c'}}, + {{'a', 0xFFFD}, {'a', char(0xC8)}}, + {{'a', 0xFFFD, 'c'}, {'a', char(0xE8), 'c'}}, + {{'a', 0xFFFD, 'c'}, {'a', char(0xE8), char(0x80), 'c'}}, + {{'a', 0xFFFD}, {'a', char(0xE8), char(0x80)}}, + {{0xFFFD, 0x7F, 0xFFFD}, {char(0xE8), 0x7F, char(0x80)}}, + {{'a', 0xFFFD, 0xFFFD}, {'a', char(0xE8), char(0xE8), char(0x80)}}, + {{'a', 0xFFFD}, {'a', char(0xF4)}}, + {{'a', 0xFFFD, 'c', 'c'}, + {'a', char(0xF4), char(0x80), char(0x80), 'c', 'c'}}, + {{'a', 0xFFFD, 'x', 0xFFFD}, + {'a', char(0xF4), char(0x80), 'x', char(0x80)}}, + {{0xDBC0, 0xDC00, 0xFFFD}, + {char(0xF4), char(0x80), char(0x80), char(0x80), char(0x80)}}, + {{'a', 0xFFFD, 'c'}, {'a', char(0xFA), 'c'}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0x7F, 0xFFFD, 'c'}, + {'a', char(0xFA), char(0x80), char(0x80), 0x7F, char(0x80), 'c'}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'c'}, + {'a', char(0xFA), char(0x80), char(0x80), char(0x80), char(0x80), + char(0x80), 'c'}}, + {{'a', 0xFFFD}, {'a', char(0xFD)}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 'c'}, + {'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), 'c'}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD, 0xFFFD}, + {'a', char(0xFD), char(0x80), char(0x80), char(0x80), char(0x80), + char(0x80), char(0x80)}}, + {{'a', 0xFFFD, 0xFFFD, 0xFFFD, 0x40, 0xFFFD, 0xFFFD, 'c'}, + {'a', char(0xFD), char(0x80), char(0x80), 0x40, char(0x80), char(0x80), + 'c'}}, +}; + +#endif diff --git a/xpcom/tests/gtest/dafsa_test_1.dat b/xpcom/tests/gtest/dafsa_test_1.dat new file mode 100644 index 0000000000..603813dbb4 --- /dev/null +++ b/xpcom/tests/gtest/dafsa_test_1.dat @@ -0,0 +1,6 @@ +%% +foo.bar.baz, 1 +a.test.string, 0 +a.test.string2, 2 +aaaa, 4 +%% diff --git a/xpcom/tests/gtest/moz.build b/xpcom/tests/gtest/moz.build new file mode 100644 index 0000000000..3347c56ba5 --- /dev/null +++ b/xpcom/tests/gtest/moz.build @@ -0,0 +1,181 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + "Helpers.cpp", + "TestArenaAllocator.cpp", + "TestArrayAlgorithm.cpp", + "TestAtoms.cpp", + "TestAutoRefCnt.cpp", + "TestBase64.cpp", + "TestCallTemplates.cpp", + "TestCloneInputStream.cpp", + "TestCOMPtrEq.cpp", + "TestCRT.cpp", + "TestDafsa.cpp", + "TestDelayedRunnable.cpp", + "TestEncoding.cpp", + "TestEscape.cpp", + "TestEventPriorities.cpp", + "TestEventTargetQI.cpp", + "TestFile.cpp", + "TestGCPostBarriers.cpp", + "TestID.cpp", + "TestIDUtils.cpp", + "TestInputStreamLengthHelper.cpp", + "TestJSHolderMap.cpp", + "TestLogCommandLineHandler.cpp", + "TestLogging.cpp", + "TestMemoryPressure.cpp", + "TestMoveString.cpp", + "TestMozPromise.cpp", + "TestMruCache.cpp", + "TestMultiplexInputStream.cpp", + "TestNonBlockingAsyncInputStream.cpp", + "TestNsDeque.cpp", + "TestNSPRLogModulesParser.cpp", + "TestObserverArray.cpp", + "TestObserverService.cpp", + "TestOwningNonNull.cpp", + "TestPLDHash.cpp", + "TestPriorityQueue.cpp", + "TestQueue.cpp", + "TestRacingServiceManager.cpp", + "TestRecursiveMutex.cpp", + "TestRustRegex.cpp", + "TestRWLock.cpp", + "TestSegmentedBuffer.cpp", + "TestSlicedInputStream.cpp", + "TestSmallArrayLRUCache.cpp", + "TestSnappyStreams.cpp", + "TestStateWatching.cpp", + "TestStorageStream.cpp", + "TestStrings.cpp", + "TestStringStream.cpp", + "TestSubstringTuple.cpp", + "TestSynchronization.cpp", + "TestTArray.cpp", + "TestTArray2.cpp", + "TestTaskQueue.cpp", + "TestTextFormatter.cpp", + "TestThreadManager.cpp", + "TestThreadMetrics.cpp", + "TestThreadPool.cpp", + "TestThreadPoolListener.cpp", + "TestThrottledEventQueue.cpp", + "TestTimeStamp.cpp", + "TestTokenizer.cpp", + "TestUTF.cpp", + "TestVariant.cpp", +] + +if CONFIG["OS_TARGET"] != "Android": + UNIFIED_SOURCES += [ + "TestPipes.cpp", + "TestThreads.cpp", + ] + +# skip the test on windows10-aarch64 due to perma-fail, bug 1422219 +if not (CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CPU_ARCH"] == "aarch64"): + UNIFIED_SOURCES += ["TestThreadUtils.cpp"] + +# skip the test on OSX due to frequent failures (bug 1571186) +if CONFIG["OS_TARGET"] != "Darwin": + UNIFIED_SOURCES += ["TestExpirationTracker.cpp"] + +# skip the test on windows10-aarch64 and Android, aarch64 due to bug 1545670 +if CONFIG["OS_TARGET"] != "Android" and not ( + CONFIG["OS_TARGET"] == "WINNT" and CONFIG["CPU_ARCH"] == "aarch64" +): + UNIFIED_SOURCES += ["TestTimers.cpp"] + + +if ( + CONFIG["MOZ_DEBUG"] + and CONFIG["OS_ARCH"] not in ("WINNT") + and CONFIG["OS_TARGET"] != "Android" +): + # FIXME bug 523392: TestDeadlockDetector doesn't like Windows + # Bug 1054249: Doesn't work on Android + UNIFIED_SOURCES += [ + "TestDeadlockDetector.cpp", + "TestDeadlockDetectorScalability.cpp", + ] + +if CONFIG["OS_TARGET"] == "WINNT": + UNIFIED_SOURCES += [ + "TestAvailableMemoryWatcherWin.cpp", + "TestFileNTFSSpecialPaths.cpp", + "TestFilePreferencesWin.cpp", + "TestHandleWatcher.cpp", + ] +else: + UNIFIED_SOURCES += [ + "TestFilePreferencesUnix.cpp", + ] + +if CONFIG["OS_TARGET"] == "Darwin": + UNIFIED_SOURCES += [ + "TestAvailableMemoryWatcherMac.cpp", + "TestMacNSURLEscaping.mm", + "TestThreads_mac.mm", + ] + +if CONFIG["OS_TARGET"] == "Linux": + UNIFIED_SOURCES += [ + "TestAvailableMemoryWatcherLinux.cpp", + ] + +if ( + CONFIG["WRAP_STL_INCLUDES"] + and CONFIG["CC_TYPE"] != "clang-cl" + and CONFIG["OS_TARGET"] != "Android" +): + UNIFIED_SOURCES += [ + "TestSTLWrappers.cpp", + ] + +# Compile TestAllocReplacement separately so Windows headers don't pollute +# the global namespace for other files. +if CONFIG["MOZ_MEMORY"]: + SOURCES += [ + "TestAllocReplacement.cpp", + ] + +SOURCES += [ + "TestCOMArray.cpp", + "TestCOMPtr.cpp", # Redefines IFoo and IBar + "TestHashtables.cpp", # Redefines IFoo + "TestNsRefPtr.cpp", # Redefines Foo +] + +LOCAL_INCLUDES += [ + "../../base", + "/toolkit/components/telemetry/tests/gtest", + "/xpcom/components", +] + +GeneratedFile( + "dafsa_test_1.inc", + script="../../ds/tools/make_dafsa.py", + inputs=["dafsa_test_1.dat"], +) + +TEST_HARNESS_FILES.gtest += [ + "wikipedia/ar.txt", + "wikipedia/de-edit.txt", + "wikipedia/de.txt", + "wikipedia/ja.txt", + "wikipedia/ko.txt", + "wikipedia/ru.txt", + "wikipedia/th.txt", + "wikipedia/tr.txt", + "wikipedia/vi.txt", +] + +FINAL_LIBRARY = "xul-gtest" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/xpcom/tests/gtest/wikipedia/README.txt b/xpcom/tests/gtest/wikipedia/README.txt new file mode 100644 index 0000000000..bd17b17c85 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/README.txt @@ -0,0 +1,13 @@ +The content of the .txt files in this directory originate from Wikipedia and +is licensed the Creative Commons Attribution-ShareAlike 3.0 Unported license +. + +The content comes from the following revisions: +ar: https://ar.wikipedia.org/w/index.php?title=%D8%A7%D9%84%D9%85%D8%B1%D9%8A%D8%AE&oldid=21144485 +de: https://de.wikipedia.org/w/index.php?title=Mars_(Planet)&oldid=158965843 +ja: https://ja.wikipedia.org/w/index.php?title=%E7%81%AB%E6%98%9F&oldid=61095795 +ko: https://ko.wikipedia.org/w/index.php?title=%ED%99%94%EC%84%B1&oldid=17394891 +ru: https://ru.wikipedia.org/w/index.php?title=%D0%9C%D0%B0%D1%80%D1%81&oldid=81533008 +th: https://th.wikipedia.org/w/index.php?title=%E0%B8%94%E0%B8%B2%E0%B8%A7%E0%B8%AD%E0%B8%B1%E0%B8%87%E0%B8%84%E0%B8%B2%E0%B8%A3&oldid=6511628 +tr: https://tr.wikipedia.org/w/index.php?title=Mars&oldid=17608551 +vi: https://vi.wikipedia.org/w/index.php?title=Sao_H%E1%BB%8Fa&oldid=24192627 diff --git a/xpcom/tests/gtest/wikipedia/ar.txt b/xpcom/tests/gtest/wikipedia/ar.txt new file mode 100644 index 0000000000..0dba6a8a9a --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/ar.txt @@ -0,0 +1,70 @@ +المÙرÙّيخ (Mars مارس) هو الكوكب الرابع ÙÙŠ البعد عن الشمس ÙÙŠ النظام الشمسي وهو الجار الخارجي للأرض ويصن٠كوكبا صخريا، من مجموعة الكواكب الأرضية (الشبيهة بالأرض). + +اطلق عليه بالعربية المريخ نسبةً إلى كلمة أمرخ أي ذو البقع الحمراء، Ùيقال ثور أَمرخ أي به بقع حمراء، وهو باللاتينية مارس الذي اتخذه الرومان آله للحرب، وهو يلقب ÙÙŠ الوقت الحالي بالكوكب الأحمر بسبب لونه المائل إلى الحمره، بÙعل نسبة غبار أكسيد الحديد الثلاثي العالية على سطحه ÙˆÙÙŠ جوه. + +يبلغ قطر المريخ حوالي 6800 كلم وهو بذلك مساو لنص٠قطر الأرض وثاني أصغر كواكب النظام الشمسي بعد عطارد. تقدّر مساحته بربع مساحة الأرض. يدور المريخ حول الشمس ÙÙŠ مدار يبعد عنها بمعدل 228 مليون كلم تقريبا، أي 1.5 مرات من المساÙØ© الÙاصلة بين مدار الأرض والشمس. + +له قمران، يسمّى الأول ديموس أي الرعب باللغة اليونانية والثاني Ùوبوس أي الخوÙ. + +يعتقد العلماء أن كوكب المريخ احتوى الماء قبل 3.8 مليار سنة، مما يجعل Ùرضية وجود حياة عليه متداولة نظريا على الأقل. به جبال أعلى من مثيلاتها الأرضية ووديان ممتدة. وبه أكبر بركان ÙÙŠ المجموعة الشمسية يطلق عليه اسم أوليمبس مونز تيمنا بجبل الأولمب. + +تبلغ درجة حرارته العليا 27 درجة مئوية ودرجة حرارته الصغرى -133 درجة مئوية. ويتكون غلاÙÙ‡ الجوي من ثاني أكسيد الكربون والنيتروجين والأرغون وبخار الماء وغازات أخرى. رمز المريخ الÙلكي هو ♂.يجذب الانتباه بلونه الأحمر + +قد يكون المريخ ÙˆÙقا لدراسة عالمين أمريكيين مجرد كوكب جنين لم يستطع أن يتم نموه، بعد أن نجا من الأصطدامات الكثيرة بين الأجرام السماوية التي شهدها النظام الشمسي ÙÙŠ بداية تكوينه والتي أدت لتضخم أغلب الكواكب الأخرى. وهذا ÙŠÙسر صغر حجم المريخ مقارنة بالأرض أو بالزهرة. خلص العالمان إلى هذه النتيجة بعد دراسة استقصائية لنواتج الاضمحلال المشعة ÙÙŠ النيازك.[1] + +يستضي٠المريخ حالياً 5 مركبات Ùضائية لا تزال تعمل، ثلاث ÙÙŠ مدار حول الكوكب وهم مارس أوديسي ومارس إكسبريس ومارس ريكونيسانس أوربيتر، واثنتان على سطح الكوكب وهما كيوريوسيتي روÙر وأبورتيونيتي، كما أن هناك مركبات Ùضائية لم تعد تعمل سواء كانت مهمتها ناجحة أم لا مثل مركبة Ùينيكس لاندر التي أنهت مهمتها عام 2008.[2] + +مقارنة بكوكب الأرض، للمريخ ربع مساحة سطح الأرض وبكتلة تعادل عÙشر كتلة الأرض. هواء المريخ لا يتمتع بنÙس كثاÙØ© هواء الأرض إذ يبلغ الضغط الجوي على سطح المريخ 0.75% من معدّل الضغط الجوي على الأرض، لذا نرى ان المجسّات الآلية التي قامت وكالة الÙضاء الأمريكية بإرسالها لكوكب المريخ، تÙغلّ٠بكÙرة٠هوائية لامتصاص الصدمة عند الارتطام بسطح كوكب المريخ. يتكون هواء المريخ من 95% ثنائي أكسيد الكربون، 3% نيتروجين، 1.6% ارجون، وجزء بسيط من الأكسجين والماء. ÙˆÙÙŠ العام 2000ØŒ توصّل الباحثون لنتائج توحي بوجود حياة على كوكب المريخ بعد معاينة قطع من نيزك عثر عليه ÙÙŠ القارة المتجمدة الجنوبية وتم تحديد أصله من كوكب المريخ نتيجة مقارنة تكوينه المعدني وتكوين الصخور التي تمت معاينتها من المركبات Ùيكينغ 1 Ùˆ2ØŒ حيث استدلّ الباحثون على وجود أحاÙير مجهرية ÙÙŠ النيزك. ولكن تبقى الÙرضية آنÙØ© الذكر مثاراً للجدل دون التوصل إلى نتيجة أكيدة بوجود حياة ÙÙŠ الماضي على كوكب المريخ. + +ويعتبر المريخ كوكب صخري ومعظم سطحه أحمر إلا بعض البقع الأغمق لوناً بسبب تربته وصخوره والغلا٠الجوي لكوكب المريخ قليل الكثاÙØ© ويتكون أساساً من ثاني أكسيد الكربون وكميات قليلة من بخار الماء والضغط الجوي على المريخ منخÙض جدًا ويصل إلى 0.01 من الضغط الجوي للأرض وجو المريخ ابرد من الأرض والسنة على المريخ 687 يوماً ارضياً. + +التركيب الداخلي[عدل] + +حدث للمريخ تماماً ما حدث للأرض من تمايز أو تباين والمقصود بالتمايز هنا العملية التي ينتج عنها اختلا٠ÙÙŠ كثاÙØ© ومكونات كل طبقة من طبقات الكوكب بحيث يكون قلب أو لب الكوكب عالي الكثاÙØ© وما Ùوقه أقل منه ÙÙŠ الكثاÙØ©. النموذج الحالي لكوكب المريخ ينطوي على التالي: القلب يمتد لمساÙØ© يبلغ نص٠قطرها 1794 ± 65 كيلومتر وهي تتكون أساساً من الحديد والنيكل والكبريت بنسبة 16-17%. هذا القلب المكون من كبريتات الحديد سائل جزئياً، وتركيزه ضع٠تركيز باقي المواد الأخ٠الموجودة ÙÙŠ القلب. يحاط هذا القلب بدثار من السليكات والتي تكون العديد من المظاهر التكتونية والبركانية على الكوكب إلا أنها الآن تبدو كامنة. بجانب السيليكون والأكسجين، Ùإن أكثر العناصر انتشاراً ÙÙŠ قشرة كوكب المريخ هي الحديد والألومنيوم والماغنسيوم والألومنيوم والكالسيوم والبوتاسيوم. يبلغ متوسط سماكة قشرة كوكب المريخ 50 كيلومتر وأقصى ارتÙاع 125 كيلومتر، ÙÙŠ حين أن قشرة الأرض تبلغ سماكتها 40 كم، وهذا السÙمك بالنسبة لحجم الأرض يعادل ثلث سماكة قشرة كوكب المريخ بالنسبة إلى حجمه. من المخطط له أن تقوم مركبة الÙضاء إن سايت بتحليل أكثر دقة لكوكب المريخ أثناء مهمتها عليه ÙÙŠ عام 2016 باستخدام جهاز مقياس الزلازل لتحدد نموذج للتركيب الداخلي للكوكب بصورة Ø£Ùضل. +التربة[عدل] + +أظهرت البيانات التي وصلت من مسبار الÙضاء Ùينيكس أن تربة المريخ قلوية قليلاً وتحتوي على مواد مثل الماغنسيوم والصوديوم والبوتاسيوم والكلورين، هذه المغذيات موجودة ÙÙŠ الحدائق على الأرض، وهي ضرورية لنمو النباتات. وأظهرت التجارب التي أجراها مسبار الÙضاء أن تربة المريخ لها تركيز هيدروجيني 8.3 وربما تحتوي على آثار لملح البيركلوريك. قال سام كوناÙيس كبير الخبراء المختصين بمختبر كيمياء الموائع الموجود على Ùينيكس للصحÙيين "وجدنا أساسا ما تبدو أنها الخصائص أو العناصر المغذية التي تدعم إمكانية الحياة سواء ÙÙŠ الماضي أو الحاضر أو المستقبل.[3] +المياه[عدل] + + Crystal Clear app kdict.png مقالة Ù…Ùصلة: المياه علي كوكب المريخ + +توجد المياه علي سطح المريخ غالبا ÙÙŠ صورة جليد ويمثل الغطائين الجليديين ÙÙŠ القطب الشمالي والجنوبي للكوكب معظم الجليد الموجود علي السطح يوجد أيضا بعض الجليد ÙÙŠ صخور القشرة المريخية. كما توجد نسبة ضئيلة من بخار الماء ÙÙŠ الغلا٠الجوي للكوكب. لكن لاتوجد مياه سائلة علي سطح المريخ إطلاقا. يرجع وجود الماء ÙÙŠ صورة جليدية الي الظرو٠المناخية للمريخ حيث درجات الحرارة المنخÙضة جدا والتي تؤدي الي تجمد المياه الÙوري. مع ذلك Ùقد أكدت الدراسات ان الوضع علي سطح المريخ كان مختلÙا كثيرا عما هو عليه الآن ولربما كان يشبه كوكب الأرض حيث كانت توجد المياة السائلة[4] ÙÙŠ مساحات كبيرة من سطح الكوكب مشكله محيطات مثل الموجودة الآن علي سطح الأرض. + +توجد الكثير من الدلائل المباشرة وغير المباشرة علي هذه النظرية منها التحليلات الطيÙية لسطح تربة المريخ وأيضا الغطائين القطبيين الجليديين وأيضاً وجود الكثير من المعادن ÙÙŠ قشرة المريخ والتي ارتبط وجودها علي سطح الأرض بوجود المياه. منها أكسيد الحديد Hematite وأكسيد الكبريت Sulfate والجوثايت goethite ومركبات السيليكا phyllosilicate.[5] لقد ساعدت كثيرا مركبات ورحلات الÙضاء غير المأهولة الي المريخ ÙÙŠ دراسة سطح الكوكب وتحليل تربته وغلاÙÙ‡ الجوي. ومن أكثر المركبات التي ساعدت علي ذلك مركبة مارس ريكونيسانس أوربيتر علي تصوير سطح المريخ بدقة عالية وتحليل سطح الكوكب بÙضل وجود الكاميرا عالية الجودة HiRISE كما كشÙت عن Ùوهات البراكين المتآكلة ومجاري النهار الجاÙØ© والأنهار الجليدية. + +كما كشÙت الدراسات الطيÙية بأشعة غاما عن وجود الجليد تحت سطح تربة المريخ. أيضا، كشÙت الدراسات بالرادار عن وجود الجليد النقي ÙÙŠ التشكيلات التي يعتقد أنها كانت أنهار جليدية قديمة.المركبة الÙضائية Ùينيكس التي هبطت قرب القطب الشمالي ورأت الجليد وهو يذوب الجليد، وشهدت تساقط الثلوج، ورأت حتي قطرات من الماء السائل. +Arabic-final.jpg +الطبوغراÙيا[عدل] + + Crystal Clear app kdict.png مقالة Ù…Ùصلة: نيازك المريخ + +طبوغراÙية كوكب المريخ جديرة بالاهتمام، ÙÙÙŠ حين يتكون الجزء الشمالي من الكوكب من سهول الحمم البركانية، وتقع البراكين العملاقة على هضبة تارسيس وأشهرها على الإطلاق أوليمبس مون وهو بدون شك أكبر بركان ÙÙŠ المجموعة الشمسية، نجد ان الجزء الجنوبي من كوكب المريخ يتمتّع بمرتÙعات شاهقة ويبدو على المرتÙعات آثار النيازك والشّهب التي ارتطمت على تلك المرتÙعات. يغطي سهول كوكب المريخ الغبار والرمل الغني بأكسيد الحديد ذو اللون الأحمر. تغطّي بعض مناطق المريخ أحيانا طبقة رقيقة من جليد الماء. ÙÙŠ حين تغطي القطبين طبقات سميكة من جليد مكون من ثاني أكسيد الكربون والماء المتجمّد. تجدرالإشارة أن أعلى قمّة جبلية ÙÙŠ النظام الشمسي هي قمّة جبل "اوليمبوس" والتي يصل ارتÙاعها إلى 25 كم. أمّا بالنسبة للأخاديد، Ùيمتاز الكوكب الأحمر بوجود أكبر أخدود ÙÙŠ النظام الشمسي، ويمتد الأخدود "وادي مارينر" إلى مساÙØ© 4000 كم، وبعمق يصل إلى 7 كم. +الغلا٠الجوي[عدل] + +لقد كانت أول الأخبار عن جو المريخ من سلسلة رحلات مارينر، حيث تم التأكيد على أن للكوكب غلا٠الجوي رقيق جداً يصل إلى 0.01 بالنسبة لغلا٠الأرض الجوي. يتأل٠هذا الجو الرقيق من CO2 ÙÙŠ أغلبه حيث تصل نسبته إلى 95% من مكوناته. ثم تم تحليل مكونات الجو بواسطة المركبة Ùايكينغ 1 لنصل إلى خلاصته عن تركيب الجو وهي كما ÙÙŠ الجدول: +المادة النسبة % + +والضغط الجوي على سطح هذا الكوكب يقارب 1/100 من الضغط الجوي على سطح الأرض عند مستوى سطح البحر. وقد تم تلمس كمية ضئيلة جداً من الأوزون يصل تركيزها إلى 0.03 جزئ /مليون جزيء.ولكن هذا التركيز لا يحمي من الأشعة Ùوق البنÙسجية الضارة. ونلاحظ من الجدول أن نسبة بخار الماء ÙÙŠ الجو ضئيلة جداً مما يجعل الجو جاÙاً. ولكن بسبب برودة سطح الكوكب Ùإن كمية بخار الماء الضئيلة هذه تكÙÙŠ لإشباعه. ومع استمرارية انخÙاض درجة الحرارة دون درجة الندى تبدأ الغازات وخاصة CO2 بالتكاث٠والتجمد والسقوط على سطح الكوكب. وتم رصد عواص٠محلية على السطح وهي عبارة عن هبوب رياح قوية تتحرك بسرعة وتكون غيوم غبارية وزوابع تدور على السطح وتنقل التربة من مكان إلى آخر. وهذه الرياح التي تعص٠على الكوكب لها كما على الأرض دورة رياح يومية ودورة موسمية. ولها تأثير كبير ÙÙŠ عمليات الحت والتجوية على سطح الكوكب. ولأن كثاÙØ© الجو 2% من كثاÙØ© جو الأرض يجب أن تكون قوة الرياح أكبر بحوالي 7 إلى 8 مرات من قوة الرياح الأرضية حتى تستطيع أن تثير وتحمل الغبار وتكون زوابع. Ùالرياح الأرضية بسرعة 24 كلم بالساعة تثير هذه العواص٠أما على المريخ Ùنحتاج إلى رياح بسرعة 180 كلم بالساعة لتقوم بمثل هذه العواصÙ. ودعيت هذه التأثير بتأثير عÙولس Eo`lian effect نسبة إلى آله الريح عÙولس E`olus. ومن الأدلة الواضحة على تأثر عÙولس لحركة الرياح هو الكثبان الرملية. حيث تحمل الرياح الرمال من مكان وتلقيها ÙÙŠ مكان آخر. Ùنجد لها امتداداً واضحاً على سطح الكوكب. وعندما تثور كمية من الغبار Ùإن العاصÙØ© تحاÙظ على بقائها بتحويل الطاقة الشمسية إلى طاقة حركية ريحية، حيث تمتص الطاقة من الإشعاع الشمسي وتسخن الجو وتزيد من سرعة الرياح. Ùيل٠الكوكب دثار مصÙر من الزوابع. ولعدم وجود ماء يغسل الغبار من الجو Ùإنه يبقى عالقاً لعدة أسابيع قبل أن يستقر على السطح ثانية. ومن الغريب أن هذه الرياح تعص٠بهدوء ومن دون أصوات Ùلا ينطبق عليها أصوات العواص٠الهادرة الأرضية.[6] +مدار الكوكب ودورانه[عدل] + +المريخ هو رابع الكواكب بعداً عن الشمس. وأول كوكب له مدار خارج مدار الأرض ويبعد عن الشمس حوالي 228 مليون كلم بالمتوسط. شذوذية مركزيته e = 0.093 وهي كبيرة نسبياً، مما يدل على أن مداره إهليلجي بشكل واضح حيث يكون وهو ÙÙŠ الحضيض على بعد 206 مليون كلم عن الشمس وعند وصوله إلى الأوج يصبح على بعد 249 مليون كلم عن الشمس. Ùنرى Ùرقاً واضحاً ÙÙŠ البعدين وهذا يؤدي إلى تباين كمية أشعة الشمس الساقطة على سطحه بنسبة تصل إلى 45% بين الأوج والحضيض، أي بÙارق 30 Ù’ س وما يتبع ذلك من تغيرات ÙÙŠ مناخ الكوكب بين الموقعين. ودرجة الحرارة تتراوح على السطح بين الشتاء والصي٠-144 ْس إلى 27 ْس أما ÙÙŠ المتوسط Ùإن درجة الحرارة تقدر بحوالي –23 Ù’ إلى -55 Ù’ س. + +ويقطع الكوكب هذا المدار ÙÙŠ زمن يعادل 687 يوم أرضي، وأثناء دورانه ÙÙŠ مداره هذا تحدث له عدد من الظواهر منها الاقتران.[6] +قمرا المريخ[عدل] +كوكب المريخ + +تم اكتشا٠قمري المريخ ÙÙŠ العام 1877 على يد "آسا٠هول" وتمّت تسميتهم تيمّناً بمراÙقي الآله اليوناني "آريس". يدور كل من القمر "Ùوبوس" والقمر "ديموس" حول الكوكب الأحمر، وخلال Ùترة الدوران، تقابل Ù†Ùس الجهة من القمر الكوكب الأحمر تماما مثلما يعرض القمر Ù†Ùس الجانب لكوكب الأرض. +القمر Ùوبوس[عدل] + +Ùوبوس قطعة صخرية صغيرة غير منتظمة الشكل لا يزيد طولها عن 21 كم (13 ميلا) ويتم دورته حول المريخ كل 7.7 ساعات. يبدو القمر هرم نوعا ما. وتغشاه Ùوهات صدم متÙاوتة القدم. ويلاحظ عليه وجود حزوز striations وسلاسل من Ùوهات صغيرة. يطلق أكبرها اسم ستيكني stickney الذي يقارب قطره 10 كم (6 أميال). يقوم القمر Ùوبوس بالدوران حول المريخ اسرع من دوران المريخ حول Ù†Ùسه، مما يؤدي بقطر دوران القمر Ùوبوس حول المريخ للتناقص يوماً بعد يوم إلى أن ينتهي به الأمر إلى التÙتت ومن ثم الارتطام بكوكب المريخ. +القمر ديموس[عدل] + +ديموس هو أحد الأقمار التابعة لكوكب المريخ إلى جانب القمر Ùوبوس وهو عبارة عن قطعة صخرية صغيرة غير منتظمة الشكل لا يزيد طولها عن 12 كم (7 ميلا) ويتم دورته حول المريخ خلال 1.3 يوم. ولبعده عن الكوكب الأحمر، Ùإن قطر مدار القمر آخذ بالزيادة. ويبدو ديموس على شكل هرمي نوعاً ما. وتغشاه Ùوهات صدم متÙاوتة القدم. +استكشا٠المريخ[عدل] +سطح كوكب المريخ + + مقال تÙصيلي: تاريخ رصد المريخ + +هناك ما يقرب من 44 محاولة[7] إرسال مركبات Ùضائية للكوكب الأحمر من Ù‚Ùبل الولايات المتحدة، الاتحاد السوÙيتي، أوروبا، واليابان. قرابة ثلثين المركبات الÙضائية Ùشلت ÙÙŠ مهمّتها أما على الأرض، أو خلال رحلتها أو خلال هبوطها على سطح الكوكب الأحمر. من أنجح المحاولات إلى كوكب المريخ تلك التي سمّيت بـ "مارينر"ØŒ "برنامج الÙيكنج"ØŒ "سورÙيور"ØŒ "باثÙيندر"ØŒ Ùˆ"أوديسي". قامت المركبة "سورÙيور" بالتقاط صور لسطح الكوكب، الأمر الذي أعطى العلماء تصوراً بوجود ماء، إمّا على السطح أو تحت سطح الكوكب بقليل. وبالنسبة للمركبة "أوديسي"ØŒ Ùقد قامت بإرسال معلومات إلى العلماء على الأرض والتي مكّنت العلماء من الاستنتاج من وجود ماء متجمّد تحت سطح الكوكب ÙÙŠ المنطقة الواقعة عند 60 درجة جنوب القطب الجنوبي للكوكب. + +ÙÙŠ العام 2003ØŒ قامت وكالة الÙضاء الأوروبية بإرسال مركبة مدارية وسيارة تعمل عن طريق التحكم عن بعد، وقامت الأولى بتأكيد المعلومة المتعلقة بوجود ماء جليد وغاز ثاني أكسيد الكربون المتجمد ÙÙŠ منطقة القطب الجنوبي لكوكب المريخ. تجدر الإشارة إلى أن أول من توصل إلى تلك المعلومة هي وكالة الÙضاء الأمريكية وان المركبة الأوروبية قامت بتأكيد المعلومة. باءت محاولات الوكالة الأوروبية بالÙشل ÙÙŠ محاولة الاتصال بالسيارة المصاحبة للمركبة الÙضائية وأعلنت الوكالة رسمياً Ùقدانها للسيارة الآلية ÙÙŠ Ùبراير من من Ù†Ùس العام. لحقت وكالة الÙضاء الأمريكية الرّكب بإرسالها مركبتين Ùضائيتين وكان Ùرق الوقت بين المركبة الأولى والثانية، 3 أسابيع، وتمكن السيارات الآلية الأمريكية من إرسال صور مذهلة لسطح الكوكب وقامت السيارات بإرسال معلومات إلى العلماء على الأرض تÙيد، بل تؤكّد على تواجد الماء على سطح الكوكب الأحمر ÙÙŠ الماضي. ÙÙŠ الشكل أدناه خريطة لسطح المريخ تظهر أماكن تواجد أهم المركبات الأمريكية على سطحه. diff --git a/xpcom/tests/gtest/wikipedia/de-edit.txt b/xpcom/tests/gtest/wikipedia/de-edit.txt new file mode 100644 index 0000000000..2fbcd2d74d --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/de-edit.txt @@ -0,0 +1,487 @@ +gezählt, der vierte Planet im Sonnensystem und der äußere Nachbar der Erde. Er zählt zu den erdähnlichen (terrestrischen) Planeten. + +Sein Durchmesser ist mit knapp 6800 Kilometer etwa halb so groß wie der der Erde, sein Volumen beträgt gut ein Siebtel des Erdevolumens. Damit ist der Mars nach dem Merkur der zweitkleinste Planet des Sonnensystems, hat jedoch eine ausgeprägte Geologie und die höchsten Vulkane des Sonnensystems. Mit einer durchschnittlichen Entfernung von 228 Millionen Kilometern ist er rund 1,5-mal so weit von der Sonne entfernt wie die Erde. + +Die Masse des Mars beträgt etwa ein Zehntel der Erdmasse. Die Fallbeschleunigung auf seiner Oberfläche beträgt 3,69 m/s², dies entspricht etwa 38 % der irdischen. Mit einer Dichte von 3,9 g/cm³ weist der Mars den geringsten Wert der terrestrischen Planeten auf. Deshalb ist die Schwerkraft auf ihm sogar geringfügig niedriger als auf dem kleineren, jedoch dichteren Merkur. + +Der Mars wird oft auch als der Rote Planet bezeichnet. Diese Färbung geht auf Eisenoxid-Staub (Rost) zurück, der sich auf der Oberfläche und in der dünnen CO2-Atmosphäre verteilt hat. Seine orange- bis blutrote Farbe und seine Helligkeitsschwankungen sind auch verantwortlich für seine Namensgebung nach dem römischen Kriegsgott Mars.[3] + +In größeren Fernrohren deutlich sichtbar sind die zwei Polkappen und mehrere dunkle Ebenen, die sich im Frühjahr etwas verfärben. Fotos von Raumsonden zeigen eine teilweise mit Kratern bedeckte Oberfläche und starke Spuren früherer Tektonik (tiefe Canyons und fünf über 20 km hohe Vulkane). Marsroboter haben schon mehrere Gebiete geologisch untersucht. + +Der Mars besitzt zwei kleine, unregelmäßig geformte Monde, die 1877 entdeckt wurden: Phobos und Deimos (griechisch für Furcht und Schrecken). + +Das astronomische Symbol des Mars ist . + +Umlauf und Rotation +Umlaufbahn + +Der Mars bewegt sich in einem Abstand von 206,62 bis 249,23 Millionen Kilometern (1,38 AE bis 1,67 AE) in knapp 687 Tagen (etwa 1,9 Jahre) auf einer elliptischen Umlaufbahn um die Sonne. Die Bahnebene ist 1,85° gegen die Erdbahnebene geneigt. + +Seine Bahngeschwindigkeit schwankt mit dem Sonnenabstand zwischen 26,50 km/s und 21,97 km/s und beträgt im Mittel 24,13 km/s. Die Bahnexzentrizität beträgt 0,0935. Nach der Umlaufbahn des Merkurs ist das die zweitgrößte Abweichung von der Kreisform unter allen Planetenbahnen des Sonnensystems. + +Jedoch hatte der Mars in der Vergangenheit eine weniger exzentrische Umlaufbahn. Vor 1,35 Millionen Jahren betrug die Exzentrizität nur etwa 0,002, weniger als die der Erde heute.[4] Die Periode der Exzentrizität des Mars beträgt etwa 96.000 Jahre, die der Erde etwa 100.000 Jahre.[5] Mars hat jedoch noch einen längeren Zyklus der Exzentrizität mit einer Periode von 2,2 Millionen Jahren, der den mit der Periode von 96.000 Jahren überlagert. In den letzten 35.000 Jahren wurde die Umlaufbahn aufgrund der gravitativen Kräfte der anderen Planeten geringfügig exzentrischer. Der minimale Abstand zwischen Erde und Mars wird in den nächsten 25.000 Jahren noch ein wenig geringer werden.[6] + +Es gibt vier bekannte Asteroiden, die sich mit dem Mars die gleiche Umlaufbahn teilen (Mars-Trojaner). Sie befinden sich auf den Lagrangepunkten L4 und L5, das heißt, sie eilen dem Planeten um 60° voraus oder folgen ihm um 60° nach. +Rotation + +Der Mars rotiert in 24 Stunden und 37,4 Minuten um die eigene Achse (Siderischer Tag). In Bezug auf die Sonne ergibt sich daraus ein Marstag (auch Sol genannt) von 24:39:35. Die Äquatorebene des Planeten ist um 25,19° gegen seine Bahnebene geneigt (Erde 23,44°), somit gibt es Jahreszeiten ähnlich wie auf der Erde. Sie dauern jedoch fast doppelt so lang, weil das siderisches Marsjahr 687 Erdtage hat. Da die Bahn des Mars aber eine deutlich größere Exzentrizität aufweist, als die der Erde, und Mars-Nord tendenziell in Richtung der großen Bahn-Ellipsenachse weist, sind die Jahreszeiten unterschiedlich lang. In den letzten 300.000 Jahren variierte die Rotationsachse zwischen 22° und 26°. Zuvor lag sie mehrmals auch über 40°, wodurch starke Klimaschwankungen auftraten, es Vereisungen auch in der Äquatorregion gab und so die starken Bodenerosionen zu erklären sind. + +Der Nordpol des Mars weist zum nördlichen Teil des Sternbilds Schwan, womit sich die Richtung um etwa 40° von jener der Erdachse unterscheidet. Der marsianische Polarstern ist Deneb (mit leichter Abweichung der Achse Richtung Alpha Cephei).[7] + +Die Rotationsachse führt eine Präzessionsbewegung aus, deren Periode 170.000 Jahre beträgt (7× langsamer als die Erde). Aus diesem Wert, der mit Hilfe der Pathfinder-Mission festgestellt wurde, können die Wissenschaftler auf die Massenkonzentration im Inneren des Planeten schließen.[8] +Atmosphäre und Klima +Ãœber dem Marshorizont ist die Atmosphäre als dunstiger Schleier erkennbar. Links ist der einem Smiley ähnelnde Krater Galle zu sehen. Viking, 1976 + +Der Mars besitzt eine sehr dünne Atmosphäre. Dadurch ist der Atmosphärendruck sehr niedrig, und Wasser kann nicht in flüssiger Form auf der Marsoberfläche existieren, ausgenommen kurzzeitig in den tiefstgelegenen Gebieten. + +Da die dünne Marsatmosphäre nur wenig Sonnenwärme speichern kann, sind die Temperaturunterschiede auf der Oberfläche sehr groß. Die Temperaturen erreichen in Äquatornähe etwa 20 °C am Tag und sinken bis auf 85 °C in der Nacht. Die mittlere Temperatur des Planeten liegt bei etwa 55 °C. +Atmosphäre + Hauptartikel: Atmosphäre des Mars + +Die Marsatmosphäre besteht zu 95,3 % aus Kohlenstoffdioxid. Dazu kommen noch 2,7 % Stickstoff, 1,6 % Argon, geringe Anteile an Sauerstoff (1300 ppm) und Kohlenstoffmonoxid (800 ppm) sowie Spuren von Wasserdampf (210 ppm) und anderen Verbindungen oder Elementen. + +Die Atmosphäre ist ziemlich staubig. Sie enthält Teilchen mit etwa 1,5 µm im Durchmesser, die den Himmel über dem Mars in einem blassen gelb- bis orange-braunen Farbton erscheinen lassen. + +Der atmosphärische Druck beträgt auf der Oberfläche des Mars im Schnitt nur 6,36 hPa (Hektopascal). Im Vergleich zu durchschnittlich 1013 hPa auf der Erde sind dies nur 0,63 %, was dem Luftdruck der Erdatmosphäre in 35 Kilometern Höhe entspricht. Die Atmosphäre wurde wahrscheinlich im Laufe der Zeit vom Sonnenwind abgetragen und in den Weltraum mitgerissen. Dies wurde durch die geringe Schwerkraft des Planeten und sein schwaches Magnetfeld begünstigt, das kaum Schutz vor den hochenergetischen Teilchen der Sonne bietet. +Klima und Wetter +Eiswolken über Mars, aufgenommen von Mars Pathfinder + +Abhängig von den Jahreszeiten und der Intensität der Sonneneinstrahlung finden in der Atmosphäre dynamische Vorgänge statt. Die vereisten Polkappen sublimieren im Sommer teilweise, und kondensierter Wasserdampf bildet ausgedehnte Zirruswolken. Die Polkappen selbst bestehen aus Kohlendioxideis und Wassereis. + +2008 entdeckte man mit Hilfe der Raumsonde Mars Express Wolken aus gefrorenem Kohlendioxid. Sie befinden sich in bis zu 80 Kilometern Höhe und haben eine horizontale Ausdehnung von bis zu 100 km. Sie absorbieren bis zu 40 % des einstrahlenden Sonnenlichts und können damit die Temperatur der Oberfläche um bis zu 10 °C verringern.[9] + +Mit Hilfe des Lasers LIDAR der Raumsonde Phoenix wurde 2009 entdeckt, dass in der zweiten Nachthälfte fünfzig Tage nach der Sonnenwende winzige Eiskristalle aus dünnen Zirruswolken auf den Marsboden fielen.[10] +Jahreszeiten +Staubsturm in der Syria-Region, fotografiert von Mars Global Surveyor im Mai 2003 + +Hätte Mars eine erdähnliche Umlaufbahn, würden die Jahreszeiten aufgrund der Achsenneigung ähnlich denen der Erde sein. Jedoch führt die vergleichsweise große Exzentrizität seines Orbits zu einer beträchtlichen Auswirkung auf die Jahreszeiten. Der Mars befindet sich während des Sommers in der Südhalbkugel und des Winters in der nördlichen Hemisphäre nahe dem Perihel seiner Bahn. Nahe dem Aphel ist in der südlichen Hemisphäre Winter und in der nördlichen Sommer. + +Das hat zur Folge, dass die Jahreszeiten in der südlichen Hemisphäre viel deutlicher ausgeprägt sind als in der nördlichen, wo das Klima ausgeglichener ist, als es sonst der Fall wäre. Die Sommertemperaturen im Süden können bis zu 30 °C höher sein als die vergleichbaren Temperaturen im Sommer des Nordens.[11] Die Jahreszeiten sind aufgrund der Exzentrizität der Umlaufbahn des Mars unterschiedlich lang. Auf der Nordhalbkugel dauert der Frühling 199,6, der Sommer 181,7, der Herbst 145,6 und der Winter 160,1 irdische Tage.[12] +Wind und Stürme + +Aufgrund der starken Tag-Nacht-Temperaturschwankungen der Oberfläche gibt es tägliche Morgen- und Abendwinde.[13] + +Während des Marsfrühjahrs können in den ausgedehnten flachen Ebenen heftige Staubstürme auftreten, die mitunter große Teile der Marsoberfläche verhüllen. Die Aufnahmen von Marssonden zeigen auch Windhosen, die über die Marsebenen ziehen und auf dem Boden dunkle Spuren hinterlassen. Stürme auf dem Mars haben wegen der sehr dünnen Atmosphäre eine wesentlich geringere Kraft als Stürme auf der Erde. Selbst bei hohen Windgeschwindigkeiten werden nur kleine Partikel (Staub) aufgeweht.[14] Allerdings verbleibt aufgewehter Staub auf dem Mars wesentlich länger in der Atmosphäre als auf der Erde, da es keine Niederschläge gibt, die die Luft reinigen, und zudem die Gravitation geringer ist. + +Staubstürme treten gewöhnlich während des Perihels auf, da der Planet zu diesem Zeitpunkt 40 Prozent mehr Sonnenlicht empfängt als während des Aphels. Während des Aphels bilden sich in der Atmosphäre Wolken aus Wassereis, die ihrerseits mit den Staubpartikeln interagieren und so die Temperatur auf dem Planeten beeinflussen.[15] Die Windgeschwindigkeiten in der oberen Atmosphäre können bis zu 650 km/h erreichen, auf dem Boden immerhin fast 400 km/h.[16] +Gewitter + +Bei heftigen Staubstürmen scheint es auch zu Gewittern zu kommen. Im Juni 2006 untersuchten Forscher mit einem Radioteleskop den Mars und stellten im Mikrowellenbereich Strahlungsausbrüche fest, wie sie bei Blitzen auftreten. In der Region, in der man die Strahlungsimpulse beobachtet hat, herrschte zu der Zeit ein heftiger Staubsturm mit hohen Staubwolken. Sowohl der beobachtete Staubsturm wie auch das Spektrum der Strahlungsimpulse deuten auf ein Staubgewitter mit Blitzen bzw. großen Entladungen hin.[17][18] +Oberfläche +Typisches Felsengestein auf der Marsoberfläche (aufgenommen von Mars Pathfinder) + +Die Oberfläche des Mars beträgt etwa ein Viertel der Erdoberfläche. Sie entspricht mit 144 Mio. km2 ungefähr der Gesamtoberfläche aller Kontinente der Erde (149 Mio. km2). + +Die rote Färbung seiner Oberfläche verdankt der Planet dem Eisenoxid-Staub, der sich auf der Oberfläche und in der Atmosphäre verteilt hat. Somit ist der Rote Planet ein rostiger Planet. + +Seine beiden Hemisphären sind sehr verschieden. Die Südhalbkugel stellt ein riesiges Hochland dar, das durchschnittlich 23 km über dem globalen Nullniveau liegt und ausgedehnte Schildvulkane aufweist. Die vielen Einschlagkrater belegen sein hohes Alter von fast 4 Milliarden Jahren. Dem steht die geologisch junge, fast kraterlose nördliche Tiefebene gegenüber. Sie liegt 35 km unter dem Nullniveau und hat ihre ursprüngliche Struktur durch noch ungeklärte geologische Prozesse verloren. Auslöser war möglicherweise eine gewaltige Kollision in der Frühzeit des Planeten. +Gesteine + Hauptartikel: Marsgestein + +An den Landestellen der Marssonden sind Gesteinsbrocken, sandige Böden und Dünen sichtbar. Die Marsgesteine weisen an der Oberfläche eine blasenartige Struktur auf und ähneln in ihrer Zusammensetzung irdischen Basalten, was bereits vor Jahrzehnten aus den auf der Erde (Antarktis) gefundenen Marsmeteoriten erschlossen wurde. Die roten Böden sind offensichtlich durch die Verwitterung von eisenhaltigen, vulkanischen Basalten entstanden. + +Die Pathfinder-Sonde fand 1997 außer verschiedensten Basalten auch quarzreichere Tiefengesteine ähnlich dem südamerikanischen Andesit, ferner das aus der Tiefe stammende Olivin und runde Kiesel aus Konglomeraten. Weitverbreitet ist metamorpher Regolith (ähnlich wie am Mond) und äolische Sedimente. Vereinzelt verwehter Sand aus schwefelhaltigen Staubteilchen. +Areografie + +Die kartografische Darstellung und Beschreibung der Marsoberfläche ist die Areografie, von Ares (, griechisch für Mars) und grafein (, griechisch für beschreiben). Die Geologie des Mars wird mitunter dementsprechend als Areologie bezeichnet. + +Zur Festlegung von Positionen auf der Marsoberfläche dienen areografische Koordinaten, die definiert sind wie geografische Breite und Länge. +Topografische Hemisphären +Topografische Karte des Mars. Die blauen Regionen befinden sich unterhalb des festgelegten Nullniveaus, die roten oberhalb + +Auffallend ist die Dichotomie, die Zweiteilung, des Mars. Die nördliche und die südliche Hemisphäre unterscheiden sich deutlich, wobei man von den Tiefebenen des Nordens und den Hochländern des Südens sprechen kann. Der mittlere Großkreis, der die topografischen Hemisphären voneinander trennt, ist rund 40° gegen den Äquator geneigt. Der Massenmittelpunkt des Mars ist gegenüber dem geometrischen Mittelpunkt um etwa drei Kilometer in Richtung der nördlichen Tiefebenen versetzt. + +Auf der nördlichen Halbkugel sind flache sand- und staubbedeckte Ebenen vorherrschend, die Namen wie Utopia Planitia oder Amazonis Planitia erhielten. Dunkle Oberflächenmerkmale, die in Teleskopen sichtbar sind, wurden einst für Meere gehalten und erhielten Namen wie Mare Erythraeum, Mare Sirenum oder Aurorae Sinus. Diese Namen werden heute nicht mehr verwendet. Die ausgedehnteste dunkle Struktur, die von der Erde aus gesehen werden kann, ist Syrtis Major, die große Syrte. + +Die südliche Halbkugel ist durchschnittlich sechs Kilometer höher als die nördliche und besteht aus geologisch älteren Formationen. Die Südhalbkugel ist zudem stärker verkratert, wie zum Beispiel in der Hochlandregion Arabia Terra. Unter den zahlreichen Einschlagkratern der Südhalbkugel befindet sich auch der größte Marskrater, Hellas Planitia, die Hellas-Tiefebene. Das Becken misst im Durchmesser bis zu 2100 km. In seinem Innern maß Mars Global Surveyor 8180 m unter Nullniveau unter dem Durchschnittsniveau des Mars den tiefsten Punkt auf dem Planeten. Der zweitgrößte Einschlagkrater des Mars, Chryse Planitia, liegt im Randbereich der nördlichen Tiefländer. +Ãœbersichtskarte des Mars mit den größten Regionen + +Die deutlichen Unterschiede der Topografie können durch innere Prozesse oder aber ein Impaktereignis verursacht worden sein. In letzterem Fall könnte in der Frühzeit der Marsentstehung ein größerer Himmelskörper, etwa ein Asteroid, auf der Nordhalbkugel eingeschlagen sein und die silikatische Kruste durchschlagen haben. Aus dem Innern könnte Lava ausgetreten sein und das Einschlagbecken ausgefüllt haben. + +Wie sich gezeigt hat, hat die Marskruste unter den nördlichen Tiefebenen eine Dicke von etwa 40 km, die im Gegensatz zum stufenartigen Ãœbergang an der Oberfläche nur langsam auf 70 km bis zum Südpol hin zunimmt. Dies könnte ein Indiz für innere Ursachen der Zweiteilung sein. +Oberflächenstrukturen +Gräben +In der Bildmitte liegt das System der Mariner-Täler. Ganz links die Tharsis-Vulkane (Bildmosaik von Viking 1 Orbiter, 1980) + +Südlich am Äquator und fast parallel zu ihm verlaufen die Valles Marineris (die Mariner-Täler), das größte bekannte Grabensystem des Sonnensystems. Es erstreckt sich über 4000 km und ist bis zu 700 km breit und bis zu 7 km tief. Es handelt sich um einen gewaltigen tektonischen Bruch. In seinem westlichen Teil, dem Noctis Labyrinthus, verästelt er sich zu einem chaotisch anmutenden Gewirr zahlreicher Schluchten und Täler, die bis zu 20 km breit und bis zu 5 km tief sind. + +Noctis Labyrinthus liegt auf der östlichen Flanke des Tharsis-Rückens, einer gewaltigen Wulst der Mars-Lithosphäre quer über dem Äquator mit einer Ausdehnung von etwa 4000 mal 3000 Kilometern und einer Höhe von bis zu rund 10 Kilometern über dem nördlichen Tiefland. Die Aufwölbung ist entlang einer offenbar zentralen Bruchlinie von drei sehr hohen, erloschenen Schildvulkanen besetzt: Ascraeus Mons, Pavonis Mons und Arsia Mons. Der Tharsis-Rücken und die Mariner-Täler dürften in ursächlichem Zusammenhang stehen. Wahrscheinlich drückten vulkanische Kräfte die Oberfläche des Planeten in dieser Region empor, wobei die Kruste im Bereich des Grabensystems aufgerissen wurde. Eine Vermutung besagt, dass diese vulkanische Tätigkeit durch ein Impaktereignis ausgelöst wurde, dessen Einschlagstelle das Hellas-Becken auf der gegenüberliegenden Seite des Mars sei. 2007 wurden im Nordosten von Arsia Mons sieben tiefere Schächte mit 100 bis 250 Metern Durchmesser entdeckt. +Vulkane +Olympus Mons, der mit 26 km höchste Berg im Sonnensystem +Die komplexe Caldera des Olympus Mons + +Dem Hellas-Becken exakt gegenüber befindet sich der Vulkanriese Alba Patera. Er ragt unmittelbar am Nordrand des Tharsis-Rückens rund 6 km über das umgebende Tiefland und ist mit einem Basisdurchmesser von über 1200 km der flächengrößte Vulkan im Sonnensystem. Patera ist die Bezeichnung für unregelmäßig begrenzte Vulkane mit flachem Relief. Alba Patera ist anscheinend einmal durch einen Kollaps in sich zusammengefallen. + +Unmittelbar westlich neben dem Tharsis-Rücken und südwestlich von Alba Patera ragt der höchste Vulkan, Olympus Mons, 26,4 km über die Umgebung des nördlichen Tieflands. Mit einer Gipfelhöhe von etwa 21,3 km über dem mittleren Null-Niveau ist er die höchste bekannte Erhebung im Sonnensystem. + +Ein weiteres, wenn auch weniger ausgedehntes vulkanisches Gebiet ist die Elysium-Region nördlich des Äquators mit den Schildvulkanen Elysium Mons, Hecates Tholus und Albor Tholus. +Stromtäler +Kasei Vallis, das größte Stromtal des Mars + +Auf der Marsoberfläche verlaufen Stromtäler, die mehrere hundert Kilometer lang und mehrere Kilometer breit sein können. Die heutigen Trockentäler beginnen ziemlich abrupt und haben keine Zuflüsse. Die meisten entspringen an den Enden der Mariner-Täler und laufen nördlich im Chryse-Becken zusammen. In den Tälern erheben sich mitunter stromlinienförmige Inseln. Sie weisen auf eine vergangene Flutperiode hin, bei der über einen geologisch relativ kurzen Zeitraum große Mengen Wasser geflossen sein müssen. Es könnte sich um Wassereis gehandelt haben, das sich unter der Marsoberfläche befand, danach durch vulkanische Prozesse geschmolzen wurde und dann abgeflossen ist. + +Darüber hinaus finden sich an Abhängen und Kraterrändern Spuren von Erosionen, die möglicherweise ebenfalls durch flüssiges Wasser verursacht wurden. + +2006 proklamierte die NASA einen einzigartigen Fund: Auf einigen NASA-Fotografien, die im Abstand von sieben Jahren vom Mars gemacht wurden, lassen sich Veränderungen auf der Marsoberfläche erkennen, die eine gewisse Ähnlichkeit mit Veränderungen durch fließendes Wasser haben. Innerhalb der NASA wird nun diskutiert, ob es neben Wassereis auch flüssiges Wasser geben könnte.[19] +Delta-Strukturen + +In alten Marslandschaften, z. B. im Eberswalde-Krater auf der Südhalbkugel oder in der äquatornahen Hochebene Xanthe Terra, finden sich typische Ablagerungen einstiger Flussdeltas. +Tharsis-Tholus-Streifen, aufgenommen mit der Hirise-Kamera des Mars Reconnaissance Orbiters. Der Streifen ist links in der Mitte zu sehen. Rechts sind die Ausläufer von Tharsis Tholus. + +Seit längerem vermutet man, dass die tief eingeschnittenen Täler in Xanthe Terra einst durch Flüsse geformt wurden. Wenn ein solcher Fluss in ein größeres Becken, beispielsweise einen Krater, mündete, lagerte er erodiertes Gesteinsmaterial als Sedimente ab. Die Art der Ablagerung hängt dabei von der Natur dieses Beckens ab: Ist es mit dem Wasser eines Sees gefüllt, so bildet sich ein Delta. Ist das Becken jedoch trocken, so verliert der Fluss an Geschwindigkeit und versickert langsam. Es bildet sich ein sogenannter Schwemmkegel, der sich deutlich vom Delta unterscheidet. + +Jüngste Analysen von Sedimentkörpern auf Basis von Orbiter-Fotos weisen an zahlreichen Stellen in Xanthe Terra auf Deltas hin Flüsse und Seen waren in der Marsfrühzeit also recht verbreitet.[20] +Dark Slope Streaks + +Dunkle Streifen an Hängen sind auf dem Mars häufig zu sehen. Sie treten an steilen Hängen von Kratern, Mulden und Tälern auf und werden mit zunehmendem Alter heller. Manchmal beginnen sie in einem kleinen punktförmigen Bereich und werden dann zunehmend breiter. Man beobachtete, dass sie sich um Hindernisse, wie Mulden, weiterbewegen. + +Es wird angenommen, dass die Farbe von dunklen darunterliegenden Schichten stammt, die durch Lawinen von hellem Staub freigelegt werden. Es wurden jedoch auch andere Hypothesen aufgestellt, wie Wasser oder sogar der Wuchs von Organismen. Das Interessanteste an diesen dunklen Streifen (engl. dark slope streaks) ist, dass sie sich auch heute noch bilden.[21] +Chaotische Gebiete + +Auf dem Mars gibt es zahlreiche Regionen mit einer Häufung von unterschiedlich großen Gesteinsbrocken und tafelbergähnlichen Erhebungen. Sie werden auch chaotische Gebiete genannt. Ariadnes Colles ist mit einer Fläche von etwa 29.000 km² so ein Gebiet. Es liegt im Terra Sirenum, einem südlichen Hochland des Mars. Dabei haben die Blöcke Ausmaße von einem bis zu zehn Kilometern Ausdehnung. Die größeren Blöcke ähneln Tafelbergen mit Erhebungen von bis zu 300 Metern. + +Es treten hierbei riefenartige Strukturen und Runzelrücken (engl. wrinkle ridges) auf. Die Ursachen dafür sind vulkanisch-tektonische Bewegungen.[22] +Gesteinsschichten und Ablagerungen +Salzlager + +Mit Hilfe der Sonde Mars Odyssey wies die NASA ein umfangreiches Salzlager in den Hochebenen der Südhalbkugel des Mars nach. Vermutlich entstanden diese Ablagerungen durch Oberflächenwasser vor etwa 3,5 bis 3,9 Milliarden Jahren.[23] +Carbonatvorkommen + +Mit Hilfe der Compact Reconnaissance Imaging Spectrometer for Mars (CRISM) an Bord der NASA-Sonde Mars Reconnaissance Orbiter konnten Wissenschaftler Carbonat-Verbindungen in Gesteinsschichten rund um das knapp 1500 Kilometer große Isidis-Einschlagbecken nachweisen. Demnach wäre das vor mehr als 3,6 Milliarden Jahren existierende Wasser hier nicht sauer, sondern eher alkalisch oder neutral gewesen. + +Carbonatgestein entsteht, wenn Wasser und Kohlendioxid mit Kalzium, Eisen oder Magnesium in vulkanischem Gestein reagiert. Bei diesem Vorgang wird Kohlendioxid aus der Atmosphäre in dem Gestein eingelagert. Dies könnte bedeuten, dass der Mars früher eine dichte kohlendioxidreiche Atmosphäre hatte, wodurch ein wärmeres Klima möglich wurde, in dem es auch flüssiges Wasser gab.[24] + +Mit Hilfe von Daten des MRO wurden 2010 Gesteine entdeckt, die durch kosmische Einschläge aus der Tiefe an die Oberfläche befördert worden waren. Anhand ihrer spezifischen spektroskopischen Fingerabdrücke konnte festgestellt werden, dass sie hydrothermal (unter Einwirkung von Wasser) verändert wurden. Neben diesen Karbonat-Mineralen wurden auch Silikate nachgewiesen, die vermutlich auf die gleiche Weise entstanden sind. Dieser neue Fund beweise, dass es sich dabei nicht um örtlich begrenzte Vorkommen handele, sondern dass Karbonate in einer sehr großen Region des frühen Mars entstanden seien.[25] +Hämatitkügelchen +Hämatitkügelchen auf dem Felsen Berry Bowl + +Die Marssonde Opportunity fand im Gebiet des Meridiani Planum millimetergroße Kügelchen des Eisenminerals Hämatit. Diese könnten sich vor Milliarden Jahren unter Einwirkung von Wasser abgelagert haben. Darüber hinaus wurden Minerale gefunden, die aus Schwefel-, Eisen- oder Bromverbindungen aufgebaut sind, wie zum Beispiel Jarosit. Auf der entgegengesetzten Hemisphäre[26] des Mars fand die Sonde Spirit in den Columbia Hills das Mineral Goethit, das ausschließlich unter dem Einfluss von Wasser gebildet werden kann. +Kieselsäure + +Forscher entdeckten 2010 mit Hilfe von MRO Ablagerungen auf einem Vulkankegel, die von Wasser verursacht wurden. Sie konnten das Mineral als Kieselsäurehydrat identifizieren, das nur in Verbindung mit Wasser entstanden sein kann. Die Wissenschaftler nehmen an, dass, falls es auf dem Mars Leben gegeben hat, es sich dort in der hydrothermalen Umgebung am längsten hätte halten können.[27] +Polkappen +Die Nordpolregion, aufgenommen von Mars Global Surveyor + Hauptartikel: Polkappen des Mars + +Der Mars besitzt zwei auffällige Polkappen, die zum größten Teil aus gefrorenem Kohlendioxid (Trockeneis) sowie einem geringen Anteil an Wassereis zusammengesetzt sind. Die nördliche Polkappe hat während des nördlichen Marssommers einen Durchmesser von rund 1000 Kilometern. Ihre Dicke wird auf 5 km geschätzt. Die südliche Polkappe ist mit 350 km Durchmesser und einer Dicke von 1½ km weniger ausgedehnt. Die Polarkappen zeigen spiralförmige Einschnitte, deren Entstehung bislang nicht geklärt ist. + +Wenn im Sommer die jeweiligen Polkappen teilweise abschmelzen, werden darunter geschichtete Ablagerungen sichtbar, die möglicherweise abwechselnd aus Staub und Eis zusammengesetzt sind. Im Marswinter nimmt der Durchmesser der dann jeweils der Sonne abgewandten Polkappe durch ausfrierendes Kohlendioxid wieder zu. + +Da ein größerer, stabilisierender Mond fehlt, taumelt der Mars mit einer Periode von etwa 5 Millionen Jahren. Die Polarregionen werden daher immer wieder so stark erwärmt, dass das Wassereis schmilzt. Durch das abfließende Wasser entstehen die Riemen und Streifen an den Polkappen. +Wasservorkommen + +Der Mars erscheint heute als trockener Wüstenplanet. Die bislang vorliegenden Ergebnisse der Marsmissionen lassen jedoch den Schluss zu, dass die Marsatmosphäre in der Vergangenheit (vor Milliarden Jahren) wesentlich dichter war und auf der Oberfläche des Planeten reichlich flüssiges Wasser vorhanden war. +Eisvorkommen an den Polen +Die Südpolregion, aufgenommen von Viking Orbiter + +Durch Radarmessungen mit der Sonde Mars Express wurden in der Südpolarregion, dem Planum Australe, Ablagerungsschichten mit eingelagertem Wassereis entdeckt, die weit größer und tiefreichender als die hauptsächlich aus Kohlendioxideis bestehende Südpolkappe sind. Die Wassereisschichten bedecken eine Fläche, die fast der Größe Europas entspricht, und reichen in eine Tiefe von bis zu 3,7 Kilometern. Das in ihnen gespeicherte Wasservolumen wird auf bis zu 1,6 Millionen Kubikkilometer geschätzt circa zwei Drittel des irdischen Grönlandeispanzers was laut der Europäischen Weltraumorganisation (ESA) ausreichen würde, die Marsoberfläche mit einer etwa 11 Meter dicken Wasserschicht zu bedecken.[28] +Weitere Eisvorkommen +Beobachtete Veränderungen könnten Anzeichen für fließendes Wasser innerhalb der letzten Jahre sein.[19] + +Die schon lange gehegte Vermutung, dass sich unter der Oberfläche des Mars Wassereis befinden könnte, erwies sich 2005 durch Entdeckungen der ESA-Sonde Mars Express als richtig. + +Geologen gehen von wiederkehrenden Vereisungsperioden auf dem Mars aus, ähnlich irdischen Eiszeiten. Dabei sollen Gletscher bis in subtropische Breiten vorgestoßen sein. Die Forscher schließen dies aus Orbiter-Fotos, die Spuren einstiger Gletscher in diesen äquatornahen Gebieten zeigen. Zusätzlich stützen auch Radarmessungen aus der Umlaufbahn die Existenz beträchtlicher Mengen an Bodeneis in ebendiesen Gebieten. Diese Bodeneisvorkommen werden als Reste solcher Mars-Eiszeiten gedeutet.[29] + +Auf der Europäischen Planetologenkonferenz EPSC im September 2008 in Münster wurden hochauflösende Bilder des Mars Reconnaissance Orbiters der NASA vorgestellt, die jüngste Einschlagkrater zeigen. Wegen der sehr dünnen Atmosphäre stürzen die Meteoriten praktisch ohne Verglühen auf die Marsoberfläche. Die fünf neuen Krater, die nur drei bis sechs Meter Durchmesser und eine Tiefe von 30 bis 60 cm aufweisen, wurden in mittleren nördlichen Breiten gefunden. Sie zeigen an ihrem Boden ein gleißend weißes Material. Wenige Monate später waren die weißen Flecken durch Sublimation verschwunden. Damit erhärten sich die Hinweise, dass auch weit außerhalb der Polgebiete Wassereis dicht unter der Marsoberfläche begraben ist.[30][31] +Flüssiges Wasser + +Unter der Kryosphäre des Mars werden große Mengen flüssigen Wassers vermutet. Nahe oder an der Oberfläche ist es für flüssiges Wasser zu kalt, und Eis würde langsam verdunsten, da der Partialdruck von Wasser in der Marsatmosphäre zu gering ist. + +Es gibt jedoch Hinweise, dass die Raumsonde Phoenix Wassertropfen auf der Oberfläche entdeckt habe. Dabei könnten Perchlorate als Frostschutz wirken. Diese Salze haben die Eigenschaft, Wasser anzuziehen. Dies kann auch Wasserdampf aus der Atmosphäre sein. Bei ausreichender Konzentration der Salze könnte Wasser sogar bis 70 °C flüssig bleiben. Durch eine Durchmischung mit Perchloraten könnte Wasser auch unter der Oberfläche in flüssigem Zustand vorhanden sein.[32] 2010 fanden Forscher der Uni Münster Belege dafür, dass zumindest im Frühjahr und in Kratern wie dem Russell-Krater flüssiges Wasser auf der Marsoberfläche existiert. Auf Fotos, die vom Mars Reconnaissance Orbiter aufgenommen wurden, entdeckten sie an steilen Hängen Erosionsrinnen, die sich zwischen November 2006 und Mai 2009 verlängert hatten. Dass die Rinnen nach unten dünner werden, deuten die Forscher als Versickern,[33] andere als Verdunsten.[34] + +Eine alternative Erklärung für die Erosionsrinnen schlugen Wissenschaftler der NASA 2010 vor: Kohlendioxid, das sich im marsianischen Winter bei unter -100 °C aus der Atmosphäre an den Berghängen als Trockeneis ansammelt, bei Erwärmung des Planeten als sublimiertes Gas die Hänge hinab"fließt" und dabei Staub erodiert.[35][36] + +Mit dem abbildenden Spektrometer (CRISM) des Mars Reconnaissance Orbiters konnten Spektren von aktiven (jahreszeitlich dunkleren) Rinnen gewonnen werden, deren Auswertung, 2015 veröffentlicht,[37] Magnesiumperchlorat, Magnesiumchlorat und Natriumperchlorat ergaben. +Siehe auch: Extraterrestrischer Ozean +Innerer Aufbau +Illustration des vermuteten Marsaufbaus + +Ãœber den inneren Aufbau des Mars ist nur wenig bekannt, da bislang nur begrenzt seismische Messungen vorgenommen werden konnten. + +Sein Inneres gliedert sich ähnlich dem Schalenaufbau der Erde in eine Kruste, einen Gesteinsmantel und einen Kern, der überwiegend aus Eisen und zu etwa 14 bis 17 Prozent aus Schwefel besteht. Der Kern beinhaltet etwa doppelt so viele leichte Elemente wie der Erdkern. Deshalb ist die Dichte des Kerns niedriger, als es bei einem reinen Eisenkern der Fall wäre.[38] + +Laut neueren experimentellen Simulationen der Bedingungen in der Ãœbergangszone zwischen Mantel und Kern (Messungen des Mars Global Surveyor ergaben eine Temperatur von 1500 Grad Celsius und einen Druck von 23 Gigapascal) hat der Kern des Mars im Unterschied zu dem der Erde keinen inneren festen Bereich, sondern ist vollständig flüssig.[39] Dies belegt auch die Analyse der Bahndaten des Mars Global Surveyor. Dabei konnte nachgewiesen werden, dass der Mars einen flüssigen Kern mit einem Radius zwischen 1520 und 1840 km besitzt und damit eine höhere Temperatur hat, als zuvor angenommen wurde. + +Der Kern ist von einem Mantel aus Silicaten umgeben, der viele der tektonischen und vulkanischen Merkmale des Planeten formte, nun aber inaktiv zu sein scheint. Die durchschnittliche Dicke der Planetenkruste beträgt etwa 50 km, mit einem Maximum von 125 km.[38] Im Vergleich dazu ist die Erdkruste mit einer Dicke von durchschnittlich 40 km nur etwa ein Drittel so dick, wenn man die relative Größe der beiden Planeten berücksichtigt. +Magnetfeld +Magnetisierung des Mars Rot und Blau kennzeichnen entgegengesetzte Richtungen des Magnetfelds, ein Drittel der Südhalbkugel + +Anders als die Erde und der Merkur besitzt der Mars kein globales Magnetfeld mehr, seit er es ca. 500 Millionen Jahre nach seiner Entstehung verlor. Vermutlich erlosch es, als der Zerfall radioaktiver Elemente nicht mehr genügend Wärmeenergie produzierte, um im flüssigen Kern Konvektionsströmungen anzutreiben. Weil der Mars keinen festen inneren Kern besitzt, konnte er den Dynamo-Effekt nicht auf die gleiche Art aufbauen wie die Erde. + +Dennoch ergaben Messungen einzelne und sehr schwache lokale Magnetfelder. Die Messung des Magnetfeldes wird erschwert durch die Magnetisierung der Kruste mit Feldstärken von bis zu 220 Nanotesla und durch externe Magnetfelder mit Stärken zwischen wenigen Nanotesla und bis zu 100 Nanotesla, die durch die Wechselwirkung des Sonnenwindes mit der Marsatmosphäre entstehen und zeitlich sehr stark variieren. Nach den Analysen der Daten des Mars Global Surveyor konnte die Stärke des Magnetfeldes trotzdem sehr genau bestimmt werden sie liegt bei weniger als 0,5 Nanotesla gegenüber 30 bis 60 Mikrotesla des Erdmagnetfeldes. + +Messungen von Magnetfeldlinien durch Mars Global Surveyor ergaben, dass Teile der planetaren Kruste durch das einstige Magnetfeld stark magnetisiert sind, aber mit unterschiedlicher Orientierung, wobei gleichgerichtete Bänder von etwa 1000 km Länge und 150 km Breite auftreten. Ihre Größe und Verteilung erinnert an die streifenförmigen Magnetanomalien auf den Ozeanböden der Erde. Durch sie wurde die Theorie der Plattentektonik gestützt, weshalb 1991 auch eine ähnliche Theorie für den Mars entwickelt wurde. Magnetische Beobachtungen auf dem Mars sind jedoch noch nicht detailliert genug, um sichere Schlussfolgerungen zu erlauben oder gar die Theorie zu bestätigen. + +Möglicherweise werden bei der mit der Zeit zwangsläufigen Abkühlung des Marskerns durch die damit einsetzende Auskristallisation des Eisens und die freigesetzte Kristallisationswärme wieder Konvektionen einsetzen, die ausreichen, dass der Planet in ein paar Milliarden Jahren wieder über ein globales Magnetfeld in alter Stärke verfügt.[39] +Monde +Die Umlaufbahnen von Phobos und Deimos +Phobos (oben) und Deimos (unten) im Größenvergleich + +Zwei kleine Monde, Phobos und Deimos (griech. Furcht und Schrecken), umkreisen den Mars. Sie wurden 1877 von dem US-amerikanischen Astronomen Asaph Hall entdeckt und nach den in der Ilias überlieferten beiden Begleitern, die den Wagen des Kriegsgottes Ares (lat. Mars) ziehen, benannt. + +Phobos (Durchmesser 26,8 × 22,4 × 18,4 km) und Deimos (Durchmesser 15,0 × 12,2 × 10,4 km) sind zwei unregelmäßig geformte Felsbrocken. Möglicherweise handelt es sich um Asteroiden, die vom Mars eingefangen wurden. Phobos große Halbachse beträgt 9.376 km, diejenige von Deimos 23.459 km. Phobos ist damit kaum mehr als 6.000 km von der Oberfläche des Mars entfernt, der Abstand ist geringer als der Durchmesser des Planeten. + +Die periodischen Umlaufbewegungen der beiden Monde befinden sich mit der Größe von 0,31891 (Phobos) und 1,262 Tagen (Deimos) zueinander in einer 1:4-Bahnresonanz. + +Die Umlaufzeit von Phobos ist kürzer als die Rotationszeit von Mars. Der Mond kommt dem Planeten durch die Gezeitenwechselwirkung auf einer Spiralbahn langsam immer näher und wird schließlich auf diesen stürzen oder durch die Gezeitenkräfte auseinandergerissen werden, so dass er für kurze Zeit zu einem Marsring wird. Für ihn berechneten DLR-Forscher, basierend auf neueren Daten der europäischen Raumsonde Mars Express, dass dies in ca. 50 Millionen Jahren geschehen wird. Deimos wird dagegen in einer noch ferneren Zukunft dem Mars entfliehen. Er driftet durch die Gezeitenwechselwirkung langsam nach außen, wie alle Monde, die langsamer (und nicht retrograd) um einen Planeten kreisen, als dieser rotiert. + +Ihre Existenz war schon lange vorher mehrmals literarisch beschrieben worden, zuletzt von Voltaire, der in seiner 1750 erschienenen Geschichte Micromégas über zwei Marsmonde schreibt. Es ist wahrscheinlich, dass Voltaire diese Idee von Jonathan Swift übernahm, dessen Buch Gullivers Reisen 1726 erschienen war. Darin wird im dritten Teil beschrieben, die Astronomen des Landes Laputa hätten ebenfalls zwei kleinere Sterne oder Satelliten entdeckt, die um den Mars kreisen, wovon der innere vom Zentrum des Hauptplaneten genau drei seiner Durchmesser entfernt ist und der äußere fünf. Es wird vermutet, dass Swift von einer Fehlinterpretation Johannes Keplers gehört hatte. Der hatte das Anagramm, das Galileo Galilei 1609 an ihn schickte, um ihm die Entdeckung der Phasen der Venus mitzuteilen, als die Entdeckung zweier Marsmonde aufgefasst. +Entstehungsgeschichte +Datei:Mars.ogvMediendatei abspielen +Animation, welche die Topographie des Mars zeigt. Olympus Mons Mariner-Täler Mars Südpol Hellas-Becken Mars Nordpol + +Anhand der astrogeologischen Formationenvielfalt und der Verteilung von Einschlagskratern kann ein Großteil der Geschichte des Planeten abgeleitet werden. Der Mars entstand, wie die übrigen Planeten des Sonnensystems, vor etwa 4,5 Milliarden Jahren durch Zusammenballung kleinerer Körper, sogenannter Planetesimale, innerhalb der protoplanetaren Scheibe zu einem Protoplaneten. Vor 4 Milliarden Jahren bildete der im Innern noch glutflüssige planetare Körper eine feste Gesteinskruste aus, die einem heftigen Bombardement von Asteroiden und Kometen ausgesetzt war. +Noachische Periode + +Die ältesten der heute noch vorhandenen Formationen, wie das Hellas-Becken, und die verkraterten Hochländer, wie Noachis Terra, wurden vor 3,8 bis 3,5 Milliarden Jahren, in der so genannten Noachischen Periode, gebildet. In dieser Periode setzte die Zweiteilung der Marsoberfläche ein, wobei die nördlichen Tiefländer gebildet wurden. Durch starke vulkanische Eruptionen wurden weite Teile des Planeten von Ablagerungen aus vulkanischer Lava und Asche bedeckt. Diese wurden an vielen Stellen durch Wind und Wasser wieder abgetragen und ließen ein Netzwerk von Tälern zurück. +Hesperianische Periode + +Das geologische Mittelalter des Mars wird als Hesperianische Periode bezeichnet. Sie umfasst den Zeitraum von vor 3,5 bis 1,8 Milliarden Jahren. In dieser Periode ergossen sich riesige Lavamengen aus ausgedehnten Spalten in der Marskruste und bildeten weite Ebenen, wie Hesperia Planum. Es entstanden auch die ältesten Vulkane der Tharsis- und der Elysium-Region, wobei die Gesteinskruste stark verformt wurde und sich das Grabensystem der Mariner-Täler öffnete. Es bildeten sich die gewaltigen Stromtäler, in denen große Wassermengen flossen und sich stellenweise aufstauten. + +Es entwickelte sich auf dem Mars ein Wasserkreislauf. Im Unterschied zur Erde gab es jedoch keinen Wetterzyklus mit Verdunstung, Wolkenbildung und anschließendem Niederschlag. Das Wasser versickerte im Untergrund und wurde später durch hydrothermale Prozesse wieder an die Oberfläche getrieben. Da jedoch der Planet immer weiter abkühlte, endete dieser Prozess vor etwa 1,5 Milliarden Jahren, und es hielten sich nur noch Gletscher an der Oberfläche. Zeichen dieser Aktivität sind vor kurzem entdeckte Moränen am Olympus Mons.[40] +Amazonische Periode + +Das jüngste geologische Zeitalter des Mars wird als Amazonische Periode bezeichnet und begann vor 1,8 Milliarden Jahren. In dieser Phase entstanden die jüngeren Vulkane der Tharsis- und der Elysium-Region, aus denen große Lavamassen flossen. So bildeten sich weite Ebenen aus wie zum Beispiel Amazonis Planitia. + +2008 fanden Forscher Hinweise auf Geysire auf dem Mars, die vor einigen Millionen Jahren aktiv gewesen sein dürften. Dabei hätten sie Fontänen von kohlensäurehaltigem Wasser einige Kilometer weit in die Höhe geschossen. Darauf deuten auch die Formen von Ablagerungen hin, die britische Forscher in der Nähe zweier ausgedehnter Grabensysteme entdeckten. Wahrscheinlich wurden diese Eruptionen durch Blasen aus Kohlendioxid ausgelöst. Dadurch wurde das Wasser aus einer Tiefe von bis zu vier Kilometern durch Spalten im Marsboden an die Oberfläche gedrückt. Die Fontänen müssen dabei mit einem so großen Druck herausgepresst worden sein, dass das schlammige Wasser erst in einer Entfernung von mehreren Kilometern von der Austrittsstelle wieder auf den Boden regnete oder, bedingt durch die tiefen Temperaturen, als Hagel niederging.[41] + +Gegenwärtig wird die Oberfläche des Mars hauptsächlich durch Winderosion und Hangrutschung geformt. +Erforschung + +Aufgrund seiner großen Helligkeit war der Mars schon im frühen Altertum als Planet bekannt. Wegen seiner langen Planetenschleifen (die alle 2 Jahre in der Opposition auftreten) galten seine Bewegungen den Ägyptern als unvorhersehbar. Den Babyloniern gelang es zwar, sie näherungsweise vorauszusagen, sie schrieben die Bahnanomalien aber den Launen und der Gewalttätigkeit des Gottes Nergal zu. +Vor dem Raumfahrtzeitalter +Marsoberfläche nach Schiaparelli (1888) +Mars auf einer astronomischen Zeichnung des 19. Jahrhunderts (Trouvelot, 1881) + + Tycho Brahe (15461601) vermaß die Planetenpositionen des Mars mit bis dahin nicht gekannter Genauigkeit und ermöglichte es so Johannes Kepler (15711630), die elliptische Bahn des Planeten zu berechnen und die drei Keplerschen Gesetze abzuleiten. + Christiaan Huygens entdeckte 1659 eine dunkle, dreieckige Zone (Syrtis Major) auf der Marsoberfläche. Aus deren Positionsveränderungen errechnete er die Eigenrotation des Mars zu 24,5 Stunden (heutiger Wert: 24,623 Stunden). + Giovanni Domenico Cassini beschrieb 1666 die weißen Polkappen des Mars. + Wilhelm Herschel bestimmte 1784 die Neigung der Rotationsachse gegenüber der Umlaufbahn mit 25° (heutiger Wert 25,19°). + Wilhelm Beer fertigte 1830 die erste Marskarte an, Angelo Secchi 1863 schon in Farbe. + Richard Proctor veröffentlichte 1869 eine detaillierte Marskarte, die er aus Zeichnungen von William Rutter Dawes erstellte. + Giovanni Schiaparelli nahm 1877 auf der Marsoberfläche zarte Linienstrukturen wahr, die er Canali (italienisch für Rinnen oder Gräben) nannte und in eine detaillierte Karte eintrug. Er machte zunächst keine Angaben über den Ursprung der Canali (die er für breiter als 100 km schätzte), doch wurden sie in englischen Medien fälschlich als Channel (Kanäle) übersetzt und bald als Werk intelligenter Marsbewohner interpretiert. Auf älteren Marskarten erhielten viele dieser Linien auch Namen. Während einige Astronomen Schiaparellis Beobachtungen bestätigten, wurde die Existenz der Canali von anderen angezweifelt und als Ergebnis optischer Täuschungen bezeichnet. Erst der Vorbeiflug der amerikanischen Mariner-Sonden beendete die Spekulationen, denn Fotos der Marsoberfläche zeigten keine so breiten Rinnen. Drei Canali entsprechen aber den riesigen Canyons Valles Marineris, andere zeichnen Geländestufen und Schattenlinien nach, einige auch längere Kraterketten. + + Hauptartikel: Marskanäle + + Asaph Hall entdeckt bei der günstigen Opposition 1877 die beiden Marsmonde Phobos und Deimos. + Percival Lowell gründet 1894 das Lowell-Observatorium in Arizona, um die Marskanäle, ihre jahreszeitlichen Verfärbungen und allfällige Lebensspuren zu erforschen. Spektroskopisch findet man biologische Moleküle, die sich allerdings später als terrestrisch erweisen. In der Atmosphäre werden Spektrallinien von Sauerstoff entdeckt, dessen Volumsanteil aber überschätzt wird. + Eugène Antoniadi bestätigte zunächst die Marskanäle, kam aber 1909 am Riesenteleskop Meudon zum Schluss, sie würden nur in kleineren Fernrohren als solche erscheinen. In seinen detaillierten Marskarten die bis zu den ersten Marssonden kaum mehr übertroffen wurden zeichnete er sie als Folge diffuser Flecken ein. + Gerard Kuiper wies in den 1950ern Kohlendioxid in der Marsatmosphäre nach und glaubte bis zu den ersten Marssonden an die mögliche Existenz von Moosen oder Flechten. + +Im Raumfahrtzeitalter +Die erste Nahaufnahme vom Mars, aufgenommen von Mariner 4 + +Viele unbemannte Raumsonden wurden schon zum Mars entsandt, von denen einige sehr erfolgreich waren. Etwa die Hälfte der Missionen endete in einem Misserfolg, die meisten davon waren sowjetische Sonden. Im Unterschied zur Erkundung des Erdmondes gibt es bis heute keine Gesteinsproben, die vom Mars geholt wurden, so dass Marsmeteoriten die einzige Möglichkeit sind, Material vom Mars in irdischen Laboratorien zu erforschen. Bislang hat es auch noch keine bemannte Marsmission gegeben ein aktueller Ansatz dafür ist Mars One. + Hauptartikel: Chronologie der Marsmissionen +1960er Jahre +Darstellung auf einer ungarischen Sondermarke von 1964 + +Die beiden sowjetischen Sonden Marsnik 1 und 2 wurden im Oktober 1960 gestartet, um am Mars vorbeizufliegen, erreichten aber noch nicht einmal die Erdumlaufbahn. 1962 versagten drei weitere sowjetische Sonden (Sputnik 22, Mars 1 und Sputnik 24), zwei von ihnen blieben im Erdorbit, die dritte verlor auf dem Weg zum Mars den Kontakt mit der Erde. Auch ein weiterer Versuch im Jahre 1964 schlug fehl. + +Zwischen 1962 und 1973 wurden zehn Mariner-Raumsonden vom Jet Propulsion Laboratory der NASA entwickelt und gebaut, um das innere Sonnensystem zu erforschen. Es waren relativ kleine Sonden, die meistens nicht einmal eine halbe Tonne wogen. + +Mariner 3 und Mariner 4 waren identische Raumsonden, die am Mars vorbeifliegen sollten. Mariner 3 wurde am 5. November 1964 gestartet, aber die Transportverkleidung löste sich nicht richtig, und die Sonde erreichte den Mars nicht. + +Drei Wochen später, am 28. November 1964, wurde Mariner 4 erfolgreich auf eine achtmonatige Reise zum Roten Planeten geschickt. Am 15. Juli 1965 flog die Sonde am Mars vorbei und lieferte die ersten Nahaufnahmen insgesamt 22 Fotos des Planeten. Die Bilder zeigten mondähnliche Krater, von denen einige mit Reif bedeckt zu sein scheinen. + +1969 folgten Mariner 6 und Mariner 7 und lieferten insgesamt 200 Fotos. +1970er Jahre + +1971 missglückte der Start von Mariner 8, dafür erhielt die NASA im gleichen Jahr von Mariner 9 mehrere tausend Bilder. + +Ebenfalls 1971 landete mit der sowjetischen Mars 3 die erste Sonde weich auf dem Mars, nachdem Mars 2 wenige Tage zuvor gescheitert war. Der Funkkontakt brach jedoch 20 Sekunden nach der Landung ab. Mögliche Ursache war ein gerade tobender globaler Staubsturm, der den Lander umgeworfen haben könnte. +Bild von Viking 1. Der große Felsen links von der Mitte ist etwa zwei Meter breit. Er wurde Big Joe getauft. + +In den 1970er-Jahren landeten die Viking-Sonden erfolgreich auf dem Mars und lieferten die ersten Farbbilder sowie Daten von Bodenproben: Viking 1 schaffte am 20. Juli 1976 als erste US-amerikanische Sonde eine weiche Landung. Die Sowjetunion versuchte noch weitere Landungen auf dem Mars, scheiterte jedoch. +1980er Jahre + +Die einzigen Raumsonden, die in den 1980er Jahren zum Mars flogen, waren die beiden sowjetischen Fobos-Sonden. Sie wurden 1988 von Baikonur aus gestartet und sollten den Mars und seinen Mond Phobos untersuchen. Dafür waren sie im Rahmen einer internationalen Kooperation neben sowjetischen auch mit zahlreichen westlichen Instrumenten bestückt. Der Kontakt zu Fobos 1 brach jedoch schon auf dem Weg zum Mars wegen eines falschen Steuerbefehls ab. Fobos 2 erreichte eine Marsumlaufbahn und einige Daten und Bilder vom Mars wurden zur Erde übertragen. Danach wurde die Sonde zu Phobos gelenkt. Jedoch brach kurz vor dem Rendezvous auch der Kontakt zu Fobos 2 ab. +1990er Jahre + +1992 wurde die US-Sonde Mars Observer gestartet. Sie ging 1993 kurz vor dem Einschwenken in die Umlaufbahn verloren. + +Am 16. November 1996 startete Mars 96, die erste russische Raumsonde seit dem Zusammenbruch der Sowjetunion. Doch versagte die Proton-Trägerrakete, so dass Mars 96 wieder in die Erdatmosphäre eintrat und verglühte. +Der Marsrover Sojourner + +Besonderes Aufsehen erregte 1997 der Mars Pathfinder, bei dem zum ersten Mal ein kleines Marsmobil, der Rover Sojourner, eingesetzt wurde. Er landete publikumswirksam am 4. Juli, dem amerikanischen Unabhängigkeitstag, und lieferte viele Aufnahmen von der Umgebung der Landestelle, die von der NASA zum ersten Mal sofort im Internet veröffentlicht wurden. + +Eine weitere erfolgreiche Mission war 1997 die des Mars Global Surveyor, bei der die Marsoberfläche in einer hohen Auflösung kartografiert wurde. Am 2. November 2006 fünf Tage vor dem 10-jährigen Jubiläum seines Starts brach der Kontakt mit dem Satelliten ab. + +Das Scheitern der Marssonden Mars Climate Orbiter, der wegen eines Programmierfehlers in der Navigation verlorenging, und Mars Polar Lander, der wahrscheinlich wegen eines fehlerhaften Sensors bei der Landung aus größerer Höhe abstürzte, stellte 1999 einen herben Rückschlag für die Marsforschung dar. + +Auch die 1998 gestartete japanische Raumsonde Nozomi konnte den Mars nicht erreichen. +2000er Jahre + +Seit dem 24. Oktober 2001 umkreist außer dem Global Surveyor noch 2001 Mars Odyssey den roten Planeten, der spezielle Instrumente zur Fernerkundung von Wasservorkommen an Bord hat. + +Von den bis 2002 insgesamt 33 Missionen zum Mars waren nur acht erfolgreich, allesamt US-amerikanisch. + +Am 2. Juni 2003 startete im Rahmen der ersten europäischen Marsmission die ESA-Raumsonde Mars Express mit dem Landegerät Beagle 2 erfolgreich zum Mars. Zwar landete Beagle 2 am 25. Dezember 2003 auf der Marsoberfläche, allerdings konnte der Funkkontakt niemals aufgebaut werden. 2014 wurde er auf Bildern des MRO entdeckt. Der Orbiter Mars Express arbeitet jedoch erfolgreich in der Marsumlaufbahn und konnte unter anderem viele Aufnahmen von Formationen machen, von denen man annimmt, dass sie ausgetrocknete oder ausgefrorene Flusstäler seien. Er kartiert den Planeten u. a. mittels Radar und einer Stereokamera im sichtbaren Licht, sowie spektroskopisch auch in Infrarot. Am 30. November 2005 fand die Sonde unter der Ebene Chryse Planitia ein Eisfeld mit 250 km Durchmesser. +Marsrover Opportunity (MER-B) + +Am 10. Juni 2003 wurde die US-amerikanische Marssonde Spirit (MER-A) zum Mars gestartet. An Bord befand sich ein Rover, der nach der Landung drei Monate lang Gesteinsproben entnehmen und nach Spuren von früher vorhandenem Wasser suchen sollte. Die Landung erfolgte am 4. Januar 2004 im Krater Gusev, in den das Ma'adim Vallis mündet. Im April 2009 fuhr sich der Rover in einer Sandanhäufung fest und konnte seit dem 22. März 2010 auch nicht mehr kontaktiert werden (Stand: März 2011). + +Am 8. Juli 2003 wurde die baugleiche Sonde Opportunity (MER-B) mit einer Delta-II-Rakete gestartet. Sie landete am 25. Januar 2004 in der Tiefebene Meridiani Planum nahe dem Marsäquator, fast genau gegenüber von Spirit.[26] Die vom Rover gesammelten Beweise, dass der Mars einst warm und feucht war, wurden im Jahresrückblick der Fachzeitschrift Science mit der Wahl zum Durchbruch des Jahres 2004 gewürdigt. Opportunity ist noch immer aktiv (Stand: April 2015). +vergrößern und Informationen zum Bild anzeigen +Das Panoramabild, von dem hier nur ein Ausschnitt zu sehen ist, wurde aus hunderten Einzelbildern montiert, die Opportunity vom 6. Oktober bis 6. November 2006 aufgenommen hat. Es zeigt annähernd in Echtfarben den Victoria-Krater vom Cap Verde + +Am 12. August 2005 wurde die US-Sonde Mars Reconnaissance Orbiter mit einer Atlas-V-Rakete auf die Reise geschickt und erreichte am 10. März 2006 den Mars. Sie soll ihn mit hochauflösenden Kameras kartografieren und auch nach geeigneten Landestellen für spätere Rover-Missionen suchen. Außerdem soll sie zur Hochgeschwindigkeits-Kommunikation zwischen zukünftigen Raumsonden auf der Marsoberfläche und der Erde dienen. +Sonnenuntergang auf dem Mars beim Krater Gusev (Spirit am 19. Mai 2005) + +2007 fotografierte Mars Reconnaissance sieben fast kreisrunde schwarze und strukturlosen Flecken, die im Nordosten des Marsvulkans Arsia Mons liegen.[42] Der größte, genannt Jeanne, hat einen Durchmesser von etwa 150 Meter. Eine Schrägaufnahme der sonnenbeschienenen Seitenwand im August 2007 zeigte, dass es sich um einen mindestens 78 Meter tiefen senkrechten Schacht handeln muss. Diese Strukturen sind sehr wahrscheinlich vulkanischer Natur und durch den Einbruch einer nicht mehr tragfähigen Deckschicht entstanden.[43] + +Am 26. Dezember 2007 machte die High Resolution Stereo Camera des Mars Express Aufnahmen von Eumenides Dorsum, einem Bergrücken westlich der Tharsis-Region. Die Aufnahmen zeigen kilometerlange lineare Strukturen, die von Kanälen unterbrochen sind. Es handelt sich um durch Winderosion entstandene Yardangs (Windhöcker bzw. Sandwälle). + +Mit der Sonde Mars Odyssey wies die NASA im März 2008 eine umfangreiche Salzlagerstätte in den Hochebenen der Südhalbkugel nach. Die Wissenschaftler des JPL in Pasadena meinen, sie habe sich vor 3,5 bis 3,9 Milliarden Jahren gebildet. Vermutlich entstanden die Salze durch mineralienreiches Grundwasser, das an die Oberfläche gelangte und dort verdunstete. Die Bilder von Mars Odyssey zeigen kanalähnliche Strukturen, die in den Salzbecken enden.[23] Insgesamt wurden über 200 Gebiete mit Salzvorkommen ausgemacht, die zwischen 1 und 25 km² groß sind. Die Entdeckung deutet darauf hin, dass der Mars vor langer Zeit ein wärmeres und deutlich feuchteres Klima hatte.[44]. Solche Klimaschwankungen dürften durch aperiodische Änderungen der Rotationsachse entstehen, deren Neigung (derzeit 25°) zwischen 14 und 50° variiert.[45] +Die Orte der sieben erfolgreichen Marslandungen + +Am 26. Mai 2008 landete die Sonde Phoenix im nördlichen Polargebiet des Planeten. Sie suchte dort bis November 2008 im Boden nach Wassereis und habitablen Zonen, also für primitive Organismen bewohnbare Umgebungen. Ihr Roboterarm konnte Proben aus etwa 50 cm Tiefe holen, um sie dann in einem Minilabor zu analysieren. Phoenix entdeckte bei einer Grabung weiße Klümpchen, die nach einigen Tagen verschwanden. Man vermutete, dass es sich dabei um Wassereis handelt,[46] was am 31. Juli bestätigt wurde beim Erhitzen einer Gesteinsprobe trat Wasserdampf aus.[47] Mit dem nasschemischen Labor MECA, das die wasserlöslichen Ionen im Marsboden bestimmte, konnten erhebliche Mengen an Perchloraten detektiert werden. Auf der Erde kommen Perchlorate in den ariden Wüstengebieten vor. Natriumperchlorat wird durch Oxidation von Natriumchlorid in der Atmosphäre erzeugt und dann mit dem Staub abgelagert. + + +2010er Jahre +Curiosity auf dem Mars + +Am 26. November 2011 um 15:02 UTC startete die Rover-Mission Mars Science Laboratory (Curiosity) der NASA mit einer Atlas V(541) von Cape Canaveral und landete am 6. August 2012 auf dem Mars. Der Rover kann weite Strecken zurücklegen und umfassende Untersuchungen eines großen Umkreises durchführen. Wichtigstes Projektziel sind geologische Analysen des Marsbodens. + +Am 18. November 2013 startete eine weitere NASA-Sonde zum Mars. Die Mission mit dem Projektnamen Mars Atmosphere and Volatile Evolution (MAVEN) soll das Rätsel der verlorenen Atmosphäre aufklären.[48] Der Orbiter umkreist den Planeten seit dem 22. September 2014 und soll sich in fünf Tiefflügen annähern. Weiters wurde am 5. November 2013 eine indische Marsmission gestartet. Sie soll ebenfalls die Atmosphäre sowie verschiedene Oberflächenphänomene untersuchen.[49] + +ExoMars Rover ist ein europäischer Rover, dessen Start für 2020 geplant ist. Er soll speziell nach Spuren von Leben suchen. Die Finanzierung dieser Mission ist allerdings noch ungewiss. +Geplante Missionen + +Weitere Pläne der NASA und ESA zur Marserforschung enthalten unter anderem das Aussetzen von kleineren Flugzeugen in der Atmosphäre und nach 2020 die Rückführung von Marsproben zur Erde (Mission Mars Sample Return). +vergrößern und Informationen zum Bild anzeigen +Panoramabild der Marsoberfläche, aufgenommen von der Sonde Pathfinder + +Im Januar 2004 kündigte der US-amerikanische Präsident George W. Bush Anstrengungen der USA für eine bemannte Marsmission an. Im Rahmen des Raumfahrtprogramms Constellation plante die NASA diese Flüge für die Zeit nach 2020. Der ehemalige NASA-Direktor Michael Griffin nannte die Zeit bis 2037. Constellation wurde aber durch die Nachfolgeregierung unter Barack Obama aus Kostengründen gestrichen.[50] + +Auch das langfristig angelegte europäische Raumfahrtprogramm Aurora strebt insbesondere die Landung eines Menschen auf dem Mars an und plant sie für das Jahr 2033. + +Darüber hinaus existieren im Rahmen von Visionen einer Marskolonisation Vorstellungen, den Mars durch Terraforming in weiter Zukunft in einen für den Menschen lebensfreundlicheren Planeten umzuwandeln. Robert Zubrin, Edwin Aldrin und andere namhafte Stimmen in den Vereinigten Staaten von Amerika treten mittlerweile dafür ein, auf dem Mars unter dem Motto Mars to Stay schon in naher Zukunft die erste menschliche Siedlung zu begründen: Das sei möglich und sinnvoll, weil es wesentlich weniger Aufwand erfordere, die ersten Marsfahrer dauerhaft auf dem Planeten siedeln zu lassen, als sie sogleich wieder zur Erde zurückzuholen. +Möglichkeit von Leben + Hauptartikel: Leben auf dem Mars + +Die Ökosphäre (oder habitable Zone) des Sonnensystems reicht von 0,95 bis 1,37 AE Abstand zur Sonne. Im Sonnensystem befindet sich nur die Erde innerhalb dieses Gürtels um die Sonne, der Mars liegt knapp außerhalb. + +Höheres oder gar intelligentes Leben gibt es auf dem Mars nicht, Wissenschaftler halten jedoch primitive Lebensformen (Mikroben) tiefer im Boden, um vor UV-Strahlen geschützt zu sein, für denkbar.[51] Tatsächlich haben die in der Antarktis im Inneren von Gesteinen lebenden Pilzarten Cryomyces antarcticus und Cryomyces minteri simulierte Mars-Umweltbedingungen relativ gut überstanden: Nach 18 Monaten auf der Internationalen Raumstation[52] enthielten knapp 10 % der Proben noch fortpflanzungsfähige Zellen.[53] Auch die Flechte Xanthoria elegans hat die simulierten Marsbedingungen während des Experiments überlebt. +Vermutungen vor dem Raumzeitalter +Marsoberfläche nach Oswald Lohse (1888). Auf der Karte ist das Kanalsystem Schiaparellis nicht eingezeichnet. Die von Lohse gewählten Namen für die Seen und Ozeane sind heute nicht mehr gebräuchlich + +Der Gedanke an die Möglichkeit von Leben auf dem Mars beflügelte oft die Fantasie der Menschen. Im 18. Jahrhundert beobachtete man, dass die dunklen Flecken auf der Marsoberfläche ihre Farbe änderten und wuchsen oder schrumpften. Man hielt sie für ausgedehnte Vegetationszonen, deren Ausdehnung sich mit den Jahreszeiten änderte. + +Durch Schiaparellis Entdeckung der Marskanäle wurden die Spekulationen um intelligentes Leben auf dem Mars angefacht. + +So entstanden zahlreiche Legenden um vermeintliche Zivilisationen auf dem Mars. Die Diskussionen um die Marsmenschen hielten etwa ein Jahrhundert an. Der US-Amerikaner Percival Lowell, einer der heftigsten Verfechter der Marskanäle-Theorie, gründete sogar eine eigene Sternwarte, um die Marsbewohner zu erforschen. Für ihn waren die Kanäle das Produkt außerirdischer Ingenieure, die geschaffen wurden, um die Marszivilisation vor einer großen Trockenheit zu retten. Lowell beschrieb seine Vorstellungen der Marswelt in zahlreichen Publikationen, die weite Verbreitung fanden. + +Obwohl nicht alle Astronomen die Kanäle sehen konnten und keine Fotos existierten, hielt sich die Theorie, begleitet von einer heftigen Debatte. Die Vorstellung von außerirdischem Leben übt bis heute eine Faszination auf die Menschen aus, die mit wissenschaftlichem Interesse alleine oft nicht erklärt werden kann. Erst die Ergebnisse der unbemannten Marsmissionen beendeten den Streit um die Kanäle. + +Untersuchungen durch Viking + +Als im Juli 1976 der Orbiter 1 der Viking-Mission Bilder der Cydonia-Region machte und diese zur Erde schickte, wurde der Mars in der Öffentlichkeit wieder zum Gesprächsthema. Eine der Aufnahmen zeigte eine Formation auf der Marsoberfläche, die einem menschlichen Gesicht ähnelte, das gen Himmel blickt. In der unmittelbaren Nähe wurden außerdem Strukturen entdeckt, die Pyramiden auf der Erde ähneln, sowie rechteckige Strukturen (von den Wissenschaftlern Inka-Stadt getauft). Erst die Mission Mars Global Surveyor der NASA brachte im April 1998 für viele die Ernüchterung: Alle entdeckten Strukturen waren das Ergebnis natürlicher Erosion. Durch neue Bilder mit wesentlich höherer Auflösung wurde deutlich, dass auf dem Mars keine künstlichen Strukturen außerirdischer Intelligenz ersichtlich sind. +Das Marsgesicht in der Cydonia-Region; Aufnahme des Orbiters von Viking 1, 1976 + +Viking 1 und 2 hatten unter anderem die Aufgabe, der Frage nach dem Leben auf dem Mars nachzugehen. Dabei wurden ein chemisches und drei biologische Experimente durchgeführt. In dem chemischen Experiment wurde versucht, organische Substanzen im Marsboden nachzuweisen. Dazu wurde eine am MIT entwickelte GC/MS-Einheit (Kopplung eines Gaschromatographen mit einem Massenspektrometer) benutzt. Es konnten allerdings keine auf Kohlenstoff aufbauenden organischen Substanzen nachgewiesen werden. + +Das erste biologische Experiment beruhte auf Stoffwechselaktivitäten von Organismen. Eine Bodenprobe wurde mit einer Nährlösung benetzt und entstehende Gase registriert. Der Marsboden reagierte auf das Experiment mit Abgabe großer Mengen Sauerstoff. Im zweiten Experiment wurde eine Nährlösung mit radioaktiven Kohlenstoffatomen versehen und auf eine Probe gegeben. Als Ergebnis eines Stoffwechsels hätten sie unter den ausgeschiedenen Gasen nachgewiesen werden müssen. Tatsächlich wurden radioaktive Kohlenstoffatome nachgewiesen. Das dritte Experiment war ein Photosynthese-Experiment. Radioaktiv markiertes Kohlendioxid wurde dem Marsboden zugesetzt. Dieses Kohlendioxid hätte assimiliert werden und später nachgewiesen werden müssen. Auch dieses Ergebnis war positiv. Obwohl die Ergebnisse der biologischen Experimente positiv waren, gaben sie aufgrund des negativen Ergebnisses des GC/MS-Versuchs keinen schlüssigen Beweis für die Existenz oder Nichtexistenz von Leben auf dem Mars. +1990er und 2000er Jahre +Marsgesicht, Aufnahme von Mars Global Surveyor, 2001 + +Im Jahr 1996 fanden David S. McKay und seine Mitarbeiter Strukturen im Marsmeteoriten ALH 84001, die sie als Spuren von fossilen Bakterien deuteten. Das in diesem Meteoriten gefundene, kettenartig angeordnete Magnetit ähnelt morphologisch dem bakteriellen Magnetit aus Magnetospirillum magnetotacticum. Allerdings wird die Beweiskraft der gefundenen Strukturen von vielen Wissenschaftlern angezweifelt, da diese auch auf rein chemischem Wege entstehen konnten. + +Am 23. Januar 2004 entdeckte die europäische Marssonde Mars Express am Südpol des Mars große Mengen gefrorenen Wassers, Ende Juli 2005 auch in einem nahe dem Nordpol gelegenen Krater. + +Ende März 2004 wurde bekannt, dass Forscher der NASA und der ESA unabhängig voneinander Methan in der Marsatmosphäre nachgewiesen haben. Ob das Methan geologischen Ursprungs ist oder etwa durch den Stoffwechsel von Mikroorganismen gebildet wurde, sollen weitere Untersuchungen zeigen. + +Ebenfalls Anfang 2004 entdeckte die Marssonde Opportunity Gesteine, die in offenstehendem Wasser abgelagert worden sein müssen und viele regelmäßig verteilte kugelige, bis 1 cm große Hämatit-Konkretionen enthalten. Solche Konkretionen kommen auch auf der Erde vor. Unter irdischen Bedingungen ist es wahrscheinlich, dass bei ihrer Entstehung Bakterien beteiligt sind. Ob dies auch für den Mars gilt, könnten nur Laboruntersuchungen auf der Erde zeigen. + +Weitere Mikrostrukturen, welche die Rover Spirit und Opportunity 2004 entdeckt hatten und in denen ein Teil der interessierten Öffentlichkeit Hinweise auf Leben hatte sehen wollen, erwiesen sich bei näherer Untersuchung als abiotisch oder künstlich, so zum Beispiel Schleifspuren auf durch die Instrumente bearbeiteten Gesteinsoberflächen oder Filamente, die sich als Textilfasern der Lande-Airbags herausstellten. + +Forschungsergebnisse auf der Erde bestätigen, dass es Leben auch in extremen Bedingungen geben kann. Bei Bohrungen im grönländischen Eis entdeckten Forscher der University of California, Berkeley im Jahre 2005 in drei Kilometern Tiefe eine auffallende Menge Methan. Dieses Gas produzierten methanogene Bakterien, die trotz unwirtlicher Lebensbedingungen wie Kälte, Dunkelheit und Nährstoffmangel im Eis überleben. Dabei erhalten sie sich nur mühsam am Leben sie reparieren Erbgutschäden, vermehren jedoch nicht nennenswert ihre Population. Methanogene Mikroben sind eine Untergruppe der Archaebakterien, die sich auf Extremstandorte spezialisiert haben. So fanden sich im Jahr 2002 Mikroben in einer 15.000 Jahre alten heißen Quelle in Idaho. Die Bakterien zählen, wie schon der Name besagt, zu den ältesten Mikroorganismen der Erde. Die Wissenschaftler schätzen das Alter der in Grönland entdeckten Bakterienkolonie auf 100.000 Jahre und vermuten, dass das in der Atmosphäre des Roten Planeten nachgewiesene Methan nicht nur von chemischen Prozessen, sondern auch von solchen Mikroben stammen könnte. +Aktuelle Forschung + +Mit dem Mars Science Laboratory wird versucht, neue Aufschlüsse über mögliches Leben auf dem Mars zu liefern. Es ist fraglich, ob der Mars-Rover tief genug bohren kann, um Leben oder zumindest Lebensreste zu finden. Aber eine Isotopenanalyse des Methans kann bereits weitere Aufschlüsse geben. Leben, wie es auf der Erde bekannt ist, bevorzugt leichtere Wasserstoffisotope. +Beobachtung +Stellung zur Erde und Bahneigenschaften +Planetenschleife des Mars im Sternbild Wassermann im Jahr 2003 + +Aufgrund der Bahneigenschaften der Planeten überholt die Erde den Mars durchschnittlich alle 779 Tage auf ihrer inneren Bahn. Diesen Zeitraum, der zwischen 764 und 811 Tagen schwankt, nennt man synodische Periode. Befinden sich Sonne, Erde und Mars in dieser Anordnung auf einer Linie, so steht der Mars von der Erde aus gesehen in Opposition zur Sonne. Zu diesem Zeitpunkt ist Mars besonders gut zu beobachten, er steht dann als rötlicher Stern auffallend hell am Nachthimmel. Beobachtet man den Mars regelmäßig, kann man feststellen, dass er vor und nach einer Opposition am Himmel eine Schleifenbewegung vollführt. Diese Planetenschleife (Oppositionsschleife) ergibt sich aus den Sichtwinkeln, die Mars bietet, während er von der Erde überholt wird. +Marsoppositionen von 2003 bis 2018, relative Bewegung des Mars zur Erde, mit der Erde im Zentrum; Ansicht auf die Ekliptikebene + +Da die Planeten sich nicht auf idealen Kreisbahnen, sondern auf mehr oder weniger stark ausgeprägten elliptischen Bahnen bewegen, haben Erde und Mars zum Zeitpunkt der Oppositionen unterschiedliche Entfernungen zueinander. Diese können zwischen 55,6 und 101,3 Millionen Kilometern bzw. 0,37 und 0,68 AE betragen. Bei einer geringen Oppositionsentfernung spricht man von einer Perihelopposition, bei einer großen von einer Aphelopposition. +Schwankung des minimalen Abstands ErdeMars bei Oppositionen. Die tatsächlichen Werte sind an den einzelnen Punkten ablesbar. Die verbindende Kurve veranschaulicht die mathematische Gesetzmäßigkeit. + +Die alle 15 bis 17 Jahre stattfindenden Periheloppositionen bieten die besten Gelegenheiten, den Mars von der Erde aus mittels Teleskop zu beobachten. Der Planet hat dann einen scheinbaren Durchmesser von bis zu 25,8 Bogensekunden. Bei einer Aphelopposition ist er mit 14,1 Bogensekunden nur etwa halb so groß. Besonders erdnahe Oppositionen fanden im Abstand von jeweils 79 Jahren, zum Beispiel in den Jahren 1766, 1845, 1924 und 2003 statt. Am 28. August 2003 betrug der Abstand ErdeMars 55,76 Millionen Kilometer. Dies war die geringste Distanz seit etwa 60.000 Jahren.[54][55] Erst im Jahre 2287 wird der Mars der Erde noch näher kommen, der Abstand beträgt dann 55,69 Millionen Kilometer. + +Im Teleskop erscheint der Mars zunächst als rötliches Scheibchen. Bei stärkerer Vergrößerung können die Polkappen und dunkle Oberflächenmerkmale wie die Große Syrte ausgemacht werden. Treten auf dem Mars größere Staubstürme auf, verblassen die Merkmale, da die Oberfläche von einer rötlichen Dunstschicht eingehüllt wird, die sich mitunter über Wochen halten kann. Durch den Einsatz von CCD-Kameras sind mittlerweile auch Amateurastronomen in der Lage, detailreiche Aufnahmen der Marsoberfläche zu erzielen, wie sie vor etwa zehn Jahren nur von den leistungsfähigsten Großteleskopen erstellt werden konnten. + +Ereignisse (Jahreszeitenbeginn gilt für die Nordhalbkugel):[56][57][58] +Ereignis Frühlingsbeginn Aphel Sommerbeginn Herbstbeginn Perihel Winterbeginn +Datum 18. Juni 2015 20. November 2015 15. Februar 2014 17. August 2014 12. Dezember 2014 11. Januar 2015 +Nächste +Termine 5. Mai 2017 7. Oktober 2017 3. Januar 2016 4. Juli 2016 29. Oktober 2016 28. November 2016 +Ereignis Konjunktion Opposition +Datum 14. Juni 2015 8. April 2014 +Nächste +Termine 27. Juli 2017 22. Mai 2016 +Sichtbarkeiten + Hauptartikel: Marspositionen + +Wegen der Exzentrizität der Marsbahn kann der erdnächste Punkt bis zu einer Woche vor oder nach der Opposition erreicht werden, und die scheinbare Helligkeit während der Opposition sowie der Erdabstand und der scheinbare Durchmesser während der Erdnähe können recht unterschiedlich ausfallen. + +Eine Opposition findet etwa alle zwei Jahre (779,94 Tage) statt. Dabei kann bei einer Perihelopposition die maximale scheinbare Helligkeit bis zu 2,91m erreichen. Zu diesem Zeitpunkt sind nur die Sonne, der Erdmond, die Venus und in seltenen Fällen Jupiter (bis zu 2,94m) noch heller. Bei Konjunktion hingegen erscheint Mars nur mehr mit einer Helligkeit von +1,8m.[1] +Kulturgeschichte +Beschäftigung mit dem Mars von der Antike bis in die Neuzeit +Allegorische Darstellung des Mars als Herrscher der Tierkreiszeichen Widder und Skorpion, von Hans Sebald Beham, 16. Jahrhundert + +Der Mars bewegte die Menschheit von alters her besonders. Im alten Ägypten wurde Mars als Horus der Rote bezeichnet. Da der Planet sich während seiner Oppositionsschleife (Planetenschleife) zeitweise rückläufig bewegt, sprachen die Ägypter davon, dass Mars rückwärts wandere. Der Name der ägyptischen Hauptstadt Kairo leitet sich von Al Qahira ab, dem altarabischen Namen für den Planeten Mars. + +Im indischen Sanskrit wird der Mars als Mangal (verheißungsvoll), Angaraka (Glühende Kohle) und Kuja (der Blonde) bezeichnet. Er repräsentiert kraftvolle Aktion, Vertrauen und Zuversicht. + +Aufgrund seiner roten Färbung wurde der Mars in verschiedenen Kulturen mit den Gottheiten des Krieges in Verbindung gebracht. Die Babylonier sahen in ihm Nergal, den Gott der Unterwelt, des Todes und des Krieges. Für die Griechen und Römer der Antike repräsentierte er deren Kriegsgötter Ares beziehungsweise Mars. In der nordischen Mythologie steht er für Tyr, den Gott des Rechts und des Krieges. Die Azteken nannten ihn Huitzilopochtli, der Zerstörer von Menschen und Städten. Für die Chinesen war er Huoxing (chin. Huxng, ), Stern des Feuers. + +In der Astrologie ist Mars unter anderem das Symbol der Triebkraft. Es wird dem Element Feuer, dem Planetenmetall Eisen, den Tierkreiszeichen Widder und Skorpion sowie dem 1. Haus zugeordnet. +Rezeption in Literatur, Film, Videospiele und Musik + +Der Mars und seine fiktiven Bewohner sind auch Thema zahlreicher Romane und Verfilmungen. + +Ein Beispiel des 18. Jahrhunderts ist Carl Ignaz Geigers Roman Reise eines Erdbewohners in den Mars von 1790. + +1880 veröffentlichte Percy Greg seinen Roman Across the Zodiac, in dem er eine Reise in einem Raumschiff namens Astronaut zum Mars beschrieb. + +Die klassische Figur des kleinen grünen Männchens mit Antennen auf dem Kopf erschien erstmals 1913 in einem Comic und ist seitdem Klischee. + +Als der Astronom Percival Lowell Ende des 19. Jahrhunderts die Vorstellung entwickelte, die mit dem Fernrohr wahrnehmbaren Marskanäle seien künstlich angelegte Wasserkanäle, wurde diese Idee in der Science-Fiction-Literatur aufgegriffen und weitergesponnen. Dort wurde der Mars häufig als eine sterbende Welt vorgestellt, in deren kalten Wüstenregionen alte und weit entwickelte Zivilisationen ums Ãœberleben kämpften. + +Kurd Laßwitz brachte 1897 seinen sehr umfangreichen Roman Auf zwei Planeten über einen Besuch bei den Marsbewohnern heraus. +Angriff der Marsianer in Krieg der Welten von H. G. Wells. Buchillustration der französischen Ausgabe von Alvim Corréa von 1906 + +In H. G. Wells bekanntem Roman Krieg der Welten, der 1898 erschien, verlassen die Marsianer ihre Heimatwelt, um die lebensfreundlichere Erde zu erobern. Die Menschheit, die den hochtechnisierten kriegerischen Marsianern hoffnungslos unterlegen ist, entgeht ihrer Auslöschung nur dadurch, dass die Invasoren von für Menschen harmlosen, irdischen Mikroben dahingerafft werden. Orson Welles verwendete den Stoff im Jahre 1938 in einem Hörspiel, wobei er die Marsianer in New Jersey landen ließ. Das Hörspiel wurde im Stil einer realistischen Reportage ausgestrahlt. Hörer, die sich später einschalteten, hielten die Invasion der Marsianer für Realität. + +Wells Romanvorlage wurde 1952 verfilmt, wobei die Handlung wiederum in die USA der Gegenwart verlegt wurde. Der Film erhielt für die damals bahnbrechenden Spezialeffekte einen Oscar. + +1923 brachte Tolstoi seinen Roman Aelita heraus, der von der Liebe eines sowjetischen Ingenieurs zur Marsprinzessin und dem Untergang der Zivilisation auf dem Planeten handelt. Dieses Werk wurde 1924 verfilmt. + +Im Jahr 1978 entstand der Film Unternehmen Capricorn. Er griff das Thema der Verschwörungstheorien zur Mondlandung auf, indem er es in sehr zugespitzter Form auf eine im Filmstudio vorgetäuschte Marsexpedition übertrug. + +Der 1996 entstandene Film Mars Attacks! setzt sich ironisch mit dem Thema Marsinvasion auseinander, wobei den Marsianern amerikanische Schnulzenmusik aus den 1950er Jahren zum Verhängnis wird. + +Unter der Regie von Brian De Palma wurden im Jahr 2000 mit dem Film Mission to Mars die Spekulationen um das Marsgesicht der Cydonia-Region als hinterlassenes Bauwerk dramatisch weitgehend thematisiert. + +Steven Spielbergs 2005 entstandenes Remake von Krieg der Welten nahm noch einmal das Thema auf und zeigte die Invasion von Außerirdischen auf der Erde aus der Sicht eines Familienvaters aus den USA. + +Weitere bekannte Science-Fiction-Filme, die auf dem Mars handeln, sind Red Planet (2000) und Die totale Erinnerung Total Recall (1990). + +Edgar Rice Burroughs, der Autor von Tarzan, schrieb von 1917 bis 1943 die elfbändige Saga John Carter vom Mars, in der sich der irdische Held in marsianische Prinzessinnen verliebt und gegen Luftpiraten, grünhäutige Unholde, weiße Riesenaffen und andere Untiere kämpft. + +Die Mars-Chroniken (1950), eine stimmungsvolle Sammlung von Erzählungen des Schriftstellers Ray Bradbury, sind ebenfalls auf dem Mars angesiedelt. + +Große Beachtung erhielt die Marstrilogie, eine von Kim Stanley Robinson von 1993 bis 1996 verfasste Romanserie über die Besiedelung des Mars. Der besondere Ansatz dieser Geschichten liegt in der vorwiegend technischen Schilderung unter vollständigem Verzicht phantastischer Elemente. +Die Route von Mark Watney in einer nachgestellten topographischen Kartierung des DLR-Instituts für Planetenforschung + +Der wohl prominenteste Auftritt des Mars in der Musik dürfte der erste Satz von Gustav Holsts Orchestersuite Die Planeten (19141916) sein, deren erster Satz Mars, the Bringer of War mit seinem drohend-martialischen Charakter die mythologische Gestalt Mars eindrucksvoll porträtiert. + +Bestsellerautor Andreas Eschbach verfasste von 2001 bis 2008 die Pentalogie Das Marsprojekt. + +2011 veröffentlichte Andy Weir den Science-Fiction-Roman Der Marsianer, in dem ein Astronaut nach einem Unfall auf dem Mars zurückgelassen wird und fortan um sein Ãœberleben kämpfen muss. Mit Der Marsianer Rettet Mark Watney erschien 2015 eine Verfilmung dieses Bestsellers. + +Helga Abret und Lucian Boa geben in ihrem Buch Das Jahrhundert der Marsianer (1984) einen literarischen Ãœberblick über Erzählungen und Romane über den Mars und seine fiktiven Bewohner. Von der Beschreibung einer ekstatischen Reise zum Mars (Itinerarium exstaticum coeleste, 1656) des Jesuitenpaters Athanasius Kircher bis hin zu Science-Fiction-Erzählungen des 20. Jahrhunderts reicht die Bandbreite der kommentierten Werke, mit denen die Autoren aufzuzeigen versuchen, dass sich aus dem Zusammenwirken von Naturwissenschaften, Astronomie und Literatur ein moderner Mythos[59] entwickelte. + diff --git a/xpcom/tests/gtest/wikipedia/de.txt b/xpcom/tests/gtest/wikipedia/de.txt new file mode 100644 index 0000000000..486c676110 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/de.txt @@ -0,0 +1,487 @@ +Der Mars ist, von der Sonne aus gezählt, der vierte Planet im Sonnensystem und der äußere Nachbar der Erde. Er zählt zu den erdähnlichen (terrestrischen) Planeten. + +Sein Durchmesser ist mit knapp 6800 Kilometer etwa halb so groß wie der der Erde, sein Volumen beträgt gut ein Siebtel des Erdevolumens. Damit ist der Mars nach dem Merkur der zweitkleinste Planet des Sonnensystems, hat jedoch eine ausgeprägte Geologie und die höchsten Vulkane des Sonnensystems. Mit einer durchschnittlichen Entfernung von 228 Millionen Kilometern ist er rund 1,5-mal so weit von der Sonne entfernt wie die Erde. + +Die Masse des Mars beträgt etwa ein Zehntel der Erdmasse. Die Fallbeschleunigung auf seiner Oberfläche beträgt 3,69 m/s², dies entspricht etwa 38 % der irdischen. Mit einer Dichte von 3,9 g/cm³ weist der Mars den geringsten Wert der terrestrischen Planeten auf. Deshalb ist die Schwerkraft auf ihm sogar geringfügig niedriger als auf dem kleineren, jedoch dichteren Merkur. + +Der Mars wird oft auch als der Rote Planet bezeichnet. Diese Färbung geht auf Eisenoxid-Staub (Rost) zurück, der sich auf der Oberfläche und in der dünnen CO2-Atmosphäre verteilt hat. Seine orange- bis blutrote Farbe und seine Helligkeitsschwankungen sind auch verantwortlich für seine Namensgebung nach dem römischen Kriegsgott Mars.[3] + +In größeren Fernrohren deutlich sichtbar sind die zwei Polkappen und mehrere dunkle Ebenen, die sich im Frühjahr etwas verfärben. Fotos von Raumsonden zeigen eine teilweise mit Kratern bedeckte Oberfläche und starke Spuren früherer Tektonik (tiefe Canyons und fünf über 20 km hohe Vulkane). Marsroboter haben schon mehrere Gebiete geologisch untersucht. + +Der Mars besitzt zwei kleine, unregelmäßig geformte Monde, die 1877 entdeckt wurden: Phobos und Deimos (griechisch für Furcht und Schrecken). + +Das astronomische Symbol des Mars ist ♂. + +Umlauf und Rotation +Umlaufbahn + +Der Mars bewegt sich in einem Abstand von 206,62 bis 249,23 Millionen Kilometern (1,38 AE bis 1,67 AE) in knapp 687 Tagen (etwa 1,9 Jahre) auf einer elliptischen Umlaufbahn um die Sonne. Die Bahnebene ist 1,85° gegen die Erdbahnebene geneigt. + +Seine Bahngeschwindigkeit schwankt mit dem Sonnenabstand zwischen 26,50 km/s und 21,97 km/s und beträgt im Mittel 24,13 km/s. Die Bahnexzentrizität beträgt 0,0935. Nach der Umlaufbahn des Merkurs ist das die zweitgrößte Abweichung von der Kreisform unter allen Planetenbahnen des Sonnensystems. + +Jedoch hatte der Mars in der Vergangenheit eine weniger exzentrische Umlaufbahn. Vor 1,35 Millionen Jahren betrug die Exzentrizität nur etwa 0,002, weniger als die der Erde heute.[4] Die Periode der Exzentrizität des Mars beträgt etwa 96.000 Jahre, die der Erde etwa 100.000 Jahre.[5] Mars hat jedoch noch einen längeren Zyklus der Exzentrizität mit einer Periode von 2,2 Millionen Jahren, der den mit der Periode von 96.000 Jahren überlagert. In den letzten 35.000 Jahren wurde die Umlaufbahn aufgrund der gravitativen Kräfte der anderen Planeten geringfügig exzentrischer. Der minimale Abstand zwischen Erde und Mars wird in den nächsten 25.000 Jahren noch ein wenig geringer werden.[6] + +Es gibt vier bekannte Asteroiden, die sich mit dem Mars die gleiche Umlaufbahn teilen (Mars-Trojaner). Sie befinden sich auf den Lagrangepunkten L4 und L5, das heißt, sie eilen dem Planeten um 60° voraus oder folgen ihm um 60° nach. +Rotation + +Der Mars rotiert in 24 Stunden und 37,4 Minuten um die eigene Achse (Siderischer Tag). In Bezug auf die Sonne ergibt sich daraus ein Marstag (auch Sol genannt) von 24:39:35. Die Äquatorebene des Planeten ist um 25,19° gegen seine Bahnebene geneigt (Erde 23,44°), somit gibt es Jahreszeiten ähnlich wie auf der Erde. Sie dauern jedoch fast doppelt so lang, weil das siderisches Marsjahr 687 Erdtage hat. Da die Bahn des Mars aber eine deutlich größere Exzentrizität aufweist, als die der Erde, und Mars-Nord tendenziell in Richtung der großen Bahn-Ellipsenachse weist, sind die Jahreszeiten unterschiedlich lang. In den letzten 300.000 Jahren variierte die Rotationsachse zwischen 22° und 26°. Zuvor lag sie mehrmals auch über 40°, wodurch starke Klimaschwankungen auftraten, es Vereisungen auch in der Äquatorregion gab und so die starken Bodenerosionen zu erklären sind. + +Der Nordpol des Mars weist zum nördlichen Teil des Sternbilds Schwan, womit sich die Richtung um etwa 40° von jener der Erdachse unterscheidet. Der marsianische Polarstern ist Deneb (mit leichter Abweichung der Achse Richtung Alpha Cephei).[7] + +Die Rotationsachse führt eine Präzessionsbewegung aus, deren Periode 170.000 Jahre beträgt (7× langsamer als die Erde). Aus diesem Wert, der mit Hilfe der Pathfinder-Mission festgestellt wurde, können die Wissenschaftler auf die Massenkonzentration im Inneren des Planeten schließen.[8] +Atmosphäre und Klima +Ãœber dem Marshorizont ist die Atmosphäre als dunstiger Schleier erkennbar. Links ist der einem Smiley ähnelnde Krater Galle zu sehen. Viking, 1976 + +Der Mars besitzt eine sehr dünne Atmosphäre. Dadurch ist der Atmosphärendruck sehr niedrig, und Wasser kann nicht in flüssiger Form auf der Marsoberfläche existieren, ausgenommen kurzzeitig in den tiefstgelegenen Gebieten. + +Da die dünne Marsatmosphäre nur wenig Sonnenwärme speichern kann, sind die Temperaturunterschiede auf der Oberfläche sehr groß. Die Temperaturen erreichen in Äquatornähe etwa 20 °C am Tag und sinken bis auf −85 °C in der Nacht. Die mittlere Temperatur des Planeten liegt bei etwa −55 °C. +Atmosphäre +→ Hauptartikel: Atmosphäre des Mars + +Die Marsatmosphäre besteht zu 95,3 % aus Kohlenstoffdioxid. Dazu kommen noch 2,7 % Stickstoff, 1,6 % Argon, geringe Anteile an Sauerstoff (1300 ppm) und Kohlenstoffmonoxid (800 ppm) sowie Spuren von Wasserdampf (210 ppm) und anderen Verbindungen oder Elementen. + +Die Atmosphäre ist ziemlich staubig. Sie enthält Teilchen mit etwa 1,5 µm im Durchmesser, die den Himmel über dem Mars in einem blassen gelb- bis orange-braunen Farbton erscheinen lassen. + +Der atmosphärische Druck beträgt auf der Oberfläche des Mars im Schnitt nur 6,36 hPa (Hektopascal). Im Vergleich zu durchschnittlich 1013 hPa auf der Erde sind dies nur 0,63 %, was dem Luftdruck der Erdatmosphäre in 35 Kilometern Höhe entspricht. Die Atmosphäre wurde wahrscheinlich im Laufe der Zeit vom Sonnenwind abgetragen und in den Weltraum mitgerissen. Dies wurde durch die geringe Schwerkraft des Planeten und sein schwaches Magnetfeld begünstigt, das kaum Schutz vor den hochenergetischen Teilchen der Sonne bietet. +Klima und Wetter +Eiswolken über Mars, aufgenommen von Mars Pathfinder + +Abhängig von den Jahreszeiten und der Intensität der Sonneneinstrahlung finden in der Atmosphäre dynamische Vorgänge statt. Die vereisten Polkappen sublimieren im Sommer teilweise, und kondensierter Wasserdampf bildet ausgedehnte Zirruswolken. Die Polkappen selbst bestehen aus Kohlendioxideis und Wassereis. + +2008 entdeckte man mit Hilfe der Raumsonde Mars Express Wolken aus gefrorenem Kohlendioxid. Sie befinden sich in bis zu 80 Kilometern Höhe und haben eine horizontale Ausdehnung von bis zu 100 km. Sie absorbieren bis zu 40 % des einstrahlenden Sonnenlichts und können damit die Temperatur der Oberfläche um bis zu 10 °C verringern.[9] + +Mit Hilfe des Lasers LIDAR der Raumsonde Phoenix wurde 2009 entdeckt, dass in der zweiten Nachthälfte fünfzig Tage nach der Sonnenwende winzige Eiskristalle aus dünnen Zirruswolken auf den Marsboden fielen.[10] +Jahreszeiten +Staubsturm in der Syria-Region, fotografiert von Mars Global Surveyor im Mai 2003 + +Hätte Mars eine erdähnliche Umlaufbahn, würden die Jahreszeiten aufgrund der Achsenneigung ähnlich denen der Erde sein. Jedoch führt die vergleichsweise große Exzentrizität seines Orbits zu einer beträchtlichen Auswirkung auf die Jahreszeiten. Der Mars befindet sich während des Sommers in der Südhalbkugel und des Winters in der nördlichen Hemisphäre nahe dem Perihel seiner Bahn. Nahe dem Aphel ist in der südlichen Hemisphäre Winter und in der nördlichen Sommer. + +Das hat zur Folge, dass die Jahreszeiten in der südlichen Hemisphäre viel deutlicher ausgeprägt sind als in der nördlichen, wo das Klima ausgeglichener ist, als es sonst der Fall wäre. Die Sommertemperaturen im Süden können bis zu 30 °C höher sein als die vergleichbaren Temperaturen im Sommer des Nordens.[11] Die Jahreszeiten sind aufgrund der Exzentrizität der Umlaufbahn des Mars unterschiedlich lang. Auf der Nordhalbkugel dauert der Frühling 199,6, der Sommer 181,7, der Herbst 145,6 und der Winter 160,1 irdische Tage.[12] +Wind und Stürme + +Aufgrund der starken Tag-Nacht-Temperaturschwankungen der Oberfläche gibt es tägliche Morgen- und Abendwinde.[13] + +Während des Marsfrühjahrs können in den ausgedehnten flachen Ebenen heftige Staubstürme auftreten, die mitunter große Teile der Marsoberfläche verhüllen. Die Aufnahmen von Marssonden zeigen auch Windhosen, die über die Marsebenen ziehen und auf dem Boden dunkle Spuren hinterlassen. Stürme auf dem Mars haben wegen der sehr dünnen Atmosphäre eine wesentlich geringere Kraft als Stürme auf der Erde. Selbst bei hohen Windgeschwindigkeiten werden nur kleine Partikel (Staub) aufgeweht.[14] Allerdings verbleibt aufgewehter Staub auf dem Mars wesentlich länger in der Atmosphäre als auf der Erde, da es keine Niederschläge gibt, die die Luft reinigen, und zudem die Gravitation geringer ist. + +Staubstürme treten gewöhnlich während des Perihels auf, da der Planet zu diesem Zeitpunkt 40 Prozent mehr Sonnenlicht empfängt als während des Aphels. Während des Aphels bilden sich in der Atmosphäre Wolken aus Wassereis, die ihrerseits mit den Staubpartikeln interagieren und so die Temperatur auf dem Planeten beeinflussen.[15] Die Windgeschwindigkeiten in der oberen Atmosphäre können bis zu 650 km/h erreichen, auf dem Boden immerhin fast 400 km/h.[16] +Gewitter + +Bei heftigen Staubstürmen scheint es auch zu Gewittern zu kommen. Im Juni 2006 untersuchten Forscher mit einem Radioteleskop den Mars und stellten im Mikrowellenbereich Strahlungsausbrüche fest, wie sie bei Blitzen auftreten. In der Region, in der man die Strahlungsimpulse beobachtet hat, herrschte zu der Zeit ein heftiger Staubsturm mit hohen Staubwolken. Sowohl der beobachtete Staubsturm wie auch das Spektrum der Strahlungsimpulse deuten auf ein Staubgewitter mit Blitzen bzw. großen Entladungen hin.[17][18] +Oberfläche +Typisches Felsengestein auf der Marsoberfläche (aufgenommen von Mars Pathfinder) + +Die Oberfläche des Mars beträgt etwa ein Viertel der Erdoberfläche. Sie entspricht mit 144 Mio. km2 ungefähr der Gesamtoberfläche aller Kontinente der Erde (149 Mio. km2). + +Die rote Färbung seiner Oberfläche verdankt der Planet dem Eisenoxid-Staub, der sich auf der Oberfläche und in der Atmosphäre verteilt hat. Somit ist der Rote Planet ein „rostiger Planet“. + +Seine beiden Hemisphären sind sehr verschieden. Die Südhalbkugel stellt ein riesiges Hochland dar, das durchschnittlich 2–3 km über dem globalen Nullniveau liegt und ausgedehnte Schildvulkane aufweist. Die vielen Einschlagkrater belegen sein hohes Alter von fast 4 Milliarden Jahren. Dem steht die geologisch junge, fast kraterlose nördliche Tiefebene gegenüber. Sie liegt 3–5 km unter dem Nullniveau und hat ihre ursprüngliche Struktur durch noch ungeklärte geologische Prozesse verloren. Auslöser war möglicherweise eine gewaltige Kollision in der Frühzeit des Planeten. +Gesteine +→ Hauptartikel: Marsgestein + +An den Landestellen der Marssonden sind Gesteinsbrocken, sandige Böden und Dünen sichtbar. Die Marsgesteine weisen an der Oberfläche eine blasenartige Struktur auf und ähneln in ihrer Zusammensetzung irdischen Basalten, was bereits vor Jahrzehnten aus den auf der Erde (Antarktis) gefundenen Marsmeteoriten erschlossen wurde. Die roten Böden sind offensichtlich durch die Verwitterung von eisenhaltigen, vulkanischen Basalten entstanden. + +Die Pathfinder-Sonde fand 1997 außer verschiedensten Basalten auch quarzreichere Tiefengesteine ähnlich dem südamerikanischen Andesit, ferner das aus der Tiefe stammende Olivin und runde Kiesel aus Konglomeraten. Weitverbreitet ist metamorpher Regolith (ähnlich wie am Mond) und äolische Sedimente. Vereinzelt verwehter Sand aus schwefelhaltigen Staubteilchen. +Areografie + +Die kartografische Darstellung und Beschreibung der Marsoberfläche ist die Areografie, von Ares (ΆÏης, griechisch für Mars) und grafein (γÏάφειν, griechisch für beschreiben). Die „Geologie“ des Mars wird mitunter dementsprechend als Areologie bezeichnet. + +Zur Festlegung von Positionen auf der Marsoberfläche dienen areografische Koordinaten, die definiert sind wie geografische Breite und Länge. +Topografische Hemisphären +Topografische Karte des Mars. Die blauen Regionen befinden sich unterhalb des festgelegten Nullniveaus, die roten oberhalb + +Auffallend ist die Dichotomie, die „Zweiteilung“, des Mars. Die nördliche und die südliche Hemisphäre unterscheiden sich deutlich, wobei man von den Tiefebenen des Nordens und den Hochländern des Südens sprechen kann. Der mittlere Großkreis, der die topografischen Hemisphären voneinander trennt, ist rund 40° gegen den Äquator geneigt. Der Massenmittelpunkt des Mars ist gegenüber dem geometrischen Mittelpunkt um etwa drei Kilometer in Richtung der nördlichen Tiefebenen versetzt. + +Auf der nördlichen Halbkugel sind flache sand- und staubbedeckte Ebenen vorherrschend, die Namen wie Utopia Planitia oder Amazonis Planitia erhielten. Dunkle Oberflächenmerkmale, die in Teleskopen sichtbar sind, wurden einst für Meere gehalten und erhielten Namen wie Mare Erythraeum, Mare Sirenum oder Aurorae Sinus. Diese Namen werden heute nicht mehr verwendet. Die ausgedehnteste dunkle Struktur, die von der Erde aus gesehen werden kann, ist Syrtis Major, die „große Syrte“. + +Die südliche Halbkugel ist durchschnittlich sechs Kilometer höher als die nördliche und besteht aus geologisch älteren Formationen. Die Südhalbkugel ist zudem stärker verkratert, wie zum Beispiel in der Hochlandregion Arabia Terra. Unter den zahlreichen Einschlagkratern der Südhalbkugel befindet sich auch der größte Marskrater, Hellas Planitia, die Hellas-Tiefebene. Das Becken misst im Durchmesser bis zu 2100 km. In seinem Innern maß Mars Global Surveyor 8180 m unter Nullniveau – unter dem Durchschnittsniveau des Mars – den tiefsten Punkt auf dem Planeten. Der zweitgrößte Einschlagkrater des Mars, Chryse Planitia, liegt im Randbereich der nördlichen Tiefländer. +Ãœbersichtskarte des Mars mit den größten Regionen + +Die deutlichen Unterschiede der Topografie können durch innere Prozesse oder aber ein Impaktereignis verursacht worden sein. In letzterem Fall könnte in der Frühzeit der Marsentstehung ein größerer Himmelskörper, etwa ein Asteroid, auf der Nordhalbkugel eingeschlagen sein und die silikatische Kruste durchschlagen haben. Aus dem Innern könnte Lava ausgetreten sein und das Einschlagbecken ausgefüllt haben. + +Wie sich gezeigt hat, hat die Marskruste unter den nördlichen Tiefebenen eine Dicke von etwa 40 km, die im Gegensatz zum stufenartigen Ãœbergang an der Oberfläche nur langsam auf 70 km bis zum Südpol hin zunimmt. Dies könnte ein Indiz für innere Ursachen der Zweiteilung sein. +Oberflächenstrukturen +Gräben +In der Bildmitte liegt das System der Mariner-Täler. Ganz links die Tharsis-Vulkane (Bildmosaik von Viking 1 Orbiter, 1980) + +Südlich am Äquator und fast parallel zu ihm verlaufen die Valles Marineris (die Mariner-Täler), das größte bekannte Grabensystem des Sonnensystems. Es erstreckt sich über 4000 km und ist bis zu 700 km breit und bis zu 7 km tief. Es handelt sich um einen gewaltigen tektonischen Bruch. In seinem westlichen Teil, dem Noctis Labyrinthus, verästelt er sich zu einem chaotisch anmutenden Gewirr zahlreicher Schluchten und Täler, die bis zu 20 km breit und bis zu 5 km tief sind. + +Noctis Labyrinthus liegt auf der östlichen Flanke des Tharsis-Rückens, einer gewaltigen Wulst der Mars-Lithosphäre quer über dem Äquator mit einer Ausdehnung von etwa 4000 mal 3000 Kilometern und einer Höhe von bis zu rund 10 Kilometern über dem nördlichen Tiefland. Die Aufwölbung ist entlang einer offenbar zentralen Bruchlinie von drei sehr hohen, erloschenen Schildvulkanen besetzt: Ascraeus Mons, Pavonis Mons und Arsia Mons. Der Tharsis-Rücken und die Mariner-Täler dürften in ursächlichem Zusammenhang stehen. Wahrscheinlich drückten vulkanische Kräfte die Oberfläche des Planeten in dieser Region empor, wobei die Kruste im Bereich des Grabensystems aufgerissen wurde. Eine Vermutung besagt, dass diese vulkanische Tätigkeit durch ein Impaktereignis ausgelöst wurde, dessen Einschlagstelle das Hellas-Becken auf der gegenüberliegenden Seite des Mars sei. 2007 wurden im Nordosten von Arsia Mons sieben tiefere Schächte mit 100 bis 250 Metern Durchmesser entdeckt. +Vulkane +Olympus Mons, der mit 26 km höchste Berg im Sonnensystem +Die komplexe Caldera des Olympus Mons + +Dem Hellas-Becken exakt gegenüber befindet sich der Vulkanriese Alba Patera. Er ragt unmittelbar am Nordrand des Tharsis-Rückens rund 6 km über das umgebende Tiefland und ist mit einem Basisdurchmesser von über 1200 km der flächengrößte Vulkan im Sonnensystem. Patera ist die Bezeichnung für unregelmäßig begrenzte Vulkane mit flachem Relief. Alba Patera ist anscheinend einmal durch einen Kollaps in sich zusammengefallen. + +Unmittelbar westlich neben dem Tharsis-Rücken und südwestlich von Alba Patera ragt der höchste Vulkan, Olympus Mons, 26,4 km über die Umgebung des nördlichen Tieflands. Mit einer Gipfelhöhe von etwa 21,3 km über dem mittleren Null-Niveau ist er die höchste bekannte Erhebung im Sonnensystem. + +Ein weiteres, wenn auch weniger ausgedehntes vulkanisches Gebiet ist die Elysium-Region nördlich des Äquators mit den Schildvulkanen Elysium Mons, Hecates Tholus und Albor Tholus. +Stromtäler +Kasei Vallis, das größte Stromtal des Mars + +Auf der Marsoberfläche verlaufen Stromtäler, die mehrere hundert Kilometer lang und mehrere Kilometer breit sein können. Die heutigen Trockentäler beginnen ziemlich abrupt und haben keine Zuflüsse. Die meisten entspringen an den Enden der Mariner-Täler und laufen nördlich im Chryse-Becken zusammen. In den Tälern erheben sich mitunter stromlinienförmige Inseln. Sie weisen auf eine vergangene Flutperiode hin, bei der über einen geologisch relativ kurzen Zeitraum große Mengen Wasser geflossen sein müssen. Es könnte sich um Wassereis gehandelt haben, das sich unter der Marsoberfläche befand, danach durch vulkanische Prozesse geschmolzen wurde und dann abgeflossen ist. + +Darüber hinaus finden sich an Abhängen und Kraterrändern Spuren von Erosionen, die möglicherweise ebenfalls durch flüssiges Wasser verursacht wurden. + +2006 proklamierte die NASA einen einzigartigen Fund: Auf einigen NASA-Fotografien, die im Abstand von sieben Jahren vom Mars gemacht wurden, lassen sich Veränderungen auf der Marsoberfläche erkennen, die eine gewisse Ähnlichkeit mit Veränderungen durch fließendes Wasser haben. Innerhalb der NASA wird nun diskutiert, ob es neben Wassereis auch flüssiges Wasser geben könnte.[19] +Delta-Strukturen + +In alten Marslandschaften, z. B. im Eberswalde-Krater auf der Südhalbkugel oder in der äquatornahen Hochebene Xanthe Terra, finden sich typische Ablagerungen einstiger Flussdeltas. +Tharsis-Tholus-Streifen, aufgenommen mit der Hirise-Kamera des Mars Reconnaissance Orbiters. Der Streifen ist links in der Mitte zu sehen. Rechts sind die Ausläufer von Tharsis Tholus. + +Seit längerem vermutet man, dass die tief eingeschnittenen Täler in Xanthe Terra einst durch Flüsse geformt wurden. Wenn ein solcher Fluss in ein größeres Becken, beispielsweise einen Krater, mündete, lagerte er erodiertes Gesteinsmaterial als Sedimente ab. Die Art der Ablagerung hängt dabei von der Natur dieses Beckens ab: Ist es mit dem Wasser eines Sees gefüllt, so bildet sich ein Delta. Ist das Becken jedoch trocken, so verliert der Fluss an Geschwindigkeit und versickert langsam. Es bildet sich ein sogenannter Schwemmkegel, der sich deutlich vom Delta unterscheidet. + +Jüngste Analysen von Sedimentkörpern auf Basis von Orbiter-Fotos weisen an zahlreichen Stellen in Xanthe Terra auf Deltas hin – Flüsse und Seen waren in der Marsfrühzeit also recht verbreitet.[20] +Dark Slope Streaks + +Dunkle Streifen an Hängen sind auf dem Mars häufig zu sehen. Sie treten an steilen Hängen von Kratern, Mulden und Tälern auf und werden mit zunehmendem Alter heller. Manchmal beginnen sie in einem kleinen punktförmigen Bereich und werden dann zunehmend breiter. Man beobachtete, dass sie sich um Hindernisse, wie Mulden, weiterbewegen. + +Es wird angenommen, dass die Farbe von dunklen darunterliegenden Schichten stammt, die durch Lawinen von hellem Staub freigelegt werden. Es wurden jedoch auch andere Hypothesen aufgestellt, wie Wasser oder sogar der Wuchs von Organismen. Das Interessanteste an diesen dunklen Streifen (engl. dark slope streaks) ist, dass sie sich auch heute noch bilden.[21] +Chaotische Gebiete + +Auf dem Mars gibt es zahlreiche Regionen mit einer Häufung von unterschiedlich großen Gesteinsbrocken und tafelbergähnlichen Erhebungen. Sie werden auch „chaotische Gebiete“ genannt. Ariadnes Colles ist mit einer Fläche von etwa 29.000 km² so ein Gebiet. Es liegt im Terra Sirenum, einem südlichen Hochland des Mars. Dabei haben die Blöcke Ausmaße von einem bis zu zehn Kilometern Ausdehnung. Die größeren Blöcke ähneln Tafelbergen mit Erhebungen von bis zu 300 Metern. + +Es treten hierbei riefenartige Strukturen und „Runzelrücken“ (engl. wrinkle ridges) auf. Die Ursachen dafür sind vulkanisch-tektonische Bewegungen.[22] +Gesteinsschichten und Ablagerungen +Salzlager + +Mit Hilfe der Sonde Mars Odyssey wies die NASA ein umfangreiches Salzlager in den Hochebenen der Südhalbkugel des Mars nach. Vermutlich entstanden diese Ablagerungen durch Oberflächenwasser vor etwa 3,5 bis 3,9 Milliarden Jahren.[23] +Carbonatvorkommen + +Mit Hilfe der Compact Reconnaissance Imaging Spectrometer for Mars (CRISM) an Bord der NASA-Sonde Mars Reconnaissance Orbiter konnten Wissenschaftler Carbonat-Verbindungen in Gesteinsschichten rund um das knapp 1500 Kilometer große Isidis-Einschlagbecken nachweisen. Demnach wäre das vor mehr als 3,6 Milliarden Jahren existierende Wasser hier nicht sauer, sondern eher alkalisch oder neutral gewesen. + +Carbonatgestein entsteht, wenn Wasser und Kohlendioxid mit Kalzium, Eisen oder Magnesium in vulkanischem Gestein reagiert. Bei diesem Vorgang wird Kohlendioxid aus der Atmosphäre in dem Gestein eingelagert. Dies könnte bedeuten, dass der Mars früher eine dichte kohlendioxidreiche Atmosphäre hatte, wodurch ein wärmeres Klima möglich wurde, in dem es auch flüssiges Wasser gab.[24] + +Mit Hilfe von Daten des MRO wurden 2010 Gesteine entdeckt, die durch kosmische Einschläge aus der Tiefe an die Oberfläche befördert worden waren. Anhand ihrer spezifischen spektroskopischen Fingerabdrücke konnte festgestellt werden, dass sie hydrothermal (unter Einwirkung von Wasser) verändert wurden. Neben diesen Karbonat-Mineralen wurden auch Silikate nachgewiesen, die vermutlich auf die gleiche Weise entstanden sind. Dieser neue Fund beweise, dass es sich dabei nicht um örtlich begrenzte Vorkommen handele, sondern dass Karbonate in einer sehr großen Region des frühen Mars entstanden seien.[25] +Hämatitkügelchen +Hämatitkügelchen auf dem Felsen „Berry Bowl“ + +Die Marssonde Opportunity fand im Gebiet des Meridiani Planum millimetergroße Kügelchen des Eisenminerals Hämatit. Diese könnten sich vor Milliarden Jahren unter Einwirkung von Wasser abgelagert haben. Darüber hinaus wurden Minerale gefunden, die aus Schwefel-, Eisen- oder Bromverbindungen aufgebaut sind, wie zum Beispiel Jarosit. Auf der entgegengesetzten Hemisphäre[26] des Mars fand die Sonde Spirit in den „Columbia Hills“ das Mineral Goethit, das ausschließlich unter dem Einfluss von Wasser gebildet werden kann. +Kieselsäure + +Forscher entdeckten 2010 mit Hilfe von MRO Ablagerungen auf einem Vulkankegel, die von Wasser verursacht wurden. Sie konnten das Mineral als Kieselsäurehydrat identifizieren, das nur in Verbindung mit Wasser entstanden sein kann. Die Wissenschaftler nehmen an, dass, falls es auf dem Mars Leben gegeben hat, es sich dort in der hydrothermalen Umgebung am längsten hätte halten können.[27] +Polkappen +Die Nordpolregion, aufgenommen von Mars Global Surveyor +→ Hauptartikel: Polkappen des Mars + +Der Mars besitzt zwei auffällige Polkappen, die zum größten Teil aus gefrorenem Kohlendioxid (Trockeneis) sowie einem geringen Anteil an Wassereis zusammengesetzt sind. Die nördliche Polkappe hat während des nördlichen Marssommers einen Durchmesser von rund 1000 Kilometern. Ihre Dicke wird auf 5 km geschätzt. Die südliche Polkappe ist mit 350 km Durchmesser und einer Dicke von 1½ km weniger ausgedehnt. Die Polarkappen zeigen spiralförmige Einschnitte, deren Entstehung bislang nicht geklärt ist. + +Wenn im Sommer die jeweiligen Polkappen teilweise abschmelzen, werden darunter geschichtete Ablagerungen sichtbar, die möglicherweise abwechselnd aus Staub und Eis zusammengesetzt sind. Im Marswinter nimmt der Durchmesser der dann jeweils der Sonne abgewandten Polkappe durch ausfrierendes Kohlendioxid wieder zu. + +Da ein größerer, stabilisierender Mond fehlt, taumelt der Mars mit einer Periode von etwa 5 Millionen Jahren. Die Polarregionen werden daher immer wieder so stark erwärmt, dass das Wassereis schmilzt. Durch das abfließende Wasser entstehen die Riemen und Streifen an den Polkappen. +Wasservorkommen + +Der Mars erscheint heute als trockener Wüstenplanet. Die bislang vorliegenden Ergebnisse der Marsmissionen lassen jedoch den Schluss zu, dass die Marsatmosphäre in der Vergangenheit (vor Milliarden Jahren) wesentlich dichter war und auf der Oberfläche des Planeten reichlich flüssiges Wasser vorhanden war. +Eisvorkommen an den Polen +Die Südpolregion, aufgenommen von Viking Orbiter + +Durch Radarmessungen mit der Sonde Mars Express wurden in der Südpolarregion, dem Planum Australe, Ablagerungsschichten mit eingelagertem Wassereis entdeckt, die weit größer und tiefreichender als die hauptsächlich aus Kohlendioxideis bestehende Südpolkappe sind. Die Wassereisschichten bedecken eine Fläche, die fast der Größe Europas entspricht, und reichen in eine Tiefe von bis zu 3,7 Kilometern. Das in ihnen gespeicherte Wasservolumen wird auf bis zu 1,6 Millionen Kubikkilometer geschätzt – circa zwei Drittel des irdischen Grönlandeispanzers – was laut der Europäischen Weltraumorganisation (ESA) ausreichen würde, die Marsoberfläche mit einer etwa 11 Meter dicken Wasserschicht zu bedecken.[28] +Weitere Eisvorkommen +Beobachtete Veränderungen könnten Anzeichen für fließendes Wasser innerhalb der letzten Jahre sein.[19] + +Die schon lange gehegte Vermutung, dass sich unter der Oberfläche des Mars Wassereis befinden könnte, erwies sich 2005 durch Entdeckungen der ESA-Sonde Mars Express als richtig. + +Geologen gehen von wiederkehrenden Vereisungsperioden auf dem Mars aus, ähnlich irdischen Eiszeiten. Dabei sollen Gletscher bis in subtropische Breiten vorgestoßen sein. Die Forscher schließen dies aus Orbiter-Fotos, die Spuren einstiger Gletscher in diesen äquatornahen Gebieten zeigen. Zusätzlich stützen auch Radarmessungen aus der Umlaufbahn die Existenz beträchtlicher Mengen an Bodeneis in ebendiesen Gebieten. Diese Bodeneisvorkommen werden als Reste solcher „Mars-Eiszeiten“ gedeutet.[29] + +Auf der Europäischen Planetologenkonferenz EPSC im September 2008 in Münster wurden hochauflösende Bilder des Mars Reconnaissance Orbiters der NASA vorgestellt, die jüngste Einschlagkrater zeigen. Wegen der sehr dünnen Atmosphäre stürzen die Meteoriten praktisch ohne Verglühen auf die Marsoberfläche. Die fünf neuen Krater, die nur drei bis sechs Meter Durchmesser und eine Tiefe von 30 bis 60 cm aufweisen, wurden in mittleren nördlichen Breiten gefunden. Sie zeigen an ihrem Boden ein gleißend weißes Material. Wenige Monate später waren die weißen Flecken durch Sublimation verschwunden. Damit erhärten sich die Hinweise, dass auch weit außerhalb der Polgebiete Wassereis dicht unter der Marsoberfläche begraben ist.[30][31] +Flüssiges Wasser + +Unter der Kryosphäre des Mars werden große Mengen flüssigen Wassers vermutet. Nahe oder an der Oberfläche ist es für flüssiges Wasser zu kalt, und Eis würde langsam verdunsten, da der Partialdruck von Wasser in der Marsatmosphäre zu gering ist. + +Es gibt jedoch Hinweise, dass die Raumsonde Phoenix Wassertropfen auf der Oberfläche entdeckt habe. Dabei könnten Perchlorate als Frostschutz wirken. Diese Salze haben die Eigenschaft, Wasser anzuziehen. Dies kann auch Wasserdampf aus der Atmosphäre sein. Bei ausreichender Konzentration der Salze könnte Wasser sogar bis −70 °C flüssig bleiben. Durch eine Durchmischung mit Perchloraten könnte Wasser auch unter der Oberfläche in flüssigem Zustand vorhanden sein.[32] 2010 fanden Forscher der Uni Münster Belege dafür, dass zumindest im Frühjahr und in Kratern wie dem Russell-Krater flüssiges Wasser auf der Marsoberfläche existiert. Auf Fotos, die vom Mars Reconnaissance Orbiter aufgenommen wurden, entdeckten sie an steilen Hängen Erosionsrinnen, die sich zwischen November 2006 und Mai 2009 verlängert hatten. Dass die Rinnen nach unten dünner werden, deuten die Forscher als Versickern,[33] andere als Verdunsten.[34] + +Eine alternative Erklärung für die Erosionsrinnen schlugen Wissenschaftler der NASA 2010 vor: Kohlendioxid, das sich im marsianischen Winter bei unter -100 °C aus der Atmosphäre an den Berghängen als Trockeneis ansammelt, bei Erwärmung des Planeten als sublimiertes Gas die Hänge hinab"fließt" und dabei Staub erodiert.[35][36] + +Mit dem abbildenden Spektrometer (CRISM) des Mars Reconnaissance Orbiters konnten Spektren von aktiven (jahreszeitlich dunkleren) Rinnen gewonnen werden, deren Auswertung, 2015 veröffentlicht,[37] Magnesiumperchlorat, Magnesiumchlorat und Natriumperchlorat ergaben. +Siehe auch: Extraterrestrischer Ozean +Innerer Aufbau +Illustration des vermuteten Marsaufbaus + +Ãœber den inneren Aufbau des Mars ist nur wenig bekannt, da bislang nur begrenzt seismische Messungen vorgenommen werden konnten. + +Sein Inneres gliedert sich ähnlich dem Schalenaufbau der Erde in eine Kruste, einen Gesteinsmantel und einen Kern, der überwiegend aus Eisen und zu etwa 14 bis 17 Prozent aus Schwefel besteht. Der Kern beinhaltet etwa doppelt so viele leichte Elemente wie der Erdkern. Deshalb ist die Dichte des Kerns niedriger, als es bei einem reinen Eisenkern der Fall wäre.[38] + +Laut neueren experimentellen Simulationen der Bedingungen in der Ãœbergangszone zwischen Mantel und Kern (Messungen des Mars Global Surveyor ergaben eine Temperatur von 1500 Grad Celsius und einen Druck von 23 Gigapascal) hat der Kern des Mars im Unterschied zu dem der Erde keinen inneren festen Bereich, sondern ist vollständig flüssig.[39] Dies belegt auch die Analyse der Bahndaten des Mars Global Surveyor. Dabei konnte nachgewiesen werden, dass der Mars einen flüssigen Kern mit einem Radius zwischen 1520 und 1840 km besitzt und damit eine höhere Temperatur hat, als zuvor angenommen wurde. + +Der Kern ist von einem Mantel aus Silicaten umgeben, der viele der tektonischen und vulkanischen Merkmale des Planeten formte, nun aber inaktiv zu sein scheint. Die durchschnittliche Dicke der Planetenkruste beträgt etwa 50 km, mit einem Maximum von 125 km.[38] Im Vergleich dazu ist die Erdkruste mit einer Dicke von durchschnittlich 40 km nur etwa ein Drittel so dick, wenn man die relative Größe der beiden Planeten berücksichtigt. +Magnetfeld +Magnetisierung des Mars – Rot und Blau kennzeichnen entgegengesetzte Richtungen des Magnetfelds, ein Drittel der Südhalbkugel + +Anders als die Erde und der Merkur besitzt der Mars kein globales Magnetfeld mehr, seit er es ca. 500 Millionen Jahre nach seiner Entstehung verlor. Vermutlich erlosch es, als der Zerfall radioaktiver Elemente nicht mehr genügend Wärmeenergie produzierte, um im flüssigen Kern Konvektionsströmungen anzutreiben. Weil der Mars keinen festen inneren Kern besitzt, konnte er den Dynamo-Effekt nicht auf die gleiche Art aufbauen wie die Erde. + +Dennoch ergaben Messungen einzelne und sehr schwache lokale Magnetfelder. Die Messung des Magnetfeldes wird erschwert durch die Magnetisierung der Kruste mit Feldstärken von bis zu 220 Nanotesla und durch externe Magnetfelder mit Stärken zwischen wenigen Nanotesla und bis zu 100 Nanotesla, die durch die Wechselwirkung des Sonnenwindes mit der Marsatmosphäre entstehen und zeitlich sehr stark variieren. Nach den Analysen der Daten des Mars Global Surveyor konnte die Stärke des Magnetfeldes trotzdem sehr genau bestimmt werden – sie liegt bei weniger als 0,5 Nanotesla gegenüber 30 bis 60 Mikrotesla des Erdmagnetfeldes. + +Messungen von Magnetfeldlinien durch Mars Global Surveyor ergaben, dass Teile der planetaren Kruste durch das einstige Magnetfeld stark magnetisiert sind, aber mit unterschiedlicher Orientierung, wobei gleichgerichtete Bänder von etwa 1000 km Länge und 150 km Breite auftreten. Ihre Größe und Verteilung erinnert an die streifenförmigen Magnetanomalien auf den Ozeanböden der Erde. Durch sie wurde die Theorie der Plattentektonik gestützt, weshalb 1991 auch eine ähnliche Theorie für den Mars entwickelt wurde. Magnetische Beobachtungen auf dem Mars sind jedoch noch nicht detailliert genug, um sichere Schlussfolgerungen zu erlauben oder gar die Theorie zu bestätigen. + +Möglicherweise werden bei der mit der Zeit zwangsläufigen Abkühlung des Marskerns durch die damit einsetzende Auskristallisation des Eisens und die freigesetzte Kristallisationswärme wieder Konvektionen einsetzen, die ausreichen, dass der Planet in ein paar Milliarden Jahren wieder über ein globales Magnetfeld in alter Stärke verfügt.[39] +Monde +Die Umlaufbahnen von Phobos und Deimos +Phobos (oben) und Deimos (unten) im Größenvergleich + +Zwei kleine Monde, Phobos und Deimos (griech. Furcht und Schrecken), umkreisen den Mars. Sie wurden 1877 von dem US-amerikanischen Astronomen Asaph Hall entdeckt und nach den in der Ilias überlieferten beiden Begleitern, die den Wagen des Kriegsgottes Ares (lat. Mars) ziehen, benannt. + +Phobos (Durchmesser 26,8 × 22,4 × 18,4 km) und Deimos (Durchmesser 15,0 × 12,2 × 10,4 km) sind zwei unregelmäßig geformte Felsbrocken. Möglicherweise handelt es sich um Asteroiden, die vom Mars eingefangen wurden. Phobos’ große Halbachse beträgt 9.376 km, diejenige von Deimos 23.459 km. Phobos ist damit kaum mehr als 6.000 km von der Oberfläche des Mars entfernt, der Abstand ist geringer als der Durchmesser des Planeten. + +Die periodischen Umlaufbewegungen der beiden Monde befinden sich mit der Größe von 0,31891 (Phobos) und 1,262 Tagen (Deimos) zueinander in einer 1:4-Bahnresonanz. + +Die Umlaufzeit von Phobos ist kürzer als die Rotationszeit von Mars. Der Mond kommt dem Planeten durch die Gezeitenwechselwirkung auf einer Spiralbahn langsam immer näher und wird schließlich auf diesen stürzen oder durch die Gezeitenkräfte auseinandergerissen werden, so dass er für kurze Zeit zu einem Marsring wird. Für ihn berechneten DLR-Forscher, basierend auf neueren Daten der europäischen Raumsonde Mars Express, dass dies in ca. 50 Millionen Jahren geschehen wird. Deimos wird dagegen in einer noch ferneren Zukunft dem Mars entfliehen. Er driftet durch die Gezeitenwechselwirkung langsam nach außen, wie alle Monde, die langsamer (und nicht retrograd) um einen Planeten kreisen, als dieser rotiert. + +Ihre Existenz war schon lange vorher mehrmals literarisch beschrieben worden, zuletzt von Voltaire, der in seiner 1750 erschienenen Geschichte Micromégas über zwei Marsmonde schreibt. Es ist wahrscheinlich, dass Voltaire diese Idee von Jonathan Swift übernahm, dessen Buch Gullivers Reisen 1726 erschienen war. Darin wird im dritten Teil beschrieben, die Astronomen des Landes Laputa hätten „ebenfalls zwei kleinere Sterne oder Satelliten entdeckt, die um den Mars kreisen, wovon der innere vom Zentrum des Hauptplaneten genau drei seiner Durchmesser entfernt ist und der äußere fünf.“ Es wird vermutet, dass Swift von einer Fehlinterpretation Johannes Keplers gehört hatte. Der hatte das Anagramm, das Galileo Galilei 1609 an ihn schickte, um ihm die Entdeckung der Phasen der Venus mitzuteilen, als die Entdeckung zweier Marsmonde aufgefasst. +Entstehungsgeschichte +Datei:Mars.ogvMediendatei abspielen +Animation, welche die Topographie des Mars zeigt. Olympus Mons → Mariner-Täler → Mars Südpol → Hellas-Becken → Mars Nordpol + +Anhand der astrogeologischen Formationenvielfalt und der Verteilung von Einschlagskratern kann ein Großteil der Geschichte des Planeten abgeleitet werden. Der Mars entstand, wie die übrigen Planeten des Sonnensystems, vor etwa 4,5 Milliarden Jahren durch Zusammenballung kleinerer Körper, sogenannter Planetesimale, innerhalb der protoplanetaren Scheibe zu einem Protoplaneten. Vor 4 Milliarden Jahren bildete der im Innern noch glutflüssige planetare Körper eine feste Gesteinskruste aus, die einem heftigen Bombardement von Asteroiden und Kometen ausgesetzt war. +Noachische Periode + +Die ältesten der heute noch vorhandenen Formationen, wie das Hellas-Becken, und die verkraterten Hochländer, wie Noachis Terra, wurden vor 3,8 bis 3,5 Milliarden Jahren, in der so genannten Noachischen Periode, gebildet. In dieser Periode setzte die Zweiteilung der Marsoberfläche ein, wobei die nördlichen Tiefländer gebildet wurden. Durch starke vulkanische Eruptionen wurden weite Teile des Planeten von Ablagerungen aus vulkanischer Lava und Asche bedeckt. Diese wurden an vielen Stellen durch Wind und Wasser wieder abgetragen und ließen ein Netzwerk von Tälern zurück. +Hesperianische Periode + +Das geologische „Mittelalter“ des Mars wird als Hesperianische Periode bezeichnet. Sie umfasst den Zeitraum von vor 3,5 bis 1,8 Milliarden Jahren. In dieser Periode ergossen sich riesige Lavamengen aus ausgedehnten Spalten in der Marskruste und bildeten weite Ebenen, wie Hesperia Planum. Es entstanden auch die ältesten Vulkane der Tharsis- und der Elysium-Region, wobei die Gesteinskruste stark verformt wurde und sich das Grabensystem der Mariner-Täler öffnete. Es bildeten sich die gewaltigen Stromtäler, in denen große Wassermengen flossen und sich stellenweise aufstauten. + +Es entwickelte sich auf dem Mars ein Wasserkreislauf. Im Unterschied zur Erde gab es jedoch keinen Wetterzyklus mit Verdunstung, Wolkenbildung und anschließendem Niederschlag. Das Wasser versickerte im Untergrund und wurde später durch hydrothermale Prozesse wieder an die Oberfläche getrieben. Da jedoch der Planet immer weiter abkühlte, endete dieser Prozess vor etwa 1,5 Milliarden Jahren, und es hielten sich nur noch Gletscher an der Oberfläche. Zeichen dieser Aktivität sind vor kurzem entdeckte Moränen am Olympus Mons.[40] +Amazonische Periode + +Das jüngste geologische Zeitalter des Mars wird als Amazonische Periode bezeichnet und begann vor 1,8 Milliarden Jahren. In dieser Phase entstanden die jüngeren Vulkane der Tharsis- und der Elysium-Region, aus denen große Lavamassen flossen. So bildeten sich weite Ebenen aus wie zum Beispiel Amazonis Planitia. + +2008 fanden Forscher Hinweise auf Geysire auf dem Mars, die vor einigen Millionen Jahren aktiv gewesen sein dürften. Dabei hätten sie Fontänen von kohlensäurehaltigem Wasser einige Kilometer weit in die Höhe geschossen. Darauf deuten auch die Formen von Ablagerungen hin, die britische Forscher in der Nähe zweier ausgedehnter Grabensysteme entdeckten. Wahrscheinlich wurden diese Eruptionen durch Blasen aus Kohlendioxid ausgelöst. Dadurch wurde das Wasser aus einer Tiefe von bis zu vier Kilometern durch Spalten im Marsboden an die Oberfläche gedrückt. Die Fontänen müssen dabei mit einem so großen Druck herausgepresst worden sein, dass das schlammige Wasser erst in einer Entfernung von mehreren Kilometern von der Austrittsstelle wieder auf den Boden regnete oder, bedingt durch die tiefen Temperaturen, als Hagel niederging.[41] + +Gegenwärtig wird die Oberfläche des Mars hauptsächlich durch Winderosion und Hangrutschung geformt. +Erforschung + +Aufgrund seiner großen Helligkeit war der Mars schon im frühen Altertum als Planet bekannt. Wegen seiner langen Planetenschleifen (die alle 2 Jahre in der Opposition auftreten) galten seine Bewegungen den Ägyptern als unvorhersehbar. Den Babyloniern gelang es zwar, sie näherungsweise vorauszusagen, sie schrieben die Bahnanomalien aber den Launen und der Gewalttätigkeit des Gottes Nergal zu. +Vor dem Raumfahrtzeitalter +Marsoberfläche nach Schiaparelli (1888) +Mars auf einer astronomischen Zeichnung des 19. Jahrhunderts (Trouvelot, 1881) + + Tycho Brahe (1546–1601) vermaß die Planetenpositionen des Mars mit bis dahin nicht gekannter Genauigkeit und ermöglichte es so Johannes Kepler (1571–1630), die elliptische Bahn des Planeten zu berechnen und die drei Keplerschen Gesetze abzuleiten. + Christiaan Huygens entdeckte 1659 eine dunkle, dreieckige Zone (Syrtis Major) auf der Marsoberfläche. Aus deren Positionsveränderungen errechnete er die Eigenrotation des Mars zu 24,5 Stunden (heutiger Wert: 24,623 Stunden). + Giovanni Domenico Cassini beschrieb 1666 die weißen Polkappen des Mars. + Wilhelm Herschel bestimmte 1784 die Neigung der Rotationsachse gegenüber der Umlaufbahn mit 25° (heutiger Wert 25,19°). + Wilhelm Beer fertigte 1830 die erste Marskarte an, Angelo Secchi 1863 schon in Farbe. + Richard Proctor veröffentlichte 1869 eine detaillierte Marskarte, die er aus Zeichnungen von William Rutter Dawes erstellte. + Giovanni Schiaparelli nahm 1877 auf der Marsoberfläche zarte Linienstrukturen wahr, die er „Canali“ (italienisch für „Rinnen“ oder „Gräben“) nannte und in eine detaillierte Karte eintrug. Er machte zunächst keine Angaben über den Ursprung der Canali (die er für breiter als 100 km schätzte), doch wurden sie in englischen Medien fälschlich als „Channel“ (Kanäle) übersetzt und bald als Werk intelligenter Marsbewohner interpretiert. Auf älteren Marskarten erhielten viele dieser Linien auch Namen. Während einige Astronomen Schiaparellis Beobachtungen bestätigten, wurde die Existenz der Canali von anderen angezweifelt und als Ergebnis optischer Täuschungen bezeichnet. Erst der Vorbeiflug der amerikanischen Mariner-Sonden beendete die Spekulationen, denn Fotos der Marsoberfläche zeigten keine so breiten Rinnen. Drei Canali entsprechen aber den riesigen Canyons Valles Marineris, andere zeichnen Geländestufen und Schattenlinien nach, einige auch längere Kraterketten. + +→ Hauptartikel: Marskanäle + + Asaph Hall entdeckt bei der günstigen Opposition 1877 die beiden Marsmonde Phobos und Deimos. + Percival Lowell gründet 1894 das Lowell-Observatorium in Arizona, um die Marskanäle, ihre jahreszeitlichen Verfärbungen und allfällige Lebensspuren zu erforschen. Spektroskopisch findet man biologische Moleküle, die sich allerdings später als terrestrisch erweisen. In der Atmosphäre werden Spektrallinien von Sauerstoff entdeckt, dessen Volumsanteil aber überschätzt wird. + Eugène Antoniadi bestätigte zunächst die Marskanäle, kam aber 1909 am Riesenteleskop Meudon zum Schluss, sie würden nur in kleineren Fernrohren als solche erscheinen. In seinen detaillierten Marskarten – die bis zu den ersten Marssonden kaum mehr übertroffen wurden – zeichnete er sie als Folge diffuser Flecken ein. + Gerard Kuiper wies in den 1950ern Kohlendioxid in der Marsatmosphäre nach und glaubte bis zu den ersten Marssonden an die mögliche Existenz von Moosen oder Flechten. + +Im Raumfahrtzeitalter +Die erste Nahaufnahme vom Mars, aufgenommen von Mariner 4 + +Viele unbemannte Raumsonden wurden schon zum Mars entsandt, von denen einige sehr erfolgreich waren. Etwa die Hälfte der Missionen endete in einem Misserfolg, die meisten davon waren sowjetische Sonden. Im Unterschied zur Erkundung des Erdmondes gibt es bis heute keine Gesteinsproben, die vom Mars geholt wurden, so dass Marsmeteoriten die einzige Möglichkeit sind, Material vom Mars in irdischen Laboratorien zu erforschen. Bislang hat es auch noch keine bemannte Marsmission gegeben — ein aktueller Ansatz dafür ist Mars One. +→ Hauptartikel: Chronologie der Marsmissionen +1960er Jahre +Darstellung auf einer ungarischen Sondermarke von 1964 + +Die beiden sowjetischen Sonden Marsnik 1 und 2 wurden im Oktober 1960 gestartet, um am Mars vorbeizufliegen, erreichten aber noch nicht einmal die Erdumlaufbahn. 1962 versagten drei weitere sowjetische Sonden (Sputnik 22, Mars 1 und Sputnik 24), zwei von ihnen blieben im Erdorbit, die dritte verlor auf dem Weg zum Mars den Kontakt mit der Erde. Auch ein weiterer Versuch im Jahre 1964 schlug fehl. + +Zwischen 1962 und 1973 wurden zehn Mariner-Raumsonden vom Jet Propulsion Laboratory der NASA entwickelt und gebaut, um das innere Sonnensystem zu erforschen. Es waren relativ kleine Sonden, die meistens nicht einmal eine halbe Tonne wogen. + +Mariner 3 und Mariner 4 waren identische Raumsonden, die am Mars vorbeifliegen sollten. Mariner 3 wurde am 5. November 1964 gestartet, aber die Transportverkleidung löste sich nicht richtig, und die Sonde erreichte den Mars nicht. + +Drei Wochen später, am 28. November 1964, wurde Mariner 4 erfolgreich auf eine achtmonatige Reise zum Roten Planeten geschickt. Am 15. Juli 1965 flog die Sonde am Mars vorbei und lieferte die ersten Nahaufnahmen – insgesamt 22 Fotos – des Planeten. Die Bilder zeigten mondähnliche Krater, von denen einige mit Reif bedeckt zu sein scheinen. + +1969 folgten Mariner 6 und Mariner 7 und lieferten insgesamt 200 Fotos. +1970er Jahre + +1971 missglückte der Start von Mariner 8, dafür erhielt die NASA im gleichen Jahr von Mariner 9 mehrere tausend Bilder. + +Ebenfalls 1971 landete mit der sowjetischen Mars 3 die erste Sonde weich auf dem Mars, nachdem Mars 2 wenige Tage zuvor gescheitert war. Der Funkkontakt brach jedoch 20 Sekunden nach der Landung ab. Mögliche Ursache war ein gerade tobender globaler Staubsturm, der den Lander umgeworfen haben könnte. +Bild von Viking 1. Der große Felsen links von der Mitte ist etwa zwei Meter breit. Er wurde Big Joe getauft. + +In den 1970er-Jahren landeten die Viking-Sonden erfolgreich auf dem Mars und lieferten die ersten Farbbilder sowie Daten von Bodenproben: Viking 1 schaffte am 20. Juli 1976 als erste US-amerikanische Sonde eine weiche Landung. Die Sowjetunion versuchte noch weitere Landungen auf dem Mars, scheiterte jedoch. +1980er Jahre + +Die einzigen Raumsonden, die in den 1980er Jahren zum Mars flogen, waren die beiden sowjetischen Fobos-Sonden. Sie wurden 1988 von Baikonur aus gestartet und sollten den Mars und seinen Mond Phobos untersuchen. Dafür waren sie im Rahmen einer internationalen Kooperation neben sowjetischen auch mit zahlreichen westlichen Instrumenten bestückt. Der Kontakt zu Fobos 1 brach jedoch schon auf dem Weg zum Mars wegen eines falschen Steuerbefehls ab. Fobos 2 erreichte eine Marsumlaufbahn und einige Daten und Bilder vom Mars wurden zur Erde übertragen. Danach wurde die Sonde zu Phobos gelenkt. Jedoch brach kurz vor dem Rendezvous auch der Kontakt zu Fobos 2 ab. +1990er Jahre + +1992 wurde die US-Sonde Mars Observer gestartet. Sie ging 1993 kurz vor dem Einschwenken in die Umlaufbahn verloren. + +Am 16. November 1996 startete Mars 96, die erste russische Raumsonde seit dem Zusammenbruch der Sowjetunion. Doch versagte die Proton-Trägerrakete, so dass Mars 96 wieder in die Erdatmosphäre eintrat und verglühte. +Der Marsrover Sojourner + +Besonderes Aufsehen erregte 1997 der Mars Pathfinder, bei dem zum ersten Mal ein kleines Marsmobil, der Rover Sojourner, eingesetzt wurde. Er landete publikumswirksam am 4. Juli, dem amerikanischen Unabhängigkeitstag, und lieferte viele Aufnahmen von der Umgebung der Landestelle, die von der NASA zum ersten Mal sofort im Internet veröffentlicht wurden. + +Eine weitere erfolgreiche Mission war 1997 die des Mars Global Surveyor, bei der die Marsoberfläche in einer hohen Auflösung kartografiert wurde. Am 2. November 2006 – fünf Tage vor dem 10-jährigen Jubiläum seines Starts – brach der Kontakt mit dem Satelliten ab. + +Das Scheitern der Marssonden Mars Climate Orbiter, der wegen eines Programmierfehlers in der Navigation verlorenging, und Mars Polar Lander, der wahrscheinlich wegen eines fehlerhaften Sensors bei der Landung aus größerer Höhe abstürzte, stellte 1999 einen herben Rückschlag für die Marsforschung dar. + +Auch die 1998 gestartete japanische Raumsonde Nozomi konnte den Mars nicht erreichen. +2000er Jahre + +Seit dem 24. Oktober 2001 umkreist außer dem Global Surveyor noch 2001 Mars Odyssey den roten Planeten, der spezielle Instrumente zur Fernerkundung von Wasservorkommen an Bord hat. + +Von den bis 2002 insgesamt 33 Missionen zum Mars waren nur acht erfolgreich, allesamt US-amerikanisch. + +Am 2. Juni 2003 startete im Rahmen der ersten europäischen Marsmission die ESA-Raumsonde Mars Express mit dem Landegerät Beagle 2 erfolgreich zum Mars. Zwar landete Beagle 2 am 25. Dezember 2003 auf der Marsoberfläche, allerdings konnte der Funkkontakt niemals aufgebaut werden. 2014 wurde er auf Bildern des MRO entdeckt. Der Orbiter Mars Express arbeitet jedoch erfolgreich in der Marsumlaufbahn und konnte unter anderem viele Aufnahmen von Formationen machen, von denen man annimmt, dass sie ausgetrocknete oder ausgefrorene Flusstäler seien. Er kartiert den Planeten u. a. mittels Radar und einer Stereokamera im sichtbaren Licht, sowie spektroskopisch auch in Infrarot. Am 30. November 2005 fand die Sonde unter der Ebene Chryse Planitia ein Eisfeld mit 250 km Durchmesser. +Marsrover Opportunity (MER-B) + +Am 10. Juni 2003 wurde die US-amerikanische Marssonde Spirit (MER-A) zum Mars gestartet. An Bord befand sich ein Rover, der nach der Landung drei Monate lang Gesteinsproben entnehmen und nach Spuren von früher vorhandenem Wasser suchen sollte. Die Landung erfolgte am 4. Januar 2004 im Krater Gusev, in den das Ma'adim Vallis mündet. Im April 2009 fuhr sich der Rover in einer Sandanhäufung fest und konnte seit dem 22. März 2010 auch nicht mehr kontaktiert werden (Stand: März 2011). + +Am 8. Juli 2003 wurde die baugleiche Sonde Opportunity (MER-B) mit einer Delta-II-Rakete gestartet. Sie landete am 25. Januar 2004 in der Tiefebene Meridiani Planum nahe dem Marsäquator, fast genau gegenüber von Spirit.[26] Die vom Rover gesammelten Beweise, dass der Mars einst warm und feucht war, wurden im Jahresrückblick der Fachzeitschrift Science mit der Wahl zum „Durchbruch des Jahres 2004“ gewürdigt. Opportunity ist noch immer aktiv (Stand: April 2015). +vergrößern und Informationen zum Bild anzeigen +Das Panoramabild, von dem hier nur ein Ausschnitt zu sehen ist, wurde aus hunderten Einzelbildern montiert, die Opportunity vom 6. Oktober bis 6. November 2006 aufgenommen hat. Es zeigt annähernd in Echtfarben den Victoria-Krater vom Cap Verde + +Am 12. August 2005 wurde die US-Sonde Mars Reconnaissance Orbiter mit einer Atlas-V-Rakete auf die Reise geschickt und erreichte am 10. März 2006 den Mars. Sie soll ihn mit hochauflösenden Kameras kartografieren und auch nach geeigneten Landestellen für spätere Rover-Missionen suchen. Außerdem soll sie zur Hochgeschwindigkeits-Kommunikation zwischen zukünftigen Raumsonden auf der Marsoberfläche und der Erde dienen. +Sonnenuntergang auf dem Mars beim Krater Gusev (Spirit am 19. Mai 2005) + +2007 fotografierte Mars Reconnaissance sieben fast kreisrunde schwarze und strukturlosen Flecken, die im Nordosten des Marsvulkans Arsia Mons liegen.[42] Der größte, genannt Jeanne, hat einen Durchmesser von etwa 150 Meter. Eine Schrägaufnahme der sonnenbeschienenen Seitenwand im August 2007 zeigte, dass es sich um einen mindestens 78 Meter tiefen senkrechten Schacht handeln muss. Diese Strukturen sind sehr wahrscheinlich vulkanischer Natur und durch den Einbruch einer nicht mehr tragfähigen Deckschicht entstanden.[43] + +Am 26. Dezember 2007 machte die High Resolution Stereo Camera des Mars Express Aufnahmen von Eumenides Dorsum, einem Bergrücken westlich der Tharsis-Region. Die Aufnahmen zeigen kilometerlange lineare Strukturen, die von Kanälen unterbrochen sind. Es handelt sich um durch Winderosion entstandene Yardangs (Windhöcker bzw. Sandwälle). + +Mit der Sonde Mars Odyssey wies die NASA im März 2008 eine umfangreiche Salzlagerstätte in den Hochebenen der Südhalbkugel nach. Die Wissenschaftler des JPL in Pasadena meinen, sie habe sich vor 3,5 bis 3,9 Milliarden Jahren gebildet. Vermutlich entstanden die Salze durch mineralienreiches Grundwasser, das an die Oberfläche gelangte und dort verdunstete. Die Bilder von „Mars Odyssey“ zeigen kanalähnliche Strukturen, die in den Salzbecken enden.[23] Insgesamt wurden über 200 Gebiete mit Salzvorkommen ausgemacht, die zwischen 1 und 25 km² groß sind. Die Entdeckung deutet darauf hin, dass der Mars vor langer Zeit ein wärmeres und deutlich feuchteres Klima hatte.[44]. Solche Klimaschwankungen dürften durch aperiodische Änderungen der Rotationsachse entstehen, deren Neigung (derzeit 25°) zwischen 14 und 50° variiert.[45] +Die Orte der sieben erfolgreichen Marslandungen + +Am 26. Mai 2008 landete die Sonde Phoenix im nördlichen Polargebiet des Planeten. Sie suchte dort bis November 2008 im Boden nach Wassereis und „habitablen Zonen“, also für primitive Organismen bewohnbare Umgebungen. Ihr Roboterarm konnte Proben aus etwa 50 cm Tiefe holen, um sie dann in einem Minilabor zu analysieren. Phoenix entdeckte bei einer Grabung weiße Klümpchen, die nach einigen Tagen verschwanden. Man vermutete, dass es sich dabei um Wassereis handelt,[46] was am 31. Juli bestätigt wurde – beim Erhitzen einer Gesteinsprobe trat Wasserdampf aus.[47] Mit dem nasschemischen Labor MECA, das die wasserlöslichen Ionen im Marsboden bestimmte, konnten erhebliche Mengen an Perchloraten detektiert werden. Auf der Erde kommen Perchlorate in den ariden Wüstengebieten vor. Natriumperchlorat wird durch Oxidation von Natriumchlorid in der Atmosphäre erzeugt und dann mit dem Staub abgelagert. + + +2010er Jahre +Curiosity auf dem Mars + +Am 26. November 2011 um 15:02 UTC startete die Rover-Mission Mars Science Laboratory (Curiosity) der NASA mit einer Atlas V(541) von Cape Canaveral und landete am 6. August 2012 auf dem Mars. Der Rover kann weite Strecken zurücklegen und umfassende Untersuchungen eines großen Umkreises durchführen. Wichtigstes Projektziel sind geologische Analysen des Marsbodens. + +Am 18. November 2013 startete eine weitere NASA-Sonde zum Mars. Die Mission mit dem Projektnamen „Mars Atmosphere and Volatile Evolution“ (MAVEN) soll das Rätsel der verlorenen Atmosphäre aufklären.[48] Der Orbiter umkreist den Planeten seit dem 22. September 2014 und soll sich in fünf Tiefflügen annähern. Weiters wurde am 5. November 2013 eine indische Marsmission gestartet. Sie soll ebenfalls die Atmosphäre sowie verschiedene Oberflächenphänomene untersuchen.[49] + +ExoMars Rover ist ein europäischer Rover, dessen Start für 2020 geplant ist. Er soll speziell nach Spuren von Leben suchen. Die Finanzierung dieser Mission ist allerdings noch ungewiss. +Geplante Missionen + +Weitere Pläne der NASA und ESA zur Marserforschung enthalten unter anderem das Aussetzen von kleineren Flugzeugen in der Atmosphäre und – nach 2020 – die Rückführung von Marsproben zur Erde (Mission Mars Sample Return). +vergrößern und Informationen zum Bild anzeigen +Panoramabild der Marsoberfläche, aufgenommen von der Sonde Pathfinder + +Im Januar 2004 kündigte der US-amerikanische Präsident George W. Bush Anstrengungen der USA für eine bemannte Marsmission an. Im Rahmen des Raumfahrtprogramms Constellation plante die NASA diese Flüge für die Zeit nach 2020. Der ehemalige NASA-Direktor Michael Griffin nannte die Zeit bis 2037. Constellation wurde aber durch die Nachfolgeregierung unter Barack Obama aus Kostengründen gestrichen.[50] + +Auch das langfristig angelegte europäische Raumfahrtprogramm Aurora strebt insbesondere die Landung eines Menschen auf dem Mars an und plant sie für das Jahr 2033. + +Darüber hinaus existieren im Rahmen von Visionen einer Marskolonisation Vorstellungen, den Mars durch Terraforming in weiter Zukunft in einen für den Menschen lebensfreundlicheren Planeten umzuwandeln. Robert Zubrin, Edwin Aldrin und andere namhafte Stimmen in den Vereinigten Staaten von Amerika treten mittlerweile dafür ein, auf dem Mars unter dem Motto Mars to Stay schon in naher Zukunft die erste menschliche Siedlung zu begründen: Das sei möglich und sinnvoll, weil es wesentlich weniger Aufwand erfordere, die ersten Marsfahrer dauerhaft auf dem Planeten siedeln zu lassen, als sie sogleich wieder zur Erde zurückzuholen. +Möglichkeit von Leben +→ Hauptartikel: Leben auf dem Mars + +Die Ökosphäre (oder habitable Zone) des Sonnensystems reicht von 0,95 bis 1,37 AE Abstand zur Sonne. Im Sonnensystem befindet sich nur die Erde innerhalb dieses Gürtels um die Sonne, der Mars liegt knapp außerhalb. + +Höheres oder gar intelligentes Leben gibt es auf dem Mars nicht, Wissenschaftler halten jedoch primitive Lebensformen (Mikroben) tiefer im Boden, um vor UV-Strahlen geschützt zu sein, für denkbar.[51] Tatsächlich haben die in der Antarktis im Inneren von Gesteinen lebenden Pilzarten Cryomyces antarcticus und Cryomyces minteri simulierte Mars-Umweltbedingungen relativ gut überstanden: Nach 18 Monaten auf der Internationalen Raumstation[52] enthielten knapp 10 % der Proben noch fortpflanzungsfähige Zellen.[53] Auch die Flechte Xanthoria elegans hat die simulierten Marsbedingungen während des Experiments überlebt. +Vermutungen vor dem Raumzeitalter +Marsoberfläche nach Oswald Lohse (1888). Auf der Karte ist das Kanalsystem Schiaparellis nicht eingezeichnet. Die von Lohse gewählten Namen für die „Seen“ und „Ozeane“ sind heute nicht mehr gebräuchlich + +Der Gedanke an die Möglichkeit von Leben auf dem Mars beflügelte oft die Fantasie der Menschen. Im 18. Jahrhundert beobachtete man, dass die dunklen Flecken auf der Marsoberfläche ihre Farbe änderten und wuchsen oder schrumpften. Man hielt sie für ausgedehnte Vegetationszonen, deren Ausdehnung sich mit den Jahreszeiten änderte. + +Durch Schiaparellis „Entdeckung“ der Marskanäle wurden die Spekulationen um intelligentes Leben auf dem Mars angefacht. + +So entstanden zahlreiche Legenden um vermeintliche Zivilisationen auf dem Mars. Die Diskussionen um die „Marsmenschen“ hielten etwa ein Jahrhundert an. Der US-Amerikaner Percival Lowell, einer der heftigsten Verfechter der Marskanäle-Theorie, gründete sogar eine eigene Sternwarte, um die Marsbewohner zu erforschen. Für ihn waren die Kanäle das Produkt außerirdischer Ingenieure, die geschaffen wurden, um die Marszivilisation vor einer großen Trockenheit zu retten. Lowell beschrieb seine Vorstellungen der Marswelt in zahlreichen Publikationen, die weite Verbreitung fanden. + +Obwohl nicht alle Astronomen die Kanäle sehen konnten und keine Fotos existierten, hielt sich die Theorie, begleitet von einer heftigen Debatte. Die Vorstellung von außerirdischem Leben übt bis heute eine Faszination auf die Menschen aus, die mit wissenschaftlichem Interesse alleine oft nicht erklärt werden kann. Erst die Ergebnisse der unbemannten Marsmissionen beendeten den Streit um die Kanäle. + +Untersuchungen durch Viking + +Als im Juli 1976 der Orbiter 1 der Viking-Mission Bilder der Cydonia-Region machte und diese zur Erde schickte, wurde der Mars in der Öffentlichkeit wieder zum Gesprächsthema. Eine der Aufnahmen zeigte eine Formation auf der Marsoberfläche, die einem menschlichen Gesicht ähnelte, das gen Himmel blickt. In der unmittelbaren Nähe wurden außerdem Strukturen entdeckt, die Pyramiden auf der Erde ähneln, sowie rechteckige Strukturen (von den Wissenschaftlern „Inka-Stadt“ getauft). Erst die Mission Mars Global Surveyor der NASA brachte im April 1998 für viele die Ernüchterung: Alle entdeckten Strukturen waren das Ergebnis natürlicher Erosion. Durch neue Bilder mit wesentlich höherer Auflösung wurde deutlich, dass auf dem Mars keine künstlichen Strukturen außerirdischer Intelligenz ersichtlich sind. +Das Marsgesicht in der Cydonia-Region; Aufnahme des Orbiters von Viking 1, 1976 + +Viking 1 und 2 hatten unter anderem die Aufgabe, der Frage nach dem Leben auf dem Mars nachzugehen. Dabei wurden ein chemisches und drei biologische Experimente durchgeführt. In dem chemischen Experiment wurde versucht, organische Substanzen im Marsboden nachzuweisen. Dazu wurde eine am MIT entwickelte GC/MS-Einheit (Kopplung eines Gaschromatographen mit einem Massenspektrometer) benutzt. Es konnten allerdings keine auf Kohlenstoff aufbauenden organischen Substanzen nachgewiesen werden. + +Das erste biologische Experiment beruhte auf Stoffwechselaktivitäten von Organismen. Eine Bodenprobe wurde mit einer Nährlösung benetzt und entstehende Gase registriert. Der Marsboden reagierte auf das Experiment mit Abgabe großer Mengen Sauerstoff. Im zweiten Experiment wurde eine Nährlösung mit radioaktiven Kohlenstoffatomen versehen und auf eine Probe gegeben. Als Ergebnis eines Stoffwechsels hätten sie unter den ausgeschiedenen Gasen nachgewiesen werden müssen. Tatsächlich wurden radioaktive Kohlenstoffatome nachgewiesen. Das dritte Experiment war ein Photosynthese-Experiment. Radioaktiv markiertes Kohlendioxid wurde dem Marsboden zugesetzt. Dieses Kohlendioxid hätte assimiliert werden und später nachgewiesen werden müssen. Auch dieses Ergebnis war positiv. Obwohl die Ergebnisse der biologischen Experimente positiv waren, gaben sie aufgrund des negativen Ergebnisses des GC/MS-Versuchs keinen schlüssigen Beweis für die Existenz oder Nichtexistenz von Leben auf dem Mars. +1990er und 2000er Jahre +Marsgesicht, Aufnahme von Mars Global Surveyor, 2001 + +Im Jahr 1996 fanden David S. McKay und seine Mitarbeiter Strukturen im Marsmeteoriten ALH 84001, die sie als Spuren von fossilen Bakterien deuteten. Das in diesem Meteoriten gefundene, kettenartig angeordnete Magnetit ähnelt morphologisch dem bakteriellen Magnetit aus Magnetospirillum magnetotacticum. Allerdings wird die Beweiskraft der gefundenen Strukturen von vielen Wissenschaftlern angezweifelt, da diese auch auf rein chemischem Wege entstehen konnten. + +Am 23. Januar 2004 entdeckte die europäische Marssonde Mars Express am Südpol des Mars große Mengen gefrorenen Wassers, Ende Juli 2005 auch in einem nahe dem Nordpol gelegenen Krater. + +Ende März 2004 wurde bekannt, dass Forscher der NASA und der ESA unabhängig voneinander Methan in der Marsatmosphäre nachgewiesen haben. Ob das Methan geologischen Ursprungs ist oder etwa durch den Stoffwechsel von Mikroorganismen gebildet wurde, sollen weitere Untersuchungen zeigen. + +Ebenfalls Anfang 2004 entdeckte die Marssonde Opportunity Gesteine, die in offenstehendem Wasser abgelagert worden sein müssen und viele regelmäßig verteilte kugelige, bis 1 cm große Hämatit-Konkretionen enthalten. Solche Konkretionen kommen auch auf der Erde vor. Unter irdischen Bedingungen ist es wahrscheinlich, dass bei ihrer Entstehung Bakterien beteiligt sind. Ob dies auch für den Mars gilt, könnten nur Laboruntersuchungen auf der Erde zeigen. + +Weitere Mikrostrukturen, welche die Rover Spirit und Opportunity 2004 entdeckt hatten und in denen ein Teil der interessierten Öffentlichkeit Hinweise auf Leben hatte sehen wollen, erwiesen sich bei näherer Untersuchung als abiotisch oder künstlich, so zum Beispiel Schleifspuren auf durch die Instrumente bearbeiteten Gesteinsoberflächen oder Filamente, die sich als Textilfasern der Lande-Airbags herausstellten. + +Forschungsergebnisse auf der Erde bestätigen, dass es Leben auch in extremen Bedingungen geben kann. Bei Bohrungen im grönländischen Eis entdeckten Forscher der University of California, Berkeley im Jahre 2005 in drei Kilometern Tiefe eine auffallende Menge Methan. Dieses Gas produzierten methanogene Bakterien, die trotz unwirtlicher Lebensbedingungen wie Kälte, Dunkelheit und Nährstoffmangel im Eis überleben. Dabei erhalten sie sich nur mühsam am Leben – sie reparieren Erbgutschäden, vermehren jedoch nicht nennenswert ihre Population. Methanogene Mikroben sind eine Untergruppe der Archaebakterien, die sich auf Extremstandorte spezialisiert haben. So fanden sich im Jahr 2002 Mikroben in einer 15.000 Jahre alten heißen Quelle in Idaho. Die Bakterien zählen, wie schon der Name besagt, zu den ältesten Mikroorganismen der Erde. Die Wissenschaftler schätzen das Alter der in Grönland entdeckten Bakterienkolonie auf 100.000 Jahre und vermuten, dass das in der Atmosphäre des Roten Planeten nachgewiesene Methan nicht nur von chemischen Prozessen, sondern auch von solchen Mikroben stammen könnte. +Aktuelle Forschung + +Mit dem Mars Science Laboratory wird versucht, neue Aufschlüsse über mögliches Leben auf dem Mars zu liefern. Es ist fraglich, ob der Mars-Rover tief genug bohren kann, um Leben oder zumindest Lebensreste zu finden. Aber eine Isotopenanalyse des Methans kann bereits weitere Aufschlüsse geben. Leben, wie es auf der Erde bekannt ist, bevorzugt leichtere Wasserstoffisotope. +Beobachtung +Stellung zur Erde und Bahneigenschaften +Planetenschleife des Mars im Sternbild Wassermann im Jahr 2003 + +Aufgrund der Bahneigenschaften der Planeten „überholt“ die Erde den Mars durchschnittlich alle 779 Tage auf ihrer inneren Bahn. Diesen Zeitraum, der zwischen 764 und 811 Tagen schwankt, nennt man synodische Periode. Befinden sich Sonne, Erde und Mars in dieser Anordnung auf einer Linie, so steht der Mars von der Erde aus gesehen in Opposition zur Sonne. Zu diesem Zeitpunkt ist Mars besonders gut zu beobachten, er steht dann als rötlicher „Stern“ auffallend hell am Nachthimmel. Beobachtet man den Mars regelmäßig, kann man feststellen, dass er vor und nach einer Opposition am Himmel eine Schleifenbewegung vollführt. Diese Planetenschleife (Oppositionsschleife) ergibt sich aus den Sichtwinkeln, die Mars bietet, während er von der Erde überholt wird. +Marsoppositionen von 2003 bis 2018, relative Bewegung des Mars zur Erde, mit der Erde im Zentrum; Ansicht auf die Ekliptikebene + +Da die Planeten sich nicht auf idealen Kreisbahnen, sondern auf mehr oder weniger stark ausgeprägten elliptischen Bahnen bewegen, haben Erde und Mars zum Zeitpunkt der Oppositionen unterschiedliche Entfernungen zueinander. Diese können zwischen 55,6 und 101,3 Millionen Kilometern bzw. 0,37 und 0,68 AE betragen. Bei einer geringen Oppositionsentfernung spricht man von einer Perihelopposition, bei einer großen von einer Aphelopposition. +Schwankung des minimalen Abstands Erde–Mars bei Oppositionen. Die tatsächlichen Werte sind an den einzelnen Punkten ablesbar. Die verbindende Kurve veranschaulicht die mathematische Gesetzmäßigkeit. + +Die alle 15 bis 17 Jahre stattfindenden Periheloppositionen bieten die besten Gelegenheiten, den Mars von der Erde aus mittels Teleskop zu beobachten. Der Planet hat dann einen scheinbaren Durchmesser von bis zu 25,8 Bogensekunden. Bei einer Aphelopposition ist er mit 14,1 Bogensekunden nur etwa halb so groß. Besonders erdnahe Oppositionen fanden im Abstand von jeweils 79 Jahren, zum Beispiel in den Jahren 1766, 1845, 1924 und 2003 statt. Am 28. August 2003 betrug der Abstand Erde–Mars 55,76 Millionen Kilometer. Dies war die geringste Distanz seit etwa 60.000 Jahren.[54][55] Erst im Jahre 2287 wird der Mars der Erde noch näher kommen, der Abstand beträgt dann 55,69 Millionen Kilometer. + +Im Teleskop erscheint der Mars zunächst als rötliches Scheibchen. Bei stärkerer Vergrößerung können die Polkappen und dunkle Oberflächenmerkmale wie die Große Syrte ausgemacht werden. Treten auf dem Mars größere Staubstürme auf, verblassen die Merkmale, da die Oberfläche von einer rötlichen Dunstschicht eingehüllt wird, die sich mitunter über Wochen halten kann. Durch den Einsatz von CCD-Kameras sind mittlerweile auch Amateurastronomen in der Lage, detailreiche Aufnahmen der Marsoberfläche zu erzielen, wie sie vor etwa zehn Jahren nur von den leistungsfähigsten Großteleskopen erstellt werden konnten. + +Ereignisse (Jahreszeitenbeginn gilt für die Nordhalbkugel):[56][57][58] +Ereignis Frühlingsbeginn Aphel Sommerbeginn Herbstbeginn Perihel Winterbeginn +Datum 18. Juni 2015 20. November 2015 15. Februar 2014 17. August 2014 12. Dezember 2014 11. Januar 2015 +Nächste +Termine 5. Mai 2017 7. Oktober 2017 3. Januar 2016 4. Juli 2016 29. Oktober 2016 28. November 2016 +Ereignis Konjunktion Opposition +Datum 14. Juni 2015 8. April 2014 +Nächste +Termine 27. Juli 2017 22. Mai 2016 +Sichtbarkeiten +→ Hauptartikel: Marspositionen + +Wegen der Exzentrizität der Marsbahn kann der erdnächste Punkt bis zu einer Woche vor oder nach der Opposition erreicht werden, und die scheinbare Helligkeit während der Opposition sowie der Erdabstand und der scheinbare Durchmesser während der Erdnähe können recht unterschiedlich ausfallen. + +Eine Opposition findet etwa alle zwei Jahre (779,94 Tage) statt. Dabei kann bei einer Perihelopposition die maximale scheinbare Helligkeit bis zu −2,91m erreichen. Zu diesem Zeitpunkt sind nur die Sonne, der Erdmond, die Venus und in seltenen Fällen Jupiter (bis zu −2,94m) noch heller. Bei Konjunktion hingegen erscheint Mars nur mehr mit einer Helligkeit von +1,8m.[1] +Kulturgeschichte +Beschäftigung mit dem Mars von der Antike bis in die Neuzeit +Allegorische Darstellung des Mars als Herrscher der Tierkreiszeichen Widder und Skorpion, von Hans Sebald Beham, 16. Jahrhundert + +Der Mars bewegte die Menschheit von alters her besonders. Im alten Ägypten wurde Mars als „Horus der Rote“ bezeichnet. Da der Planet sich während seiner Oppositionsschleife (Planetenschleife) zeitweise rückläufig bewegt, sprachen die Ägypter davon, dass Mars rückwärts wandere. Der Name der ägyptischen Hauptstadt „Kairo“ leitet sich von „Al Qahira“ ab, dem altarabischen Namen für den Planeten Mars. + +Im indischen Sanskrit wird der Mars als „Mangal“ (verheißungsvoll), „Angaraka“ (Glühende Kohle) und „Kuja“ (der Blonde) bezeichnet. Er repräsentiert kraftvolle Aktion, Vertrauen und Zuversicht. + +Aufgrund seiner roten Färbung wurde der Mars in verschiedenen Kulturen mit den Gottheiten des Krieges in Verbindung gebracht. Die Babylonier sahen in ihm Nergal, den Gott der Unterwelt, des Todes und des Krieges. Für die Griechen und Römer der Antike repräsentierte er deren Kriegsgötter Ares beziehungsweise Mars. In der nordischen Mythologie steht er für Tyr, den Gott des Rechts und des Krieges. Die Azteken nannten ihn Huitzilopochtli, der Zerstörer von Menschen und Städten. Für die Chinesen war er Huoxing (chin. HuÅxÄ«ng, ç«æ˜Ÿ), Stern des Feuers. + +In der Astrologie ist Mars unter anderem das Symbol der Triebkraft. Es wird dem Element Feuer, dem Planetenmetall Eisen, den Tierkreiszeichen Widder und Skorpion sowie dem 1. Haus zugeordnet. +Rezeption in Literatur, Film, Videospiele und Musik + +Der Mars und seine fiktiven Bewohner sind auch Thema zahlreicher Romane und Verfilmungen. + +Ein Beispiel des 18. Jahrhunderts ist Carl Ignaz Geigers Roman Reise eines Erdbewohners in den Mars von 1790. + +1880 veröffentlichte Percy Greg seinen Roman Across the Zodiac, in dem er eine Reise in einem Raumschiff namens Astronaut zum Mars beschrieb. + +Die klassische Figur des kleinen grünen Männchens mit Antennen auf dem Kopf erschien erstmals 1913 in einem Comic und ist seitdem Klischee. + +Als der Astronom Percival Lowell Ende des 19. Jahrhunderts die Vorstellung entwickelte, die mit dem Fernrohr wahrnehmbaren Marskanäle seien künstlich angelegte Wasserkanäle, wurde diese Idee in der Science-Fiction-Literatur aufgegriffen und weitergesponnen. Dort wurde der Mars häufig als eine sterbende Welt vorgestellt, in deren kalten Wüstenregionen alte und weit entwickelte Zivilisationen ums Ãœberleben kämpften. + +Kurd Laßwitz brachte 1897 seinen sehr umfangreichen Roman Auf zwei Planeten über einen Besuch bei den Marsbewohnern heraus. +Angriff der Marsianer in Krieg der Welten von H. G. Wells. Buchillustration der französischen Ausgabe von Alvim Corréa von 1906 + +In H. G. Wells’ bekanntem Roman Krieg der Welten, der 1898 erschien, verlassen die Marsianer ihre Heimatwelt, um die lebensfreundlichere Erde zu erobern. Die Menschheit, die den hochtechnisierten kriegerischen Marsianern hoffnungslos unterlegen ist, entgeht ihrer Auslöschung nur dadurch, dass die Invasoren von für Menschen harmlosen, irdischen Mikroben dahingerafft werden. Orson Welles verwendete den Stoff im Jahre 1938 in einem Hörspiel, wobei er die Marsianer in New Jersey landen ließ. Das Hörspiel wurde im Stil einer realistischen Reportage ausgestrahlt. Hörer, die sich später einschalteten, hielten die Invasion der Marsianer für Realität. + +Wells’ Romanvorlage wurde 1952 verfilmt, wobei die Handlung wiederum in die USA der Gegenwart verlegt wurde. Der Film erhielt für die damals bahnbrechenden Spezialeffekte einen Oscar. + +1923 brachte Tolstoi seinen Roman Aelita heraus, der von der Liebe eines sowjetischen Ingenieurs zur Marsprinzessin und dem Untergang der Zivilisation auf dem Planeten handelt. Dieses Werk wurde 1924 verfilmt. + +Im Jahr 1978 entstand der Film Unternehmen Capricorn. Er griff das Thema der Verschwörungstheorien zur Mondlandung auf, indem er es in sehr zugespitzter Form auf eine im Filmstudio vorgetäuschte Marsexpedition übertrug. + +Der 1996 entstandene Film Mars Attacks! setzt sich ironisch mit dem Thema Marsinvasion auseinander, wobei den Marsianern amerikanische Schnulzenmusik aus den 1950er Jahren zum Verhängnis wird. + +Unter der Regie von Brian De Palma wurden im Jahr 2000 mit dem Film Mission to Mars die Spekulationen um das Marsgesicht der Cydonia-Region als hinterlassenes Bauwerk dramatisch weitgehend thematisiert. + +Steven Spielbergs 2005 entstandenes Remake von Krieg der Welten nahm noch einmal das Thema auf und zeigte die Invasion von Außerirdischen auf der Erde aus der Sicht eines Familienvaters aus den USA. + +Weitere bekannte Science-Fiction-Filme, die auf dem Mars handeln, sind Red Planet (2000) und Die totale Erinnerung – Total Recall (1990). + +Edgar Rice Burroughs, der Autor von Tarzan, schrieb von 1917 bis 1943 die elfbändige Saga John Carter vom Mars, in der sich der irdische Held in marsianische Prinzessinnen verliebt und gegen Luftpiraten, grünhäutige Unholde, weiße Riesenaffen und andere Untiere kämpft. + +Die Mars-Chroniken (1950), eine stimmungsvolle Sammlung von Erzählungen des Schriftstellers Ray Bradbury, sind ebenfalls auf dem Mars angesiedelt. + +Große Beachtung erhielt die Marstrilogie, eine von Kim Stanley Robinson von 1993 bis 1996 verfasste Romanserie über die Besiedelung des Mars. Der besondere Ansatz dieser Geschichten liegt in der vorwiegend technischen Schilderung unter vollständigem Verzicht phantastischer Elemente. +Die Route von Mark Watney in einer nachgestellten topographischen Kartierung des DLR-Instituts für Planetenforschung + +Der wohl prominenteste Auftritt des Mars in der Musik dürfte der erste Satz von Gustav Holsts Orchestersuite Die Planeten (1914–1916) sein, deren erster Satz Mars, the Bringer of War mit seinem drohend-martialischen Charakter die mythologische Gestalt Mars eindrucksvoll porträtiert. + +Bestsellerautor Andreas Eschbach verfasste von 2001 bis 2008 die Pentalogie Das Marsprojekt. + +2011 veröffentlichte Andy Weir den Science-Fiction-Roman Der Marsianer, in dem ein Astronaut nach einem Unfall auf dem Mars zurückgelassen wird und fortan um sein Ãœberleben kämpfen muss. Mit Der Marsianer – Rettet Mark Watney erschien 2015 eine Verfilmung dieses Bestsellers. + +Helga Abret und Lucian Boa geben in ihrem Buch Das Jahrhundert der Marsianer (1984) einen literarischen Ãœberblick über Erzählungen und Romane über den Mars und seine fiktiven Bewohner. Von der Beschreibung einer „ekstatischen Reise“ zum Mars (Itinerarium exstaticum coeleste, 1656) des Jesuitenpaters Athanasius Kircher bis hin zu Science-Fiction-Erzählungen des 20. Jahrhunderts reicht die Bandbreite der kommentierten Werke, mit denen die Autoren aufzuzeigen versuchen, dass „sich aus dem Zusammenwirken von Naturwissenschaften, Astronomie und Literatur ein moderner Mythos“[59] entwickelte. + diff --git a/xpcom/tests/gtest/wikipedia/ja.txt b/xpcom/tests/gtest/wikipedia/ja.txt new file mode 100644 index 0000000000..f5ad44dc5f --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/ja.txt @@ -0,0 +1,151 @@ + + +ç«æ˜Ÿï¼ˆã‹ã›ã„ã€ãƒ©ãƒ†ãƒ³èªž: Mars マールスã€è‹±èªž: マーズã€ã‚®ãƒªã‚·ã‚¢èªž: ΆÏης アレース)ã¯ã€å¤ªé™½ç³»ã®å¤ªé™½ã«è¿‘ã„æ–¹ã‹ã‚‰4番目ã®æƒ‘星ã§ã‚る。地çƒåž‹æƒ‘星ã«åˆ†é¡žã•ã‚Œã€åœ°çƒã®å¤–å´ã®è»Œé“を公転ã—ã¦ã„る。 + +英語åœã§ã¯ã€ãã®è¡¨é¢ã®è‰²ã‹ã‚‰ã€Red Planet(レッド・プラãƒãƒƒãƒˆã€ã€Œèµ¤ã„惑星ã€ã®æ„)ã¨ã„ã†é€šç§°ãŒã‚る。 + +物ç†çš„性質[編集] +地çƒã¨ç«æ˜Ÿã®å¤§ãã•æ¯”較。 + +ç«æ˜Ÿã¯åœ°çƒåž‹æƒ‘星ã«åˆ†é¡žã•ã‚Œã‚‹ã€ã„ã‚ゆる硬ã„岩石ã®åœ°è¡¨ã‚’æŒã£ãŸæƒ‘星ã§ã‚る。ç«æ˜ŸãŒèµ¤ã見ãˆã‚‹ã®ã¯ã€ãã®è¡¨é¢ã«åœ°çƒã®ã‚ˆã†ãªæ°´ã®æµ·ãŒç„¡ãã€åœ°è¡¨ã«é…¸åŒ–鉄(赤ã•ã³ï¼‰ãŒå¤§é‡ã«å«ã¾ã‚Œã¦ã„ã‚‹ãŸã‚ã§ã‚る。直径ã¯åœ°çƒã®åŠåˆ†ã»ã©ã§ã€è³ªé‡ã¯åœ°çƒã®ç´„ 1/10 ã«éŽãŽãªã„ãŸã‚ã€ç«æ˜Ÿã®åœ°è¡¨ã§ã®é‡åŠ›ã®å¼·ã•ã¯åœ°çƒã®40%ã»ã©ã—ã‹ãªã„。ç«æ˜Ÿã®è¡¨é¢ç©ã¯ã€åœ°çƒã®è¡¨é¢ç©ã®ç´„ 1/4ã§ã‚ã‚‹ãŒã€ã“ã‚Œã¯åœ°çƒã®é™¸åœ°ã®é¢ç©ï¼ˆç´„1.5å„„km2)ã¨ã»ã¼ç­‰ã—ã„。ç«æ˜Ÿã®è‡ªè»¢å‘¨æœŸã¯åœ°çƒã®ãã‚Œã¨éžå¸¸ã«è¿‘ãã€ç«æ˜Ÿã®1日(1ç«æ˜Ÿå¤ªé™½æ—¥ã€1 sol)ã¯ã€24時間39分35.244秒ã§ã‚る。ã¾ãŸåœ°çƒã¨åŒã˜ã‚ˆã†ã«å¤ªé™½ã«å¯¾ã—ã¦è‡ªè»¢è»¸ã‚’傾ã‘ãŸã¾ã¾å…¬è»¢ã—ã¦ã„ã‚‹ã®ã§ã€ç«æ˜Ÿã«ã¯å­£ç¯€ãŒå­˜åœ¨ã™ã‚‹ã€‚ +質é‡[編集] + +地çƒã‚„金星ã¨æ¯”ã¹ã¦ç«æ˜Ÿã®è³ªé‡ã¯å°ã•ã„[2]。太陽系ã®æƒ‘星移動ã®ãƒ¢ãƒ‡ãƒ«ã§ã‚るグランド・タックモデルã«ã‚ˆã‚‹ã¨ã€æœ¨æ˜Ÿã¯ç«æ˜Ÿå½¢æˆå‰ã«ä¸€åº¦ç«æ˜Ÿè»Œé“程度ã¾ã§å¤ªé™½ã«è¿‘ã¥ãã€å¾Œã«ç¾åœ¨ã®è»Œé“ã«è½ã¡ç€ã„ãŸã¨ã—ã¦ã„ã‚‹[2]。ãã®éš›ã€ç«æ˜Ÿã®æ§‹æˆã«ä½¿ç”¨ã•ã‚ŒãŸã§ã‚ã‚ã†è³ªé‡ã®å°å¤©ä½“ã‚’ã¯ã˜ã飛ã°ã—ã¦ã—ã¾ã£ãŸãŸã‚ã€ç«æ˜ŸãŒå分æˆé•·ã§ããªã‹ã£ãŸå¯èƒ½æ€§ã‚’示唆ã—ã¦ã„ã‚‹[2]。 +大気[編集] +詳細ã¯ã€Œç«æ˜Ÿã®å¤§æ°—ã€ã‚’å‚ç…§ +ç«æ˜Ÿï¼ˆã“ã®ä½Žè»Œé“写真ã®ä¸­ã®åœ°å¹³ç·šã§è¦‹ãˆã‚‹ï¼‰ã®è–„ã„大気 + +ç«æ˜Ÿã®å¤§æ°—ã¯å¸Œè–„ã§ã€åœ°è¡¨ã§ã®å¤§æ°—圧ã¯ç´„750Paã¨åœ°çƒã§ã®å¹³å‡å€¤ã®ç´„0.75%ã«éŽãŽãªã„。逆ã«å¤§æ°—ã®åŽšã•ã‚’示ã™ã‚¹ã‚±ãƒ¼ãƒ«ãƒã‚¤ãƒˆã¯ç´„11kmã«é”ã—ã€ãŠã‚ˆã6kmã§ã‚る地çƒã‚ˆã‚Šã‚‚高ã„。ã“れらã¯ã„ãšã‚Œã‚‚ã€ç«æ˜Ÿã®é‡åŠ›ãŒåœ°çƒã‚ˆã‚Šã‚‚å¼±ã„ã“ã¨ã«èµ·å› ã—ã¦ã„る。大気ãŒå¸Œè–„ãªãŸã‚ã«ç†±ã‚’ä¿æŒã™ã‚‹ä½œç”¨ãŒå¼±ãã€è¡¨é¢æ¸©åº¦ã¯æœ€é«˜ã§ã‚‚ç´„20℃ã§ã‚る。大気ã®çµ„æˆã¯äºŒé…¸åŒ–ç‚­ç´ ãŒ95%ã€çª’ç´ ãŒ3%ã€ã‚¢ãƒ«ã‚´ãƒ³ãŒ1.6%ã§ã€ä»–ã«é…¸ç´ ã‚„水蒸気ãªã©ã®å¾®é‡æˆåˆ†ã‚’å«ã‚€ã€‚ãŸã ã—ã€ç«æ˜Ÿã®å¤§æ°—ã®ä¸Šå±¤éƒ¨ã¯å¤ªé™½é¢¨ã®å½±éŸ¿ã‚’å—ã‘ã¦å®‡å®™ç©ºé–“ã¸ã¨æµå‡ºã—ã¦ã„ã‚‹ã“ã¨ãŒã€ã‚½ãƒ“エト連邦ã®ç„¡äººç«æ˜ŸæŽ¢æŸ»æ©Ÿã®ãƒ•ã‚©ãƒœã‚¹2å·ã«ã‚ˆã£ã¦è¦³æ¸¬ã•ã‚Œã¦ã„る。ã—ãŸãŒã£ã¦ä¸Šè¨˜ã®ç«æ˜Ÿã®å¤§æ°—圧や大気組æˆã¯ã€é•·ã„ç›®ã§è¦‹ã‚‹ã¨å¤‰åŒ–ã—ã¦ã„ã‚‹å¯èƒ½æ€§ã€ãã—ã¦ä»Šå¾Œã‚‚変化ã—ã¦ã‚†ãå¯èƒ½æ€§ãŒæŒ‡æ‘˜ã•ã‚Œã¦ã„る。 + +2003å¹´ã«åœ°çƒã‹ã‚‰ã®æœ›é é¡ã«ã‚ˆã‚‹è¦³æ¸¬ã§å¤§æ°—ã«ãƒ¡ã‚¿ãƒ³ãŒå«ã¾ã‚Œã¦ã„ã‚‹å¯èƒ½æ€§ãŒæµ®ä¸Šã—ã€2004å¹´3月ã®ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス探査機ã®èª¿æŸ»ã«ã‚ˆã‚‹å¤§æ°—ã®è§£æžã§ãƒ¡ã‚¿ãƒ³ã®å­˜åœ¨ãŒç¢ºèªã•ã‚ŒãŸã€‚ç¾åœ¨è¦³æ¸¬ã•ã‚Œã¦ã„るメタンã®é‡ã®å¹³å‡å€¤ã¯ä½“ç©æ¯”ã§ç´„11±4 ppb ã§ã‚る。 + +ç«æ˜Ÿã®ç’°å¢ƒä¸‹ã§ã¯ä¸å®‰å®šãªæ°—体ã§ã‚るメタンã®å­˜åœ¨ã¯ã€ç«æ˜Ÿã«ãƒ¡ã‚¿ãƒ³ã®ã‚¬ã‚¹æºãŒå­˜åœ¨ã™ã‚‹ï¼ˆã¾ãŸã¯ã€å°‘ãªãã¨ã‚‚最近100年以内ã«ã¯å­˜åœ¨ã—ã¦ã„ãŸï¼‰ã¨ã„ã†èˆˆå‘³æ·±ã„事実を示唆ã—ã¦ã„る。ガスã®ç”Ÿæˆæºã¨ã—ã¦ã¯ç«å±±æ´»å‹•ã‚„彗星ã®è¡çªã€ã‚ã‚‹ã„ã¯ãƒ¡ã‚¿ãƒ³èŒã®ã‚ˆã†ãªå¾®ç”Ÿç‰©ã®å½¢ã§ç”Ÿå‘½ãŒå­˜åœ¨ã™ã‚‹ãªã©ã®å¯èƒ½æ€§ãŒè€ƒãˆã‚‰ã‚Œã¦ã„ã‚‹ãŒã€ã„ãšã‚Œã‚‚未確èªã§ã‚る。地çƒã®æµ·ã§ã¯ã€ç”Ÿç‰©ã«ã‚ˆã£ã¦ãƒ¡ã‚¿ãƒ³ãŒç”Ÿæˆã•ã‚Œã‚‹éš›ã«ã¯åŒæ™‚ã«ã‚¨ã‚¿ãƒ³ã‚‚生æˆã•ã‚Œã‚‹å‚¾å‘ãŒã‚る。一方ã€ç«å±±æ´»å‹•ã‹ã‚‰æ”¾å‡ºã•ã‚Œã‚‹ãƒ¡ã‚¿ãƒ³ã«ã¯äºŒé…¸åŒ–硫黄ãŒä»˜éšã™ã‚‹ã€‚メタンã¯ç«æ˜Ÿè¡¨é¢ã®æ‰€ã€…ã«å±€æ‰€çš„ã«å­˜åœ¨ã—ã¦ã„るよã†ã«è¦‹ãˆã‚‹äº‹ã‹ã‚‰ã€ç™ºç”Ÿã—ãŸãƒ¡ã‚¿ãƒ³ã¯å¤§æ°—中ã«ä¸€æ§˜ã«åˆ†å¸ƒã™ã‚‹ã‚ˆã‚Šã‚‚短時間ã§åˆ†è§£ã•ã‚Œã¦ã„ã‚‹ã“ã¨ãŒã†ã‹ãŒãˆã‚‹ã€‚ãれゆãˆã€ãŠãらãæŒç¶šçš„ã«å¤§æ°—中ã«æ”¾å‡ºã•ã‚Œã¦ã„ã‚‹ã¨ã‚‚推測ã•ã‚Œã‚‹ã€‚発生æºã«é–¢ã™ã‚‹ä»®èª¬ã§ã©ã‚ŒãŒæœ€ã‚‚有力ã‹ã‚’推定ã™ã‚‹ãŸã‚ã«ã€ãƒ¡ã‚¿ãƒ³ã¨åŒæ™‚ã«æ”¾å‡ºã•ã‚Œã‚‹åˆ¥ã®æ°—体を検出ã™ã‚‹è¨ˆç”»ã‚‚ç¾åœ¨é€²ã‚られã¦ã„る。 + +ç«æ˜Ÿå¤§æ°—ã«ã¯å¤§ãã変化ã™ã‚‹é¢ã‚‚ã‚る。冬ã®æ•°ãƒ¶æœˆé–“ã«æ¥µåœ°æ–¹ã§å¤œãŒç¶šãã¨ã€åœ°è¡¨ã¯éžå¸¸ã«ä½Žæ¸©ã«ãªã‚Šã€å¤§æ°—全体ã®25%ã‚‚ãŒå‡å›ºã—ã¦åŽšã•æ•°ãƒ¡ãƒ¼ãƒˆãƒ«ã«é”ã™ã‚‹äºŒé…¸åŒ–ç‚­ç´ ã®æ°·ï¼ˆãƒ‰ãƒ©ã‚¤ã‚¢ã‚¤ã‚¹ï¼‰ã®å±¤ã‚’ã¤ãる。やãŒã¦ã€æ¥µã«å†ã³æ—¥å…‰ãŒå½“ãŸã‚‹å­£ç¯€ã«ãªã‚‹ã¨äºŒé…¸åŒ–ç‚­ç´ ã®æ°·ã¯æ˜‡è¯ã—ã¦ã€æ¥µåœ°æ–¹ã«å¹ã付ã‘ã‚‹400km/hã«é”ã™ã‚‹å¼·ã„風ãŒç™ºç”Ÿã™ã‚‹ã€‚ã“れらã®å­£ç¯€çš„活動ã«ã‚ˆã£ã¦å¤§é‡ã®å¡µã‚„水蒸気ãŒé‹ã°ã‚Œã€åœ°çƒã¨ä¼¼ãŸéœœã‚„大è¦æ¨¡ãªå·»é›²ãŒç”Ÿã˜ã‚‹ã€‚ã“ã®ã‚ˆã†ãªæ°´ã®æ°·ã‹ã‚‰ãªã‚‹é›²ã®å†™çœŸãŒ2004å¹´ã«ã‚ªãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã£ã¦æ’®å½±ã•ã‚Œã¦ã„る(撮影画åƒï¼‰ã€‚ã¾ãŸã€å—極ã§äºŒé…¸åŒ–ç‚­ç´ ãŒçˆ†ç™ºçš„ã«å™´å‡ºã—ãŸè·¡ãŒãƒžãƒ¼ã‚ºãƒ»ã‚ªãƒ‡ãƒƒã‚»ã‚¤ã«ã‚ˆã£ã¦æ’®å½±ã•ã‚Œã¦ã„ã‚‹[3]。 + +ç«æ˜Ÿã¯çŸ­ã„時間尺度ã§ã¯æ¸©æš–化ã—ã¦ã„ã‚‹ã“ã¨ã‚’示唆ã™ã‚‹è¨¼æ‹ ã‚‚発見ã•ã‚Œã¦ã„ã‚‹[4]。ã—ã‹ã—21世紀åˆé ­ã®ç«æ˜Ÿã¯1970年代よりã¯å¯’冷ã§ã‚ã‚‹[5]。 +地質[編集] +スピリットãŒæŠ‰ã£ãŸåœ°è¡¨ã€‚明るã„シリカ(二酸化ケイ素)ãŒå‰ã出ã—ã«ãªã£ã¦ã„る。 + +ç«æ˜Ÿã®è¡¨é¢ã¯ä¸»ã¨ã—ã¦çŽ„武岩ã¨å®‰å±±å²©ã®å²©çŸ³ã‹ã‚‰ãªã£ã¦ã„る。ã„ãšã‚Œã‚‚地çƒä¸Šã§ã¯ãƒžã‚°ãƒžãŒåœ°è¡¨è¿‘ãã§å›ºã¾ã£ã¦ç”Ÿæˆã™ã‚‹å²©çŸ³ã§ã‚ã‚Šã€å«ã¾ã‚Œã‚‹äºŒé…¸åŒ–ケイ素 (SiO2) ã®é‡ã§åŒºåˆ¥ã•ã‚Œã‚‹ã€‚ç«æ˜Ÿã§ã¯å¤šãã®å ´æ‰€ãŒåŽšã•æ•°ãƒ¡ãƒ¼ãƒˆãƒ«ã‚ã‚‹ã„ã¯ãれ以上ã®æ»‘石粉ã®ã‚ˆã†ãªç´°ã‹ã„塵ã§è¦†ã‚ã‚Œã¦ã„る。 + +マーズ・グローãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼æŽ¢æŸ»æ©Ÿã«ã‚ˆã‚‹ç«æ˜Ÿã®ç£å ´ã®è¦³æ¸¬ã‹ã‚‰ã€ç«æ˜Ÿã®åœ°æ®»ãŒå‘ãã®å転を繰り返ã™ãƒãƒ³ãƒ‰çŠ¶ã«ç£åŒ–ã•ã‚Œã¦ã„ã‚‹ã“ã¨ãŒåˆ†ã‹ã£ã¦ã„る。ã“ã®ç£åŒ–ãƒãƒ³ãƒ‰ã¯å…¸åž‹çš„ã«ã¯å¹…160kmã€é•·ã•1,000kmã«ã‚ãŸã£ã¦ã„る。ã“ã®ã‚ˆã†ãªç£åŒ–ã®ãƒ‘ターンã¯åœ°çƒã®æµ·åº•ã«è¦‹ã‚‰ã‚Œã‚‹ã‚‚ã®ã¨ä¼¼ã¦ã„る。1999å¹´ã«ç™ºè¡¨ã•ã‚ŒãŸèˆˆå‘³æ·±ã„説ã«ã‚ˆã‚‹ã¨ã€ã“れらã®ãƒãƒ³ãƒ‰ã¯éŽåŽ»ã®ç«æ˜Ÿã®ãƒ—レートテクトニクス作用ã®è¨¼æ‹ ã‹ã‚‚ã—ã‚Œãªã„ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。ã—ã‹ã—ãã®ã‚ˆã†ãªãƒ—レート活動ãŒã‚ã£ãŸè¨¼æ‹ ã¯ã¾ã ç¢ºèªã•ã‚Œã¦ã„ãªã„[6]。2005å¹´10月ã«ç™ºè¡¨ã•ã‚ŒãŸæ–°ãŸãªç™ºè¦‹ã¯ä¸Šè¨˜ã®èª¬ã‚’支æŒã™ã‚‹ã‚‚ã®ã§ã€åœ°çƒã§ç™ºè¦‹ã•ã‚Œã¦ã„る海底拡大ã«ã‚ˆã‚‹ãƒ†ã‚¯ãƒˆãƒ‹ã‚¯ã‚¹æ´»å‹•ã¨åŒæ§˜ã®æ´»å‹•ãŒå¤ªå¤ã®ç«æ˜Ÿã«ã‚ã£ãŸã“ã¨ã‚’示ã—ã¦ã„ã‚‹[7]。もã—ã“れらãŒæ­£ã—ã‘ã‚Œã°ã€ã“れらã®æ´»å‹•ã«ã‚ˆã£ã¦ç‚­ç´ ã®è±Šå¯Œãªå²©çŸ³ãŒåœ°è¡¨ã«é‹ã°ã‚Œã‚‹ã“ã¨ã«ã‚ˆã£ã¦åœ°çƒã«è¿‘ã„大気ãŒç¶­æŒã•ã‚Œã€ä¸€æ–¹ã§ç£å ´ã®å­˜åœ¨ã«ã‚ˆã£ã¦ç«æ˜Ÿè¡¨é¢ãŒå®‡å®™æ”¾å°„ç·šã‹ã‚‰å®ˆã‚‰ã‚Œã‚‹ã“ã¨ã«ãªã£ãŸã‹ã‚‚ã—ã‚Œãªã„。ã¾ãŸã“れらã¨ã¯åˆ¥ã®ç†è«–的説明もæ案ã•ã‚Œã¦ã„る。 +オãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã£ã¦æ’®å½±ã•ã‚ŒãŸç«æ˜Ÿã®å²©çŸ³ã®é¡•å¾®é¡å†™çœŸã€‚éŽåŽ»ã«æ°´ã®ä½œç”¨ã«ã‚ˆã£ã¦ä½œã‚‰ã‚ŒãŸã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。 + +オãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã‚‹ç™ºè¦‹ã®ä¸­ã«ã€ãƒ¡ãƒªãƒ‡ã‚£ã‚¢ãƒ‹å¹³åŽŸã§æŽ¡å–ã—ãŸå²©çŸ³ã‹ã‚‰å°ã•ãªçƒå½¢ã®èµ¤é‰„鉱(ヘマタイト)ãŒç™ºè¦‹ã•ã‚ŒãŸã€‚ã“ã®çƒä½“ã¯ç›´å¾„ã‚ãšã‹æ•°mmã—ã‹ãªãã€æ•°åå„„å¹´å‰ã«æ°´ã®å¤šã„環境ã®ä¸‹ã§å †ç©å²©ã¨ã—ã¦ä½œã‚‰ã‚ŒãŸã‚‚ã®ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。他ã«ã‚‚鉄ミョウãƒãƒ³çŸ³ãªã©ã€ç¡«é»„ã€é‰„ã€è‡­ç´ ã‚’å«ã‚€é‰±ç‰©ãŒç™ºè¦‹ã•ã‚Œã¦ã„る。ã“れらをå«ã‚€å¤šãã®è¨¼æ‹ ã‹ã‚‰ã€å­¦è¡“誌「サイエンス〠2004å¹´12月9æ—¥å·ã«ãŠã„ã¦50åã®ç ”究者ã‹ã‚‰ãªã‚‹ç ”究グループã¯ã€ã€Œç«æ˜Ÿè¡¨é¢ã®ãƒ¡ãƒªãƒ‡ã‚£ã‚¢ãƒ‹å¹³åŽŸã§ã¯éŽåŽ»ã«æ¶²ä½“ã®æ°´ãŒæ–­ç¶šçš„ã«å­˜åœ¨ã—ã€åœ°è¡¨ã®ä¸‹ãŒæ°´ã§æº€ãŸã•ã‚Œã¦ã„ãŸæ™‚代ãŒä½•å›žã‹ã‚ã£ãŸã€‚液体ã®æ°´ã¯ç”Ÿå‘½ã«ã¨ã£ã¦éµã¨ãªã‚‹å¿…è¦æ¡ä»¶ã§ã‚ã‚‹ãŸã‚ã€æˆ‘々ã¯ç«æ˜Ÿã®æ­´å²ã®ä¸­ã§ãƒ¡ãƒªãƒ‡ã‚£ã‚¢ãƒ‹ã§ã¯ç”Ÿå‘½ã®å­˜åœ¨å¯èƒ½ãªç’°å¢ƒãŒä½•åº¦ã‹ä½œã‚‰ã‚Œã¦ã„ãŸã¨æŽ¨æ¸¬ã—ã¦ã„ã‚‹ã€ã¨çµè«–ã—ã¦ã„る。メリディアニã®å対å´ã®ç«æ˜Ÿè¡¨é¢ã§ã¯ã€ã‚³ãƒ­ãƒ³ãƒ“ア・ヒルズã«ãŠã„ã¦ã‚¹ãƒ”リットãŒé‡é‰„鉱を発見ã—ã¦ã„る。ã“ã‚Œã¯ï¼ˆèµ¤é‰„鉱ã¨ã¯ç•°ãªã‚Šï¼‰æ°´ãŒå­˜åœ¨ã™ã‚‹ç’°å¢ƒã§ã€Œã®ã¿ã€ä½œã‚‰ã‚Œã‚‹é‰±ç‰©ã§ã‚る。スピリットã¯ä»–ã«ã‚‚æ°´ã®å­˜åœ¨ã‚’示ã™è¨¼æ‹ ã‚’発見ã—ã¦ã„る。 + +マーズ・グローãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼ãŒ2006å¹´ã«æ’®å½±ã—ãŸå†™çœŸã‹ã‚‰ã€ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼å†…å£ã®æ–œé¢ã‚’液体ãŒæµã‚ŒãŸç—•è·¡ãŒè¦‹ã¤ã‹ã£ãŸãŒã€1999å¹´ã«åŒã˜å ´æ‰€ã‚’撮影ã—ãŸå†™çœŸã«ã¯å†™ã£ã¦ãŠã‚‰ãšã€ãれ以é™ã«ã§ããŸã‚‚ã®ã¨æ€ã‚れる。 + +1996å¹´ã€ç«æ˜Ÿèµ·æºã§ã‚ã‚‹ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る隕石「ALH84001ã€ã‚’調査ã—ã¦ã„ãŸç ”究者ãŒã€ç«æ˜Ÿã®ç”Ÿå‘½ã«ã‚ˆã£ã¦æ®‹ã•ã‚ŒãŸã¨æ€ã‚れる微å°åŒ–石ãŒã“ã®éš•çŸ³ã«å«ã¾ã‚Œã¦ã„ã‚‹ã“ã¨ã‚’報告ã—ãŸã€‚2005å¹´ç¾åœ¨ã€ã“ã®è§£é‡ˆã«ã¤ã„ã¦ã¯ã„ã¾ã ã«è­°è«–ãŒã‚ã‚Šã€åˆæ„ã¯å¾—られã¦ã„ãªã„。 +地形[編集] +ç«æ˜Ÿã®åœ°å½¢å›³ã€‚特徴的ãªåœ°å½¢ã¨ã—ã¦ã€è¥¿éƒ¨ã®ã‚¿ãƒ«ã‚·ã‚¹ç«å±±ç¾¤ï¼ˆã‚ªãƒªãƒ³ãƒã‚¹å±±ã‚’å«ã‚€ï¼‰ã€ã‚¿ãƒ«ã‚·ã‚¹ã®æ±ã«ã‚るマリãƒãƒªã‚¹å³¡è°·ã€å—åŠçƒã®ãƒ˜ãƒ©ã‚¹ç›†åœ°ãªã©ãŒã‚ã‚‹ +「ç«æ˜Ÿã®åœ°å½¢ä¸€è¦§ã€ã‚‚å‚ç…§ + +ç«æ˜Ÿã®åœ°å½¢ã¯å¤§ãã二通りã«åˆ†ã‹ã‚Œã¦ãŠã‚Šã€ç‰¹å¾´çš„ã§ã‚る。北åŠçƒã¯æº¶å²©æµã«ã‚ˆã£ã¦å¹³ã‚‰ã«å‡ã•ã‚ŒãŸå¹³åŽŸï¼ˆåŒ—部平原ã®æˆå› ã¨ã—ã¦ã¯å¤§é‡ã®æ°´ã«ã‚ˆã‚‹ä¾µé£Ÿèª¬ã‚‚ã‚る)ãŒåºƒãŒã£ã¦ãŠã‚Šã€ä¸€æ–¹ã€å—åŠçƒã¯å¤ªå¤ã®éš•çŸ³è¡çªã«ã‚ˆã‚‹çªªåœ°ã‚„クレーターãŒå­˜åœ¨ã™ã‚‹é«˜åœ°ãŒå¤šã„。地çƒã‹ã‚‰è¦‹ãŸç«æ˜Ÿè¡¨é¢ã‚‚ã“ã®ãŸã‚ã«äºŒç¨®é¡žã®åœ°åŸŸã«åˆ†ã‘られã€ä¸¡è€…ã¯å…‰ã®å射率ã§ã‚るアルベドãŒç•°ãªã£ã¦ã„る。明るã見ãˆã‚‹å¹³åŽŸã¯èµ¤ã„酸化鉄を多ãå«ã‚€å¡µã¨ç ‚ã«è¦†ã‚ã‚Œã¦ãŠã‚Šã€ã‹ã¤ã¦ã¯ç«æ˜Ÿã®å¤§é™¸ã¨è¦‹ç«‹ã¦ã‚‰ã‚Œã¦ã‚¢ãƒ©ãƒ“ア大陸 (Arabia Terra) やアマゾニス平原 (Amazonis Planitia) ãªã©ã¨å‘½åã•ã‚Œã¦ã„る。暗ã„模様ã¯æµ·ã¨è€ƒãˆã‚‰ã‚Œã€ã‚¨ãƒªãƒˆãƒªã‚¢æµ· (Mare Erythraeum)ã€ã‚·ãƒ¬ãƒ¼ãƒŒã‚¹ï¼ˆã‚»ã‚¤ãƒ¬ãƒ¼ãƒ³ãŸã¡ï¼‰ã®æµ· (Mare Sirenum)ã€ã‚ªãƒ¼ãƒ­ãƒ©æ¹¾ (Aurorae Sinus) ãªã©ã¨åã¥ã‘られã¦ã„る。地çƒã‹ã‚‰è¦‹ãˆã‚‹æœ€ã‚‚大ããªæš—ã„模様ã¯å¤§ã‚·ãƒ«ãƒã‚¹ (Syrtis Major) ã§ã‚る。 +北極地ã®åˆå¤æ¥µå†  + +ç«æ˜Ÿã«ã¯æ°´ã¨äºŒé…¸åŒ–ç‚­ç´ ã®æ°·ã‹ã‚‰ãªã‚‹æ¥µå† ãŒã‚ã‚Šã€ç«æ˜Ÿã®å­£ç¯€ã«ã‚ˆã£ã¦å¤‰åŒ–ã™ã‚‹ã€‚二酸化炭素ã®æ°·ã¯å¤ã«ã¯æ˜‡è¯ã—ã¦å²©çŸ³ã‹ã‚‰ãªã‚‹è¡¨é¢ãŒç¾ã‚Œã€å†¬ã«ã¯å†ã³æ°·ãŒã§ãる。楯状ç«å±±ã§ã‚るオリンãƒã‚¹å±±ã¯æ¨™é«˜27kmã®å¤ªé™½ç³»æœ€é«˜ã®å±±ã§ã‚ã‚‹[8]。ã“ã®å±±ã¯ã‚¿ãƒ«ã‚·ã‚¹é«˜åœ°ã¨å‘¼ã°ã‚Œã‚‹åºƒå¤§ãªé«˜åœ°ã«ã‚ã‚Šã€ã“ã®åœ°æ–¹ã«ã¯ã„ãã¤ã‹ã®å¤§ããªç«å±±ãŒã‚る。ç«æ˜Ÿã«ã¯å¤ªé™½ç³»æœ€å¤§ã®å³¡è°·ã§ã‚るマリãƒãƒªã‚¹å³¡è°·ã‚‚存在ã™ã‚‹ã€‚ã“ã®å³¡è°·ã¯å…¨é•·4,000kmã€æ·±ã•7kmã«é”ã™ã‚‹ã€‚ç«æ˜Ÿã«ã¯å¤šãã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã‚‚存在ã™ã‚‹ã€‚最大ã®ã‚‚ã®ã¯ãƒ˜ãƒ©ã‚¹ç›†åœ°ã§ã€æ˜Žã‚‹ã„赤色ã®ç ‚ã§è¦†ã‚ã‚Œã¦ã„る。 + +ç«æ˜Ÿã®æœ€é«˜åœ°ç‚¹ã¨æœ€ä½Žåœ°ç‚¹ã®æ¨™é«˜å·®ã¯ç´„31kmã§ã‚る。オリンãƒã‚¹å±±ã®å±±é ‚ 27km ãŒæœ€ã‚‚高ãã€ãƒ˜ãƒ©ã‚¹ç›†åœ°ã®åº•éƒ¨ã€æ¨™é«˜åŸºæº–é¢ã®ç´„ 4km 下ãŒæœ€ã‚‚低ã„。ã“ã‚Œã¨æ¯”ã¹ã¦åœ°çƒã®æœ€é«˜ç‚¹ã¨æœ€ä½Žç‚¹ï¼ˆã‚¨ãƒ™ãƒ¬ã‚¹ãƒˆã¨ãƒžãƒªã‚¢ãƒŠæµ·æºï¼‰ã®å·®ã¯19.7kmã«éŽãŽãªã„。両惑星ã®åŠå¾„ã®å·®ã‚’考ãˆã‚‹ã¨ã€ç«æ˜ŸãŒåœ°çƒã‚ˆã‚Šã‚‚ãŠã‚ˆã3å€ã‚‚凸凹ã§ã‚ã‚‹ã“ã¨ã‚’示ã—ã¦ã„る。 + +21世紀åˆé ­ç¾åœ¨ã§ã¯ã€å›½éš›å¤©æ–‡å­¦é€£åˆ (IAU) ã®æƒ‘星系命åワーキンググループãŒç«æ˜Ÿè¡¨é¢ã®åœ°å½¢åã®å‘½åを担当ã—ã¦ã„る。 +座標ã®åŸºæº–[編集] + +ç«æ˜Ÿã«ã¯æµ·ãŒãªã„ã®ã§æµ·æŠœã¨ã„ã†å®šç¾©ã¯ä½¿ãˆãªã„。従ã£ã¦é«˜åº¦0ã®é¢ã€ã™ãªã‚ã¡å¹³å‡é‡åŠ›é¢ã‚’é¸ã¶å¿…è¦ãŒã‚る。ç«æ˜Ÿã®åŸºæº–測地系ã¯4階4次ã®çƒé¢èª¿å’Œé–¢æ•°é‡åŠ›å ´ã§å®šç¾©ã•ã‚Œã€é«˜åº¦0ã¯æ¸©åº¦273.16Kã§ã®å¤§æ°—圧ãŒ610.5Pa(地çƒã®ç´„0.6%)ã¨ãªã‚‹é¢ã¨ã—ã¦å®šç¾©ã•ã‚Œã¦ã„る。ã“ã®åœ§åŠ›ã¨æ¸©åº¦ã¯æ°´ã®ä¸‰é‡ç‚¹ã«å¯¾å¿œã—ã¦ã„る。 + +ç«æ˜Ÿã®èµ¤é“ã¯ãã®è‡ªè»¢ã‹ã‚‰å®šç¾©ã•ã‚Œã¦ã„ã‚‹ãŒã€åŸºæº–å­åˆç·šã®ä½ç½®ã¯åœ°çƒã®å ´åˆã¨åŒæ§˜ã«ä»»æ„ã®ç‚¹ãŒé¸ã°ã‚Œã€å¾Œä¸–ã®è¦³æ¸¬è€…ã«ã‚ˆã£ã¦å—ã‘入れられã¦ã„ã£ãŸã€‚ドイツã®å¤©æ–‡å­¦è€…ヴィルヘルム・ベーアã¨ãƒ¨ãƒãƒ³ãƒ»ãƒã‚¤ãƒ³ãƒªãƒƒãƒ’・メドラーã¯1830å¹´ã‹ã‚‰32å¹´ã«ã‹ã‘ã¦æœ€åˆã®ç«æ˜Ÿã®ä½“系的ãªåœ°å›³ã‚’作æˆã—ãŸéš›ã«ã€ã‚ã‚‹å°ã•ãªå††å½¢ã®æ¨¡æ§˜ã‚’基準点ã¨ã—ãŸã€‚彼らã®é¸æŠžã—ãŸåŸºæº–点ã¯1877å¹´ã«ã€ã‚¤ã‚¿ãƒªã‚¢ã®å¤©æ–‡å­¦è€…ジョヴァンニ・スキアパレッリãŒæœ‰åãªç«æ˜Ÿå›³ã®ä½œæˆã‚’始ã‚ãŸéš›ã«åŸºæº–å­åˆç·šã¨ã—ã¦æŽ¡ç”¨ã•ã‚ŒãŸã€‚1972å¹´ã«æŽ¢æŸ»æ©ŸãƒžãƒªãƒŠãƒ¼9å·ãŒç«æ˜Ÿã®åºƒç¯„囲ã®ç”»åƒã‚’撮影ã—ãŸå¾Œã€å­åˆç·šã®æ¹¾ã®ãƒ™ãƒ¼ã‚¢ã¨ãƒ¡ãƒ‰ãƒ©ãƒ¼ã®å­åˆç·šä¸Šã«ã‚ã‚‹å°ã•ãªã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ï¼ˆå¾Œã«ã‚¨ã‚¢ãƒªãƒ¼0ã¨å‘¼ã°ã‚Œã‚‹ï¼‰ãŒã‚¢ãƒ¡ãƒªã‚«ã€RAND社ã®ãƒ¡ãƒ«ãƒˆãƒ³ãƒ»ãƒ‡ãƒ¼ãƒ´ã‚£ã‚¹ã«ã‚ˆã£ã¦ã€æƒ‘星撮影時ã®åˆ¶å¾¡ç‚¹ãƒãƒƒãƒˆãƒ¯ãƒ¼ã‚¯ã‚’決ã‚ã‚‹éš›ã«ã‚ˆã‚Šæ­£ç¢ºãªçµŒåº¦0.0度ã®å®šç¾©ã¨ã—ã¦æŽ¡ç”¨ã•ã‚ŒãŸã€‚ +「é‹æ²³ã€[編集] + +ç«æ˜Ÿã«ã¯ã‹ã¤ã¦ç”Ÿå‘½ãŒå­˜åœ¨ã—ãŸã¨ã„ã†è€ƒãˆã®ãŸã‚ã«ã€ç«æ˜Ÿã¯äººé¡žã®æƒ³åƒã®ä¸–ç•Œã®ä¸­ã§é‡è¦ãªä½ç½®ã‚’å ã‚ã¦ã„る。ã“ã†ã„ã£ãŸè€ƒãˆã¯ä¸»ã«19世紀ã«å¤šãã®äººã€…ã«ã‚ˆã£ã¦è¡Œã‚ã‚Œã€ç‰¹ã«ãƒ‘ーシヴァル・ローウェルやジョヴァンニ・スキアパレッリã«ã‚ˆã‚‹ç«æ˜Ÿè¦³æ¸¬ã‹ã‚‰ç”Ÿã¾ã‚Œã€ä¸€èˆ¬ã«çŸ¥ã‚‰ã‚Œã‚‹ã‚ˆã†ã«ãªã£ãŸã€ã‚¹ã‚­ã‚¢ãƒ‘レッリã¯è¦³æ¸¬ã•ã‚ŒãŸæ¨¡æ§˜ã‚’イタリア語: canali(æºï¼‰ã¨ã„ã†èªžã§è¨˜è¿°ã—ãŸã€‚ã“ã‚ŒãŒè‹±èªž: canal(é‹æ²³ï¼‰ã¨èª¤è¨³ã•ã‚Œã€ã“ã“ã‹ã‚‰ã€Œç«æ˜Ÿã®é‹æ²³ã€ã¨ã„ã†èª¬ãŒå§‹ã¾ã£ãŸ[9]。ã“れらã®ç«æ˜Ÿè¡¨é¢ã®æ¨¡æ§˜ã¯äººå·¥çš„ãªç›´ç·šçŠ¶ã®æ¨¡æ§˜ã®ã‚ˆã†ã«è¦‹ãˆãŸãŸã‚ã«é‹æ²³ã§ã‚ã‚‹ã¨ä¸»å¼µã•ã‚Œã€ã¾ãŸã‚る領域ã®æ˜Žã‚‹ã•ãŒå­£ç¯€ã«ã‚ˆã£ã¦å¤‰åŒ–ã™ã‚‹ã®ã¯æ¤ç‰©ã®æˆé•·ã«ã‚ˆã‚‹ã‚‚ã®ã ã¨è€ƒãˆã‚‰ã‚ŒãŸã€‚ã“れらã®è€ƒãˆã‹ã‚‰ç«æ˜Ÿäººã«é–¢é€£ã—ãŸå¤šãã®è©±ãŒç”Ÿã¾ã‚ŒãŸã€‚ã—ã‹ã—今日ã§ã¯è‰²ã®å¤‰åŒ–ã¯å¡µã®åµã®ãŸã‚ã§ã‚ã‚‹ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る。 +ç«æ˜Ÿã®è¡›æ˜Ÿ[編集] +詳細ã¯ã€Œç«æ˜Ÿã®è¡›æ˜Ÿã€ã‚’å‚ç…§ + +ç«æ˜Ÿã«ã¯ãƒ•ã‚©ãƒœã‚¹ã¨ãƒ€ã‚¤ãƒ¢ã‚¹ã®2ã¤ã®è¡›æ˜ŸãŒå­˜åœ¨ã™ã‚‹ã€‚ã¨ã‚‚ã«1877å¹´ã«ã‚¢ã‚µãƒ•ãƒ»ãƒ›ãƒ¼ãƒ«ã«ã‚ˆã£ã¦ç™ºè¦‹ã•ã‚Œã€ã‚®ãƒªã‚·ã‚¢ç¥žè©±ã§è»ç¥žã‚¢ãƒ¬ãƒ¼ã‚¹ã®æˆ¦ã„ã«åŒè¡Œã—ãŸæ¯å­ã®ãƒ•ã‚©ãƒœã‚¹ï¼ˆã€Œç‹¼ç‹½ã€ã®æ„)ã€ãƒ€ã‚¤ãƒ¢ã‚¹ï¼ˆã€Œæ怖ã€ã®æ„)ã‹ã‚‰å付ã‘られãŸã€‚アレースã¯ãƒ­ãƒ¼ãƒžç¥žè©±ã§ã¯æˆ¦äº‰ã®ç¥žãƒžãƒ«ã‚¹ã¨ã—ã¦çŸ¥ã‚‰ã‚Œã¦ã„る。 +ç«æ˜ŸæŽ¢æŸ»[編集] +ヴァイキング1å·ã®ç€é™¸åœ°ç‚¹ +詳細ã¯ã€Œç«æ˜ŸæŽ¢æŸ»ã€ã€ã€Œç«æ˜ŸæŽ¢æŸ»æ©Ÿã€ã€ãŠã‚ˆã³ã€Œç«æ˜Ÿã«ã‚る人工物ã®ä¸€è¦§ã€ã‚’å‚ç…§ + +ç«æ˜Ÿã®åœ°è¡¨ã‚„気候ã€åœ°å½¢ã‚’研究ã™ã‚‹ãŸã‚ã«ã€ã‚½é€£ã€ã‚¢ãƒ¡ãƒªã‚«ã€ãƒ¨ãƒ¼ãƒ­ãƒƒãƒ‘ã€æ—¥æœ¬ã«ã‚ˆã£ã¦ä»Šã¾ã§ã«è»Œé“探査機ã€ç€é™¸æ©Ÿã€ãƒ­ãƒ¼ãƒãƒ¼ãªã©ã®å¤šãã®æŽ¢æŸ»æ©ŸãŒç«æ˜Ÿã«é€ã‚Šè¾¼ã¾ã‚ŒãŸã€‚ç«æ˜Ÿã‚’目指ã—ãŸæŽ¢æŸ»æ©Ÿã®ã†ã¡ã€ç´„ 2/3 ãŒãƒŸãƒƒã‚·ãƒ§ãƒ³å®Œäº†å‰ã«ã€ã¾ãŸã¯ãƒŸãƒƒã‚·ãƒ§ãƒ³é–‹å§‹ç›´å¾Œã«ä½•ã‚‰ã‹ã®å¤±æ•—ã‚’èµ·ã“ã—ã¦ã„る。ã“ã®é«˜ã„失敗率ã®ä¸€éƒ¨ã¯æŠ€è¡“上ã®å•é¡Œã«ã‚ˆã‚‹ã‚‚ã®ã¨è€ƒãˆã‚‰ã‚Œã‚‹ãŒã€ç‰¹ã«è€ƒãˆã‚‰ã‚Œã‚‹åŽŸå› ãŒãªã„ã¾ã¾å¤±æ•—ã—ãŸã‚Šäº¤ä¿¡ãŒé€”絶ãˆãŸã‚Šã—ãŸã‚‚ã®ã‚‚多ãã€ç ”究者ã®ä¸­ã«ã¯å†—談åŠåˆ†ã«åœ°çƒ-ç«æ˜Ÿé–“ã®ã€ŒãƒãƒŸãƒ¥ãƒ¼ãƒ€ãƒˆãƒ©ã‚¤ã‚¢ãƒ³ã‚°ãƒ«ã€ã¨å‘¼ã‚“ã ã‚Šã€ç«æ˜ŸæŽ¢æŸ»æ©Ÿã‚’食ã¹ã¦æš®ã‚‰ã—ã¦ã„る宇宙悪霊ãŒã„ã‚‹ã¨è¨€ã£ãŸã‚Šã€ç«æ˜Ÿã®å‘ªã„ã¨è¨€ã†äººã‚‚ã„る。 + +最もæˆåŠŸã—ãŸãƒŸãƒƒã‚·ãƒ§ãƒ³ã¨ã—ã¦ã¯ã€ã‚½é€£ã®ç«æ˜ŸæŽ¢æŸ»æ©Ÿè¨ˆç”»ã‚„アメリカã®ãƒžãƒªãƒŠãƒ¼è¨ˆç”»ã€ãƒã‚¤ã‚­ãƒ³ã‚°è¨ˆç”»ã€ãƒžãƒ¼ã‚ºãƒ»ã‚°ãƒ­ãƒ¼ãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼ã€ãƒžãƒ¼ã‚ºãƒ»ãƒ‘スファインダーã€2001マーズ・オデッセイãªã©ãŒã‚る。グローãƒãƒ«ãƒ»ã‚µãƒ¼ãƒ™ã‚¤ãƒ¤ãƒ¼ã¯å³¡è°·ã‚„土石æµã®å†™çœŸã‚’撮影ã—ã€å¸¯æ°´å±¤ã¨åŒæ§˜ã®æ¶²ä½“ã®æ°´ãŒæµã‚Œã‚‹æ°´æºãŒç«æ˜Ÿã®åœ°è¡¨ã¾ãŸã¯åœ°è¡¨è¿‘ãã«å­˜åœ¨ã™ã‚‹å¯èƒ½æ€§ã‚’示唆ã—ãŸã€‚2001マーズ・オデッセイã¯ã€ç«æ˜Ÿã®å—ç·¯60度以å—ã®å—極地方ã®åœ°ä¸‹ç´„3m以内ã®è¡¨åœŸã«ã¯å¤§é‡ã®æ°´ã®æ°·ãŒå †ç©ã—ã¦ã„ã‚‹ã“ã¨ã‚’明らã‹ã«ã—ãŸã€‚ + +2003å¹´ã€æ¬§å·žå®‡å®™æ©Ÿé–¢ (ESA) ã¯ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス・オービタã¨ç€é™¸æ©Ÿãƒ“ーグル2ã‹ã‚‰ãªã‚‹ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス探査機を打ã¡ä¸Šã’ãŸã€‚マーズ・エクスプレス・オービタã¯ç«æ˜Ÿã®å—極ã«æ°´ã¨äºŒé…¸åŒ–ç‚­ç´ ã®æ°·ãŒå­˜åœ¨ã™ã‚‹ã“ã¨ã‚’確èªã—ãŸã€‚NASA ã¯ãれ以å‰ã«åŒ—極ã«ã¤ã„ã¦ã€åŒæ§˜ã®æ°·ãŒå­˜åœ¨ã™ã‚‹ã“ã¨ã‚’確èªã—ã¦ã„ãŸã€‚ビーグル2ã¨ã®äº¤ä¿¡ã«ã¯å¤±æ•—ã—ã€2004å¹´2月åˆæ—¬ã«ãƒ“ーグル2ãŒå¤±ã‚ã‚ŒãŸã“ã¨ãŒå®£è¨€ã•ã‚ŒãŸã€‚ +スピリットã«ã‚ˆã£ã¦æ’®å½±ã•ã‚ŒãŸã‚³ãƒ­ãƒ³ãƒ“ア・ヒルズã®ãƒ‘ノラマ画åƒã€‚アメリカã«ã‚るカホキア墳丘ã¨ã„ã†å…ˆä½æ°‘éºè·¡ã«ã¡ãªã‚“㧠Cahokia panorama ã¨å‘¼ã°ã‚Œã¦ã„ã‚‹ + +åŒã˜2003å¹´ã« NASA ã¯ã‚¹ãƒ”リット (MER-A)ã€ã‚ªãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ (MER-B) ã¨å‘½åã•ã‚ŒãŸ2æ©Ÿã®ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—ロレーション・ローãƒãƒ¼ã‚’打ã¡ä¸Šã’ãŸã€‚2æ©Ÿã¨ã‚‚2004å¹´1月ã«ç„¡äº‹ã«ç€é™¸ã—ã€å…¨ã¦ã®æŽ¢æŸ»ç›®æ¨™ã‚’調査ã—ãŸã€‚当åˆè¨ˆç”»ã•ã‚ŒãŸãƒŸãƒƒã‚·ãƒ§ãƒ³ã¯90日間ã ã£ãŸãŒã€ãƒŸãƒƒã‚·ãƒ§ãƒ³ã¯æ•°å›žå»¶é•·ã•ã‚Œã€ã„ãã¤ã‹ã®æ©Ÿæ¢°çš„トラブルã¯èµ·ããŸã‚‚ã®ã®ã€2007å¹´ç¾åœ¨ã‚‚ãªãŠç§‘学的æˆæžœã‚’地çƒã«é€ã‚Šç¶šã‘ã¦ã„る。最大ã®ç§‘学的æˆæžœã¯ã€ä¸¡æ–¹ã®ç€é™¸åœ°ç‚¹ã§éŽåŽ»ã®ã‚る時期ã«æ¶²ä½“ã®æ°´ãŒå­˜åœ¨ã—ãŸè¨¼æ‹ ã‚’発見ã—ãŸã“ã¨ã§ã‚る。ã¾ãŸã€ç«æ˜Ÿã®åœ°ä¸Šã§æ’®å½±ã•ã‚ŒãŸæ—‹é¢¨ (dust devil) ãŒç«æ˜Ÿã®åœ°è¡¨ã‚’å‹•ã„ã¦ã„ã様å­ãŒã‚¹ãƒ”リットã«ã‚ˆã£ã¦æ¤œå‡ºã•ã‚ŒãŸã€‚ã“ã®æ—‹é¢¨ã¯ãƒžãƒ¼ã‚ºãƒ»ãƒ‘スファインダーã§åˆã‚ã¦æ’®å½±ã•ã‚Œã¦ã„ãŸã€‚ +スピリットã«ã‚ˆã£ã¦æ’®å½±ã•ã‚ŒãŸç«æ˜Ÿã®æ—‹é¢¨ + +2012å¹´ã«ãƒžãƒ¼ã‚ºãƒ»ã‚µã‚¤ã‚¨ãƒ³ã‚¹ãƒ»ãƒ©ãƒœãƒ©ãƒˆãƒªãƒ¼ãŒç«æ˜Ÿã«åˆ°ç€ã—ã€ã‚­ãƒ¥ãƒªã‚ªã‚·ãƒ†ã‚£ãƒ¼ç€é™¸ã®éŽç¨‹ã‚’撮影ã—ãŸ720p10fpsã®é«˜ç²¾ç´°ãªå‹•ç”»ãŒåœ°çƒã«é€ã‚‰ã‚ŒãŸã€‚ キュリオシティーã«ã¯éŽåŽ»ç«æ˜Ÿã«æŠ•å…¥ã•ã‚ŒãŸæŽ¢æŸ»æ©Ÿã®ä¸­ã§ã¯æœ€é«˜ã®è§£åƒåº¦ (1600×1200) ã®ã‚«ãƒ¡ãƒ©ãŒæ­è¼‰ã•ã‚Œã¦ãŠã‚Šã€æ¬¡ã€…ã«é«˜ç²¾ç´°ãªãƒ‘ノラマ画åƒãŒé€ã‚‰ã‚Œã¦ã„る。 +有人ç«æ˜ŸæŽ¢æŸ»[編集] +詳細ã¯ã€Œæœ‰äººç«æ˜ŸæŽ¢æŸ»ã€ãŠã‚ˆã³ã€Œç«æ˜Ÿã®æ¤æ°‘ã€ã‚’å‚ç…§ +有人ç«æ˜ŸæŽ¢æŸ»ã®æƒ³åƒå›³ã€‚ + +ヴェルナー・フォン・ブラウンをã¯ã˜ã‚ã€å¤šãã®äººã€…ãŒæœ‰äººæœˆæŽ¢æŸ»ã®æ¬¡ã®ã‚¹ãƒ†ãƒƒãƒ—ã¯ã€æœ‰äººç«æ˜ŸæŽ¢æŸ»ã§ã‚ã‚‹ã¨è€ƒãˆã¦ããŸã€‚有人探査ã®è³›åŒè€…ã¯ã€äººé–“ã¯ç„¡äººæŽ¢æŸ»æ©Ÿã‚ˆã‚Šã‚‚幾分優れã¦ãŠã‚Šã€æœ‰äººæŽ¢æŸ»ã‚’進ã‚ã‚‹ã¹ãã ã¨ä¸»å¼µã—ã¦ã„る。 + +アメリカåˆè¡†å›½ã®ãƒ–ッシュ大統領(父)ã¯1989å¹´ã«æœˆãŠã‚ˆã³ç«æ˜Ÿã®æœ‰äººæŽ¢æŸ»æ§‹æƒ³ã‚’明らã‹ã«ã—ãŸãŒã€å¤šé¡ã®äºˆç®—ã‚’å¿…è¦ã¨ã™ã‚‹ãŸã‚ã«æ–­å¿µã•ã‚ŒãŸã€‚ã¾ãŸã€ãƒ–ッシュ大統領(æ¯å­ï¼‰ã‚‚2004å¹´1月14æ—¥ã«ã€Œå®‡å®™æŽ¢æŸ»ã®å°†æ¥ã€ã¨é¡Œã—ãŸæ–°ãŸãªè¨ˆç”»ã‚’発表ã—ãŸã€‚ã“ã‚Œã«ã‚ˆã‚‹ã¨ã€ã‚¢ãƒ¡ãƒªã‚«ã¯2015å¹´ã¾ã§ã«ã‚‚ã†ä¸€åº¦æœˆã«æœ‰äººæŽ¢æŸ»æ©Ÿã‚’é€ã‚Šã€ãã®å¾Œæœ‰äººã§ã®ç«æ˜ŸæŽ¢æŸ»ã®å¯èƒ½æ€§ã‚’探るã“ã¨ã¨ãªã£ã¦ã„ãŸï¼ˆã‚³ãƒ³ã‚¹ãƒ†ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³è¨ˆç”»ï¼‰ã€‚ã¾ãŸã€ãƒ­ã‚·ã‚¢ã‚‚å°†æ¥çš„ã«æœ‰äººç«æ˜ŸæŽ¢æŸ»ã‚’è¡Œã†ã“ã¨ã‚’予定ã—ã¦ãŠã‚Šã€æŠ€è¡“的・経済的ã«åˆ¤æ–­ã—ã¦2025å¹´ã¾ã§ã«ã¯å®Ÿç¾å¯èƒ½ã§ã‚ã‚‹ã¨ã—ã¦ã„る。更ã«ESAã‚‚ã€2030å¹´ã¾ã§ã«äººé–“ã‚’ç«æ˜Ÿã«é€ã‚‹ã€Œã‚ªãƒ¼ãƒ­ãƒ©ãƒ»ãƒ—ログラムã€ã¨å‘¼ã°ã‚Œã‚‹é•·æœŸè¨ˆç”»ã‚’æŒã£ã¦ã„る。 + +特ã«ãƒãƒƒã‚¯ã¨ãªã‚‹ã®ã¯ã€ç«æ˜Ÿã¸ã®å¾€å¾©ã¨æ»žåœ¨æœŸé–“ã®åˆè¨ˆã§1å¹´å¼·ã‹ã‚‰3å¹´å¼±ã¨ã„ã†ã€æœˆæŽ¢æŸ»ã¨ã¯æ¯”較ã«ãªã‚‰ãªã„長期間ã®ãƒŸãƒƒã‚·ãƒ§ãƒ³ã§ã‚ã‚‹ã“ã¨ã¨ã€é‹ã°ãªã‘ã‚Œã°ãªã‚‰ãªã„物資ã®é‡ã§ã‚る。ã“ã®ãŸã‚ã€ç«æ˜Ÿã®å¤§æ°—ã‹ã‚‰å¸°é‚„用燃料を製造ã™ã‚‹ç„¡äººå·¥å ´ã‚’先行ã—ã¦é€ã‚Šè¾¼ã¿ã€æœ‰äººå®‡å®™èˆ¹ã¯å¾€è·¯åˆ†ã®ã¿ã®ç‡ƒæ–™ã§ç«æ˜Ÿã«åˆ°é”ã—ã€æŽ¢æŸ»å¾Œã«ç„¡äººå·¥å ´ã§è£½é€ ã•ã‚Œã¦ã„ãŸç‡ƒæ–™ã§å¸°é‚„ã™ã‚‹ã¨ã„ã†ãƒ—ラン「マーズ・ダイレクトã€ãªã©ã‚‚æ案ã•ã‚Œã¦ã„る。 + +2010å¹´ã€ã‚ªãƒãƒžå¤§çµ±é ˜ã¯ã‚³ãƒ³ã‚¹ãƒ†ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³è¨ˆç”»ã®ä¸­æ­¢ã‚’表明ã—ãŸãŒã€åŒæ™‚ã«äºˆç®—ã‚’æ–°åž‹ã®ãƒ­ã‚±ãƒƒãƒˆã‚¨ãƒ³ã‚¸ãƒ³é–‹ç™ºãªã©ã®å°†æ¥æ€§ã®é«˜ã„新技術開発ã«æŒ¯ã‚Šå‘ã‘ã‚‹ã¨ã—ã¦ãŠã‚Šã€ã‚ˆã‚ŠçŸ­æœŸé–“ã§ç«æ˜Ÿã«åˆ°é”ã§ãる航行手段ãŒå®Ÿç”¨åŒ–ã•ã‚Œã‚‹äº‹ãŒæœŸå¾…ã•ã‚Œã‚‹ã€‚ã¾ãŸã€åŒè¨ˆç”»ã®ä»£ã‚ã‚Šã«ã‚ªãƒãƒžå¤§çµ±é ˜ã¯ã€2030年代åŠã°ã‚’目標ã«ã—ãŸæ–°ãŸãªæœ‰äººç«æ˜ŸæŽ¢æŸ»è¨ˆç”»ã‚‚発表ã—ã¦ã„る。 +ç«æ˜ŸæŽ¢æŸ»æ‰¹åˆ¤[編集] + +ç«æ˜ŸæŽ¢æŸ»ã¯è¿‘年根強ã実施ã•ã‚Œã¦ã„ã‚‹ãŒã€å‰è¿°ã®ã‚ˆã†ã«æŽ¢æŸ»è¨ˆç”»ã®ç´„2/3ãŒå¤±æ•—ã«çµ‚ã‚る上ã«ã€èŽ«å¤§ãªäºˆç®—ãŒã‹ã‹ã‚‹ã¨ã—ã¦æ‰¹åˆ¤ã™ã‚‹å£°ã‚‚大ãã„。「ç«æ˜Ÿã«æ°´ãŒã‹ã¤ã¦ã‚ã£ãŸã€‚ãã‚ŒãŒã©ã†ã—ãŸã€‚我々ã®ç”Ÿæ´»ã«é–¢ä¿‚ã‚ã‚‹ã®ã‹? 予算を地çƒã®ãŸã‚ã«ä½¿ã†ã¹ãã ã€ã¨ã„ã†ã‚ˆã†ãªã‚‚ã®ã§ã‚る。実際ã«ã¯ï¼ˆã‚¢ãƒ¡ãƒªã‚«åˆè¡†å›½ã‚’例ã«å–ã‚Œã°ï¼‰å›½é˜²è²»ã®1/20以下ã®NASAã®äºˆç®—ã®ã€æ›´ã«ã”ã一部ãŒç«æ˜ŸæŽ¢æŸ»ã«å‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦ã„ã‚‹ã«éŽãŽãªã„ã®ã ãŒã€ã“ã†ã—ãŸå£°ã‚’無視ã™ã‚‹ã“ã¨ã‚‚出æ¥ãšã€æŽ¢æŸ»è¨ˆç”»ã®ä½Žã‚³ã‚¹ãƒˆåŒ–ãŒé€²ã‚られã¦ã„る。 +ç«æ˜Ÿã®è¦³æ¸¬[編集] +天çƒä¸Šã®ç«æ˜Ÿã®å‹•ã。 + +16世紀デンマークã®å¤©æ–‡å­¦è€…ティコ・ブラーエã¯ã€åœ°çƒã‚’中心ã«å¤ªé™½ï¼ˆç«æ˜Ÿãªã©æƒ‘星ã¯å¤ªé™½ã®å‘¨ã‚Šã‚’廻る)ãŒå»»ã‚‹å¤‰å‰‡çš„ãªå¤©å‹•èª¬ã‚’ã¨ã£ã¦ã„ãŸãŒã€è‚‰çœ¼ã«ã‚ˆã‚‹ã‚‚ã®ã§ã¯æœ€ã‚‚精密ã«ç«æ˜Ÿã®è»Œé“を観測ã—ãŸã€‚ティコ(慣習ã¨ã—ã¦å§“ã§ãªãåを通称ã¨ã™ã‚‹ï¼‰ã®åŠ©æ‰‹ã§ã‚ã£ãŸãƒ¨ãƒãƒã‚¹ãƒ»ã‚±ãƒ—ラーã¯å¸«ã®æ­»å¾Œã€è¦³æ¸¬ãƒ‡ãƒ¼ã‚¿ã‚’解æžã™ã‚‹ã“ã¨ã§æƒ‘星ã®è»Œé“ãŒå††ã§ã¯ãªã楕円ã§ã‚ã‚‹ã“ã¨ã€ã•ã‚‰ã«ç«æ˜Ÿã®è»Œé“ã‹ã‚‰ä»–ã®æƒ‘星ã®è»Œé“も楕円ã§ã‚りケプラーã®æ³•å‰‡ã«å¾“ã†ã¨ã„ã†åœ°å‹•èª¬ã‚’主張ã—ãŸã€‚公転速度ãŒé€Ÿã観測ã—ã‚„ã™ã„ç«æ˜Ÿã®è»Œé“離心率ãŒå†¥çŽ‹æ˜Ÿã‚„水星ã«æ¬¡ã„ã§å¤§ãã„0.0934ã§ã‚ã£ãŸã“ã¨ã‚‚幸é‹ã§ã‚ã£ãŸã€‚ + +1877å¹´ã®ç«æ˜Ÿå¤§æŽ¥è¿‘ã¨ã‚¹ã‚­ã‚¢ãƒ‘レッリã®ç™ºè¡¨ã«å§‹ã¾ã£ãŸç«æ˜Ÿé‹æ²³èª¬ã«é‡å¤§ãªç–‘å•ã‚’投ã’ã‹ã‘ãŸã®ãŒã€ã‚¨ãƒƒã‚¸ãƒ¯ãƒ¼ã‚¹ãƒ»ã‚«ã‚¤ãƒ‘ーベルトã®æ唱者ã®ä¸€äººã§ã‚るカイパーã§ã‚る。1947å¹´ã€ç«æ˜Ÿã‚’赤外線帯ã§è¦³æ¸¬ã—ã€å¤§æ°—ã®æˆåˆ†ãŒäºŒé…¸åŒ–ç‚­ç´ ã§ã‚ã‚‹ã¨ä¸»å¼µã—ãŸã€‚地çƒå¤§æ°—ã®é‡è¦ãªæˆåˆ†ã§ã‚る窒素ã€é…¸ç´ ã€æ°´è’¸æ°—ã®ç—•è·¡ã¯è¦‹å½“ãŸã‚‰ãšã€æ–‡æ˜Žã‚’æŒã¤ç«æ˜Ÿäººã®å­˜åœ¨ã¯ã»ã¼å¦å®šã•ã‚ŒãŸã€‚ +ãƒãƒƒãƒ–ル宇宙望é é¡ãŒå†™ã—ãŸç«æ˜Ÿã€‚ + +地çƒã¯780日(2å¹´ã¨7週間ã¨1日)ã”ã¨ã«ç«æ˜Ÿã‚’追ã„越ã—ã€ãã®ã¨ãã®è·é›¢ã¯ç´„8000万km(約4光分)ã¾ã§æŽ¥è¿‘ã™ã‚‹ã€‚ã—ã‹ã—ã€ç«æ˜Ÿè»Œé“ãŒæ¥•å††ã§ã‚ã‚‹ãŸã‚ã«æœ€æŽ¥è¿‘時ã®è·é›¢ã¯å¤‰åŒ–ã™ã‚‹ã€‚ç«æ˜Ÿã®è¿‘日点付近ã§æŽ¥è¿‘ã™ã‚Œã°æŽ¥è¿‘è·é›¢ã¯5600万km程度ã¨ãªã‚‹ãŒã€é æ—¥ç‚¹ä»˜è¿‘ã§æŽ¥è¿‘ã™ã‚Œã°1å„„km程度ã¨2å€è¿‘ãè·é›¢ãŒç•°ãªã‚‹ã€‚肉眼ã§è¦³æ¸¬ã—ã¦ã„ã‚‹ã¨ã€ç«æ˜Ÿã¯é€šå¸¸ã€ä»–ã®æ˜Ÿã¨ã¯ã£ãã‚Šç•°ãªã‚‹é»„色ã‚ã‚‹ã„ã¯ã‚ªãƒ¬ãƒ³ã‚¸è‰²ã‚„赤ã£ã½ã„色ã«è¦‹ãˆã€è»Œé“を公転ã™ã‚‹ã«ã¤ã‚Œã¦åœ°çƒã‹ã‚‰è¦‹ã‚‹ä»–ã®ã©ã®æƒ‘星よりも大ãã明るã•ãŒå¤‰åŒ–ã™ã‚‹ã€‚ã“ã‚Œã¯ã€ç«æ˜ŸãŒåœ°çƒã‹ã‚‰æœ€ã‚‚離れる時ã«ã¯æœ€ã‚‚è¿‘ã¥ã„ãŸæ™‚ã®7å€ä»¥ä¸Šã‚‚è·é›¢ãŒé›¢ã‚Œã‚‹ãŸã‚ã§ã‚る。ãªãŠã€å¤ªé™½ã¨åŒã˜æ–¹å‘ã«ã‚ã‚‹åˆå‰å¾Œã®æ•°ãƒ¶æœˆé–“ã¯å¤ªé™½ã®å…‰ã§è¦‹ãˆãªããªã‚‹ã“ã¨ã‚‚ã‚る。最も観測ã«é©ã—ãŸæ™‚期ã¯32å¹´ã”ã¨ã«2回ã€15å¹´ã¨17å¹´ã‚’ãŠã„ã¦äº¤äº’ã«ã‚„ã£ã¦ãã¦ã€Œå¤§æŽ¥è¿‘ã€ã¨å‘¼ã°ã‚Œã‚‹ã€‚ã“ã®æ™‚期ã¯å¸¸ã«7月終ã‚ã‚Šã‹ã‚‰9月終ã‚ã‚Šã®é–“ã«ãªã‚‹ã€‚ã“ã®æ™‚期ã«ç«æ˜Ÿã‚’望é é¡ã§è¦‹ã‚‹ã¨è¡¨é¢ã®æ§˜ã€…ãªæ§˜å­ã‚’詳細ã«è¦‹ã‚‹ã“ã¨ãŒã§ãる。低å€çŽ‡ã§ã‚‚見ãˆã‚‹ç‰¹ã«ç›®ç«‹ã¤ç‰¹å¾´ã¯æ¥µå† ã§ã‚る。 + +2003å¹´8月27æ—¥9時51分13秒(世界時)ã«ç«æ˜Ÿã¯éŽåŽ»60,000å¹´ã§æœ€ã‚‚è¿‘ãã€55,758,006 kmã¾ã§åœ°çƒã«æŽ¥è¿‘ã—ãŸï¼ˆæƒ‘星光行差補正ãªã—ã§ã®å€¤ï¼‰ã€‚ã“ã®å¤§æŽ¥è¿‘ã¯ç«æ˜Ÿã®è¿‘日点通éŽã®3日後ãŒç«æ˜Ÿã®è¡ã®ç¿Œæ—¥ã¨é‡ãªã£ãŸãŸã‚ã«ç”Ÿã˜ãŸã‚‚ã®ã§ã€åœ°çƒã‹ã‚‰ç«æ˜Ÿã‚’特ã«è¦‹ã‚„ã™ããªã£ãŸã€‚ã“れ以å‰ã«æœ€ã‚‚è¿‘ã接近ã—ãŸã®ã¯ç´€å…ƒå‰57617å¹´9月12æ—¥ã¨è¨ˆç®—ã•ã‚Œã¦ã„ã‚‹[10]。太陽系ã®é‡åŠ›è¨ˆç®—ã®è©³ç´°ãªè§£æžã‹ã‚‰ã€2287å¹´ã«ã¯2003年よりも近ã„接近ãŒèµ·ã“ã‚‹ã¨è¨ˆç®—ã•ã‚Œã¦ã„る。ã—ã‹ã—正確ã«è¦‹ã¦ã„ãã¨ã€ã“ã®è¨˜éŒ²çš„ãªå¤§æŽ¥è¿‘ã¯284å¹´ã”ã¨ã«4回起ãã¦ã„る別ã®å¤§æŽ¥è¿‘よりもã”ãã‚ãšã‹ã«è¿‘ã„ã ã‘ã§ã‚ã‚‹ã“ã¨ãŒåˆ†ã‹ã‚‹ã€‚例ãˆã°ã€2003å¹´8月27æ—¥ã®æœ€æŽ¥è¿‘è·é›¢ãŒ 0.37271AU ã§ã‚ã‚‹ã®ã«å¯¾ã—ã¦1924å¹´8月22æ—¥ã®æœ€æŽ¥è¿‘è·é›¢ã¯ 0.37284AU ã§ã‚ã‚Šã€2208å¹´8月24æ—¥ã®æŽ¥è¿‘㯠0.37278AU ã§ã‚る。 + +2084å¹´11月10æ—¥ã«ã¯ç«æ˜Ÿã‹ã‚‰è¦‹ã¦åœ°çƒã®å¤ªé™½é¢é€šéŽãŒèµ·ã“る。ã“ã®æ™‚ã«ã¯å¤ªé™½ã¨åœ°çƒã€ç«æ˜ŸãŒä¸€ç›´ç·šä¸Šã«ä¸¦ã¶ã€‚åŒæ§˜ã«ç«æ˜Ÿã‹ã‚‰è¦‹ãŸæ°´æ˜Ÿã‚„金星ã®å¤ªé™½é¢é€šéŽã‚‚èµ·ã“る。ç«æ˜Ÿã®è¡›æ˜Ÿã§ã‚るダイモスã¯ç«æ˜Ÿã‹ã‚‰è¦‹ãŸè§’直径ãŒå¤ªé™½ã®ãれよりå分ã«å°ã•ã„ãŸã‚ã€ãƒ€ã‚¤ãƒ¢ã‚¹ã«ã‚ˆã‚‹éƒ¨åˆ†æ—¥é£Ÿã‚‚太陽é¢é€šéŽã¨è¦‹ãªã›ã‚‹ã€‚ + +1590å¹´10月13æ—¥ã«ã¯éŽåŽ»å”¯ä¸€ã®é‡‘星ã«ã‚ˆã‚‹ç«æ˜Ÿé£ŸãŒèµ·ã“ã‚Š[11]ã€ãƒ‰ã‚¤ãƒ„ã®ãƒã‚¤ãƒ‡ãƒ«ãƒ™ãƒ«ã‚¯ã§ãƒ¡ã‚¹ãƒˆãƒªãƒ³ã«ã‚ˆã£ã¦è¦³æ¸¬ã•ã‚ŒãŸã€‚ +ç«æ˜Ÿèµ·æºã®éš•çŸ³[編集] +「ALH84001ã€éš•çŸ³ + +地çƒä¸Šã§ç™ºè¦‹ã•ã‚ŒãŸã‚‚ã®ã®ã†ã¡ã€ç¢ºå®Ÿã«éš•çŸ³ã§ã‚ã‚Šã€ã‹ã¤ç«æ˜Ÿã«èµ·æºã‚’æŒã¤ã¨æ€ã‚れる岩石ãŒã„ãã¤ã‹çŸ¥ã‚‰ã‚Œã¦ã„る。ã“れらã®éš•çŸ³ã®ã†ã¡2ã¤ã‹ã‚‰ã¯å¤ä»£ã®ç´°èŒã®æ´»å‹•ã®ç—•è·¡ã‹ã‚‚ã—ã‚Œãªã„特徴ãŒè¦‹ã¤ã‹ã£ã¦ã„る。1996å¹´8月6æ—¥ã€NASAã¯ç«æ˜Ÿèµ·æºã¨è€ƒãˆã‚‰ã‚Œã¦ã„る「ALH84001ã€éš•çŸ³ã®åˆ†æžã‹ã‚‰ã€å˜ç´°èƒžç”Ÿå‘½ä½“ã®åŒ–石ã®å¯èƒ½æ€§ãŒã‚る特徴ãŒç™ºè¦‹ã•ã‚ŒãŸã¨ç™ºè¡¨ã—ãŸã€‚ã—ã‹ã—ã“ã®è§£é‡ˆã«ã¯ã„ã¾ã ã«è­°è«–ã®ä½™åœ°ãŒã‚る。 + +『Solar System Researchã€2004å¹´3æœˆå· (38, p.97) ã«æŽ²è¼‰ã•ã‚ŒãŸè«–æ–‡ã§ã¯ã€ã‚¤ã‚¨ãƒ¡ãƒ³ã§ç™ºè¦‹ã•ã‚ŒãŸã‚«ã‚¤ãƒ‰ã‚¥ãƒ³éš•çŸ³ãŒç«æ˜Ÿã®è¡›æ˜Ÿãƒ•ã‚©ãƒœã‚¹ã«èµ·æºã‚’æŒã¤å¯èƒ½æ€§ãŒã‚ã‚‹ã¨ç¤ºå”†ã—ã¦ã„る。 + +2004å¹´4月14æ—¥ã«NASAã¯ã€ã‚ªãƒãƒãƒ¥ãƒ‹ãƒ†ã‚£ã«ã‚ˆã£ã¦èª¿æŸ»ã•ã‚ŒãŸ "Bounce" ã¨ã„ã†åå‰ã®å²©çŸ³ãŒã€1979å¹´ã«å—極ã§ç™ºè¦‹ã•ã‚ŒãŸéš•çŸ³ã€ŒEETA79001-Bã€ã¨ä¼¼ãŸçµ„æˆã‚’æŒã£ã¦ã„ã‚‹ã“ã¨ã‚’明らã‹ã«ã—ãŸ[12]。ã“ã®å²©çŸ³ã¯ã“ã®éš•çŸ³ã¨åŒã˜ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã‹ã‚‰é£›æ•£ã—ãŸã‹ã€ã‚ã‚‹ã„ã¯ç«æ˜Ÿè¡¨é¢ã®åŒã˜åœ°åŸŸã«ã‚る別々ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã‹ã‚‰é£›ã°ã•ã‚ŒãŸå¯èƒ½æ€§ãŒã‚る。 +æ°·ã®æ¹–[編集] + +2005å¹´7月29æ—¥ã€BBCã¯ç«æ˜Ÿã®åŒ—極地方ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã§æ°·ã®æ¹–ãŒç™ºè¦‹ã•ã‚ŒãŸã¨å ±ã˜ãŸ[13]。ESAã®ãƒžãƒ¼ã‚ºãƒ»ã‚¨ã‚¯ã‚¹ãƒ—レス探査機ã«æ­è¼‰ã•ã‚ŒãŸé«˜è§£åƒåº¦ã‚¹ãƒ†ãƒ¬ã‚ªã‚«ãƒ¡ãƒ©ã§æ’®å½±ã•ã‚ŒãŸã“ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®ç”»åƒã«ã¯ã€åŒ—ç·¯70.5度ã€æ±çµŒ103度ã«ä½ç½®ã—ç«æ˜ŸåŒ—極域ã®å¤§åŠã‚’å ã‚るボレアリス平野ã«ã‚ã‚‹ç„¡åã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®åº•ã«å¹³ã‚‰ãªæ°·ãŒåºƒãŒã£ã¦ã„る様å­ãŒã¯ã£ãã‚Šã¨å†™ã£ã¦ã„る。ã“ã®ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯ç›´å¾„35kmã§æ·±ã•ç´„2kmã§ã‚る。 + +BBCã®å ±é“ã§ã¯ã‚„や誇張ã•ã‚Œã¦ã„ã‚‹ãŒã€å…ƒã€…ã®ESAã®ç™ºè¡¨ã§ã¯ã“ã‚ŒãŒæ¹–ã§ã‚ã‚‹ã¨ã¯ä¸»å¼µã—ã¦ã„ãªã„[14]。ç«æ˜Ÿã®æ•°å¤šãã®ä»–ã®å ´æ‰€ã«è¦‹ã‚‰ã‚Œã‚‹ã‚‚ã®ã¨åŒæ§˜ã«ã€ã“ã®å††æ¿çŠ¶ã®æ°·ã¯æš—ã低温ã®ç ‚丘ã®é ‚上(高度約200m)ã«è–„ã„層状ã®éœœãŒå‡çµã—ã¦ã‚¯ãƒ¬ãƒ¼ã‚¿ãƒ¼ã®åº•ã«åºƒãŒã£ãŸã‚‚ã®ã§ã‚る。報ã˜ã‚‰ã‚ŒãŸã“ã®æ°·ãŒç‰¹ã«çã—ã„ã®ã¯ã€éœœã®ã„ãらã‹ãŒä¸€å¹´ä¸­æ®‹ã‚Šã†ã‚‹ã»ã©ã“ã®å ´æ‰€ãŒé«˜ç·¯åº¦ã«ã‚ã‚‹ã¨ã„ã†ç‚¹ã ã‘ã§ã‚る。赤é“付近ã¯æ—¥ä¸­20℃を越ã™ã“ã¨ã‚‚ã‚ã‚Šã€é«˜ç·¯åº¦ã§ãªã‘ã‚Œã°æ°·ã¯å­˜åœ¨ã§ããªã„[15]。ã¾ãŸã€æ¶²ä½“ã®æ°´ã‚‚ã€ç«æ˜Ÿã®å¤§æ°—ã¯å¸Œè–„ã€ã™ãªã‚ã¡å¤§æ°—中ã®æ°´è’¸æ°—圧ãŒå°ã•ã„ãŸã‚ã€ç«æ˜Ÿè¡¨é¢ã®ã»ã¨ã‚“ã©ã®åœ°åŸŸã§ã¯ã™ã蒸発ã—ã¦ã—ã¾ã†ã®ã§å­˜åœ¨ã§ããªã„。液体ã®æ°´ãŒå­˜åœ¨ã§ãã‚‹ã®ã¯ãƒ˜ãƒ©ã‚¹ç›†åœ°ãªã©é™ã‚‰ã‚ŒãŸå ´æ‰€ã®ã¿ã§ã‚る。 +ç«æ˜Ÿã®ç”Ÿå‘½[編集] +詳細ã¯ã€Œç«æ˜Ÿã®ç”Ÿå‘½ã€ã‚’å‚ç…§ +å¡©æ°´ã®ã‚ˆã†ãªæ¶²ä½“ãŒæµã‚Œå‡ºãŸã¨ã¿ã‚‰ã‚Œã‚‹è·¡ã€‚ + +ç«æ˜Ÿã¯ã‹ã¤ã¦ã¯ç¾åœ¨ã‚ˆã‚Šã‚‚確実ã«ç”Ÿå‘½ã«é©ã—ãŸç’°å¢ƒã ã£ãŸã¨ã„ã†è¨¼æ‹ ãŒå­˜åœ¨ã™ã‚‹ãŒã€ç«æ˜Ÿã«ã‹ã¤ã¦å®Ÿéš›ã«ç”Ÿå‘½ä½“ãŒç”Ÿå­˜ã—ã¦ã„ãŸã‹ã©ã†ã‹ã¨ã„ã†ç–‘å•ã¯æœªè§£æ±ºã§ã‚る。ç«æ˜Ÿèµ·æºã§ã‚ã‚‹ã¨è€ƒãˆã‚‰ã‚Œã¦ã„る岩石(特ã«ã€ŒALH84001ã€éš•çŸ³ï¼‰ã«éŽåŽ»ã®ç”Ÿå‘½æ´»å‹•ã®è¨¼æ‹ ãŒå«ã¾ã‚Œã¦ã„ã‚‹ã¨è€ƒãˆã¦ã„る研究者もã„ã‚‹ãŒã€ã“ã®ä¸»å¼µã«å¯¾ã—ã¦ã¯ç¾çŠ¶ã§ã¯åˆæ„ã¯å¾—られã¦ã„ãªã„。ã“ã®éš•çŸ³ã¯æ•°åå„„å¹´å‰ã«ç”Ÿã¾ã‚Œã¦ä»¥æ¥ã€æ¶²ä½“ã®æ°´ãŒå­˜åœ¨ã§ãるよã†ãªæ¸©åº¦ã«ä¸€å®šæœŸé–“ã•ã‚‰ã•ã‚ŒãŸã“ã¨ã¯ãªã„ã“ã¨ã‚’示ã™ç ”究もã‚る。 + +ãƒã‚¤ã‚­ãƒ³ã‚°æŽ¢æŸ»æ©Ÿã«ã¯ãã‚Œãžã‚Œã®ç€é™¸åœ°ç‚¹ã§ç«æ˜Ÿã®åœŸå£Œã«å«ã¾ã‚Œã‚‹å¾®ç”Ÿç‰©ã‚’検出ã™ã‚‹ãŸã‚ã®å®Ÿé¨“装置ãŒæ­è¼‰ã•ã‚Œã€é™½æ€§ã®çµæžœã‚’ã„ãã¤ã‹å¾—ãŸãŒã€å¾Œã«å¤šãã®ç§‘学者ã«ã‚ˆã£ã¦å¦å®šã•ã‚ŒãŸã€‚ã“ã®ä»¶ã«ã¤ã„ã¦ã¯ç¾åœ¨ã‚‚è­°è«–ãŒç¶šã„ã¦ã„る。ã¾ãŸã€ç«æ˜Ÿã®å¤§æ°—ã«ãƒ¡ã‚¿ãƒ³ãŒã”ãå¾®é‡å­˜åœ¨ã—ã¦ã„る原因ã«ã¤ã„ã¦ã€ç¾åœ¨ç”Ÿå‘½æ´»å‹•ãŒé€²è¡Œã—ã¦ã„ã‚‹ã¨ã„ã†èª¬ãŒä¸€ã¤ã®è§£é‡ˆã¨ã—ã¦æ案ã•ã‚Œã¦ã„ã‚‹ãŒã€ç”Ÿå‘½æ´»å‹•ã«ç”±æ¥ã—ãªã„別ã®èª¬ã®æ–¹ãŒã‚ˆã‚Šã‚‚ã£ã¨ã‚‚らã—ã„ã¨ä¸€èˆ¬ã«è€ƒãˆã‚‰ã‚Œã¦ã„る。 + +ç¾åœ¨ã®ç«æ˜Ÿã¯ã€ãƒãƒ“タブルゾーン内(生命存在ã®å¯èƒ½ãªå¤©ä½“ãŒã€å­˜åœ¨ã§ãる領域)ã«ã‚ã‚‹ã¨ã„ã†[16]。 + +å°†æ¥æ¤æ°‘地化ãŒè¡Œãªã‚れるã¨ã™ã‚Œã°ã€ç«æ˜Ÿã¯ï¼ˆå¤ªé™½ç³»ã«å±žã™ã‚‹åœ°çƒä»¥å¤–ã®æƒ‘星ã¨æ¯”較ã—ã¦ï¼‰ã‹ãªã‚Šç”Ÿå‘½ã®ç”Ÿå­˜ã«é©ã—ãŸæ¡ä»¶ã«ã‚ã‚‹ãŸã‚ã€æœ‰åŠ›ãªé¸æŠžè‚¢ã¨ãªã‚‹ã¨æ€ã‚れる。 +人類ã¨ç«æ˜Ÿ[編集] +æ­´å²ã¨ç¥žè©±[編集] + +ç«æ˜Ÿã®å称 (Marsï¼ãƒžãƒ¼ã‚º) ã¯ã€ãƒ­ãƒ¼ãƒžç¥žè©±ã®ç¥žãƒžãƒ«ã‚¹ï¼ˆã‚®ãƒªã‚·ã‚¢ç¥žè©±ã®è»ç¥žã‚¢ãƒ¬ãƒ¼ã‚¹ï¼‰ã‹ã‚‰å付ã‘られãŸã€‚メソãƒã‚¿ãƒŸã‚¢ã®æ°‘ã¯èµ¤ã„惑星ã«æˆ¦ç«ã¨è¡€ã‚’連想ã—ã¦å½¼ã‚‰ã®æˆ¦ç¥žãƒãƒ«ã‚¬ãƒ«ã®åを冠ã—ã¦ä»¥æ¥ã€ç«æ˜Ÿã«ã¯å„々ã®åœ°ã§ãã®åœ°ã®æˆ¦ç¥žã®åãŒã¤ã‘られã¦ã„る(他ã®æƒ‘星åã«ã¤ã„ã¦ã‚‚ã»ã¼åŒæ§˜ã®ç¶™æ‰¿ãŒèªã‚られる)。 +æ±æ´‹[編集] + +ç«æ˜Ÿã¯äº”行説ã«åŸºã¥ãオカルト的ãª[è¦å‡ºå…¸]呼ã³åã§ã‚ã£ã¦ï¼ˆäº”行説ã¯æ±æ´‹åŒ»å­¦ã®åŸºç¤Žç†è«–ã§ã‚‚ã‚る)ã€å­¦å•ä¸Šï¼ˆå¤©æ–‡å²æ–™ï¼‰ã§ã¯ç†’惑(ケイコクã€ã‚¨ã‚¤ã‚³ã‚¯ï¼‰ã¨ã„ã£ãŸã€‚「熒ã€ã¯ã—ã°ã—ã°åŒéŸ³ã®ã€Œèž¢ã€ã¨èª¤ã‚‰ã‚Œã‚‹ã€‚ã¾ãŸã€ã“ã®å ´åˆã®ã€Œæƒ‘ã€ã¯ã€Œãƒ¯ã‚¯ã€ã§ã¯ãªã「コクã€ã¨èª­ã‚€ã€‚営惑ã¨ã‚‚書ã。江戸時代ã«ã¯ã€Œãªã¤ã²ã¼ã—ã€ã¨è¨“ã˜ã‚‰ã‚ŒãŸã€‚ãã®ãŸã‚å¤æ—¥æ˜Ÿã¨ã„ã†å’Œåã‚‚ã‚る。 + +ç«æ˜ŸãŒã•ãり座ã®ã‚¢ãƒ³ã‚¿ãƒ¬ã‚¹ï¼ˆé»„é“ã®è¿‘ãã«ä½ç½®ã—ã¦ã„ã‚‹ãŸã‚)ã«æŽ¥è¿‘ã™ã‚‹ã“ã¨ã‚’熒惑守心(熒惑心を守る)ã¨ã„ã„ã€ä¸å‰ã®å‰å…†ã¨ã•ã‚ŒãŸã€‚「心ã€ã¨ã¯ã€ã‚¢ãƒ³ã‚¿ãƒ¬ã‚¹ãŒæ‰€å±žã™ã‚‹æ˜Ÿå®˜ï¼ˆä¸­å›½ã®æ˜Ÿåº§ï¼‰å¿ƒå®¿ã®ã“ã¨ã€‚ +å æ˜Ÿè¡“[編集] + +ç«æ˜Ÿã¯ä¸ƒæ›œãƒ»ä¹æ›œã®1ã¤ã§ã€10大天体ã®1ã¤ã§ã‚る。 + +西洋å æ˜Ÿè¡“ã§ã¯ã€ç™½ç¾Šå®®ã®æ”¯é…星ã§ã€å¤©èŽå®®ã®å‰¯æ”¯é…星ã§ã€å‡¶æ˜Ÿã§ã‚る。ç©æ¥µæ€§ã‚’示ã—ã€é‹å‹•ã€äº‰ã„ã€å¤–科ã€å¹´ä¸‹ã®ç”·ã«å½“ã¦ã¯ã¾ã‚‹[17]。 +惑星記å·[編集] +Mars symbol.ant.png + +ç«æ˜Ÿã®æƒ‘星記å·ã¯ãƒžãƒ«ã‚¹ã‚’象徴ã™ã‚‹ç›¾ã¨æ§ã‚’図案化ã—ãŸã‚‚ã®ãŒã€å æ˜Ÿè¡“・天文学を通ã—ã¦ç”¨ã„られる。ã“れを雌雄ã®è¡¨è¨˜ã«è»¢ç”¨ã—ãŸã®ã¯ã‚«ãƒ¼ãƒ«ãƒ»ãƒ•ã‚©ãƒ³ãƒ»ãƒªãƒ³ãƒã§ã‚ã‚Šã€ç”Ÿæ®–器ã®å›³æ¡ˆã§ã¯ãªã„。 +ç«æ˜Ÿã‚’扱ã£ãŸä½œå“[編集] +詳細ã¯ã€Œç«æ˜Ÿã‚’扱ã£ãŸä½œå“一覧ã€ã‚’å‚ç…§ diff --git a/xpcom/tests/gtest/wikipedia/ko.txt b/xpcom/tests/gtest/wikipedia/ko.txt new file mode 100644 index 0000000000..7c11333ad4 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/ko.txt @@ -0,0 +1,110 @@ +화성(ç«æ˜Ÿ, Mars)ì€ íƒœì–‘ê³„ì˜ ë„¤ 번째 행성ì´ë‹¤. 붉ì€ìƒ‰ì„ ë ê¸° ë•Œë¬¸ì— ë™ì–‘권ì—서는 ë¶ˆì„ ëœ»í•˜ëŠ” í™”(ç«)를 ì¨ì„œ 화성 ë˜ëŠ” 형혹성(熒惑星)ì´ë¼ 부르고, 서양권ì—서는 로마 ì‹ í™”ì˜ ì „ìŸì˜ ì‹  ë§ˆë¥´ìŠ¤ì˜ ì´ë¦„ì„ ë”° Marsë¼ ë¶€ë¥¸ë‹¤. 오늘날 ì˜ì–´ì—ì„œ 3ì›”ì„ ëœ»í•˜ëŠ” Marchë„ ì—¬ê¸°ì„œ ìƒê²¼ë‹¤. + +매리너 4호가 1965ë…„ì— í™”ì„±ì„ ì²˜ìŒìœ¼ë¡œ 근접 ë¹„í–‰ì„ í•˜ê¸° 전까지 과학계 ì•ˆíŒŽì˜ ì‚¬ëžŒë“¤ì€ í™”ì„±ì— ëŒ€ëŸ‰ì˜ ë¬¼ì´ ì¡´ìž¬í•˜ë¦¬ë¼ê³  기대하였다. ì´ëŸ¬í•œ ê¸°ëŒ€ì˜ ê·¼ê±°ëŠ” í™”ì„±ì˜ ê·¹ì§€ë°©ì—ì„œ ë°ê³  ì–´ë‘ìš´ 무늬가 주기ì ìœ¼ë¡œ 변화한다는 사실ì´ì—ˆë‹¤. 60년대 중반 ì´ì „까지 ì‚¬ëžŒë“¤ì€ ë†ì—…ì„ ìœ„í•œ 관개수로가 í™”ì„±ì— ìžˆìœ¼ë¦¬ë¼ ê¸°ëŒ€í•˜ê¸°ê¹Œì§€ 했다. ì´ëŠ” 사실 20세기 ì´ˆÂ·ì¤‘ë°˜ì˜ ê³µìƒê³¼í•™ ìž‘ê°€ë“¤ì˜ ìƒìƒì— ì˜í–¥ë°›ì€ 것으로, 1950년대 ì´í›„ì˜ íƒì‚¬ì„ ì— ì˜í•œ 관측으로 화성 운하는 존재하지 않았ìŒì´ ë°í˜€ì¡Œë‹¤. + +물과 ìƒëª…ì²´ì˜ ë°œê²¬ì— ëŒ€í•œ 기대로 ë§Žì€ íƒì‚¬ì„ ë“¤ì— 미ìƒë¬¼ì„ 찾기 위한 ì„¼ì„œë“¤ì´ íƒ‘ìž¬ë˜ì–´ í™”ì„±ì— ë³´ë‚´ì¡Œë‹¤. 화성ì—서는 ë‹¤ëŸ‰ì˜ ì–¼ìŒì´ 발견ë˜ì—ˆê³ , ìƒëª…ì²´ê°€ 존재할 ê°€ëŠ¥ì„±ì´ ì œê¸°ë˜ê³  있다.[4] + +í™”ì„±ì˜ ìžì „ 주기와 ê³„ì ˆì˜ ë³€í™” 주기는 지구와 비슷하다. 화성ì—는 태양계ì—ì„œ 가장 ë†’ì€ ì‚°ì¸ ì˜¬ë¦¼í‘¸ìŠ¤ í™”ì‚°ì´ ìžˆìœ¼ë©°, ì—­ì‹œ 태양계ì—ì„œ 가장 í° ê³„ê³¡ì¸ ë§¤ë¦¬ë„ˆìŠ¤ 협곡과 ê·¹ê´€ì„ ê°€ì§€ê³  있다. + +물리ì ì¸ 특성[편집] +지구와 í™”ì„±ì˜ í¬ê¸° ë¹„êµ + +í™”ì„±ì€ ë¶‰ê²Œ 타는 듯한 ì™¸í˜•ì„ ê°€ì§€ê³  있다. í™”ì„±ì˜ í‘œë©´ì ì€ ì§€êµ¬ì˜ 4ë¶„ì˜ 1ë°–ì— ë˜ì§€ 않으며, 부피는 10ë¶„ì˜ 1ë°–ì— ë˜ì§€ 않는다. í™”ì„±ì€ ë‘ ê°œì˜ ìž‘ì€ ìœ„ì„±ì„ ê°€ì§€ê³  있다. í™”ì„±ì˜ ëŒ€ê¸°ê¶Œì€ ë§¤ìš° 얇으며, í‘œë©´ì˜ ê¸°ì••ì€ 7.5ë°€ë¦¬ë°”ë°–ì— ë˜ì§€ 않는다. 화성 í‘œë©´ì˜ 95%는 ì´ì‚°í™”탄소로 ë®ì—¬ 있으며, ì´ ë°–ì— 3%ì˜ ì§ˆì†Œ, 1.6%ì˜ ì•„ë¥´ê³¤ê³¼ í”ì ë§Œì´ 남아 있는 산소와 2015ë…„ NASAì—ì„œ 발견한 ì•¡ì²´ ìƒíƒœì˜ ë¬¼ì´ í¬í•¨ë˜ì–´ 있다. +지질[편집] + +궤ë„ì„ ì˜ ê´€ì¸¡ê³¼ 화성 기ì›ì˜ ìš´ì„ì— ëŒ€í•œ ë¶„ì„ ê²°ê³¼ì— ì˜í•˜ë©´, í™”ì„±ì˜ í‘œë©´ì€ ê¸°ë³¸ì ìœ¼ë¡œ 현무암으로 ë˜ì–´ 있다. 화성 í‘œë©´ì˜ ì¼ë¶€ëŠ” ì§€êµ¬ì˜ ì•ˆì‚°ì•”ê³¼ ê°™ì´ ì¢€ ë” ì´ì‚°í™”규소가 í’부하다는 ì¦ê±°ê°€ 있으나 ì´ëŸ¬í•œ ê´€ì¸¡ì€ ê·œì‚°ì—¼ê³¼ ê°™ì€ ìœ ë¦¬ì˜ ì¡´ìž¬ë¥¼ 통해서 ì„¤ëª…ë  ìˆ˜ë„ ìžˆê¸° ë•Œë¬¸ì— ê²°ì •ì ì´ì§€ëŠ” 않다. í‘œë©´ì˜ ëŒ€ë¶€ë¶„ì€ ì‚°í™”ì² ì˜ ë¨¼ì§€ë¡œ ë®ì—¬ìžˆë‹¤. í™”ì„±ì˜ í‘œë©´ì— ì¼ì‹œì ì´ë‚˜ë§ˆ ë¬¼ì´ ì¡´ìž¬í–ˆë‹¤ëŠ” ê²°ì •ì ì¸ ì¦ê±°ê°€ 있다. 화성 표면ì—ì„œ ë°œê²¬ëœ ì•”ì—¼ì´ë‚˜ 침철ì„ê³¼ ê°™ì´ ëŒ€ì²´ë¡œ ë¬¼ì´ ì¡´ìž¬í•  ë•Œ ìƒì„±ë˜ëŠ” ê´‘ë¬¼ì´ ë°œê²¬ë˜ì—ˆê¸° 때문ì´ë‹¤. + +ë¹„ë¡ í™”ì„± ìžì²´ì˜ ìžê¸°ìž¥ì€ 없지만, 과거 행성 í‘œë©´ì˜ ì¼ë¶€ëŠ” ìží™”ëœ ì ì´ 있ìŒì´ ê´€ì¸¡ì„ í†µí•´ ë°í˜€ì¡Œë‹¤. 화성ì—ì„œ ë°œê²¬ëœ ìží™”ì˜ í”ì (고지ìžê¸°)ì€ ì§€êµ¬ì˜ í•´ì–‘ì§€ê°ì—ì„œ 발견ë˜ëŠ” êµëŒ€í•˜ëŠ” ë  ëª¨ì–‘ì˜ ê³ ì§€ìžê¸°ì™€ 비êµë˜ì–´ 왔다. 1999ë…„ì— ë°œí‘œë˜ê³  2005ë…„ì— ë§ˆìŠ¤ 글로벌 서베ì´ì–´ë¡œë¶€í„°ì˜ 관측 ê²°ê³¼ì˜ ë„움으로 ìž¬ê²€í† ëœ ì´ë¡ ì— 따르면, ì´ë“¤ 지ìžê¸°ì˜ ë ë“¤ì€ ê³¼ê±°ì— ìžˆì—ˆë˜ í™”ì„±ì˜ íŒêµ¬ì¡° 활ë™ì˜ ì¦ê±°ì¼ 수 있다. ê·¹ ì´ë™(polar wandering)ìœ¼ë¡œë„ í™”ì„±ì—ì„œ ë°œê²¬ëœ ê³ ì§€ìžê¸°ë¥¼ 설명할 수 있었다. + +í™”ì„±ì˜ ë‚´ë¶€ë¥¼ 설명하는 ì´ë¡ ì— 따르면, 화성 í•µì˜ ë°˜ì§€ë¦„ì€ ì•½ 1,480kmë¡œ 주로 ì² ê³¼ 15~17%ì˜ í™©ìœ¼ë¡œ ì´ë£¨ì–´ì ¸ 있다. í™©í™”ì² ì˜ í•µì€ ë¶€ë¶„ì ìœ¼ë¡œ 용융ë˜ì–´ 있으며, ì§€êµ¬ì˜ í•µì— ë¹„í•˜ë©´ 가벼운 ì›ì†Œì˜ í•¨ëŸ‰ì´ ì•½ 2ë°°ì •ë„ ëœë‹¤. í•µì€ ê·œì‚°ì—¼ì§ˆ ë§¨í‹€ì— ë‘˜ëŸ¬ì‹¸ì—¬ 있다. ë§¨í‹€ì€ í™”ì„±ì—ì„œ ë³¼ 수 있는 ë§Žì€ íŒêµ¬ì¡° 활ë™ê³¼ 화산 활ë™ì„ ì¼ìœ¼ì¼œ 왔으나 현재는 ë” ì´ìƒ 활ë™í•˜ì§€ 않는다. 화성 지ê°ì˜ ë‘께는 약 50kmì´ê³ , ìµœëŒ“ê°’ì€ 125kmì •ë„ì´ë‹¤. + +í™”ì„±ì˜ ì§€ì§ˆ 시대는 세 시대로 구분ëœë‹¤. + +노아키안 시대는 노아키스 í…Œë¼ì˜ ì´ë¦„ì„ ë”°ì„œ 붙여진 ì´ë¦„ì´ë‹¤. í™”ì„±ì˜ í˜•ì„±ìœ¼ë¡œë¶€í„° 38ì–µ~35ì–µ ë…„ ì „ê¹Œì§€ì˜ ì‹œëŒ€ì´ë‹¤. 노아키안 ì‹œëŒ€ì˜ í‘œë©´ì€ ë§Žì€ ê±°ëŒ€í•œ í¬ë ˆì´í„°ë¡œ ë®ì—¬ 있다. 타르시스 벌지는 ì´ ì‹œëŒ€ì— í˜•ì„±ëœ ê²ƒìœ¼ë¡œ 여겨진다. ì´ ì‹œëŒ€ì˜ í›„ê¸°ì—는 ì—„ì²­ë‚œ ì–‘ì˜ ì•¡ì²´ ë¬¼ì— ì˜í•œ í™ìˆ˜ê°€ 있었다고 ìƒê°ëœë‹¤. + +헤스í¼ë¦¬ì•ˆ 시대는 헤스í¼ë¦¬ì•ˆ í‰ì›ìœ¼ë¡œë¶€í„° ì´ë¦„ì´ ë¶™ì—¬ì¡Œë‹¤. 35ì–µ ë…„ 전부터 18ì–µ ë…„ ì „ê¹Œì§€ì˜ ì‹œëŒ€ì´ë‹¤. 헤스í¼ë¦¬ì•ˆ ì‹œëŒ€ì˜ í™”ì„±ì—서는 ë„“ì€ ìš©ì•”ëŒ€ì§€ê°€ 형성ë˜ì—ˆë‹¤. + +아마조니안 시대는 아마조니스 í‰ì›ì˜ ì´ë¦„ì„ ë”°ì„œ 붙여졌다. 18ì–µ ë…„ 전부터 í˜„ìž¬ì— ì´ë¥´ëŠ” 시대ì´ë‹¤. 아마조니안 ì§€ì—­ì€ í¬ë ˆì´í„°ê°€ ê±°ì˜ ì—†ìœ¼ë‚˜ ìƒë‹¹í•œ 변화가 있는 지형ì´ë‹¤. 올림푸스 í™”ì‚°ì´ ì´ ì‹œëŒ€ì— í˜•ì„±ë˜ì—ˆê³ , 다른 지역ì—ì„œ 용암류가 형성ë˜ì—ˆë‹¤. + +마스 ìµìŠ¤í”„레스 ì˜¤ë¹„í„°ì˜ OMEGA 가시광-ì ì™¸ì„  광물학 매핑 스팩트로메터 ìžë£Œë¥¼ 기초로 ë˜ ë‹¤ë¥¸ 시대 êµ¬ë¶„ì´ ì œì‹œë˜ê³  있다. +지형[편집] + +í™”ì„±ì˜ ì¢Œí‘œë¥¼ 설정하기 위하여서는 ìžì˜¤ì„ ê³¼ 0ì  ê³ ë„ê°€ 정해져야 한다. 화성ì—는 바다가 없기 때문ì—, '해수면'ì´ ì—†ì–´ì„œ, 0ì  ê³ ë„ë©´ì´ë‚˜ í‰ê·  중력 í‘œë©´ì´ ìž„ì˜ì˜ 지ì ìœ¼ë¡œ ì„ íƒë  ìˆ˜ë°–ì— ì—†ë‹¤. ë˜í•œ ì ë„와는 달리 ê²½ë„ì˜ ê¸°ì¤€ì ì€ ìž„ì˜ë¡œ ì„ íƒì´ 가능하기 ë•Œë¬¸ì— ê³µí†µëœ ê·œì•½ì„ ì •í•  필요가 있다. 그리하여 ìž„ì˜ì ìœ¼ë¡œ 사ì´ë„ˆìŠ¤ 메리디아니(Sinus Meridiani, ì ë„만('Equatorial Gulf')) ì•ˆì˜ ë¶„í™”êµ¬ê°€ 0ì  ìžì˜¤ì„ ì„ 나타내는 것으로 ì„ íƒë˜ì—ˆë‹¤. + +화성 ì§€í˜•ì˜ ëª‡ 가지 기본ì ì¸ íŠ¹ì§•ì€ ë‹¤ìŒê³¼ 같다. í™”ì„±ì€ ê·¹ ì§€ë°©ì´ ì–¸ 물과 ì´ì‚°í™”탄소를 í¬í•¨í•˜ëŠ” ì–¼ìŒ ì§€ëŒ€ë¡œ ë®ì—¬ 있다. ë˜í•œ 화성ì—는 발레스 매리너리스(Valles Marineris) ë˜ëŠ” í™”ì„±ì˜ í‰í„°ë¼ê³  불리는 태양계ì—ì„œ 가장 í° [협곡 지대]ê°€ 있다. ì´ í˜‘ê³¡ 지대는 4000kmì˜ ê¸¸ì´ì— 깊ì´ëŠ” 7kmì— ì´ë¥¸ë‹¤. + +화성 ë¶ë°˜êµ¬ì™€ 남반구 ì§€í˜•ì˜ ë¹„ëŒ€ì¹­ì„±ì€ ë§¤ìš° ì¸ìƒì ì´ë‹¤. ë¶ìª½ ë¶€ë¶„ì€ ìš©ì•”ì¸µì´ í˜ëŸ¬ë‚´ë¦¼ìœ¼ë¡œ ì¸í•´ í‰í‰í•˜ê³ , ë‚¨ìª½ì€ ê³ ì§€ëŒ€ì— ì˜¤ëž˜ì „ì˜ ì¶©ê²©ìœ¼ë¡œ ì¸í•´ 구ë©ì´ 파ì´ê³  분화구가 ìƒê²¨ë‚˜ 있다. 지구ì—ì„œ 본 í™”ì„±ì˜ í‘œë©´ì€ í™•ì‹¤ížˆ ë‘ ë¶€ë¶„ì˜ êµ¬ì—­ìœ¼ë¡œ 나뉘어 있다. 먼지와 ì‚°í™”ì² ì´ ì„žì¸ ëª¨ëž˜ë¡œ ë’¤ë®ì¸ 좀 ë” ì°½ë°±í•œ ë¶€ë¶„ì€ í•œë•Œ 'ì•„ë¼ë¹„ì•„ì˜ ë•…'ì´ë¼ 불리며 í™”ì„±ì˜ ëŒ€ë¥™ìœ¼ë¡œ 여겨졌고, ì–´ë‘ìš´ ë¶€ë¶„ì€ ë°”ë‹¤ë¡œ 여겨졌다. 지구ì—ì„œ ë³´ì´ëŠ” 가장 ì–´ë‘ìš´ ë¶€ë¶„ì€ ì‹œë¥´í‹°ìŠ¤ ë©”ì´ì €(Syrtis Major)ì´ë‹¤. 화성ì—ì„œ 가장 í° ë¶„í™”êµ¬ëŠ” í—¬ë¼ìŠ¤ ì¶©ëŒ ë¶„ì§€(Hellas impact basin)ì¸ë°, 가벼운 ë¶‰ì€ ëª¨ëž˜ë¡œ ë®ì—¬ 있다. + +화성 표면 ì§€ì—­ì˜ ì´ë¦„ì„ ì§“ëŠ” ìž‘ì—…ì€ êµ­ì œ 천문 ì—°ë§¹ì˜ '행성계 명명법 워킹 그룹'ì´ ë‹´ë‹¹í•˜ê³  있다.. +대기[편집] +ì´ ë¶€ë¶„ì˜ ë³¸ë¬¸ì€ í™”ì„±ì˜ ëŒ€ê¸°ìž…ë‹ˆë‹¤. +í™”ì„±ì˜ ì„ì–‘. '스피릿'호 ì´¬ì˜ + +í™”ì„±ì˜ ëŒ€ê¸°ì••ì€ 0.7ì—ì„œ 0.9kPaë¡œ, ì§€êµ¬ì˜ ëŒ€ê¸° ë°€ë„와 비êµí•˜ë©´ 1/100 ì •ë„ë¡œ 매우 낮다. 대기가 ì ìœ¼ë¯€ë¡œ ê¸°ì••ì´ ë§¤ìš° 낮고 ë¬¼ì´ ìžˆë”ë¼ë„ 기압 ë•Œë¬¸ì— ë¹¨ë¦¬ ì¦ë°œí•˜ê²Œ ëœë‹¤. 과학ìžë“¤ì€ ê³¼ê±°ì˜ í™”ì„±ì€ ë¬¼ì´ í’부하고 ëŒ€ê¸°ë„ ì§€ê¸ˆë³´ë‹¤ 컸으리ë¼ê³  추측한다. ëŒ€ê¸°ì˜ ì£¼ì„±ë¶„ì¸ ì´ì‚°í™”탄소가 얼어 거대한 ê·¹ê´€ì„ í˜•ì„±í•˜ëŠ” ê³¼ì •ì´ ì–‘ê·¹ì—ì„œ êµëŒ€ë¡œ ì¼ì–´ë‚˜ê³  ì´ì‚°í™”탄소는 ëˆˆì¸µì„ í˜•ì„±í•˜ê³  ë´„ì´ ë˜ë©´ ì¦ë°œí•œë‹¤. +ìžê¸°ê¶Œ[편집] + +아주 오래전 í™”ì„±ì€ íƒœì–‘í’ì„ ë§‰ì„ ìˆ˜ ìžˆì„ ë§Œí¼ ì¶©ë¶„ížˆ ê°•í•œ ìžê¸°ê¶Œì„ 가지고 ìžˆì—ˆìœ¼ë¦¬ë¼ ì—¬ê²¨ì§„ë‹¤. 그러나 40ì–µ ë…„ ì „ í™”ì„±ì˜ ë‹¤ì´ë‚˜ëª¨ê°€ 멈추고 ë‚œ ë’¤ì—는 투ìžìœ¨ì´ ë†’ì€ ê´‘ë¬¼ì— ìž”ë¥˜ìžê¸°ê°€ 남아있는 ì •ë„ë°–ì—는 ìžê¸°ìž¥ì„ 가지고 있지 않다. ì‹œê°„ì´ ì§€ë‚¨ì— ë”°ë¼ ì´ëŸ° ê´‘ë¬¼ì€ í’í™”ë˜ì—ˆê¸° ë•Œë¬¸ì— í˜„ìž¬ëŠ” ë‚¨ë°˜êµ¬ì˜ ê³ ì§€ì˜ ì¼ë¶€ì—서만 고지ìžê¸°ë¥¼ 관측할 수 있다. 태양í’ì€ í™”ì„±ì˜ ì „ë¦¬ì¸µì— ì§ì ‘ 닿기 ë•Œë¬¸ì— í™”ì„±ì˜ ëŒ€ê¸°ëŠ” 조금씩 벗겨져 나가고 있다고 여겨지나 ê·¸ ì–‘ì€ ì•„ì§ í™•ì‹¤í•˜ì§€ 않다. 마스 글로벌 서베ì´ì–´ì™€ 마스 ìµìŠ¤í”„레스는 í™”ì„±ì´ ì§€ë‚˜ê°„ ìžë¦¬ì— 남아있는 ì´ì˜¨í™”ëœ ëŒ€ê¸°ì˜ ìž…ìžë¥¼ íƒì§€í•˜ì˜€ë‹¤. +공전과 ìžì „[편집] + +í™”ì„±ì˜ ê¶¤ë„ ì´ì‹¬ë¥ ì€ 약 9%ë¡œ ìƒëŒ€ì ìœ¼ë¡œ í° íŽ¸ì´ë‹¤. 태양계ì—ì„œ ì´ë³´ë‹¤ ë” ì´ì‹¬ë¥ ì´ í° ê¶¤ë„를 가지는 í–‰ì„±ì€ ìˆ˜ì„±ë°–ì— ì—†ë‹¤. íƒœì–‘ê¹Œì§€ì˜ í‰ê· ê±°ë¦¬ëŠ” 약 2ì–µ 2천만 km(1.5 천문단위)ì´ë©°, 공전 주기는 686.98ì¼ì´ë‹¤. í™”ì„±ì˜ íƒœì–‘ì¼(솔; sol)ì€ ì§€êµ¬ë³´ë‹¤ 약간 길어서 24시간 39분 35.244ì´ˆ ì •ë„ì´ë‹¤. + +í™”ì„±ì˜ ìžì „ì¶•ì€ 25.19ë„ë§Œí¼ ê¸°ìš¸ì–´ì ¸ 있어서 ì§€êµ¬ì˜ ê¸°ìš¸ê¸°ì™€ ê±°ì˜ ë¹„ìŠ·í•˜ë‹¤. ê·¸ ê²°ê³¼ 화성ì—서는 지구와 마찬가지로 ê³„ì ˆì´ ë‚˜íƒ€ë‚œë‹¤. 하지만 공전 ê°ì†ë„ê°€ ëŠë¦¬ê¸° ë•Œë¬¸ì— ê³„ì ˆì˜ ê¸¸ì´ëŠ” ì§€êµ¬ì— ë¹„í•´ 약 2ë°°ì •ë„ ëœë‹¤. +위성[편집] +ì´ ë¶€ë¶„ì˜ ë³¸ë¬¸ì€ í™”ì„±ì˜ ìœ„ì„±ìž…ë‹ˆë‹¤. + +í¬ë³´ìŠ¤(Phobos)와 ë°ì´ëª¨ìŠ¤(Deimos)ê°€ í™”ì„±ì˜ ìœ„ì„±ì´ë‹¤. ì´ë“¤ì€ 늘 달 쪽으로 ê°™ì€ ë©´ì„ í–¥í•˜ê³  있다. í¬ë³´ìŠ¤ì˜ 화성 주위 궤ë„ê°€ 화성 ìžì²´ê°€ ë„는 ì†ë„보다 빠르며 아주 서서히 그러나 꾸준히 í™”ì„±ì— ê°€ê¹Œì›Œì§€ê³  있다. 언젠가 미래ì—는 í¬ë³´ìŠ¤ê°€ 화성 í‘œë©´ì— ì¶©ëŒí•˜ê²Œ ë  ê²ƒì´ë¼ê³  예측한다. ë°˜ë©´ì— ë°ì´ëª¨ìŠ¤ëŠ” 충분히 멀리 떨어져 있고 서서히 멀어지고 있다. + +ë‘ ìœ„ì„±ì€ ëª¨ë‘ 1877ë…„ ë¯¸êµ­ì¸ ì²œë¬¸í•™ìž ì•„ì‚¬í”„ 홀(Asaph Hall)ì´ ë°œê²¬í–ˆê³ , 그리스 ì‹ í™”ì— ë‚˜ì˜¤ëŠ” ë§ˆë¥´ìŠ¤ì˜ ë‘ ì•„ë“¤ì˜ ì´ë¦„ì„ ë”° 명명ë˜ì—ˆë‹¤. +í™”ì„±ì˜ ìœ„ì„± ì´ë¦„ ì§ê²½ (km) 질량 (kg) í‰ê·  ê¶¤ë„ ë°˜ì§€ë¦„ (km) 공전 주기 +í¬ë³´ìŠ¤ 22.2 (27 × 21.6 × 18.8) 1.08×1016 9378 7.66 시간 +ë°ì´ëª¨ìŠ¤ 12.6 (10 × 12 × 16) 2×1015 23,400 30.35 시간 +ìƒëª…ì²´[편집] + +여러 ì¦ê±°ë¡œë¶€í„° 미루어 ë³¼ ë•Œ í™”ì„±ì´ ê³¼ê±°ì—는 지금보다 ë” ìƒëª…ì´ ì‚´ê¸°ì— ì í•©í•œ 환경ì´ì—ˆë˜ 것으로 추정ë˜ì—ˆìœ¼ë‚˜, 지금까지는, 실제 í™”ì„±ì— ìƒëª…ì´ ì¡´ìž¬í•œ ì ì´ 있는가 하는 ì§ˆë¬¸ì— ëŒ€í•´ì„œëŠ” ì•„ì§ í™•ì‹¤í•œ ë‹µì„ ì–»ì§€ 못하고 있다. ë°”ì´í‚¹ íƒì‚¬ì„ ì€ 70년대 ì¤‘ë°˜ì— í™”ì„± 표면ì—ì„œ 미ìƒë¬¼ì„ íƒì§€í•˜ê¸° 위한 ì‹¤í—˜ì„ ìˆ˜í–‰í•˜ì—¬, 과학ìžë“¤ 사ì´ì—ì„œ ë§Žì€ ë…¼ìŸì´ ë˜ê³  있다. 존슨 우주센터 연구소는 화성ì—ì„œ ë‚ ì•„ì™”ì„ ê²ƒìœ¼ë¡œ 추정ë˜ëŠ” ìš´ì„ AL[5] 빨리 분해ë˜ê¸° ë•Œë¬¸ì— ì†ŒëŸ‰ì˜ ì´ë“¤ 분ìžëŠ” í™”ì„±ì— ìƒë¬¼ì´ 사는 ì¦ê±°ë¡œ 여겨질 수 있으나, ì´ë“¤ ì›ì†ŒëŠ” 화산ì´ë‚˜ 사문함화작용 ê°™ì€ ì§€ì§ˆí•™ì  ìž‘ìš©ì— ì˜í•´ì„œë„ ê³µê¸‰ë  ìˆ˜ 있다. + + í™”ì„±ì€ ìƒë¬¼ì´ ì‚´ê¸°ì— ë¶€ì í•©í•œ 특성 ì—­ì‹œ 가지고 있다. í™”ì„±ì˜ ìœ„ì¹˜ëŠ” íƒœì–‘ì˜ ê±°ì£¼ 가능 지대보다 ë°˜ ì²œë¬¸ë‹¨ìœ„ì •ë„ ë©€ë¦¬ 떨어져 있고[6] ë¬¼ì€ ì–¼ì–´ 있다. + +물론 ê³¼ê±°ì— ë¬¼ì´ í˜ë €ë˜ ì ì´ 있기는 하다. 화성ì—는 ë˜í•œ ìžê¸°ê¶Œì´ 없으며 대기가 í¬ë°•í•˜ë©°, ì§€ê° ì—´ë¥˜ëŸ‰ì€ ë§¤ìš° ì ìœ¼ë©°, ì™¸ë¶€ì˜ ìš´ì„ ë˜ëŠ” ì†Œí–‰ì„±ë“¤ê³¼ì˜ ì¶©ëŒ~ ë˜ëŠ” 태양í’으로부터 보호받지 못한다. ë‚®ì€ ëŒ€ê¸°ì•• ë•Œë¬¸ì— ì–¼ìŒì€ ì•¡ì²´ìƒíƒœë¥¼ 거치지 ì•Šê³  곧바로 기화해버리며, 지질학ì ìœ¼ë¡œ ì‚¬ì‹¤ìƒ ì™„ì „ížˆ ì£½ì€ í–‰ì„±ìœ¼ë¡œ 본다. {화산 활ë™ì´ 없기 ë•Œë¬¸ì— í‘œë©´ê³¼ 행성 내부 사ì´ì˜ 화학 물질과 ê´‘ë¬¼ì˜ ìˆœí™˜ì´ ì¼ì–´ë‚˜ì§€ 않는다.} + + 다른한편으론, ì•„ì§ ìƒëª…ì²´ê°€ 존재하고 있다는 ì£¼ìž¥ì˜ ê·¼ê±°ë¡œ, 대기ì—ì„œ ë©”íƒ„ì´ ê²€ì¶œì„ ë“ ë‹¤. + +그러나, ì´ëŠ” 지질활ë™ì´ 멈춘 í™”ì„±ì˜ í™˜ê²½ì—ì„œ ìžì—°ì ìœ¼ë¡œ ë°œìƒí•  수 없으며, ìƒëª…활ë™ì— ì˜í•´ì„œë§Œ 공급ë˜ë¯€ë¡œ, 안면ì„ì´ë‚˜ 화성 피ë¼ë¯¸ë“œì™€ ê°™ì€ ìŒëª¨ë¡ ì ì¸ ê°€ì„¤ë„ ìžˆìœ¼ë‚˜ 과학ì ì¸ ì˜ë¯¸ë¡œ 주목받지는 못하다. +화성 íƒì‚¬[편집] +ì´ ë¶€ë¶„ì˜ ë³¸ë¬¸ì€ í™”ì„± íƒì‚¬ìž…니다. +ë¬´ì¸ íƒì‚¬ì„ [편집] +ë°”ì´í‚¹ 1호 ì°©ë¥™ì„ ì´ ì „ì†¡í•œ 사진 +1978ë…„ 2ì›” 11ì¼ Sol 556ì—ì„œ ì´¬ì˜ + +지금까지 ì¸ë¥˜ëŠ” ë‹¤ìˆ˜ì˜ ë¡œë´‡ íƒì‚¬ì„ ì„ í™”ì„±ì— ë³´ëƒˆê³ , 그중 ëª‡ëª‡ì€ ëŒ€ë‹¨í•œ 성과를 ê±°ë‘었지만, íƒì‚¬ì˜ ì‹¤íŒ¨ìœ¨ì€ ë§¤ìš° 높았다. 실패 사례 중 ëª‡ì€ ëª…ë°±í•œ ê¸°ìˆ ì  ê²°í•¨ì— ë”°ë¥¸ 것ì´ì—ˆì§€ë§Œ, ë§Žì€ ê²½ìš° 연구ìžë“¤ì€ 확실한 실패 ì´ìœ ë¥¼ ì°¾ì„ ìˆ˜ 없었다. 그래서 ì´ëŸ° 사례는 지구-화성 "버뮤다 삼ê°ì§€ëŒ€" í˜¹ì€ í™”ì„±íƒì‚¬ì„ ì„ 먹고 사는 ì€í•˜ê·€ì‹ (Ghoul)ë¼ëŠ” ë†ë‹´ì„ 낳았다. 화성 로봇 íƒì‚¬ì˜ 역사를 ì´í•´í•˜ê¸° 위해서는, 발사 시간대가 약 2ë…„ 남짓(í™”ì„±ì˜ ê³µì „ 주기)ì˜ ê¸°ê°„ì„ ì£¼ê¸°ë¡œ ë°œìƒí•œë‹¤ëŠ” ì‚¬ì‹¤ì„ ì•Œì•„ë‘어야 한다. + +1960ë…„ ì†Œë ¨ì€ ë‘ ê¸°ì˜ íƒì‚¬ì„ ì„ 화성궤ë„를 ì§€ë‚˜ì³ ëŒì•„오는 계íšìœ¼ë¡œ 발사하였으나, 지구궤ë„ì— ë„달하는 ë°ì— 실패한다. 1962ë…„ ì†Œë ¨ì€ ì„¸ 기를 ë” ì‹œë„하지만, 실패했다. ë‘ ê¸°ëŠ” 지구 궤ë„ì— ë¨¸ë¬¼ë €ê³ , 나머지 하나는 í™”ì„±ì„ ëŒì•„오는 ë™ì•ˆ ì§€êµ¬ì™€ì˜ êµì‹ ì´ ëŠì–´ì¡Œë‹¤. 1964ë…„ì— ë˜ í•œë²ˆì˜ ì‹œë„ê°€ 실패한다. + +1962ë…„ì—ì„œ 1973ë…„ 사ì´ì—, NASA(나사)ì˜ ì œíŠ¸ 추진 연구소(Jet Propulsion Laboratory)는 내태양계(inner solar system)를 íƒí—˜í•  10ê°œì˜ ë§¤ë¦¬ë„ˆ ìš°ì£¼ì„ ì„ ì„¤ê³„Â·ì œìž‘í•˜ì˜€ë‹¤. ì´ ìš°ì£¼ì„ ì€ ê¸ˆì„±, 화성, ìˆ˜ì„±ì„ ìµœì´ˆë¡œ íƒì‚¬í•˜ê¸° 위해서 만들어졌다. 매리너 ìš°ì£¼ì„ ì€ ë¹„êµì  ìž‘ì€ ë¡œë´‡ íƒì‚¬ì„ ìœ¼ë¡œ ì•„í‹€ë¼ìŠ¤ ë¡œì¼“ì— ì‹¤ë ¤ 발사ë˜ì—ˆë‹¤. ê° ìš°ì£¼ì„ ì˜ ë¬´ê²ŒëŠ” 0.5í†¤ì„ ë„˜ì§€ 않았다. + +매리너 3호와 4호는 ë™ì¼í•œ 기체로, 최초로 í™”ì„±ì„ ì§€ë‚˜ì¹˜ë©° 관찰하ë„ë¡ ì„¤ê³„ë˜ì—ˆë‹¤. 매리너 3호는 1964ë…„ 11ì›” 5ì¼ ë°œì‚¬ë˜ì—ˆìœ¼ë‚˜, ìš°ì£¼ì„ ì˜ ìœ—ë¶€ë¶„ì„ ë®ì€ ëšœê»‘ì´ ì ë‹¹ížˆ 열리지 않았고, í™”ì„±ì— ë„달하지 못했다. 3주 후 1964ë…„ 11ì›” 28ì¼ ë§¤ë¦¬ë„ˆ 4호는 성공ì ìœ¼ë¡œ 발사ë˜ì–´ 8ê°œì›”ì˜ í•­í•´ë¥¼ 시작한다. + +매리너 4호는 1965ë…„ 6ì›” 14ì¼ í™”ì„±ì„ ì§€ë‚˜ë©°, 다른 í–‰ì„±ì˜ ê·¼ì ‘ ì‚¬ì§„ì„ ìµœì´ˆë¡œ ì°ì–´ëƒˆë‹¤. 오랜 기간 ë™ì•ˆ ìž‘ì€ í…Œì´í”„ 레코ë”ì— ê¸°ë¡ëœ ê·¸ ì‚¬ì§„ë“¤ì€ ë‹¬ ëª¨ì–‘ì˜ ë¶„í™”êµ¬ë“¤ì„ ë³´ì—¬ 주었다. ê·¸ 분화구 들 중 ëª‡ëª‡ì€ ì„œë¦¬ê°€ ë®ì—¬ 추운 í™”ì„±ì˜ ë°¤ì„ ë³´ì—¬ì£¼ì—ˆë‹¤. + +NASA는 계ì†í•´ì„œ 매리너 계íšì„ 수행했다. ê·¸ë“¤ì€ ë‹¤ìŒ ë°œì‚¬ ì‹œê°„ëŒ€ì— ê·¼ì ‘ 비행 ì‹œí—˜ì„ ë˜ë‹¤ì‹œ 수행하였다. ì´ ë¹„í–‰ì„ ë“¤ì€ 1969ë…„ì— í™”ì„±ì— ë„달하였다. ì´ì— 관해서는 매리너 6호 와 7호를 참조하ë¼. ë‹¤ìŒ ë°œì‚¬ ë•Œ 매리너 계íšì€ ë‘ ëŒ€ì˜ ë¹„í–‰ì„  중 í•œ 대를 잃는 사고를 겪었다. ì‚´ì•„ë‚¨ì€ ë§¤ë¦¬ë„ˆ 9호는 성공ì ìœ¼ë¡œ 화성 궤ë„ì— ì§„ìž…í•˜ì˜€ë‹¤. 매리너 9호가 í™”ì„±ì— ë„ë‹¬í–ˆì„ ë•Œ, 그것과 ë‘ ëŒ€ì˜ ì†Œë ¨ ì¸ê³µìœ„ì„±ì€ í–‰ì„± ì „ì˜ì—­ì— ê±¸ì³ ë¨¼ì§€ í­í’ì´ ì¼ì–´ë‚˜ê³  있는 ê²ƒì„ ë°œê²¬í•˜ì˜€ë‹¤. ê·¸ í­í’ì´ ê°€ë¼ì•‰ëŠ” ê²ƒì„ ê¸°ë‹¤ë¦¬ëŠ” ë™ì•ˆ 화성 í‘œë©´ì˜ ì‚¬ì§„ì„ ì°ëŠ” ê²ƒì€ ë¶ˆê°€ëŠ¥í•˜ì˜€ìœ¼ë¯€ë¡œ, 매리너 9호는 í¬ë³´ìŠ¤ì˜ ì‚¬ì§„ì„ ì°ì—ˆë‹¤. í­í’ì´ í™”ì„±ì˜ í‘œë©´ ì‚¬ì§„ì„ ì°ê¸°ì— 충분할 ë§Œí¼ ê°€ë¼ì•‰ì•˜ì„ ë•Œ, ì „ì†¡ëœ ì‚¬ì§„ì€ ì´ì „ ìž„ë¬´ì˜ ê²°ê³¼ë¡œ 온 사진보다 ë” ë†’ì€ í’ˆì§ˆì„ ê°€ì§€ê³  있었다. ì´ ì‚¬ì§„ë“¤ì´ í™”ì„±ì— í•œë•Œ ì•¡ì²´ í˜•íƒœì˜ ë¬¼ì´ ìžˆì—ˆì„ëŠ”ì§€ë„ ëª¨ë¥¸ë‹¤ëŠ” ê²ƒì„ ì¦ê±°í•˜ëŠ” 첫 번째 사진ì´ì—ˆë‹¤. + +1976ë…„ì— ë‘ ëŒ€ì˜ ë°”ì´í‚¹ 호가 화성 궤ë„ì— ë“¤ì–´ê°€ ê°ê° 착륙 ëª¨ë“ˆì„ ë‚´ë ¤ 화성 í‘œë©´ì— ë‚´ë ¤ 앉았다. ì´ ìž„ë¬´ë¥¼ 통해 ì¸ë¥˜ëŠ” 첫 번째 컬러 사진과 ë”ìš± í™•ìž¥ëœ ê³¼í•™ì  ì •ë³´ë¥¼ ì–»ì„ ìˆ˜ 있었다. + +소비ì—트 ì—°ë°©ì˜ í™”ì„± íƒì‚¬ 계íšì—ì„œ 발사한 ìš°ì£¼ì„ ë“¤ì€ ë°”ì´í‚¹ë³´ë‹¤ 몇 ë…„ ì¼ì° ìˆ˜ë§Žì€ ì°©ë¥™ì„ ì‹œë„했다. 그러나 매리너 계íšì´ ìˆ˜í–‰í–ˆë˜ ê²ƒë³´ë‹¤ 성공ì ì¸ 결과를 얻지는 못했다. + +마스 패스파ì¸ë”는 1997ë…„ 7ì›” 4ì¼ì— í™”ì„±ì— ì°©ë¥™í•˜ì—¬, 소저너ë¼ëŠ” 매우 ìž‘ì€ ì›ê²© 조정체를 움ì§ì—¬ 착륙 ì§€ì  ì£¼ìœ„ì˜ ëª‡ 미터를 여행하고, í™”ì„±ì˜ í™˜ê²½ ì¡°ê±´ì„ íƒìƒ‰í•˜ê³  í‘œë©´ì˜ ëŒë“¤ì„ 수집해왔다. + +ë‹¤ìŒ íƒì‚¬ëŠ” 마스 글로벌 서베ì´ì–´(Mars Global Surveyor)ì— ì˜í•´ ì´ë£¨ì–´ì¡Œë‹¤. ì´ ìž„ë¬´ëŠ” 20ì—¬ ë…„ê°„ì˜ í™”ì„± íƒì‚¬ì—­ì‚¬ì—ì„œ 첫 번째로 성공ì ì¸ 것ì´ì—ˆê³ , 1996ë…„ 11ì›” 7ì¼ì— 발사ë˜ì–´ 1997ë…„ 9ì›” 12ì¼ì— 화성 궤ë„ì— ë„달하였다. 1ë…„ ë°˜ ì •ë„ê°€ í른 후, 회전 궤ë„ê°€ 타ì›í˜•ì—ì„œ ì›í˜•ìœ¼ë¡œ ìžë¦¬ë¥¼ 잡았고, ìš°ì£¼ì„ ì€ 1999ë…„ 3월부터 기초ì ì¸ 매핑 ìž„ë¬´ì— ëŒìž…했다. ìš°ì£¼ì„ ì€ í™”ì„±ì„ í™”ì„±ë ¥ìœ¼ë¡œ 1ë…„, 지구력으로는 ê±°ì˜ 2ë…„ê°„ 저고ë„ì—ì„œ 관찰했다. 마스 글로벌 서베ì´ì–´í˜¸ëŠ” ìµœê·¼ì¸ 2001ë…„ 1ì›” 31ì¼ ê·¸ 기초ì ì¸ 임무를 완료하고 현재는 2단계 임무를 수행하고 있다. + +ì´ íƒì‚¬ëŠ” 화성 표면, 대기권, 그리고 ë‚´ë¶€ì— ëŒ€í•œ ì „ì²´ì ì¸ 연구를 수행하고, 지난 íƒì‚¬ 계íšì—ì„œ ê±°ë‘¬ë“¤ì¸ ëª¨ë“  결과물보다 ë” ë§Žì€ ë°ì´í„°ë¥¼ 가져왔다. ì´ ê°€ì¹˜ìžˆëŠ” ë°ì´í„°ë“¤ì€ 마스 글로벌 서베ì´ì–´: MOLA ì—ì„œ 찾아볼 수 있다. + +2008ë…„ 7ì›” 31ì¼ ë¯¸êµ­ êµ­ë¦½í•­ê³µìš°ì£¼êµ­ì€ í™”ì„±íƒì‚¬ì„  피닉스가 í™”ì„±ì— ë¬¼ì´ ì¡´ìž¬í•¨ì„ í™•ì¸í•˜ì˜€ë‹¤ê³  발표했다. 피닉스는 2008ë…„ 11ì›” 10ì¼ ìž„ë¬´ê°€ 종료ë˜ì—ˆë‹¤. +ê´€ì¸¡ì˜ ì—­ì‚¬[편집] + +기ì›ì „ 1600ë…„ê²½ì— í™”ì„±ì— ëŒ€í•œ ê´€ì¸¡ì´ ì‹œìž‘ë˜ì—ˆë‹¤ê³  여겨지며, í™”ì„±ì€ ë¶ˆê³¼ ê°™ì´ ë¶‰ê²Œ 빛나고 다른 천체와 달리 하늘ì—ì„œ ì´ìƒí•˜ê²Œ 움ì§ì¸ë‹¤ê³  알려졌다. + + 바빌로니아ì¸ì€ ì´ë¯¸ 기ì›ì „ 400ë…„ê²½ì— ì²œë¬¸í˜„ìƒì„ 연구했었으며 ì¼ì‹, ì›”ì‹ê³¼ ê°™ì€ ì²œë¬¸í˜„ìƒì„ 예측하기 위해 ê³ ë„ë¡œ ë°œë‹¬ëœ ë°©ë²•ì„ ì‚¬ìš©í•˜ì˜€ë‹¤. ê·¸ë“¤ì€ ê·¸ë“¤ì˜ ë‹¬ë ¥ê³¼ 종êµì ì¸ ì´ìœ ì—ì„œ ê·¸ë“¤ì„ ì£¼ì˜ê¹Šê²Œ 연구하였다. 그러나 ê·¸ë“¤ì´ ëª©ê²©í•œ 현ìƒì— 대해서 깊게 분ì„한다거나 설명하려고 하지는 않았다. 바빌로니아ì¸ë“¤ì€ í™”ì„±ì„ ë„¤ë¥´ê°ˆ(Nergal, ‘위대한 ì˜ì›…’ ë˜ëŠ” ‘전ìŸì˜ 왕.’ ì›ëœ»ì€ ‘커다란 ì§‘ì˜ ì£¼ì¸â€™)ì´ë¼ 불렀다. + ì´ì§‘트ì¸ì€ ë³„ì´ â€œê³ ì •ëœâ€ ë“¯ì´ ë³´ì´ë©°, íƒœì–‘ì´ ê³ ì •ëœ ë³„ì— ëŒ€í•˜ì—¬ ìƒëŒ€ì ìœ¼ë¡œ ì´ë™í•œë‹¤ê³  ìƒê°í–ˆë‹¤. ë˜í•œ ê·¸ë“¤ì€ í•˜ëŠ˜ì˜ 5ê°œì˜ ë¹›ë‚˜ëŠ” 천체가 ê³ ì •ëœ ë³„ 사ì´ë¥¼ 움ì§ì¸ë‹¤ëŠ” ê²ƒì„ ì•Œì•˜ë‹¤. ì´ì§‘트ì¸ì€ í™”ì„±ì„ Har Decher(ë¶‰ì€ ê²ƒ) í˜¹ì€ '죽ìŒì˜ 별'ì´ë¼ê³  불렀다. + 그리스ì¸ì€ í™”ì„±ì„ ì „ìŸì˜ ì‹ ì˜ ì´ë¦„ì„ ë”°ì„œ 아레스(Ares)ë¼ê³  불렀다. 로마ì—ì„œë„ ì´ ì´ë¦„ì„ ê·¸ëŒ€ë¡œ 번역하여 í™”ì„±ì„ ë§ˆë¥´ìŠ¤(Mars)ë¼ê³  불렀다. í™”ì„±ì˜ ê¸°í˜¸ëŠ” ë§ˆë¥´ìŠ¤ì˜ ë°©íŒ¨ì™€ 칼로 여겨진다. + 조반니 스키아파ë ë¦¬(Giovanni Virginio Schiaparelli, 1835ë…„~1910ë…„)는 1877ë…„, 화성ì—ì„œ "cannali"ë¡œ ë³´ì´ëŠ” ê²ƒì´ ë°œê²¬ë˜ì—ˆë‹¤ê³  발표했다. ì´ ë‹¨ì–´ëŠ” ì´íƒˆë¦¬ì•„ì–´ë¡œ "거대한 홈"ì„ ëœ»í•œë‹¤. ì´ê²ƒì´ 제대로 번역ë˜ì—ˆë‹¤ë©´ "channels"ê°€ ë˜ì–´ì•¼ 했다. 하지만 당시 수ì—즈 ìš´í•˜ë„ ê±´ì„¤ë˜ê³  ê´€ì‹¬ì´ ê°€ë˜ ì°¨ì— "운하(canals)"ë¡œ 번역ë˜ì—ˆë‹¤. ì´ê²ƒìœ¼ë¡œ 화성 íƒì‚¬ ì—´í’ì˜ ì—­ì‚¬ê°€ ì‹œìž‘ëœ ê²ƒì´ë‹¤. + +※ ë™ì–‘ì˜ ê³ ëŒ€ê¸°ë¡ì—는 ë‚®ì— í™”ì„±ì„ ë³¸ ê²ƒì´ ìžˆìœ¼ë‚˜, ê²€ì¦ê²°ê³¼ ê¸ˆì„±ì˜ ì°©ì˜¤ì˜€ìœ¼ë©°, í™”ì„±ì„ ë‚®ì— ë§¨ 눈으로 본다는 ê²ƒì€ ì‚¬ì‹¤ìƒ ë¶ˆê°€ëŠ¥í•˜ë‹¤ diff --git a/xpcom/tests/gtest/wikipedia/ru.txt b/xpcom/tests/gtest/wikipedia/ru.txt new file mode 100644 index 0000000000..9467849e6f --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/ru.txt @@ -0,0 +1,410 @@ +ÐœÐ°Ñ€Ñ â€” Ñ‡ÐµÑ‚Ð²Ñ‘Ñ€Ñ‚Ð°Ñ Ð¿Ð¾ удалённоÑти от Солнца и ÑÐµÐ´ÑŒÐ¼Ð°Ñ Ð¿Ð¾ размерам планета Солнечной ÑиÑтемы; маÑÑа планеты ÑоÑтавлÑет 10,7 % маÑÑÑ‹ Земли. Ðазвана в чеÑÑ‚ÑŒ МарÑа — древнеримÑкого бога войны, ÑоответÑтвующего древнегречеÑкому ÐреÑу. Иногда ÐœÐ°Ñ€Ñ Ð½Ð°Ð·Ñ‹Ð²Ð°ÑŽÑ‚ «краÑной планетой» из-за краÑноватого оттенка поверхноÑти, придаваемого ей окÑидом железа. + +ÐœÐ°Ñ€Ñ â€” планета земной группы Ñ Ñ€Ð°Ð·Ñ€ÐµÐ¶ÐµÐ½Ð½Ð¾Ð¹ атмоÑферой (давление у поверхноÑти в 160 раз меньше земного). ОÑобенноÑÑ‚Ñми поверхноÑтного рельефа МарÑа можно Ñчитать ударные кратеры наподобие лунных, а также вулканы, долины, пуÑтыни и полÑрные ледниковые шапки наподобие земных. + +У МарÑа еÑÑ‚ÑŒ два еÑтеÑтвенных Ñпутника — Ð¤Ð¾Ð±Ð¾Ñ Ð¸ Ð”ÐµÐ¹Ð¼Ð¾Ñ (в переводе Ñ Ð´Ñ€ÐµÐ²Ð½ÐµÐ³Ñ€ÐµÑ‡ÐµÑкого — «Ñтрах» и «ужаÑ», имена двух Ñыновей ÐреÑа, Ñопровождавших его в бою), которые отноÑительно малы (Ð¤Ð¾Ð±Ð¾Ñ â€” 26,8×22,4×18,4 км, Ð”ÐµÐ¹Ð¼Ð¾Ñ â€” 15×12,2×10,4 км)[6][7] и имеют неправильную форму. + +ÐÐ°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1960-Ñ… годов непоÑредÑтвенным иÑÑледованием МарÑа Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ ÐМС занималиÑÑŒ СССР (программы «МарÑ» и «ФобоÑ»), СШР(программы «Маринер», «Викинг», «Mars Global Surveyor» и другие), ЕвропейÑкое коÑмичеÑкое агентÑтво (программа «МарÑ-ÑкÑпреÑÑ») и Ð˜Ð½Ð´Ð¸Ñ (программа «МангальÑн»). Ðа ÑегоднÑшний день, поÑле Земли, ÐœÐ°Ñ€Ñ â€” ÑÐ°Ð¼Ð°Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð¾ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð½Ð°Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ð° Солнечной ÑиÑтемы. + +ОÑновные ÑведениÑ[править | править вики-текÑÑ‚] + +ÐœÐ°Ñ€Ñ â€” Ñ‡ÐµÑ‚Ð²Ñ‘Ñ€Ñ‚Ð°Ñ Ð¿Ð¾ удалённоÑти от Солнца (поÑле МеркуриÑ, Венеры и Земли) и ÑÐµÐ´ÑŒÐ¼Ð°Ñ Ð¿Ð¾ размерам (превоÑходит по маÑÑе и диаметру только Меркурий) планета Солнечной ÑиÑтемы[8]. МаÑÑа МарÑа ÑоÑтавлÑет 10,7 % маÑÑÑ‹ Земли (6,423·1023 кг против 5,9736·1024 кг Ð´Ð»Ñ Ð—ÐµÐ¼Ð»Ð¸), объём — 0,15 объёма Земли, а Ñредний линейный диаметр — 0,53 диаметра Земли (6800 км)[7]. + +Рельеф МарÑа обладает многими уникальными чертами. МарÑианÑкий потухший вулкан гора Олимп — ÑÐ°Ð¼Ð°Ñ Ð²Ñ‹ÑÐ¾ÐºÐ°Ñ Ð¸Ð·Ð²ÐµÑÑ‚Ð½Ð°Ñ Ð³Ð¾Ñ€Ð° на планетах Солнечной ÑиÑтемы[9] (ÑÐ°Ð¼Ð°Ñ Ð²Ñ‹ÑÐ¾ÐºÐ°Ñ Ð¸Ð·Ð²ÐµÑÑ‚Ð½Ð°Ñ Ð³Ð¾Ñ€Ð° в Солнечной ÑиÑтеме — на аÑтероиде ВеÑта[10]), а долины Маринер — Ñамый крупный извеÑтный каньон на планетах (Ñамый большой каньон в Ñолнечной ÑиÑтеме обнаружен на Ñпутнике Плутона — Хароне[11]). Помимо Ñтого, в июне 2008 года три Ñтатьи, опубликованные в журнале «Nature», предÑтавили доказательÑтва ÑущеÑÑ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² Ñеверном полушарии МарÑа Ñамого крупного извеÑтного ударного кратера в Солнечной ÑиÑтеме. Его длина — 10,6 Ñ‚Ñ‹Ñ. км, а ширина — 8,5 Ñ‚Ñ‹Ñ. км, что примерно в четыре раза больше, чем крупнейший ударный кратер, до того также обнаруженный на МарÑе, вблизи его южного полюÑа[12]. + +ÐœÐ°Ñ€Ñ Ð¸Ð¼ÐµÐµÑ‚ период Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¸ Ñмену времён года, аналогичные земным, но его климат значительно холоднее и Ñуше земного. + +Вплоть до полёта к МарÑу автоматичеÑкой межпланетной Ñтанции «Маринер-4» в 1965 году многие иÑÑледователи полагали, что на его поверхноÑти еÑÑ‚ÑŒ вода в жидком ÑоÑтоÑнии. Это мнение было оÑновано на наблюдениÑÑ… за периодичеÑкими изменениÑми в Ñветлых и тёмных учаÑтках, оÑобенно в полÑрных широтах, которые были похожи на континенты и морÑ. Тёмные длинные линии на поверхноÑти МарÑа интерпретировалиÑÑŒ некоторыми наблюдателÑми как ирригационные каналы Ð´Ð»Ñ Ð¶Ð¸Ð´ÐºÐ¾Ð¹ воды. Позднее было доказано, что большинÑтво Ñтих тёмных линий ÑвлÑÑŽÑ‚ÑÑ Ð¾Ð¿Ñ‚Ð¸Ñ‡ÐµÑкой иллюзией[13]. +Великие противоÑтоÑÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа (раÑÑтоÑние до Земли менее 60 млн. км), 1830—2050 годы Дата РаÑÑÑ‚., +а. e. +19 ÑентÑÐ±Ñ€Ñ 1830 0,388 +18 авгуÑта 1845 0,373 +17 Ð¸ÑŽÐ»Ñ 1860 0,393 +5 ÑентÑÐ±Ñ€Ñ 1877 0,377 +4 авгуÑта 1892 0,378 +24 ÑентÑÐ±Ñ€Ñ 1909 0,392 +23 авгуÑта 1924 0,373 +23 Ð¸ÑŽÐ»Ñ 1939 0,390 +10 ÑентÑÐ±Ñ€Ñ 1956 0,379 +10 авгуÑта 1971 0,378 +22 ÑентÑÐ±Ñ€Ñ 1988 0,394 +28 авгуÑта 2003 0,373 +27 Ð¸ÑŽÐ»Ñ 2018 0,386 +15 ÑентÑÐ±Ñ€Ñ 2035 0,382 +14 авгуÑта 2050 0,374 + +Ðа Ñамом деле из-за низкого Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð²Ð¾Ð´Ð° не может ÑущеÑтвовать в жидком ÑоÑтоÑнии на большей чаÑти (около 70 %) поверхноÑти МарÑа[14]. Вода в ÑоÑтоÑнии льда была обнаружена в марÑианÑком грунте коÑмичеÑким аппаратом ÐÐСР«ФеникÑ»[15][16]. Ð’ то же Ð²Ñ€ÐµÐ¼Ñ Ñобранные марÑоходами «Спирит» и «Opportunity» геологичеÑкие данные позволÑÑŽÑ‚ предположить, что в далёком прошлом вода покрывала значительную чаÑÑ‚ÑŒ поверхноÑти МарÑа. ÐÐ°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð² течение поÑледнего деÑÑÑ‚Ð¸Ð»ÐµÑ‚Ð¸Ñ Ð¿Ð¾Ð·Ð²Ð¾Ð»Ð¸Ð»Ð¸ обнаружить в некоторых меÑтах на поверхноÑти МарÑа Ñлабую гейзерную активноÑÑ‚ÑŒ[17]. По наблюдениÑм Ñ ÐºÐ¾ÑмичеÑкого аппарата «Mars Global Surveyor», некоторые чаÑти южной полÑрной шапки МарÑа поÑтепенно отÑтупают[18]. + +С Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 2009 по наÑтоÑщее Ð²Ñ€ÐµÐ¼Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð¸ÑÑледовательÑÐºÐ°Ñ Ð³Ñ€ÑƒÐ¿Ð¿Ð¸Ñ€Ð¾Ð²ÐºÐ° на орбите МарÑа наÑчитывает три функционирующих коÑмичеÑких аппарата: Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей», «МарÑ-ÑкÑпреÑÑ» и «Mars Reconnaissance Orbiter». Это больше, чем около любой другой планеты, помимо Земли. + +ПоверхноÑÑ‚ÑŒ МарÑа в наÑтоÑщий момент иÑÑледуют два марÑохода: «Opportunity» и «Curiosity». Ðа поверхноÑти МарÑа также находÑÑ‚ÑÑ Ð½ÐµÑколько неактивных поÑадочных модулей и марÑоходов, завершивших иÑÑледованиÑ. + +ÐœÐ°Ñ€Ñ Ñ…Ð¾Ñ€Ð¾ÑˆÐ¾ виден Ñ Ð—ÐµÐ¼Ð»Ð¸ невооружённым глазом. Его Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð·Ð²Ñ‘Ð·Ð´Ð½Ð°Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð° доÑтигает −2,91m (при макÑимальном Ñближении Ñ Ð—ÐµÐ¼Ð»Ñ‘Ð¹), уÑÑ‚ÑƒÐ¿Ð°Ñ Ð¿Ð¾ ÑркоÑти лишь Юпитеру (и то далеко не вÑегда во Ð²Ñ€ÐµÐ¼Ñ Ð²ÐµÐ»Ð¸ÐºÐ¾Ð³Ð¾ противоÑтоÑниÑ) и Венере (но лишь утром или вечером). ПротивоÑтоÑние МарÑа можно наблюдать каждые два года. ПоÑледний раз такое Ñвление на Земле наблюдалоÑÑŒ Ñ 9 по 14 Ð°Ð¿Ñ€ÐµÐ»Ñ 2014 года[129 1]. Как правило, во Ð²Ñ€ÐµÐ¼Ñ Ð²ÐµÐ»Ð¸ÐºÐ¾Ð³Ð¾ противоÑтоÑÐ½Ð¸Ñ (то еÑÑ‚ÑŒ при Ñовпадении противоÑтоÑÐ½Ð¸Ñ Ñ Ð—ÐµÐ¼Ð»Ñ‘Ð¹ и Ð¿Ñ€Ð¾Ñ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñом Ð¿ÐµÑ€Ð¸Ð³ÐµÐ»Ð¸Ñ Ñвоей орбиты) оранжевый ÐœÐ°Ñ€Ñ ÑвлÑетÑÑ Ñрчайшим объектом земного ночного неба (не ÑÑ‡Ð¸Ñ‚Ð°Ñ Ð›ÑƒÐ½Ñ‹), но Ñто проиÑходит лишь один раз в 15—17 лет в течение одной-двух недель. +Орбитальные характериÑтики[править | править вики-текÑÑ‚] + +Минимальное раÑÑтоÑние от МарÑа до Земли ÑоÑтавлÑет 55,76 млн. км[19] (когда Ð—ÐµÐ¼Ð»Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ñ‚Ð¾Ñ‡Ð½Ð¾ между Солнцем и МарÑом), макÑимальное — около 401 млн. км (когда Солнце находитÑÑ Ñ‚Ð¾Ñ‡Ð½Ð¾ между Землёй и МарÑом). +РаÑÑтоÑние между Землёй и МарÑом (в а. е.) во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð¾ÑтоÑний 2014—2061 гг. + +Среднее раÑÑтоÑние от МарÑа до Солнца ÑоÑтавлÑет 228 млн. км (1,52 а. e.), период Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð²Ð¾ÐºÑ€ÑƒÐ³ Солнца равен 687 земным Ñуткам[2]. Орбита МарÑа имеет довольно заметный ÑкÑцентриÑитет (0,0934), поÑтому раÑÑтоÑние до Солнца менÑетÑÑ Ð¾Ñ‚ 206,6 до 249,2 млн. км. Ðаклонение орбиты МарÑа к плоÑкоÑти Ñклиптики равно 1,85°[2]. + +ÐœÐ°Ñ€Ñ Ð±Ð»Ð¸Ð¶Ðµ вÑего к Земле во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ñ€Ð¾Ñ‚Ð¸Ð²Ð¾ÑтоÑниÑ, когда планета находитÑÑ Ð½Ð° небе в направлении, противоположном Солнцу. ПротивоÑтоÑÐ½Ð¸Ñ Ð¿Ð¾Ð²Ñ‚Ð¾Ñ€ÑÑŽÑ‚ÑÑ ÐºÐ°Ð¶Ð´Ñ‹Ðµ 26 меÑÑцев в разных точках орбиты МарÑа и Земли. Раз в 15—17 лет противоÑтоÑÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ñ…Ð¾Ð´ÑÑ‚ÑÑ Ð½Ð° то времÑ, когда ÐœÐ°Ñ€Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð²Ð±Ð»Ð¸Ð·Ð¸ Ñвоего перигелиÑ; в Ñтих традиционно называемых великими противоÑтоÑниÑÑ… раÑÑтоÑние до планеты минимально (менее 60 млн км), и ÐœÐ°Ñ€Ñ Ð´Ð¾Ñтигает наибольшего углового размера 25,1″ и ÑркоÑти −2,88m[20]. +ФизичеÑкие характериÑтики[править | править вики-текÑÑ‚] + +По линейному размеру ÐœÐ°Ñ€Ñ Ð¿Ð¾Ñ‡Ñ‚Ð¸ вдвое меньше Земли — его Ñкваториальный Ñ€Ð°Ð´Ð¸ÑƒÑ Ñ€Ð°Ð²ÐµÐ½ 3396,9 км (53,2 % земного). Площадь поверхноÑти МарÑа примерно равна площади Ñуши на Земле[21]. + +ПолÑрный Ñ€Ð°Ð´Ð¸ÑƒÑ ÐœÐ°Ñ€Ñа примерно на 20 км меньше Ñкваториального, Ñ…Ð¾Ñ‚Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´ Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñƒ планеты больший, чем у Земли, что даёт повод предположить изменение ÑкороÑти Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа Ñо временем[22]. +Сравнение размеров Земли (Ñредний Ñ€Ð°Ð´Ð¸ÑƒÑ 6371 км) и МарÑа (Ñредний Ñ€Ð°Ð´Ð¸ÑƒÑ 3386,2 км) + +МаÑÑа планеты — 6,418·1023 кг (11 % маÑÑÑ‹ Земли). УÑкорение Ñвободного Ð¿Ð°Ð´ÐµÐ½Ð¸Ñ Ð½Ð° Ñкваторе равно 3,711 м/Ѳ (0,378 земного); Ð¿ÐµÑ€Ð²Ð°Ñ ÐºÐ¾ÑмичеÑÐºÐ°Ñ ÑкороÑÑ‚ÑŒ ÑоÑтавлÑет 3,6 км/Ñ, Ð²Ñ‚Ð¾Ñ€Ð°Ñ â€” 5,027 км/Ñ. + +Период Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹ — 24 чаÑа 37 минут 22,7 Ñекунд (отноÑительно звёзд), длина Ñредних Ñолнечных Ñуток (называемых Ñолами) ÑоÑтавлÑет 24 чаÑа 39 минут 35,24409 Ñекунды, вÑего на 2,7 % длиннее земных Ñуток. МарÑианÑкий год ÑоÑтоит из 668,6 марÑианÑких Ñолнечных Ñуток. + +ÐœÐ°Ñ€Ñ Ð²Ñ€Ð°Ñ‰Ð°ÐµÑ‚ÑÑ Ð²Ð¾ÐºÑ€ÑƒÐ³ Ñвоей оÑи, наклонённой к перпендикулÑру плоÑкоÑти орбиты под углом 25,19°[2]. Ðаклон оÑи Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа обеÑпечивает Ñмену времён года. При Ñтом вытÑнутоÑÑ‚ÑŒ орбиты приводит к большим различиÑм в их продолжительноÑти — так, ÑÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð²ÐµÑна и лето, вмеÑте взÑтые, длÑÑ‚ÑÑ 371 Ñол, то еÑÑ‚ÑŒ заметно больше половины марÑианÑкого года. Ð’ то же Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð½Ð¸ приходÑÑ‚ÑÑ Ð½Ð° учаÑток орбиты МарÑа, удалённый от Солнца. ПоÑтому на МарÑе Ñеверное лето долгое и прохладное, а южное — короткое и отноÑительно тёплое. +ÐтмоÑфера и климат[править | править вики-текÑÑ‚] +ОÑновные Ñтатьи: ÐтмоÑфера МарÑа, Климат МарÑа +ÐтмоÑфера МарÑа, Ñнимок получен иÑкуÑÑтвенным Ñпутником «Викинг» в 1976. Слева виден «кратер-Ñмайлик» Галле + +Температура на планете колеблетÑÑ Ð¾Ñ‚ −153 °C[23] на полюÑе зимой и до более +20 °C[24] на Ñкваторе в полдень. СреднÑÑ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° ÑоÑтавлÑет −50 °C[23]. + +ÐтмоÑфера МарÑа, ÑоÑтоÑÑ‰Ð°Ñ Ð² оÑновном из углекиÑлого газа, очень разрежена. Давление у поверхноÑти МарÑа в 160 раз меньше земного — 6,1 мбар на Ñреднем уровне поверхноÑти. Из-за большого перепада выÑот на МарÑе давление у поверхноÑти Ñильно изменÑетÑÑ. ÐŸÑ€Ð¸Ð¼ÐµÑ€Ð½Ð°Ñ Ñ‚Ð¾Ð»Ñ‰Ð¸Ð½Ð° атмоÑферы — 110 км. + +По данным ÐÐСР(2004), атмоÑфера МарÑа ÑоÑтоит на 95,32 % из углекиÑлого газа; также в ней ÑодержитÑÑ 2,7 % азота, 1,6 % аргона, 0,13 % киÑлорода, 210 ppm водÑного пара, 0,08 % угарного газа, окÑид азота (NO) — 100 ppm, неон (Ne) — 2,5 ppm, полутÑÐ¶Ñ‘Ð»Ð°Ñ Ð²Ð¾Ð´Ð° водород-дейтерий-киÑлород (HDO) 0,85 ppm, криптон (Kr) 0,3 ppm, кÑенон (Xe) — 0,08 ppm[2] (ÑоÑтав приведён в объёмных долÑÑ…). + +По данным ÑпуÑкаемого аппарата ÐМС «Викинг» (1976), в марÑианÑкой атмоÑфере было определено около 1—2 % аргона, 2—3 % азота, а 95 % — углекиÑлый газ[25]. СоглаÑно данным ÐМС «МарÑ-2» и «МарÑ-3», нижнÑÑ Ð³Ñ€Ð°Ð½Ð¸Ñ†Ð° ионоÑферы находитÑÑ Ð½Ð° выÑоте 80 км, макÑимум Ñлектронной концентрации 1,7×105 Ñлектронов/Ñм³ раÑположен на выÑоте 138 км, другие два макÑимума находÑÑ‚ÑÑ Ð½Ð° выÑотах 85 и 107 км[26]. + +РадиопроÑвечивание атмоÑферы на радиоволнах 8 и 32 Ñм, проведённое ÐМС «МарÑ-4» 10 Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 1974 года, показало наличие ночной ионоÑферы МарÑа Ñ Ð³Ð»Ð°Ð²Ð½Ñ‹Ð¼ макÑимумом ионизации на выÑоте 110 км и концентрацией Ñлектронов 4,6×103 Ñлектронов/Ñм³, а также вторичными макÑимумами на выÑоте 65 и 185 км[26]. + +РазреженноÑÑ‚ÑŒ марÑианÑкой атмоÑферы и отÑутÑтвие магнитоÑферы ÑвлÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð¾Ð¹ того, что уровень ионизирующей радиации на поверхноÑти МарÑа ÑущеÑтвенно выше, чем на поверхноÑти Земли. МощноÑÑ‚ÑŒ Ñквивалентной дозы на поверхноÑти МарÑа ÑоÑтавлÑет в Ñреднем 0,7 мЗв/Ñутки (изменÑÑÑÑŒ в завиÑимоÑти от Ñолнечной активноÑти и атмоÑферного Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² пределах от 0,35 до 1,15 мЗв/Ñутки)[27] и обуÑловлена главным образом коÑмичеÑким излучением; Ð´Ð»Ñ ÑравнениÑ, на Земле ÑÑ€ÐµÐ´Ð½ÐµÐ¼Ð¸Ñ€Ð¾Ð²Ð°Ñ ÑÐºÐ²Ð¸Ð²Ð°Ð»ÐµÐ½Ñ‚Ð½Ð°Ñ Ð´Ð¾Ð·Ð° Ð¾Ð±Ð»ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¾Ñ‚ еÑтеÑтвенных иÑточников, Ð½Ð°ÐºÐ°Ð¿Ð»Ð¸Ð²Ð°ÐµÐ¼Ð°Ñ Ð·Ð° год, равна 2,4 мЗв, в том чиÑле от коÑмичеÑких лучей 0,4 мЗв[28]. Таким образом, за один-два Ð´Ð½Ñ ÐºÐ¾Ñмонавт на поверхноÑти МарÑа получит такую же Ñквивалентную дозу облучениÑ, какую на поверхноÑти Земли он получил бы за год. +ÐтмоÑферное давление[править | править вики-текÑÑ‚] + +По данным ÐÐСРна 2004 год, давление атмоÑферы на Ñреднем радиуÑе ÑоÑтавлÑет 636 Па (6,36 мбар). ПлотноÑÑ‚ÑŒ атмоÑферы у поверхноÑти — около 0,020 кг/м³, Ð¾Ð±Ñ‰Ð°Ñ Ð¼Ð°ÑÑа атмоÑферы МарÑа — около 2,5×1016 кг[2]. +Изменение атмоÑферного Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð½Ð° МарÑе в завиÑимоÑти от времени Ñуток, зафикÑированное поÑадочным модулем «Mars Pathfinder» в 1997 году + +Ð’ отличие от Земли, маÑÑа марÑианÑкой атмоÑферы Ñильно изменÑетÑÑ Ð² течение года в ÑвÑзи Ñ Ñ‚Ð°Ñнием и намерзанием полÑрных шапок, Ñодержащих углекиÑлый газ. Зимой 20—30 процентов вÑей атмоÑферы намораживаетÑÑ Ð½Ð° полÑрной шапке, ÑоÑтоÑщей из углекиÑлоты[29]. Сезонные перепады давлениÑ, по разным иÑточникам, ÑоÑтавлÑÑŽÑ‚ Ñледующие значениÑ: + + По данным ÐÐСР(2004): от 4,0 до 8,7 мбар на Ñреднем радиуÑе[2]; + По данным Encarta (2000): от 6 до 10 мбар[30]; + По данным Zubrin и Wagner (1996): от 7 до 10 мбар[31]; + По данным поÑадочного аппарата «Викинг-1»: от 6,9 до 9 мбар[2]; + По данным поÑадочного аппарата «Mars Pathfinder»: от 6,7 мбар[29]. + +Ð’ меÑте поÑадки зонда ÐМС «МарÑ-6» в районе ЭритрейÑкого Ð¼Ð¾Ñ€Ñ Ð±Ñ‹Ð»Ð¾ зафикÑировано давление у поверхноÑти 6,1 мбар, что на тот момент ÑчиталоÑÑŒ Ñредним давлением на планете, и от Ñтого ÑƒÑ€Ð¾Ð²Ð½Ñ Ð±Ñ‹Ð»Ð¾ уÑловлено отÑчитывать выÑоÌÑ‚Ñ‹ и глубиÌны на МарÑе. По данным Ñтого аппарата, полученным во Ð²Ñ€ÐµÐ¼Ñ ÑпуÑка, тропопауза находитÑÑ Ð½Ð° выÑоте примерно 30 км, где давление ÑоÑтавлÑет 5×10−7 г/Ñм³ (как на Земле на выÑоте 57 км)[32]. +Ð£Ð´Ð°Ñ€Ð½Ð°Ñ Ð²Ð¿Ð°Ð´Ð¸Ð½Ð° Эллада — Ñамое глубокое меÑто МарÑа, где можно зафикÑировать Ñамое выÑокое атмоÑферное давление. + +ОблаÑÑ‚ÑŒ Эллада наÑтолько глубока, что атмоÑферное давление доÑтигает примерно 12,4 мбар[14], что выше тройной точки воды (около 6,1 мбар)[33], поÑтому при доÑтаточно выÑокой температуре вода могла бы ÑущеÑтвовать там в жидком ÑоÑтоÑнии; при таком давлении, однако, вода закипает и превращаетÑÑ Ð² пар уже при +10 °C[14]. + +Ðа вершине выÑочайшей горы МарÑа, 27-километрового вулкана Олимп, давление может ÑоÑтавлÑÑ‚ÑŒ от 0,5 до 1 мбар[33]. + +До выÑадки на поверхноÑÑ‚ÑŒ МарÑа поÑадочных модулей давление было измерено за Ñчёт оÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ñ€Ð°Ð´Ð¸Ð¾Ñигналов Ñ ÐМС «Маринер-4», «Маринер-6», «Маринер-7» и «Маринер-9» при их захождении за марÑианÑкий диÑк и выходе из-за марÑианÑкого диÑка — 6,5±2,0 мбар на Ñреднем уровне поверхноÑти, что в 160 раз меньше земного; такой же результат показали Ñпектральные Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ ÐМС «МарÑ-3». При Ñтом в раÑположенных ниже Ñреднего ÑƒÑ€Ð¾Ð²Ð½Ñ Ð¾Ð±Ð»Ð°ÑÑ‚ÑÑ… (например, в марÑианÑкой Ðмазонии) давление, ÑоглаÑно Ñтим измерениÑм, доÑтигает 12 мбар[34]. + +ÐÐ°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1930-Ñ… годов, ÑоветÑкие аÑтрономы пыталиÑÑŒ определÑÑ‚ÑŒ давление атмоÑферы методами фотографичеÑкой фотометрии — по раÑпределению ÑркоÑти вдоль диаметра диÑка в разных диапазонах Ñветовых волн. ФранцузÑкие учёные Б. Лио и О. Ð”Ð¾Ð»ÑŒÑ„ÑŽÑ Ð¿Ñ€Ð¾Ð¸Ð·Ð²Ð¾Ð´Ð¸Ð»Ð¸ Ñ Ñтой целью Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ñризации раÑÑеÑнного атмоÑферой МарÑа Ñвета. Сводку оптичеÑких наблюдений опубликовал американÑкий аÑтроном Ж. де Вокулёр в 1951 году, и по ним получалоÑÑŒ давление 85\мбар, завышенное почти в 15 раз, поÑкольку не было отдельно учтено раÑÑеÑние Ñвета пылью, взвешенной в атмоÑфере МарÑа. Вклад пыли был припиÑан газовой атмоÑфере[35]. +Климат[править | править вики-текÑÑ‚] +Циклон возле Ñеверного полюÑа МарÑа, Ñнимки Ñ Ñ‚ÐµÐ»ÐµÑкопа «Хаббл» (27 Ð°Ð¿Ñ€ÐµÐ»Ñ 1999 года). + +Климат, как и на Земле, ноÑит Ñезонный характер. Угол наклона МарÑа к плоÑкоÑти орбиты почти равен земному и ÑоÑтавлÑет 25,1919°[5]; ÑоответÑтвенно, на МарÑе, так же как и на Земле, проиÑходит Ñмена времён года. ОÑобенноÑтью марÑианÑкого климата также ÑвлÑетÑÑ Ñ‚Ð¾, что ÑкÑцентриÑитет орбиты МарÑа значительно больше земного, и на климат также влиÑет раÑÑтоÑние до Солнца. Перигелий ÐœÐ°Ñ€Ñ Ð¿Ñ€Ð¾Ñ…Ð¾Ð´Ð¸Ñ‚ во Ð²Ñ€ÐµÐ¼Ñ Ñ€Ð°Ð·Ð³Ð°Ñ€Ð° зимы в Ñеверном полушарии и лета в южном, афелий — во Ð²Ñ€ÐµÐ¼Ñ Ñ€Ð°Ð·Ð³Ð°Ñ€Ð° зимы в южном полушарии и ÑоответÑтвенно лета в Ñеверном. Ð’ÑледÑтвие Ñтого климат Ñеверного и южного полушарий различаетÑÑ. Ð”Ð»Ñ Ñеверного Ð¿Ð¾Ð»ÑƒÑˆÐ°Ñ€Ð¸Ñ Ñ…Ð°Ñ€Ð°ÐºÑ‚ÐµÑ€Ð½Ñ‹ более мÑÐ³ÐºÐ°Ñ Ð·Ð¸Ð¼Ð° и прохладное лето; в южном полушарии зима более холоднаÑ, а лето более жаркое[36]. Ð’ холодное Ð²Ñ€ÐµÐ¼Ñ Ð³Ð¾Ð´Ð° даже вне полÑрных шапок на поверхноÑти может образовыватьÑÑ Ñветлый иней. Ðппарат «ФеникÑ» зафикÑировал Ñнегопад, однако Ñнежинки иÑпарÑлиÑÑŒ, не доÑÑ‚Ð¸Ð³Ð°Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти[37]. + +По ÑведениÑм ÐÐСР(2004 год), ÑреднÑÑ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° ÑоÑтавлÑет ~210 K (−63 °C). По данным поÑадочных аппаратов «Викинг», Ñуточный температурный диапазон ÑоÑтавлÑет от 184 K до 242 K (от −89 до −31 °C) («Викинг-1»), а ÑкороÑÑ‚ÑŒ ветра 2—7 м/Ñ (лето), 5—10 м/Ñ (оÑень), 17—30 м/Ñ (пылевой шторм)[2]. + +По данным поÑадочного зонда «МарÑ-6», ÑреднÑÑ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° тропоÑферы МарÑа ÑоÑтавлÑет 228 K, в тропоÑфере температура убывает в Ñреднем на 2,5 градуÑа на километр, а находÑщаÑÑÑ Ð²Ñ‹ÑˆÐµ тропопаузы (30 км) ÑтратоÑфера имеет почти поÑтоÑнную температуру 144 K[32]. + +ИÑÑледователи из Центра имени Карла Сагана в 2007—2008 годах пришли к выводу, что в поÑледние деÑÑÑ‚Ð¸Ð»ÐµÑ‚Ð¸Ñ Ð½Ð° МарÑе идёт процеÑÑ Ð¿Ð¾Ñ‚ÐµÐ¿Ð»ÐµÐ½Ð¸Ñ. СпециалиÑÑ‚Ñ‹ ÐÐСРподтвердили Ñту гипотезу на оÑнове анализа изменений альбедо разных чаÑтей планеты. Другие ÑпециалиÑÑ‚Ñ‹ Ñчитают, что такие выводы делать пока рано[38][39]. Ð’ мае 2016 года иÑÑледователи из Юго-Западного иÑÑледовательÑкого инÑтитута в Боулдере (Колорадо) опубликовали в журнале Science Ñтатью, в которой предъÑвили новые доказательÑтва идущего Ð¿Ð¾Ñ‚ÐµÐ¿Ð»ÐµÐ½Ð¸Ñ ÐºÐ»Ð¸Ð¼Ð°Ñ‚Ð° (на оÑнове анализа данных Mars Reconnaissance Orbiter). По их мнению, Ñтот процеÑÑ Ð´Ð»Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ð¹ и идёт, возможно, уже в течение 370 Ñ‚Ñ‹Ñ. лет.[40] + +СущеÑтвуют предположениÑ, что в прошлом атмоÑфера могла быть более плотной, а климат — тёплым и влажным, и на поверхноÑти МарÑа ÑущеÑтвовала Ð¶Ð¸Ð´ÐºÐ°Ñ Ð²Ð¾Ð´Ð° и шли дожди[41][42]. ДоказательÑтвом Ñтой гипотезы ÑвлÑетÑÑ Ð°Ð½Ð°Ð»Ð¸Ð· метеорита ALH 84001, показавший, что около 4 миллиардов лет назад температура МарÑа ÑоÑтавлÑла 18 ± 4 °C[43]. + +Главной оÑобенноÑтью общей циркулÑции атмоÑферы МарÑа ÑвлÑÑŽÑ‚ÑÑ Ñ„Ð°Ð·Ð¾Ð²Ñ‹Ðµ переходы углекиÑлого газа в полÑрных шапках, приводÑщие к значительным меридиональным потокам. ЧиÑленное моделирование общей циркулÑции атмоÑферы МарÑа[44] указывает на ÑущеÑтвенный годовой ход Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñ Ð´Ð²ÑƒÐ¼Ñ Ð¼Ð¸Ð½Ð¸Ð¼ÑƒÐ¼Ð°Ð¼Ð¸ незадолго перед равноденÑтвиÑми, что подтверждаетÑÑ Ð¸ наблюдениÑми по программе «Викинг». Ðнализ данных о давлении[45] выÑвил годовой и полугодовой циклы. ИнтереÑно, что, как и на Земле, макÑимум полугодовых колебаний зональной ÑкороÑти ветра Ñовпадает Ñ Ñ€Ð°Ð²Ð½Ð¾Ð´ÐµÐ½ÑтвиÑми[46]. ЧиÑленное моделирование[44] выÑвлÑет также и ÑущеÑтвенный цикл индекÑа Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´Ð¾Ð¼ 4—6 Ñуток в периоды ÑолнцеÑтоÑний. «Викингом» обнаружено подобие цикла индекÑа на МарÑе Ñ Ð°Ð½Ð°Ð»Ð¾Ð³Ð¸Ñ‡Ð½Ñ‹Ð¼Ð¸ колебаниÑми в атмоÑферах других планет. +Пылевые бури и пыльные вихри[править | править вики-текÑÑ‚] + +ВеÑеннее таÑние полÑрных шапок приводит к резкому повышению Ð´Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð°Ñ‚Ð¼Ð¾Ñферы и перемещению больших маÑÑ Ð³Ð°Ð·Ð° в противоположное полушарие. СкороÑÑ‚ÑŒ дующих при Ñтом ветров ÑоÑтавлÑет 10—40 м/Ñ, иногда до 100 м/Ñ. Ветер поднимает Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти большое количеÑтво пыли, что приводит к пылевым бурÑм. Сильные пылевые бури практичеÑки полноÑтью Ñкрывают поверхноÑÑ‚ÑŒ планеты. Пылевые бури оказывают заметное воздейÑтвие на раÑпределение температуры в атмоÑфере МарÑа[47]. +Фотографии МарÑа, на которых видна Ð¿Ñ‹Ð»ÑŒÐ½Ð°Ñ Ð±ÑƒÑ€Ñ (июнь — ÑентÑбрь 2001). + +22 ÑентÑÐ±Ñ€Ñ 1971 года в Ñветлой облаÑти Noachis в южном полушарии началаÑÑŒ Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ð¿Ñ‹Ð»ÐµÐ²Ð°Ñ Ð±ÑƒÑ€Ñ. К 29 ÑентÑÐ±Ñ€Ñ Ð¾Ð½Ð° охватила двеÑти градуÑов по долготе от Ausonia до Thaumasia, а 30 ÑентÑÐ±Ñ€Ñ Ð·Ð°ÐºÑ€Ñ‹Ð»Ð° южную полÑрную шапку. Ð‘ÑƒÑ€Ñ Ð¿Ñ€Ð¾Ð´Ð¾Ð»Ð¶Ð°Ð»Ð° бушевать вплоть до Ð´ÐµÐºÐ°Ð±Ñ€Ñ 1971 года, когда на орбиту МарÑа прибыли ÑоветÑкие Ñтанции «МарÑ-2» и «МарÑ-3». «МарÑы» проводили Ñъёмку поверхноÑти, но пыль полноÑтью Ñкрывала рельеф — не видно было даже горы Олимп, возвышающейÑÑ Ð½Ð° 27 км. Ð’ одном из ÑеанÑов Ñъёмки была получена Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ð¿Ð¾Ð»Ð½Ð¾Ð³Ð¾ диÑка МарÑа Ñ Ñ‡Ñ‘Ñ‚ÐºÐ¾ выраженным тонким Ñлоем марÑианÑких облаков над пылью. Во Ð²Ñ€ÐµÐ¼Ñ Ñтих иÑÑледований в декабре 1971 года Ð¿Ñ‹Ð»ÐµÐ²Ð°Ñ Ð±ÑƒÑ€Ñ Ð¿Ð¾Ð´Ð½Ñла в атмоÑферу Ñтолько пыли, что планета выглÑдела мутным краÑноватым диÑком. Только примерно к 10 ÑÐ½Ð²Ð°Ñ€Ñ 1972 года Ð¿Ñ‹Ð»ÐµÐ²Ð°Ñ Ð±ÑƒÑ€Ñ Ð¿Ñ€ÐµÐºÑ€Ð°Ñ‚Ð¸Ð»Ð°ÑÑŒ, и ÐœÐ°Ñ€Ñ Ð¿Ñ€Ð¸Ð½Ñл обычный вид[48]. +Пыльные вихри, Ñфотографированные марÑоходом «Спирит» 15 Ð¼Ð°Ñ 2005 года. Цифры в левом нижнем углу отображают Ð²Ñ€ÐµÐ¼Ñ Ð² Ñекундах Ñ Ð¼Ð¾Ð¼ÐµÐ½Ñ‚Ð° первого кадра. + +ÐÐ°Ñ‡Ð¸Ð½Ð°Ñ Ñ 1970-Ñ… годов, в рамках программы «Викинг», а также марÑоходом «Спирит» и другими аппаратами были зафикÑированы многочиÑленные пыльные вихри. Это воздушные завихрениÑ, возникающие у поверхноÑти планеты и поднимающие в воздух большое количеÑтво пеÑка и пыли. Вихри чаÑто наблюдаютÑÑ Ð¸ на Земле (в англоÑзычных Ñтранах их называют «пыльными демонами» — англ. dust devil), однако на МарÑе они могут доÑтигать гораздо больших размеров: в 10 раз выше и в 50 раз шире земных. Ð’ марте 2005 года такой вихрь очиÑтил Ñолнечные батареи у марÑохода «Спирит»[49][50]. +ПоверхноÑÑ‚ÑŒ[править | править вики-текÑÑ‚] +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ПоверхноÑÑ‚ÑŒ МарÑа +ОÑновные регионы[править | править вики-текÑÑ‚] +Иней на поверхноÑти МарÑа (Ñнимок марÑианÑкой Ñтанции «Викинг-2», 18 Ð¼Ð°Ñ 1979 года). + +УчаÑток кратера ГуÑева (мозаика Ñнимков марÑохода «Спирит»). + +ТопографичеÑÐºÐ°Ñ ÐºÐ°Ñ€Ñ‚Ð° МарÑа, по данным Mars Global Surveyor (1999). Ðулевой меридиан МарÑа принÑÑ‚ проходÑщим через кратер Эйри-0. + +Две трети поверхноÑти МарÑа занимают Ñветлые облаÑти, получившие название материков, около трети — тёмные учаÑтки, называемые морÑми. ÐœÐ¾Ñ€Ñ ÑоÑредоточены главным образом в южном полушарии планеты, между 10 и 40° широты. Ð’ Ñеверном полушарии еÑÑ‚ÑŒ только два крупных Ð¼Ð¾Ñ€Ñ â€” ÐцидалийÑкое и Большой Сирт. + +Характер тёмных учаÑтков до Ñих пор оÑтаётÑÑ Ð¿Ñ€ÐµÐ´Ð¼ÐµÑ‚Ð¾Ð¼ Ñпоров. Они ÑохранÑÑŽÑ‚ÑÑ, неÑÐ¼Ð¾Ñ‚Ñ€Ñ Ð½Ð° то, что на МарÑе бушуют пылевые бури. Ð’ Ñвоё Ð²Ñ€ÐµÐ¼Ñ Ñто Ñлужило доводом в пользу предположениÑ, что тёмные учаÑтки покрыты раÑтительноÑтью. Ð¡ÐµÐ¹Ñ‡Ð°Ñ Ð¿Ð¾Ð»Ð°Ð³Ð°ÑŽÑ‚, что Ñто проÑто учаÑтки, Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ…, в Ñилу их рельефа, легко выдуваетÑÑ Ð¿Ñ‹Ð»ÑŒ. КрупномаÑштабные Ñнимки показывают, что на Ñамом деле тёмные учаÑтки ÑоÑтоÑÑ‚ из групп тёмных Ð¿Ð¾Ð»Ð¾Ñ Ð¸ пÑтен, ÑвÑзанных Ñ ÐºÑ€Ð°Ñ‚ÐµÑ€Ð°Ð¼Ð¸, холмами и другими препÑÑ‚ÑтвиÑми на пути ветров. Сезонные и долговременные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ñ… размера и формы ÑвÑзаны, по-видимому, Ñ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸ÐµÐ¼ ÑÐ¾Ð¾Ñ‚Ð½Ð¾ÑˆÐµÐ½Ð¸Ñ ÑƒÑ‡Ð°Ñтков поверхноÑти, покрытых Ñветлым и тёмным вещеÑтвом. + +ÐŸÐ¾Ð»ÑƒÑˆÐ°Ñ€Ð¸Ñ ÐœÐ°Ñ€Ñа довольно Ñильно различаютÑÑ Ð¿Ð¾ характеру поверхноÑти. Ð’ южном полушарии поверхноÑÑ‚ÑŒ находитÑÑ Ð½Ð° 1—2 км над Ñредним уровнем и гуÑто уÑеÑна кратерами. Эта чаÑÑ‚ÑŒ МарÑа напоминает лунные материки. Ðа Ñевере Ð±Ð¾Ð»ÑŒÑˆÐ°Ñ Ñ‡Ð°ÑÑ‚ÑŒ поверхноÑти находитÑÑ Ð½Ð¸Ð¶Ðµ Ñреднего уровнÑ, здеÑÑŒ мало кратеров, и оÑновную чаÑÑ‚ÑŒ занимают отноÑительно гладкие равнины, вероÑтно, образовавшиеÑÑ Ð² результате Ð·Ð°Ñ‚Ð¾Ð¿Ð»ÐµÐ½Ð¸Ñ Ð»Ð°Ð²Ð¾Ð¹ и Ñрозии. Такое различие полушарий оÑтаётÑÑ Ð¿Ñ€ÐµÐ´Ð¼ÐµÑ‚Ð¾Ð¼ диÑкуÑÑий. Граница между полушариÑми Ñледует примерно по большому кругу, наклонённому на 30° к Ñкватору. Граница ÑˆÐ¸Ñ€Ð¾ÐºÐ°Ñ Ð¸ Ð½ÐµÐ¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð°Ñ Ð¸ образует Ñклон в направлении на Ñевер. Вдоль неё вÑтречаютÑÑ Ñамые Ñродированные учаÑтки марÑианÑкой поверхноÑти. + +Выдвинуто две альтернативных гипотезы, объÑÑнÑющих аÑимметрию полушарий. СоглаÑно одной из них, на раннем геологичеÑком Ñтапе литоÑферные плиты «ÑъехалиÑь» (возможно, Ñлучайно) в одно полушарие, подобно континенту ÐŸÐ°Ð½Ð³ÐµÑ Ð½Ð° Земле, а затем «заÑтыли» в Ñтом положении. Ð”Ñ€ÑƒÐ³Ð°Ñ Ð³Ð¸Ð¿Ð¾Ñ‚ÐµÐ·Ð° предполагает Ñтолкновение МарÑа Ñ ÐºÐ¾ÑмичеÑким телом размером Ñ ÐŸÐ»ÑƒÑ‚Ð¾Ð½[51]. + +Большое количеÑтво кратеров в южном полушарии предполагает, что поверхноÑÑ‚ÑŒ здеÑÑŒ древнÑÑ â€” 3—4 млрд. лет. ВыделÑÑŽÑ‚ неÑколько типов кратеров: большие кратеры Ñ Ð¿Ð»Ð¾Ñким дном, более мелкие и молодые чашеобразные кратеры, похожие на лунные, кратеры, окружённые валом, и возвышенные кратеры. ПоÑледние два типа уникальны Ð´Ð»Ñ ÐœÐ°Ñ€Ñа — кратеры Ñ Ð²Ð°Ð»Ð¾Ð¼ образовалиÑÑŒ там, где по поверхноÑти текли жидкие выброÑÑ‹, а возвышенные кратеры образовалиÑÑŒ там, где покрывало выброÑов кратера защитило поверхноÑÑ‚ÑŒ от ветровой Ñрозии. Самой крупной деталью ударного проиÑÑ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ ÑвлÑетÑÑ Ñ€Ð°Ð²Ð½Ð¸Ð½Ð° Эллада (примерно 2100 км в поперечнике[52]). + +Ð’ облаÑти хаотичеÑкого ландшафта вблизи границы полушарий поверхноÑÑ‚ÑŒ иÑпытала разломы и ÑÐ¶Ð°Ñ‚Ð¸Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ñ… учаÑтков, за которыми иногда Ñледовала ÑÑ€Ð¾Ð·Ð¸Ñ (вÑледÑтвие оползней или катаÑтрофичеÑкого выÑÐ²Ð¾Ð±Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´Ð·ÐµÐ¼Ð½Ñ‹Ñ… вод), а также затопление жидкой лавой. ХаотичеÑкие ландшафты чаÑто находÑÑ‚ÑÑ Ñƒ иÑтока больших каналов, прорезанных водой. Ðаиболее приемлемой гипотезой их ÑовмеÑтного Ð¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ ÑвлÑетÑÑ Ð²Ð½ÐµÐ·Ð°Ð¿Ð½Ð¾Ðµ таÑние подповерхноÑтного льда. +Долины Маринер на МарÑе. + +Ð’ Ñеверном полушарии, помимо обширных вулканичеÑких равнин, находÑÑ‚ÑÑ Ð´Ð²Ðµ облаÑти крупных вулканов — ФарÑида и Элизий. ФарÑида — Ð¾Ð±ÑˆÐ¸Ñ€Ð½Ð°Ñ Ð²ÑƒÐ»ÐºÐ°Ð½Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ñ€Ð°Ð²Ð½Ð¸Ð½Ð° протÑжённоÑтью 2000 км, доÑÑ‚Ð¸Ð³Ð°ÑŽÑ‰Ð°Ñ Ð²Ñ‹Ñоты 10 км над Ñредним уровнем. Ðа ней находÑÑ‚ÑÑ Ñ‚Ñ€Ð¸ крупных щитовых вулкана — гора ÐÑ€ÑиÑ, гора Павлина и гора ÐÑкрийÑкаÑ. Ðа краю ФарÑиды находитÑÑ Ð²Ñ‹ÑÐ¾Ñ‡Ð°Ð¹ÑˆÐ°Ñ Ð½Ð° МарÑе и выÑÐ¾Ñ‡Ð°Ð¹ÑˆÐ°Ñ Ð¸Ð·Ð²ÐµÑÑ‚Ð½Ð°Ñ Ð² Солнечной ÑиÑтеме[9] гора Олимп. Олимп доÑтигает 27 км выÑоты по отношению к его оÑнованию[9] и 25 км по отношению к Ñреднему уровню поверхноÑти МарÑа, и охватывает площадь 550 км диаметром, окружённую обрывами, меÑтами доÑтигающими 7 км выÑоты. Объём Олимпа в 10 раз превышает объём крупнейшего вулкана Земли Мауна-Кеа. ЗдеÑÑŒ же раÑположено неÑколько менее крупных вулканов. Элизий — возвышенноÑÑ‚ÑŒ до шеÑти километров над Ñредним уровнем, Ñ Ñ‚Ñ€ÐµÐ¼Ñ Ð²ÑƒÐ»ÐºÐ°Ð½Ð°Ð¼Ð¸ — купол Гекаты, гора Элизий и купол Ðльбор. + +По другим данным, выÑота Олимпа ÑоÑтавлÑет 21 287 метров над нулевым уровнем и 18 километров над окружающей меÑтноÑтью, а диаметр оÑÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ â€” примерно 600 км. ОÑнование охватывает площадь 282 600 км²[53]. Кальдера (углубление в центре вулкана) имеет ширину 70 км и глубину 3 км[54]. + +ВозвышенноÑÑ‚ÑŒ ФарÑида также переÑечена множеÑтвом тектоничеÑких разломов, чаÑто очень Ñложных и протÑжённых. Крупнейший из них — долины Маринер — Ñ‚ÑнетÑÑ Ð² широтном направлении почти на 4000 км (четверть окружноÑти планеты), доÑÑ‚Ð¸Ð³Ð°Ñ ÑˆÐ¸Ñ€Ð¸Ð½Ñ‹ 600 и глубины 7—10 км[55][56]; по размерам Ñтот разлом Ñравним Ñ Ð’Ð¾ÑточноафриканÑким рифтом на Земле. Ðа его крутых Ñклонах проиÑходÑÑ‚ крупнейшие в Солнечной ÑиÑтеме оползни. Долины Маринер ÑвлÑÑŽÑ‚ÑÑ Ñамым большим извеÑтным каньоном в Солнечной ÑиÑтеме. Каньон, который был открыт коÑмичеÑким аппаратом «Маринер-9» в 1971 году, мог бы занÑÑ‚ÑŒ вÑÑŽ территорию СШÐ, от океана до океана. +Панорама ударного кратера Ð’Ð¸ÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð¸Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ около 800 метров, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Оппортьюнити». Панорама ÑоÑтавлена из Ñнимков, которые были получены за три недели, в период Ñ 16 октÑÐ±Ñ€Ñ Ð¿Ð¾ 6 ноÑÐ±Ñ€Ñ 2006. +Панорама ударного кратера Ð’Ð¸ÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð¸Ð°Ð¼ÐµÑ‚Ñ€Ð¾Ð¼ около 800 метров, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Оппортьюнити». Панорама ÑоÑтавлена из Ñнимков, которые были получены за три недели, в период Ñ 16 октÑÐ±Ñ€Ñ Ð¿Ð¾ 6 ноÑÐ±Ñ€Ñ 2006. +Панорама поверхноÑти МарÑа в районе Husband Hill, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Спирит» 23-28 ноÑÐ±Ñ€Ñ 2005. +Панорама поверхноÑти МарÑа в районе Husband Hill, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Спирит» 23-28 ноÑÐ±Ñ€Ñ 2005. +Лёд и полÑрные шапки[править | править вики-текÑÑ‚] +Ð¡ÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð¿Ð¾Ð»ÑÑ€Ð½Ð°Ñ ÑˆÐ°Ð¿ÐºÐ° в летний период, фото ÐœÐ°Ñ€Ñ Ð“Ð»Ð¾Ð±Ð°Ð» Сервейор. Длинный широкий разлом, раÑÑекающий шапку Ñлева — Каньон Северный. + +Внешний вид МарÑа Ñильно изменÑетÑÑ Ð² завиÑимоÑти от времени года. Прежде вÑего, броÑаютÑÑ Ð² глаза Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ñрных шапок. Они разраÑтаютÑÑ Ð¸ уменьшаютÑÑ, ÑÐ¾Ð·Ð´Ð°Ð²Ð°Ñ Ñезонные ÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð² атмоÑфере и на поверхноÑти МарÑа. ПолÑрные шапки в макÑимуме разраÑÑ‚Ð°Ð½Ð¸Ñ Ð¼Ð¾Ð³ÑƒÑ‚ доÑтигать широты 50°. Диаметр поÑтоÑнной чаÑти Ñеверной полÑрной шапки ÑоÑтавлÑет 1000 км[57]. По мере того, как веÑной полÑÑ€Ð½Ð°Ñ ÑˆÐ°Ð¿ÐºÐ° в одном из полушарий отÑтупает, детали поверхноÑти планеты начинают темнеть. + +Ð¡ÐµÐ²ÐµÑ€Ð½Ð°Ñ Ð¸ Ð®Ð¶Ð½Ð°Ñ Ð¿Ð¾Ð»Ñрные шапки ÑоÑтоÑÑ‚ из двух ÑоÑтавлÑющих: Ñезонной — углекиÑлого газа[57] и вековой — водÑного льда[58]. По данным Ñо Ñпутника «МарÑ-ÑкÑпреÑÑ», толщина шапок может ÑоÑтавлÑÑ‚ÑŒ от 1 м до 3,7 км. Ðппарат Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей» обнаружил на южной полÑрной шапке МарÑа дейÑтвующие гейзеры. Как Ñчитают ÑпециалиÑÑ‚Ñ‹ ÐÐСÐ, Ñтруи углекиÑлого газа Ñ Ð²ÐµÑенним потеплением вырываютÑÑ Ð²Ð²ÐµÑ€Ñ… на большую выÑоту, уноÑÑ Ñ Ñобой пыль и пеÑок[59][60]. + +Ð’ 1784 году аÑтроном У. Гершель обратил внимание на Ñезонные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ñ€Ð°Ð·Ð¼ÐµÑ€Ð° полÑрных шапок, по аналогии Ñ Ñ‚Ð°Ñнием и намерзанием льдов в земных полÑрных облаÑÑ‚ÑÑ…[61]. Ð’ 1860-Ñ… годах французÑкий аÑтроном Э. ЛÑи наблюдал волну Ð¿Ð¾Ñ‚ÐµÐ¼Ð½ÐµÐ½Ð¸Ñ Ð²Ð¾ÐºÑ€ÑƒÐ³ тающей веÑенней полÑрной шапки, что тогда было иÑтолковано как раÑтекание талых вод и развитие раÑтительноÑти. СпектрометричеÑкие измерениÑ, которые были проведены в начале XX века в обÑерватории Ловелла во ФлагÑтаффе Ð’. Слайфером, однако, не показали Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ Ð»Ð¸Ð½Ð¸Ð¸ хлорофилла — зелёного пигмента земных раÑтений[62]. + +По фотографиÑм «Маринера-7» удалоÑÑŒ определить, что полÑрные шапки имеют толщину в неÑколько метров, а Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð½Ð°Ñ Ñ‚ÐµÐ¼Ð¿ÐµÑ€Ð°Ñ‚ÑƒÑ€Ð° 115 K (−158 °C) подтвердила возможноÑÑ‚ÑŒ того, что она ÑоÑтоит из замёрзшей углекиÑлоты — «Ñухого льда»[63]. + +ВозвышенноÑÑ‚ÑŒ, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð¿Ð¾Ð»ÑƒÑ‡Ð¸Ð»Ð° название гор Митчелла, раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð½Ð°Ñ Ð±Ð»Ð¸Ð· южного полюÑа МарÑа, при таÑнии полÑрной шапки выглÑдит как белый оÑтровок, поÑкольку в горах ледники тают позднее, в том чиÑле и на Земле[64]. + +Данные аппарата Mars Reconnaissance Orbiter позволили обнаружить под камениÑтыми оÑыпÑми у Ð¿Ð¾Ð´Ð½Ð¾Ð¶Ð¸Ñ Ð³Ð¾Ñ€ значительный Ñлой льда. Ледник толщиной в Ñотни метров занимает площадь в Ñ‚Ñ‹ÑÑчи квадратных километров, и его дальнейшее изучение ÑпоÑобно дать информацию об иÑтории марÑианÑкого климата[65][66]. +РуÑла «рек» и другие оÑобенноÑти[править | править вики-текÑÑ‚] +Дельта выÑохшей реки в кратере ЭберÑвальде (фото Mars Global Surveyor). +ÐœÐ¸ÐºÑ€Ð¾Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ ÐºÐ¾Ð½ÐºÑ€ÐµÑ†Ð¸Ð¸ гематита в марÑианÑком грунте, ÑнÑÑ‚Ð°Ñ Ð¼Ð°Ñ€Ñоходом «Оппортьюнити» 2 марта 2004 года (поле Ð·Ñ€ÐµÐ½Ð¸Ñ 1,3 Ñм), что ÑвидетельÑтвует о приÑутÑтвии в геологичеÑком прошлом воды в жидком ÑоÑтоÑнии[67]. +Так Ð½Ð°Ð·Ñ‹Ð²Ð°ÐµÐ¼Ð°Ñ Â«Ñ‡Ñ‘Ñ€Ð½Ð°Ñ Ð´Ñ‹Ñ€Ð°Â» (колодец) диаметром более 150 м на поверхноÑти МарÑа. Видна чаÑÑ‚ÑŒ боковой Ñтенки. Склон горы ÐÑ€ÑÐ¸Ñ (фото «МарÑианÑкого разведывательного Ñпутника»). +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ГидроÑфера МарÑа + +Ðа МарÑе имеетÑÑ Ð¼Ð½Ð¾Ð¶ÐµÑтво геологичеÑких образований, напоминающих водную Ñрозию, в чаÑтноÑти, выÑохшие руÑла рек. СоглаÑно одной из гипотез, Ñти руÑла могли ÑформироватьÑÑ Ð² результате кратковременных катаÑтрофичеÑких Ñобытий и не ÑвлÑÑŽÑ‚ÑÑ Ð´Ð¾ÐºÐ°Ð·Ð°Ñ‚ÐµÐ»ÑŒÑтвом длительного ÑущеÑÑ‚Ð²Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ€ÐµÑ‡Ð½Ð¾Ð¹ ÑиÑтемы. Однако поÑледние данные ÑвидетельÑтвуют о том, что реки текли в течение геологичеÑки значимых промежутков времени. Ð’ чаÑтноÑти, обнаружены инвертированные руÑла (то еÑÑ‚ÑŒ руÑла, приподнÑтые над окружающей меÑтноÑтью). Ðа Земле подобные Ð¾Ð±Ñ€Ð°Ð·Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ„Ð¾Ñ€Ð¼Ð¸Ñ€ÑƒÑŽÑ‚ÑÑ Ð±Ð»Ð°Ð³Ð¾Ð´Ð°Ñ€Ñ Ð´Ð»Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð¾Ð¼Ñƒ накоплению плотных донных отложений Ñ Ð¿Ð¾Ñледующим выÑыханием и выветриванием окружающих пород. Кроме того, еÑÑ‚ÑŒ ÑвидетельÑтва ÑÐ¼ÐµÑ‰ÐµÐ½Ð¸Ñ Ñ€ÑƒÑел в дельте реки при поÑтепенном поднÑтии поверхноÑти[68]. + +Ð’ юго-западном полушарии, в кратере ЭберÑвальде обнаружена дельта реки площадью около 115 км²[69]. ÐÐ°Ð¼Ñ‹Ð²ÑˆÐ°Ñ Ð´ÐµÐ»ÑŒÑ‚Ñƒ река имела в длину более 60 км[70]. + +Данные марÑоходов ÐÐСР«Спирит» и «Оппортьюнити» ÑвидетельÑтвуют также о наличии воды в прошлом (найдены минералы, которые могли образоватьÑÑ Ñ‚Ð¾Ð»ÑŒÐºÐ¾ в результате длительного воздейÑÑ‚Ð²Ð¸Ñ Ð²Ð¾Ð´Ñ‹). Ðппарат «ФеникÑ» обнаружил залежи льда непоÑредÑтвенно в грунте. + +Кроме того, обнаружены тёмные полоÑÑ‹ на Ñклонах холмов, ÑвидетельÑтвующие о поÑвлении жидкой Ñолёной воды на поверхноÑти в наше времÑ. Они поÑвлÑÑŽÑ‚ÑÑ Ð²Ñкоре поÑле наÑÑ‚ÑƒÐ¿Ð»ÐµÐ½Ð¸Ñ Ð»ÐµÑ‚Ð½ÐµÐ³Ð¾ периода и иÑчезают к зиме, «обтекают» различные препÑÑ‚ÑтвиÑ, ÑливаютÑÑ Ð¸ раÑходÑÑ‚ÑÑ. «Сложно предÑтавить, что подобные Ñтруктуры могли ÑформироватьÑÑ Ð½Ðµ из потоков жидкоÑти, а из чего-то иного», — заÑвил Ñотрудник ÐÐСРРичард Зурек[71]. Дальнейший Ñпектральный анализ показал приÑутÑтвие в указанных облаÑÑ‚ÑÑ… перхлоратов — Ñолей, ÑпоÑобных обеÑпечить ÑущеÑтвование жидкой воды в уÑловиÑÑ… марÑианÑкого давлениÑ[72][73]. + +28 ÑентÑÐ±Ñ€Ñ 2012 года на МарÑе обнаружены Ñледы переÑохшего водного потока. Об Ñтом объÑвили ÑпециалиÑÑ‚Ñ‹ американÑкого коÑмичеÑкого агентÑтва ÐÐСРпоÑле Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ð¹, полученных Ñ Ð¼Ð°Ñ€Ñохода «КьюриоÑити», на тот момент работавшего на планете лишь Ñемь недель. Речь идёт о фотографиÑÑ… камней, которые, по мнению учёных, Ñвно подвергалиÑÑŒ воздейÑтвию воды[74]. + +Ðа вулканичеÑкой возвышенноÑти ФарÑида обнаружено неÑколько необычных глубоких колодцев. Ð¡ÑƒÐ´Ñ Ð¿Ð¾ Ñнимку аппарата «МарÑианÑкий разведывательный Ñпутник», Ñделанному в 2007 году, один из них имеет диаметр 150 метров, а оÑÐ²ÐµÑ‰Ñ‘Ð½Ð½Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ Ñтенки уходит в глубину не менее чем на 178 метров. Ð’Ñ‹Ñказана гипотеза о вулканичеÑком проиÑхождении Ñтих образований[75]. + +Ðа МарÑе имеетÑÑ Ð½ÐµÐ¾Ð±Ñ‹Ñ‡Ð½Ñ‹Ð¹ регион — Лабиринт Ðочи, предÑтавлÑющий Ñобой ÑиÑтему переÑекающихÑÑ ÐºÐ°Ð½ÑŒÐ¾Ð½Ð¾Ð²[76]. Их образование не было ÑвÑзано Ñ Ð²Ð¾Ð´Ð½Ð¾Ð¹ Ñрозией, и вероÑÑ‚Ð½Ð°Ñ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð° поÑÐ²Ð»ÐµÐ½Ð¸Ñ â€” тектоничеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ[77][78]. Когда ÐœÐ°Ñ€Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð²Ð±Ð»Ð¸Ð·Ð¸ перигелиÑ, над лабиринтом Ðочи и долинами Маринера поÑвлÑÑŽÑ‚ÑÑ Ð²Ñ‹Ñокие (40—50 км) облака. ВоÑточный ветер вытÑгивает их вдоль Ñкватора и ÑноÑит к западу, где они поÑтепенно размываютÑÑ. Их длина доÑтигает неÑкольких Ñотен (до Ñ‚Ñ‹ÑÑчи) километров, а ширина — неÑкольких деÑÑтков. СоÑтоÑÑ‚ они, ÑÑƒÐ´Ñ Ð¿Ð¾ уÑловиÑм в Ñтих ÑлоÑÑ… атмоÑферы, тоже из водÑного льда. Они довольно гуÑтые и отбраÑывают на поверхноÑÑ‚ÑŒ хорошо заметные тени. Их поÑвление объÑÑнÑÑŽÑ‚ тем, что неровноÑти рельефа вноÑÑÑ‚ Ð²Ð¾Ð·Ð¼ÑƒÑ‰ÐµÐ½Ð¸Ñ Ð² воздушные потоки, направлÑÑ Ð¸Ñ… вверх. Там они охлаждаютÑÑ, а ÑодержащийÑÑ Ð² них водÑной пар конденÑируетÑÑ[79]. +Грунт[править | править вики-текÑÑ‚] +Ð¤Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ð¼Ð°Ñ€ÑианÑкого грунта в меÑте поÑадки аппарата «ФеникÑ». + +Элементный ÑоÑтав поверхноÑтного ÑÐ»Ð¾Ñ Ð³Ñ€ÑƒÐ½Ñ‚Ð°, определённый по данным поÑадочных аппаратов, неодинаков в разных меÑтах. ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑоÑтавлÑÑŽÑ‰Ð°Ñ Ð¿Ð¾Ñ‡Ð²Ñ‹ — кремнезём (20—25 %), Ñодержащий примеÑÑŒ гидратов окÑидов железа (до 15 %), придающих почве краÑноватый цвет. ИмеютÑÑ Ð·Ð½Ð°Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ñ‹Ðµ примеÑи Ñоединений Ñеры, кальциÑ, алюминиÑ, магниÑ, Ð½Ð°Ñ‚Ñ€Ð¸Ñ (единицы процентов Ð´Ð»Ñ ÐºÐ°Ð¶Ð´Ð¾Ð³Ð¾)[80][81]. + +СоглаÑно данным зонда ÐÐСР«ФеникÑ» (поÑадка на ÐœÐ°Ñ€Ñ 25 Ð¼Ð°Ñ 2008 года), Ñоотношение pH и некоторые другие параметры марÑианÑких почв близки к земным, и на них теоретичеÑки можно было бы выращивать раÑтениÑ[82][83]. «ФактичеÑки мы обнаружили, что почва на МарÑе отвечает требованиÑм, а также Ñодержит необходимые Ñлементы Ð´Ð»Ñ Ð²Ð¾Ð·Ð½Ð¸ÐºÐ½Ð¾Ð²ÐµÐ½Ð¸Ñ Ð¸ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸ как в прошлом, так и в наÑтоÑщем и будущем», Ñообщил ведущий иÑÑледователь-химик проекта СÑм КунейвÑ[84]. Также, по его Ñловам, данный щелочной тип грунта (pH = 7,7) многие могут вÑтретить на «Ñвоём заднем дворе», и он вполне пригоден Ð´Ð»Ñ Ð²Ñ‹Ñ€Ð°Ñ‰Ð¸Ð²Ð°Ð½Ð¸Ñ Ñпаржи[85]. + +Ð’ меÑте поÑадки аппарата в грунте имеетÑÑ Ñ‚Ð°ÐºÐ¶Ðµ значительное количеÑтво водÑного льда[86]. Орбитальный зонд Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей» также обнаружил, что под поверхноÑтью краÑной планеты еÑÑ‚ÑŒ залежи водÑного льда[87]. Позже Ñто предположение было подтверждено и другими аппаратами, но окончательно Ð²Ð¾Ð¿Ñ€Ð¾Ñ Ð¾ наличии воды на МарÑе был решён в 2008 году, когда зонд «ФеникÑ», Ñевший вблизи Ñеверного полюÑа планеты, получил воду из марÑианÑкого грунта[15][88]. + +Данные, полученные марÑоходом Curiosity и обнародованные в ÑентÑбре 2013 года, показали, что Ñодержание воды под поверхноÑтью МарÑа гораздо выше, чем ÑчиталоÑÑŒ ранее. Ð’ породе, из которой брал образцы марÑоход, её Ñодержание может доÑтигать 2 % по веÑу[89]. +Ð“ÐµÐ¾Ð»Ð¾Ð³Ð¸Ñ Ð¸ внутреннее Ñтроение[править | править вики-текÑÑ‚] + +Ð’ прошлом на МарÑе, как и на Земле, проиÑходило движение литоÑферных плит. Это подтверждаетÑÑ Ð¾ÑобенноÑÑ‚Ñми магнитного Ð¿Ð¾Ð»Ñ ÐœÐ°Ñ€Ñа, меÑтами раÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… вулканов, например, в провинции ФарÑида, а также формой долины Маринер[90]. Современное положение дел, когда вулканы могут ÑущеÑтвовать гораздо более длительное времÑ, чем на Земле, и доÑтигать гигантÑких размеров, говорит о том, что ÑÐµÐ¹Ñ‡Ð°Ñ Ð´Ð°Ð½Ð½Ð¾Ðµ движение Ñкорее отÑутÑтвует. Ð’ пользу Ñтого говорит тот факт, что щитовые вулканы раÑтут в результате повторных извержений из одного и того же жерла в течение длительного времени. Ðа Земле из-за Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð»Ð¸Ñ‚Ð¾Ñферных плит вулканичеÑкие точки поÑтоÑнно менÑли Ñвоё положение, что ограничивало роÑÑ‚ щитовых вулканов и, возможно, не позволÑло доÑтичь им такой выÑоты, как на МарÑе. С другой Ñтороны, разница в макÑимальной выÑоте вулканов может объÑÑнÑÑ‚ÑŒÑÑ Ñ‚ÐµÐ¼, что из-за меньшей Ñилы Ñ‚ÑжеÑти на МарÑе возможно поÑтроение более выÑоких Ñтруктур, которые не обрушилиÑÑŒ бы под ÑобÑтвенным веÑом[91]. Возможно, на планете имеетÑÑ ÑÐ»Ð°Ð±Ð°Ñ Ñ‚ÐµÐºÑ‚Ð¾Ð½Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ, приводÑÑ‰Ð°Ñ Ðº образованию наблюдаемых Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ñ‹ пологих каньонов[92][93]. +Сравнение ÑÑ‚Ñ€Ð¾ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа и других планет земной группы + +Современные модели внутреннего ÑÑ‚Ñ€Ð¾ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа предполагают, что ÐœÐ°Ñ€Ñ ÑоÑтоит из коры Ñо Ñредней толщиной 50 км (макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð¾Ñ†ÐµÐ½ÐºÐ° — не более 125 км)[94], Ñиликатной мантии и Ñдра радиуÑом, по разным оценкам, от 1480[94] до 1800 км[95]. ПлотноÑÑ‚ÑŒ в центре планеты должна доÑтигать 8,5 г/Ñм³. Ядро чаÑтично жидкое и ÑоÑтоит в оÑновном из железа Ñ Ð¿Ñ€Ð¸Ð¼ÐµÑью 14—18 % (по маÑÑе) Ñеры[95], причём Ñодержание лёгких Ñлементов вдвое выше, чем в Ñдре Земли. СоглаÑно Ñовременным оценкам, формирование Ñдра Ñовпало Ñ Ð¿ÐµÑ€Ð¸Ð¾Ð´Ð¾Ð¼ раннего вулканизма и продолжалоÑÑŒ около миллиарда лет. Примерно то же Ð²Ñ€ÐµÐ¼Ñ Ð·Ð°Ð½Ñло чаÑтичное плавление мантийных Ñиликатов[91]. Из-за меньшей Ñилы Ñ‚ÑжеÑти на МарÑе диапазон давлений в мантии МарÑа гораздо меньше, чем на Земле, а значит, в ней меньше фазовых переходов. ПредполагаетÑÑ, что фазовый переход оливина в шпинелевую модификацию начинаетÑÑ Ð½Ð° довольно больших глубинах — 800 км (400 км на Земле). Характер рельефа и другие признаки позволÑÑŽÑ‚ предположить наличие аÑтеноÑферы, ÑоÑтоÑщей из зон чаÑтично раÑплавленного вещеÑтва[96]. Ð”Ð»Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… районов МарÑа ÑоÑтавлена Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð°Ñ Ð³ÐµÐ¾Ð»Ð¾Ð³Ð¸Ñ‡ÐµÑÐºÐ°Ñ ÐºÐ°Ñ€Ñ‚Ð°[97]. + +СоглаÑно наблюдениÑм Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ñ‹ и анализу коллекции марÑианÑких метеоритов, поверхноÑÑ‚ÑŒ МарÑа ÑоÑтоит главным образом из базальта. ЕÑÑ‚ÑŒ некоторые оÑÐ½Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ñ€ÐµÐ´Ð¿Ð¾Ð»Ð°Ð³Ð°Ñ‚ÑŒ, что на чаÑти марÑианÑкой поверхноÑти материал ÑвлÑетÑÑ Ð±Ð¾Ð»ÐµÐµ кварцеÑодержащим, чем обычный базальт, и может быть подобен андезитным камнÑм на Земле. Однако Ñти же Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð¶Ð½Ð¾ толковать в пользу Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ñ ÐºÐ²Ð°Ñ€Ñ†ÐµÐ²Ð¾Ð³Ð¾ Ñтекла. Ð—Ð½Ð°Ñ‡Ð¸Ñ‚ÐµÐ»ÑŒÐ½Ð°Ñ Ñ‡Ð°ÑÑ‚ÑŒ более глубокого ÑÐ»Ð¾Ñ ÑоÑтоит из зерниÑтой пыли окÑида железа[98][99]. +Магнитное поле[править | править вики-текÑÑ‚] + +У МарÑа было зафикÑировано Ñлабое магнитное поле. + +СоглаÑно показаниÑм магнетометров Ñтанций «МарÑ-2» и «МарÑ-3», напрÑжённоÑÑ‚ÑŒ магнитного Ð¿Ð¾Ð»Ñ Ð½Ð° Ñкваторе ÑоÑтавлÑет около 60 гамм, на полюÑе — 120 гамм, что в 500 раз Ñлабее земного. По данным ÐМС «МарÑ-5», напрÑжённоÑÑ‚ÑŒ магнитного Ð¿Ð¾Ð»Ñ Ð½Ð° Ñкваторе ÑоÑтавлÑла 64 гаммы, а магнитный момент планетарного Ð´Ð¸Ð¿Ð¾Ð»Ñ â€” 2,4×1022 ÑÑ€Ñтед·Ñм²[100]. +Магнитное поле МарÑа + +Магнитное поле МарÑа крайне неуÑтойчиво, в различных точках планеты его напрÑжённоÑÑ‚ÑŒ может отличатьÑÑ Ð¾Ñ‚ 1,5 до 2 раз, а магнитные полюÑа не Ñовпадают Ñ Ñ„Ð¸Ð·Ð¸Ñ‡ÐµÑкими. Это говорит о том, что железное Ñдро МарÑа находитÑÑ Ð² Ñравнительной неподвижноÑти по отношению к его коре, то еÑÑ‚ÑŒ механизм планетарного динамо, ответÑтвенный за магнитное поле Земли, на МарÑе не работает. Ð¥Ð¾Ñ‚Ñ Ð½Ð° МарÑе не имеетÑÑ ÑƒÑтойчивого вÑепланетного магнитного полÑ[101], Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°Ð»Ð¸, что чаÑти планетной коры намагничены и что наблюдалаÑÑŒ Ñмена магнитных полюÑов Ñтих чаÑтей в прошлом. ÐамагниченноÑÑ‚ÑŒ данных чаÑтей оказалаÑÑŒ похожей на полоÑовые магнитные аномалии в мировом океане[102]. + +По одной теории, опубликованной в 1999 году и перепроверенной в 2005 году (Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ беÑпилотной Ñтанции Â«ÐœÐ°Ñ€Ñ Ð“Ð»Ð¾Ð±Ð°Ð» Сервейор»), Ñти полоÑÑ‹ демонÑтрируют тектонику плит 4 миллиарда лет назад — до того, как динамо-машина планеты прекратила выполнÑÑ‚ÑŒ Ñвою функцию, что поÑлужило причиной резкого оÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ð¼Ð°Ð³Ð½Ð¸Ñ‚Ð½Ð¾Ð³Ð¾ полÑ[103]. Причины такого резкого оÑÐ»Ð°Ð±Ð»ÐµÐ½Ð¸Ñ Ð½ÐµÑÑны. СущеÑтвует предположение, что функционирование динамо-машины 4 млрд. лет назад объÑÑнÑетÑÑ Ð½Ð°Ð»Ð¸Ñ‡Ð¸ÐµÐ¼ аÑтероида, который вращалÑÑ Ð½Ð° раÑÑтоÑнии 50—75 Ñ‚Ñ‹ÑÑч километров вокруг МарÑа и вызывал неÑтабильноÑÑ‚ÑŒ в его Ñдре. Затем аÑтероид ÑнизилÑÑ Ð´Ð¾ предела Роша и разрушилÑÑ[104]. Тем не менее, Ñто объÑÑнение Ñамо Ñодержит неÑÑные моменты и оÑпариваетÑÑ Ð² научном ÑообщеÑтве[105]. +Ð“Ð»Ð¾Ð±Ð°Ð»ÑŒÐ½Ð°Ñ Ð¼Ð¾Ð·Ð°Ð¸ÐºÐ° из 102 Ñнимков, полученных иÑкуÑÑтвенным Ñпутником МарÑа «Викинг-1» 22 Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 1980 +ГеологичеÑÐºÐ°Ñ Ð¸ÑториÑ[править | править вики-текÑÑ‚] + +СоглаÑно одной из гипотез, в далёком прошлом в результате ÑÑ‚Ð¾Ð»ÐºÐ½Ð¾Ð²ÐµÐ½Ð¸Ñ Ñ ÐºÑ€ÑƒÐ¿Ð½Ñ‹Ð¼ небеÑным телом произошла оÑтановка Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ Ñдра[106], а также Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¾Ñновного объёма атмоÑферы. ÐŸÐ¾Ñ‚ÐµÑ€Ñ Ð»ÐµÐ³ÐºÐ¸Ñ… атомов и молекул из атмоÑферы — ÑледÑтвие Ñлабого притÑÐ¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа. СчитаетÑÑ, что Ð¿Ð¾Ñ‚ÐµÑ€Ñ Ð¼Ð°Ð³Ð½Ð¸Ñ‚Ð½Ð¾Ð³Ð¾ Ð¿Ð¾Ð»Ñ Ð¿Ñ€Ð¾Ð¸Ð·Ð¾ÑˆÐ»Ð° около 4 млрд. лет назад. Ð’ÑледÑтвие ÑлабоÑти магнитного Ð¿Ð¾Ð»Ñ Ñолнечный ветер практичеÑки беÑпрепÑÑ‚Ñтвенно проникает в атмоÑферу МарÑа, и многие из фотохимичеÑких реакций под дейÑтвием Ñолнечной радиации, которые на Земле проиÑходÑÑ‚ в ионоÑфере и выше, на МарÑе могут наблюдатьÑÑ Ð¿Ñ€Ð°ÐºÑ‚Ð¸Ñ‡ÐµÑки у Ñамой его поверхноÑти. + +ГеологичеÑÐºÐ°Ñ Ð¸ÑÑ‚Ð¾Ñ€Ð¸Ñ ÐœÐ°Ñ€Ñа заключает в ÑÐµÐ±Ñ Ñ‚Ñ€Ð¸ нижеÑледующие Ñпохи[107][108]: + +ÐойÑÐºÐ°Ñ Ñра[109] (названа в чеÑÑ‚ÑŒ «Ðоевой земли», района МарÑа): формирование наиболее Ñтарой ÑохранившейÑÑ Ð´Ð¾ наших дней поверхноÑти МарÑа. ПродолжалаÑÑŒ в период 4,5—3,5 млрд. лет назад. Ð’ Ñту Ñпоху поверхноÑÑ‚ÑŒ была изрубцована многочиÑленными ударными кратерами. Плато провинции ФарÑида было, вероÑтно, Ñформировано в Ñтот период Ñ Ð¸Ð½Ñ‚ÐµÐ½Ñивным обтеканием водой позднее. + +ГеÑперийÑÐºÐ°Ñ Ñра: от 3,5 млрд. лет назад до 2,9—3,3 млрд. лет назад. Эта Ñпоха отмечена образованием огромных лавовых полей. + +ÐмазонийÑÐºÐ°Ñ Ñра (названа в чеÑÑ‚ÑŒ «ÐмазонÑкой равнины» на МарÑе): 2,9—3,3 млрд. лет назад до наших дней. Районы, образовавшиеÑÑ Ð² Ñту Ñпоху, имеют очень мало метеоритных кратеров, но во вÑём оÑтальном они полноÑтью различаютÑÑ. Гора Олимп Ñформирована в Ñтот период. Ð’ Ñто Ð²Ñ€ÐµÐ¼Ñ Ð² других чаÑÑ‚ÑÑ… МарÑа разливалиÑÑŒ лавовые потоки. +Спутники[править | править вики-текÑÑ‚] +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: Спутники МарÑа +См. также: ТроÑнÑкие аÑтероиды МарÑа + + ФобоÑ, ÑнÑтый 23 марта 2008 года Ñпутником Mars Reconnaissance Orbiter + + ДеймоÑ, ÑнÑтый 21 Ñ„ÐµÐ²Ñ€Ð°Ð»Ñ 2009 года Ñпутником Mars Reconnaissance Orbiter + + Прохождение ФобоÑа по диÑку Солнца. Снимки «Оппортьюнити» + +ЕÑтеÑтвенными Ñпутниками МарÑа ÑвлÑÑŽÑ‚ÑÑ Ð¤Ð¾Ð±Ð¾Ñ Ð¸ ДеймоÑ. Оба они открыты американÑким аÑтрономом ÐÑафом Холлом в 1877 году. Ð¤Ð¾Ð±Ð¾Ñ Ð¸ Ð”ÐµÐ¹Ð¼Ð¾Ñ Ð¸Ð¼ÐµÑŽÑ‚ неправильную форму и очень маленькие размеры. По одной из гипотез, они могут предÑтавлÑÑ‚ÑŒ Ñобой захваченные гравитационным полем МарÑа аÑтероиды наподобие (5261) Эврика из ТроÑнÑкой группы аÑтероидов. Спутники названы в чеÑÑ‚ÑŒ перÑонажей, Ñопровождающих бога ÐреÑа (то еÑÑ‚ÑŒ МарÑа), — ФобоÑа и ДеймоÑа, олицетворÑющих Ñтрах и ужаÑ, которые помогали богу войны в битвах[110]. + +Оба Ñпутника вращаютÑÑ Ð²Ð¾ÐºÑ€ÑƒÐ³ Ñвоих оÑей Ñ Ñ‚ÐµÐ¼ же периодом, что и вокруг МарÑа, поÑтому вÑегда повёрнуты к планете одной и той же Ñтороной (Ñто вызвано Ñффектом приливного захвата и характерно Ð´Ð»Ñ Ð±Ð¾Ð»ÑŒÑˆÐ¸Ð½Ñтва Ñпутников планет в Солнечной ÑиÑтеме, в том чиÑле Ð´Ð»Ñ Ð›ÑƒÐ½Ñ‹). Приливное воздейÑтвие МарÑа поÑтепенно замедлÑет движение ФобоÑа, и, в конце концов, приведёт к падению Ñпутника на ÐœÐ°Ñ€Ñ (при Ñохранении текущей тенденции), или к его раÑпаду[111]. Ðапротив, Ð”ÐµÐ¹Ð¼Ð¾Ñ ÑƒÐ´Ð°Ð»ÑетÑÑ Ð¾Ñ‚ МарÑа. + +Орбитальный период ФобоÑа меньше, чем период Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа, поÑтому Ð´Ð»Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°Ñ‚ÐµÐ»Ñ Ð½Ð° поверхноÑти планеты Ð¤Ð¾Ð±Ð¾Ñ (в отличие от ДеймоÑа и вообще от вÑех извеÑтных еÑтеÑтвенных Ñпутников планет Солнечной ÑиÑтемы, кроме Метиды и ÐдраÑтеи) воÑходит на западе и заходит на воÑтоке[111]. + +Оба Ñпутника имеют форму, приближающуюÑÑ Ðº трёхоÑному ÑллипÑоиду, Ð¤Ð¾Ð±Ð¾Ñ (26,8×22,4×18,4 км)[6] неÑколько крупнее ДеймоÑа (15×12,2×11 км)[112]. ПоверхноÑÑ‚ÑŒ ДеймоÑа выглÑдит гораздо более гладкой за Ñчёт того, что большинÑтво кратеров покрыто тонкозерниÑтым вещеÑтвом. Очевидно, на ФобоÑе, более близком к планете и более маÑÑивном, вещеÑтво, выброшенное при ударах метеоритов, либо наноÑило повторные удары по поверхноÑти, либо падало на МарÑ, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº на ДеймоÑе оно долгое Ð²Ñ€ÐµÐ¼Ñ Ð¾ÑтавалоÑÑŒ на орбите вокруг Ñпутника, поÑтепенно оÑаждаÑÑÑŒ и ÑÐºÑ€Ñ‹Ð²Ð°Ñ Ð½ÐµÑ€Ð¾Ð²Ð½Ð¾Ñти рельефа. +Жизнь[править | править вики-текÑÑ‚] +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: Жизнь на МарÑе +ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð²Ð¾Ð¿Ñ€Ð¾Ñа[править | править вики-текÑÑ‚] + +ПопулÑÑ€Ð½Ð°Ñ Ð¸Ð´ÐµÑ, что ÐœÐ°Ñ€Ñ Ð½Ð°Ñелён разумными марÑианами, широко раÑпроÑтранилаÑÑŒ в конце XIX века. + +ÐÐ°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¡ÐºÐ¸Ð°Ð¿Ð°Ñ€ÐµÐ»Ð»Ð¸ так называемых каналов, в Ñочетании Ñ ÐºÐ½Ð¸Ð³Ð¾Ð¹ ПерÑÐ¸Ð²Ð°Ð»Ñ Ð›Ð¾ÑƒÑлла по той же теме Ñделали популÑрной идею о планете, климат которой ÑтановилÑÑ Ð²ÑÑ‘ Ñуше, холоднее, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ ÑƒÐ¼Ð¸Ñ€Ð°Ð»Ð° и на которой ÑущеÑтвовала древнÑÑ Ñ†Ð¸Ð²Ð¸Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ, выполнÑÑŽÑ‰Ð°Ñ Ð¸Ñ€Ñ€Ð¸Ð³Ð°Ñ†Ð¸Ð¾Ð½Ð½Ñ‹Ðµ работы[113]. + + Карта МарÑа Скиапарелли, 1888 г. + + МарÑианÑкие каналы, зариÑованные аÑтрономом П. ЛоуÑллом, 1898. + +Другие многочиÑленные Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð¸ объÑÐ²Ð»ÐµÐ½Ð¸Ñ Ð¸Ð·Ð²ÐµÑтных лиц породили вокруг Ñтой темы так называемую «МарÑианÑкую лихорадку» (англ. Mars Fever)[114]. Ð’ 1899 году во Ð²Ñ€ÐµÐ¼Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð°Ñ‚Ð¼Ð¾Ñферных радиопомех Ñ Ð¸Ñпользованием приёмников в КолорадÑкой обÑерватории, изобретатель Ðикола ТеÑла наблюдал повторÑющийÑÑ Ñигнал. Он выÑказал догадку, что Ñто может быть радиоÑигнал Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… планет, например, МарÑа. Ð’ интервью 1901 года ТеÑла Ñказал, что ему пришла в голову мыÑль о том, что помехи могут быть вызваны иÑкуÑÑтвенно. Ð¥Ð¾Ñ‚Ñ Ð¾Ð½ не Ñмог раÑшифровать их значение, Ð´Ð»Ñ Ð½ÐµÐ³Ð¾ было невозможным то, что они возникли Ñовершенно Ñлучайно. По его мнению, Ñто было приветÑтвие одной планеты другой[115]. + +Гипотеза ТеÑлы вызвала горÑчую поддержку извеÑтного британÑкого учёного-физика УильÑма ТомÑона (лорда Кельвина), который, поÑетив СШРв 1902 году, Ñказал, что, по его мнению, ТеÑла поймал Ñигнал марÑиан, поÑланный в СШÐ[116]. Однако ещё до Ð¾Ñ‚Ð±Ñ‹Ñ‚Ð¸Ñ Ð¸Ð· Ðмерики Кельвин Ñтал решительно отрицать Ñто заÑвление: «Ðа Ñамом деле Ñ Ñказал, что жители МарÑа, еÑли они ÑущеÑтвуют, неÑомненно могут видеть Ðью-Йорк, в чаÑтноÑти, Ñвет от ÑлектричеÑтва»[117]. +ФактичеÑкие данные[править | править вики-текÑÑ‚] + +Ðаучные гипотезы о ÑущеÑтвовании в прошлом жизни на МарÑе приÑутÑтвуют давно. По результатам наблюдений Ñ Ð—ÐµÐ¼Ð»Ð¸ и данным коÑмичеÑкого аппарата «МарÑ-ÑкÑпреÑÑ» в атмоÑфере МарÑа обнаружен метан. Позднее, в 2014 году, марÑоход ÐÐСРCuriosity зафикÑировал вÑплеÑк ÑÐ¾Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¼ÐµÑ‚Ð°Ð½Ð° в атмоÑфере МарÑа и обнаружил органичеÑкие молекулы в образцах, извлечённых в ходе Ð±ÑƒÑ€ÐµÐ½Ð¸Ñ Ñкалы Камберленд.[118] +РаÑпределение метана в атмоÑфере МарÑа в летний период в Ñеверном полушарии. + +Ð’ уÑловиÑÑ… МарÑа Ñтот газ довольно быÑтро разлагаетÑÑ, поÑтому должен ÑущеÑтвовать поÑтоÑнный иÑточник его пополнениÑ. Таким иÑточником может быть либо геологичеÑÐºÐ°Ñ Ð°ÐºÑ‚Ð¸Ð²Ð½Ð¾ÑÑ‚ÑŒ (но дейÑтвующие вулканы на МарÑе не обнаружены), либо жизнедеÑтельноÑÑ‚ÑŒ бактерий. ИнтереÑно, что в некоторых метеоритах марÑианÑкого проиÑÑ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ñ‹ образованиÑ, по форме напоминающие клетки, Ñ…Ð¾Ñ‚Ñ Ð¾Ð½Ð¸ и уÑтупают мельчайшим земным организмам по размерам[118][119]. Одним из таких метеоритов ÑвлÑетÑÑ ALH 84001, найденный в Ðнтарктиде в 1984 году. +ALH84001 под микроÑкопом. + +Важные Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ Ñделаны марÑоходом «Curiosity». Ð’ декабре 2012 года были получены данные о наличии на МарÑе органичеÑких вещеÑтв, а также перхлоратов. Те же иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾ÐºÐ°Ð·Ð°Ð»Ð¸ наличие водÑного пара в нагретых образцах грунта[120]. ИнтереÑным фактом ÑвлÑетÑÑ Ñ‚Ð¾, что «Curiosity» на МарÑе приземлилÑÑ Ð½Ð° дно выÑохшего озера[121]. + +Ðнализ наблюдений говорит, что планета ранее имела значительно более благоприÑтные Ð´Ð»Ñ Ð¶Ð¸Ð·Ð½Ð¸ уÑловиÑ, нежели теперь. СоглаÑно программе «Викинг», оÑущеÑтвлённой в Ñередине 1970-Ñ… годов, была проведена ÑÐµÑ€Ð¸Ñ ÑкÑпериментов Ð´Ð»Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ð¼Ð¸ÐºÑ€Ð¾Ð¾Ñ€Ð³Ð°Ð½Ð¸Ð·Ð¼Ð¾Ð² в марÑианÑкой почве. Она дала положительные результаты: например, временное увеличение Ð²Ñ‹Ð´ÐµÐ»ÐµÐ½Ð¸Ñ CO2 при помещении чаÑтиц почвы в воду и питательную Ñреду. Однако затем данное ÑвидетельÑтво жизни на МарÑе было оÑпорено учёными команды «Викингов»[122]. Это привело к их продолжительным Ñпорам Ñ ÑƒÑ‡Ñ‘Ð½Ñ‹Ð¼ из NASA Гильбертом Левиным, который утверждал, что «Викинг» обнаружил жизнь. ПоÑле переоценки данных «Викинга» в Ñвете Ñовременных научных знаний об ÑкÑтремофилах было уÑтановлено, что проведённые ÑкÑперименты были недоÑтаточно Ñовершенны Ð´Ð»Ñ Ð¾Ð±Ð½Ð°Ñ€ÑƒÐ¶ÐµÐ½Ð¸Ñ Ñтих форм жизни. Более того, Ñти теÑÑ‚Ñ‹ могли убить организмы, даже еÑли поÑледние ÑодержалиÑÑŒ в пробах[123]. ТеÑÑ‚Ñ‹, проведённые в рамках программы «ФеникÑ», показали, что почва имеет очень щелочной pH и Ñодержит магний, натрий, калий и хлориды[124]. Питательных вещеÑтв в почве доÑтаточно Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸, однако жизненные формы должны иметь защиту от интенÑивного ультрафиолетового Ñвета[125]. + +Ðа ÑегоднÑшний день уÑловием Ð´Ð»Ñ Ñ€Ð°Ð·Ð²Ð¸Ñ‚Ð¸Ñ Ð¸ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸ на планете ÑчитаетÑÑ Ð½Ð°Ð»Ð¸Ñ‡Ð¸Ðµ жидкой воды на её поверхноÑти, а также нахождение орбиты планеты в так называемой зоне обитаемоÑти, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð² Солнечной ÑиÑтеме начинаетÑÑ Ð·Ð° орбитой Венеры и заканчиваетÑÑ Ð±Ð¾Ð»ÑŒÑˆÐ¾Ð¹ полуоÑью орбиты МарÑа[126]. Вблизи Ð¿ÐµÑ€Ð¸Ð³ÐµÐ»Ð¸Ñ ÐœÐ°Ñ€Ñ Ð½Ð°Ñ…Ð¾Ð´Ð¸Ñ‚ÑÑ Ð²Ð½ÑƒÑ‚Ñ€Ð¸ Ñтой зоны, однако Ñ‚Ð¾Ð½ÐºÐ°Ñ Ð°Ñ‚Ð¼Ð¾Ñфера Ñ Ð½Ð¸Ð·ÐºÐ¸Ð¼ давлением препÑÑ‚Ñтвует поÑвлению жидкой воды на длительный период. Ðедавние ÑвидетельÑтва говорÑÑ‚ о том, что Ð»ÑŽÐ±Ð°Ñ Ð²Ð¾Ð´Ð° на поверхноÑти МарÑа ÑвлÑетÑÑ Ñлишком Ñолёной и киÑлотной Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¿Ð¾ÑтоÑнной земноподобной жизни[127]. + +ОтÑутÑтвие магнитоÑферы и крайне Ñ€Ð°Ð·Ñ€ÐµÐ¶Ñ‘Ð½Ð½Ð°Ñ Ð°Ñ‚Ð¼Ð¾Ñфера МарÑа также ÑвлÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¾Ð±Ð»ÐµÐ¼Ð¾Ð¹ Ð´Ð»Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶Ð°Ð½Ð¸Ñ Ð¶Ð¸Ð·Ð½Ð¸. Ðа поверхноÑти планеты идёт очень Ñлабое перемещение тепловых потоков, она плохо изолирована от бомбардировки чаÑтицами Ñолнечного ветра; помимо Ñтого, при нагревании вода мгновенно иÑпарÑетÑÑ, Ð¼Ð¸Ð½ÑƒÑ Ð¶Ð¸Ð´ÐºÐ¾Ðµ ÑоÑтоÑние из-за низкого давлениÑ. Кроме того, ÐœÐ°Ñ€Ñ Ñ‚Ð°ÐºÐ¶Ðµ находитÑÑ Ð½Ð° пороге Ñ‚. н. «геологичеÑкой Ñмерти». Окончание вулканичеÑкой активноÑти, по вÑей видимоÑти, оÑтановило круговорот минералов и химичеÑких Ñлементов между поверхноÑтью и внутренней чаÑтью планеты[128]. +Терраформированный ÐœÐ°Ñ€Ñ Ð² предÑтавлении художника. + +БлизоÑÑ‚ÑŒ МарÑа и отноÑительное его ÑходÑтво Ñ Ð—ÐµÐ¼Ð»Ñ‘Ð¹ породило Ñ€Ñд фантаÑтичеÑких проектов Ñ‚ÐµÑ€Ñ€Ð°Ñ„Ð¾Ñ€Ð¼Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¸ колонизации МарÑа землÑнами в будущем. + +МарÑоход Curiosity обнаружил Ñразу два иÑточника органичеÑких молекул на поверхноÑти МарÑа. Помимо кратковременного ÑƒÐ²ÐµÐ»Ð¸Ñ‡ÐµÐ½Ð¸Ñ Ð´Ð¾Ð»Ð¸ метана в атмоÑфере, аппарат зафикÑировал наличие углеродных Ñоединений в порошкообразном образце, оÑтавшемÑÑ Ð¾Ñ‚ Ð±ÑƒÑ€ÐµÐ½Ð¸Ñ Ð¼Ð°Ñ€ÑианÑкой Ñкалы. Первое открытие позволил Ñделать инÑтрумент SAM на борту марÑохода. За 20 меÑÑцев он 12 раз измерил ÑоÑтав марÑианÑкой атмоÑферы. Ð’ двух ÑлучаÑÑ… — в конце 2013 года и начале 2014 — Curiosity удалоÑÑŒ обнаружить деÑÑтикратное увеличение Ñредней доли метана. Этот вÑплеÑк, по мнению членов научной команды марÑохода, ÑвидетельÑтвует об обнаружении локального иÑточника метана. Имеет ли он биологичеÑкое или же иное проиÑхождение, ÑпециалиÑÑ‚Ñ‹ утверждать затруднÑÑŽÑ‚ÑÑ Ð²ÑледÑтвие нехватки данных Ð´Ð»Ñ Ð¿Ð¾Ð»Ð½Ð¾Ñ†ÐµÐ½Ð½Ð¾Ð³Ð¾ анализа. +ÐÑтрономичеÑкие Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти МарÑа[править | править вики-текÑÑ‚] + +ПоÑле поÑадок автоматичеÑких аппаратов на поверхноÑÑ‚ÑŒ МарÑа поÑвилаÑÑŒ возможноÑÑ‚ÑŒ веÑти аÑтрономичеÑкие Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ð½ÐµÐ¿Ð¾ÑредÑтвенно Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти планеты. Ð’ÑледÑтвие аÑтрономичеÑкого Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа в Солнечной ÑиÑтеме, характериÑтик атмоÑферы, периода Ð¾Ð±Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа и его Ñпутников картина ночного неба МарÑа (и аÑтрономичеÑких Ñвлений, наблюдаемых Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹) отличаетÑÑ Ð¾Ñ‚ земной и во многом предÑтавлÑетÑÑ Ð½ÐµÐ¾Ð±Ñ‹Ñ‡Ð½Ð¾Ð¹ и интереÑной. +ÐебеÑÐ½Ð°Ñ Ñфера[править | править вики-текÑÑ‚] + +Северный Ð¿Ð¾Ð»ÑŽÑ Ð½Ð° МарÑе, вÑледÑтвие наклона оÑи планеты, находитÑÑ Ð² Ñозвездии Ð›ÐµÐ±ÐµÐ´Ñ (Ñкваториальные координаты: прÑмое воÑхождение 21ч 10м 42Ñ, Ñклонение +52° 53.0′ и не отмечен Ñркой звездой: Ð±Ð»Ð¸Ð¶Ð°Ð¹ÑˆÐ°Ñ Ðº полюÑу — туÑÐºÐ»Ð°Ñ Ð·Ð²ÐµÐ·Ð´Ð° шеÑтой величины BD +52 2880 (другие её Ð¾Ð±Ð¾Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ â€” HR 8106, HD 201834, SAO 33185). Южный Ð¿Ð¾Ð»ÑŽÑ Ð¼Ð¸Ñ€Ð° (координаты 9ч 10м 42Ñ Ð¸ −52° 53,0) находитÑÑ Ð² паре градуÑов от звезды Каппа ПаруÑов (Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð·Ð²Ñ‘Ð·Ð´Ð½Ð°Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð° 2,5) — её, в принципе, можно Ñчитать Южной ПолÑрной звездой МарÑа. + +Вид неба похож на наблюдаемый Ñ Ð—ÐµÐ¼Ð»Ð¸, Ñ Ð¾Ð´Ð½Ð¸Ð¼ отличием: при наблюдении годичного Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð¡Ð¾Ð»Ð½Ñ†Ð° по ÑозвездиÑм Зодиака оно (как и другие планеты, Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ Ð—ÐµÐ¼Ð»ÑŽ), Ð²Ñ‹Ð¹Ð´Ñ Ð¸Ð· воÑточной чаÑти ÑÐ¾Ð·Ð²ÐµÐ·Ð´Ð¸Ñ Ð Ñ‹Ð±, будет проходить в течение 6 дней через Ñеверную чаÑÑ‚ÑŒ ÑÐ¾Ð·Ð²ÐµÐ·Ð´Ð¸Ñ ÐšÐ¸Ñ‚Ð° перед тем, как Ñнова вÑтупить в западную чаÑÑ‚ÑŒ Рыб. + +Во Ð²Ñ€ÐµÐ¼Ñ Ð²Ð¾Ñхода и захода Солнца марÑианÑкое небо в зените имеет краÑновато-розовый цвет[129], а в непоÑредÑтвенной близоÑти к диÑку Солнца — от голубого до фиолетового, что Ñовершенно противоположно картине земных зорь. +Закат на МарÑе 19 Ð¼Ð°Ñ 2005 года. Снимок марÑохода «Спирит», который находилÑÑ Ð² кратере ГуÑева. +Закат на МарÑе 19 Ð¼Ð°Ñ 2005 года. Снимок марÑохода «Спирит», который находилÑÑ Ð² кратере ГуÑева. + +Ð’ полдень небо МарÑа жёлто-оранжевое. Причина таких отличий от цветовой гаммы земного неба — ÑвойÑтва тонкой, разреженной, Ñодержащей взвешенную пыль атмоÑферы МарÑа. Ðа МарÑе Ñ€ÑлеевÑкое раÑÑеÑние лучей (которое на Земле и ÑвлÑетÑÑ Ð¿Ñ€Ð¸Ñ‡Ð¸Ð½Ð¾Ð¹ голубого цвета неба) играет незначительную роль, Ñффект его Ñлаб, но проÑвлÑетÑÑ Ð² виде голубого ÑÐ²ÐµÑ‡ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ воÑходе\закате Солнца, когда Ñвет проходит более толÑтый Ñлой воздуха. Предположительно, жёлто-Ð¾Ñ€Ð°Ð½Ð¶ÐµÐ²Ð°Ñ Ð¾ÐºÑ€Ð°Ñка неба также вызываетÑÑ Ð¿Ñ€Ð¸ÑутÑтвием 1 % магнетита в чаÑтицах пыли, поÑтоÑнно взвешенной в марÑианÑкой атмоÑфере и поднимаемой Ñезонными пылевыми бурÑми. Сумерки начинаютÑÑ Ð·Ð°Ð´Ð¾Ð»Ð³Ð¾ до воÑхода Солнца и длÑÑ‚ÑÑ Ð´Ð¾Ð»Ð³Ð¾ поÑле его захода. Иногда цвет марÑианÑкого неба приобретает фиолетовый оттенок в результате раÑÑеÑÐ½Ð¸Ñ Ñвета на микрочаÑтицах водÑного льда в облаках (поÑледнее — довольно редкое Ñвление)[129]. +Солнце и планеты[править | править вики-текÑÑ‚] + +Угловой размер Солнца, наблюдаемый Ñ ÐœÐ°Ñ€Ñа, меньше видимого Ñ Ð—ÐµÐ¼Ð»Ð¸ и ÑоÑтавлÑет 2â„3 от поÑледнего. Меркурий Ñ ÐœÐ°Ñ€Ñа будет практичеÑки недоÑтупен Ð´Ð»Ñ Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ð¹ невооружённым глазом из-за чрезвычайной близоÑти к Солнцу. Самой Ñркой планетой на небе МарÑа ÑвлÑетÑÑ Ð’ÐµÐ½ÐµÑ€Ð°, на втором меÑте — Юпитер (его четыре крупнейших Ñпутника чаÑÑ‚ÑŒ времени можно наблюдать без телеÑкопа), на третьем — ЗемлÑ[130]. + +Ð—ÐµÐ¼Ð»Ñ Ð¿Ð¾ отношению к МарÑу ÑвлÑетÑÑ Ð²Ð½ÑƒÑ‚Ñ€ÐµÐ½Ð½ÐµÐ¹ планетой, так же, как Венера Ð´Ð»Ñ Ð—ÐµÐ¼Ð»Ð¸. СоответÑтвенно, Ñ ÐœÐ°Ñ€Ñа Ð—ÐµÐ¼Ð»Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°ÐµÑ‚ÑÑ ÐºÐ°Ðº утреннÑÑ Ð¸Ð»Ð¸ вечернÑÑ Ð·Ð²ÐµÐ·Ð´Ð°, воÑходÑÑ‰Ð°Ñ Ð¿ÐµÑ€ÐµÐ´ раÑÑветом или Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð½Ð° вечернем небе поÑле захода Солнца. + +МакÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ ÑÐ»Ð¾Ð½Ð³Ð°Ñ†Ð¸Ñ Ð—ÐµÐ¼Ð»Ð¸ на небе МарÑа ÑоÑтавлÑет 38 градуÑов. Ð”Ð»Ñ Ð½ÐµÐ²Ð¾Ð¾Ñ€ÑƒÐ¶Ñ‘Ð½Ð½Ð¾Ð³Ð¾ глаза Ð—ÐµÐ¼Ð»Ñ Ð±ÑƒÐ´ÐµÑ‚ видна как ÑÑ€ÐºÐ°Ñ (макÑÐ¸Ð¼Ð°Ð»ÑŒÐ½Ð°Ñ Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ Ð·Ð²Ñ‘Ð·Ð´Ð½Ð°Ñ Ð²ÐµÐ»Ð¸Ñ‡Ð¸Ð½Ð° около −2,5m) Ð·ÐµÐ»ÐµÐ½Ð¾Ð²Ð°Ñ‚Ð°Ñ Ð·Ð²ÐµÐ·Ð´Ð°, Ñ€Ñдом Ñ ÐºÐ¾Ñ‚Ð¾Ñ€Ð¾Ð¹ будет легко различима Ð¶ÐµÐ»Ñ‚Ð¾Ð²Ð°Ñ‚Ð°Ñ Ð¸ более туÑÐºÐ»Ð°Ñ (около +0,9m) звёздочка Луны[131]. Ð’ телеÑкоп оба объекта будут видны Ñ Ð¾Ð´Ð¸Ð½Ð°ÐºÐ¾Ð²Ñ‹Ð¼Ð¸ фазами. Обращение Луны вокруг Земли будет наблюдатьÑÑ Ñ ÐœÐ°Ñ€Ñа Ñледующим образом: на макÑимальном угловом удалении Луны от Земли невооружённый глаз легко разделит Луну и Землю: через неделю «звёздочки» Луны и Земли ÑольютÑÑ Ð² неразделимую глазом единую звезду, ещё через неделю Луна будет Ñнова видна на макÑимальном раÑÑтоÑнии, но уже Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð¹ Ñтороны от Земли. ПериодичеÑки наблюдатель на МарÑе Ñможет видеть проход (транзит) Луны по диÑку Земли либо, наоборот, покрытие Луны диÑком Земли. МакÑимальное видимое удаление Луны от Земли (и их Ð²Ð¸Ð´Ð¸Ð¼Ð°Ñ ÑркоÑÑ‚ÑŒ) при наблюдении Ñ ÐœÐ°Ñ€Ñа будет значительно изменÑÑ‚ÑŒÑÑ Ð² завиÑимоÑти от взаимного Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð—ÐµÐ¼Ð»Ð¸ и МарÑа, и, ÑоответÑтвенно, раÑÑтоÑÐ½Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ планетами. Ð’ Ñпохи противоÑтоÑний оно ÑоÑтавит около 17 минут дуги (около половины углового диаметра Солнца и Луны при наблюдении Ñ Ð—ÐµÐ¼Ð»Ð¸), на макÑимальном удалении Земли и МарÑа — 3,5 минуты дуги. ЗемлÑ, как и другие планеты, будет наблюдатьÑÑ Ð² полоÑе Ñозвездий Зодиака. ÐÑтроном на МарÑе также Ñможет наблюдать прохождение Земли по диÑку Солнца; ближайшее такое Ñвление произойдёт 10 ноÑÐ±Ñ€Ñ 2084 года[132]. +ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ[править | править вики-текÑÑ‚] +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ИÑÑледование МарÑа +ИÑÑледование МарÑа клаÑÑичеÑкими методами аÑтрономии[править | править вики-текÑÑ‚] +Ð˜Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа Ñ Ñ€Ð°Ð·Ð½Ð¾Ð¹ Ñтепенью детализации в разные годы. + +Первые Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа проводилиÑÑŒ до Ð¸Ð·Ð¾Ð±Ñ€ÐµÑ‚ÐµÐ½Ð¸Ñ Ñ‚ÐµÐ»ÐµÑкопа. Это были позиционные Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ Ñ Ñ†ÐµÐ»ÑŒÑŽ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ð¹ планеты по отношению к звёздам. СущеÑтвование МарÑа как блуждающего объекта в ночном небе было пиÑьменно заÑвидетельÑтвовано древнеегипетÑкими аÑтрономами в 1534 году до н. Ñ. Ими же было уÑтановлено ретроградное (попÑтное) движение планеты и раÑÑчитана Ñ‚Ñ€Ð°ÐµÐºÑ‚Ð¾Ñ€Ð¸Ñ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð²Ð¼ÐµÑте Ñ Ñ‚Ð¾Ñ‡ÐºÐ¾Ð¹, где планета менÑет Ñвоё движение отноÑительно Земли Ñ Ð¿Ñ€Ñмого на попÑтное[133]. + +Ð’ вавилонÑкой планетарной теории были впервые получены временные Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ð°Ñ€Ð½Ð¾Ð³Ð¾ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа и уточнено положение планеты на ночном небе[134][135]. ПользуÑÑÑŒ данными египтÑн и вавилонÑн, древнегречеÑкие (ÑллиниÑтичеÑкие) филоÑофы и аÑтрономы разработали подробную геоцентричеÑкую модель Ð´Ð»Ñ Ð¾Ð±ÑŠÑÑÐ½ÐµÐ½Ð¸Ñ Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚. СпуÑÑ‚Ñ Ð½ÐµÑколько веков индийÑкими и иÑламÑкими аÑтрономами был оценен размер МарÑа и раÑÑтоÑние до него от Земли. Ð’ XVI веке Ðиколай Коперник предложил гелиоцентричеÑкую модель Ð´Ð»Ñ Ð¾Ð¿Ð¸ÑÐ°Ð½Ð¸Ñ Ð¡Ð¾Ð»Ð½ÐµÑ‡Ð½Ð¾Ð¹ ÑиÑтемы Ñ ÐºÑ€ÑƒÐ³Ð¾Ð²Ñ‹Ð¼Ð¸ планетарными орбитами. Его результаты были переÑмотрены Иоганном Кеплером, который ввёл более точную ÑллиптичеÑкую орбиту МарÑа, Ñовпадающую Ñ Ð½Ð°Ð±Ð»ÑŽÐ´Ð°ÐµÐ¼Ð¾Ð¹. + +ГолландÑкий аÑтроном ХриÑтиан Ð“ÑŽÐ¹Ð³ÐµÐ½Ñ Ð¿ÐµÑ€Ð²Ñ‹Ð¼ ÑоÑтавил карту поверхноÑти МарÑа, отражающую множеÑтво деталей. 28 ноÑÐ±Ñ€Ñ 1659 года он Ñделал неÑколько риÑунков МарÑа, на которых были отображены различные темные облаÑти, позже ÑопоÑтавленные Ñ Ð¿Ð»Ð°Ñ‚Ð¾ Большой Сирт[136]. + +Предположительно первые наблюдениÑ, уÑтановившие ÑущеÑтвование у МарÑа ледÑной шапки на южном полюÑе, были Ñделаны итальÑнÑким аÑтрономом Джованни Доменико КаÑÑини в 1666 году. Ð’ том же году он при наблюдениÑÑ… МарÑа делал зариÑовки видимых деталей поверхноÑти и выÑÑнил, что через 36 или 37 дней Ð¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ð¸Ñ Ð´ÐµÑ‚Ð°Ð»ÐµÐ¹ поверхноÑти повторÑÑŽÑ‚ÑÑ, а затем вычиÑлил период Ð²Ñ€Ð°Ñ‰ÐµÐ½Ð¸Ñ â€” 24 ч. 40 м. (Ñтот результат отличаетÑÑ Ð¾Ñ‚ правильного Ð·Ð½Ð°Ñ‡ÐµÐ½Ð¸Ñ Ð¼ÐµÐ½ÐµÐµ чем на 3 минуты)[136]. + +Ð’ 1672 году ХриÑтиан Ð“ÑŽÐ¹Ð³ÐµÐ½Ñ Ð·Ð°Ð¼ÐµÑ‚Ð¸Ð» нечёткую белую шапочку и на Ñеверном полюÑе[137] + +Ð’ 1888 году Джованни Скиапарелли дал первые имена отдельным деталÑм поверхноÑти[138]: Ð¼Ð¾Ñ€Ñ Ðфродиты, ЭритрейÑкое, ÐдриатичеÑкое, КиммерийÑкое; озёра Солнца, Лунное и ФеникÑ. + +РаÑцвет телеÑкопичеÑких наблюдений МарÑа пришёлÑÑ Ð½Ð° конец XIX — Ñередину XX века. Во многом он обуÑловлен общеÑтвенным интереÑом и извеÑтными научными Ñпорами вокруг наблюдавшихÑÑ Ð¼Ð°Ñ€ÑианÑких каналов. Среди аÑтрономов докоÑмичеÑкой Ñры, проводивших телеÑкопичеÑкие Ð½Ð°Ð±Ð»ÑŽÐ´ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа в Ñтот период, наиболее извеÑтны Скиапарелли, ПерÑиваль Ловелл, Слайфер, Ðнтониади, Барнард, Жарри-Делож, Л. Эдди, Тихов, Вокулёр. Именно ими были заложены оÑновы ареографии и ÑоÑтавлены первые подробные карты поверхноÑти МарÑа — Ñ…Ð¾Ñ‚Ñ Ð¾Ð½Ð¸ и оказалиÑÑŒ практичеÑки полноÑтью неверными поÑле полётов к МарÑу автоматичеÑких зондов. +ИÑÑледование МарÑа коÑмичеÑкими аппаратами[править | править вики-текÑÑ‚] +Изучение Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ орбитальных телеÑкопов[править | править вики-текÑÑ‚] +КоÑмичеÑкий телеÑкоп «Хаббл». + +Ð”Ð»Ñ ÑиÑтематичеÑкого иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа были иÑпользованы[139] возможноÑти коÑмичеÑкого телеÑкопа «Хаббл» (КТХ или HST — Hubble Space Telescope), при Ñтом были получены фотографии МарÑа Ñ Ñамым выÑоким разрешением из когда-либо Ñделанных на Земле[140]. КТХ может Ñоздать Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð»ÑƒÑˆÐ°Ñ€Ð¸Ð¹, что позволÑет промоделировать погодные ÑиÑтемы. Ðаземные телеÑкопы, оÑнащенные ПЗС, могут Ñделать Ñ„Ð¾Ñ‚Ð¾Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ ÐœÐ°Ñ€Ñа выÑокой чёткоÑти, что позволÑет в противоÑтоÑнии регулÑрно проводить мониторинг планетной погоды[141]. + +РентгеновÑкое излучение Ñ ÐœÐ°Ñ€Ñа, впервые обнаруженное аÑтрономами в 2001 году Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ коÑмичеÑкой рентгеновÑкой обÑерватории «Чандра», ÑоÑтоит из двух компонентов. ÐŸÐµÑ€Ð²Ð°Ñ ÑоÑтавлÑÑŽÑ‰Ð°Ñ ÑвÑзана Ñ Ñ€Ð°ÑÑеиванием в верхней атмоÑфере МарÑа рентгеновÑких лучей Солнца, в то Ð²Ñ€ÐµÐ¼Ñ ÐºÐ°Ðº Ð²Ñ‚Ð¾Ñ€Ð°Ñ Ð¿Ñ€Ð¾Ð¸Ñходит от взаимодейÑÑ‚Ð²Ð¸Ñ Ð¼ÐµÐ¶Ð´Ñƒ ионами Ñ Ð¾Ð±Ð¼ÐµÐ½Ð¾Ð¼ зарÑдами[142]. +ИÑÑледование МарÑа межпланетными ÑтанциÑми[править | править вики-текÑÑ‚] + +С 1960-Ñ… годов к МарÑу Ð´Ð»Ñ Ð¿Ð¾Ð´Ñ€Ð¾Ð±Ð½Ð¾Ð³Ð¾ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹ Ñ Ð¾Ñ€Ð±Ð¸Ñ‚Ñ‹ и Ñ„Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти были направлены неÑколько автоматичеÑких межпланетных Ñтанций (ÐМС). Кроме того, продолжалоÑÑŒ диÑтанционное зондирование МарÑа Ñ Ð—ÐµÐ¼Ð»Ð¸ в большей чаÑти Ñлектромагнитного Ñпектра Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ наземных и орбитальных телеÑкопов, например, в инфракраÑном Ð´Ð»Ñ Ð¾Ð¿Ñ€ÐµÐ´ÐµÐ»ÐµÐ½Ð¸Ñ ÑоÑтава поверхноÑти[143], в ультрафиолетовом и Ñубмиллиметровом диапазонах — Ð´Ð»Ñ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÑоÑтава атмоÑферы[144][145], в радиодиапазоне — Ð´Ð»Ñ Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ ÑкороÑти ветра[146]. +СоветÑкие иÑÑледованиÑ[править | править вики-текÑÑ‚] +Одна из первых цветных фотографий МарÑа, полученных Ñ ÐМС «МарÑ-3». + +СоветÑкие иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа включали в ÑÐµÐ±Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ñƒ «МарÑ», в рамках которой Ñ 1962 по 1973 год были запущены автоматичеÑкие межпланетные Ñтанции четырёх поколений Ð´Ð»Ñ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð»Ð°Ð½ÐµÑ‚Ñ‹ ÐœÐ°Ñ€Ñ Ð¸ околопланетного проÑтранÑтва. Первые ÐМС («МарÑ-1», «Зонд-2») иÑÑледовали также и межпланетное проÑтранÑтво. + +КоÑмичеÑкие аппараты четвёртого Ð¿Ð¾ÐºÐ¾Ð»ÐµÐ½Ð¸Ñ (ÑÐµÑ€Ð¸Ñ Ðœ-71 — «МарÑ-2», «МарÑ-3», запущены в 1971 году) ÑоÑтоÑли из орбитальной Ñтанции — иÑкуÑÑтвенного Ñпутника МарÑа и ÑпуÑкаемого аппарата Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой марÑианÑкой Ñтанцией, комплектовавшейÑÑ Ð¼Ð°Ñ€Ñоходом «ПрОП-М». КоÑмичеÑкие аппараты Ñерии Ðœ-73С «МарÑ-4» и «МарÑ-5» должны были выйти на орбиту вокруг МарÑа и обеÑпечивать ÑвÑзь Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкими марÑианÑкими ÑтанциÑми, которые неÑли ÐМС Ñерии Ðœ-73П «МарÑ-6» и «МарÑ-7»; Ñти четыре ÐМС были запущены в 1973 году. + +Из-за неудач ÑпуÑкаемых аппаратов Ð³Ð»Ð°Ð²Ð½Ð°Ñ Ñ‚ÐµÑ…Ð½Ð¸Ñ‡ÐµÑÐºÐ°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° вÑей программы «МарÑ» — проведение иÑÑледований на поверхноÑти планеты Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ автоматичеÑкой марÑианÑкой Ñтанции — не была решена. Тем не менее, многие научные задачи, такие, как получение фотографий поверхноÑти МарÑа и различные Ð¸Ð·Ð¼ÐµÑ€ÐµÐ½Ð¸Ñ Ð°Ñ‚Ð¼Ð¾Ñферы, магнитоÑферы, ÑоÑтава почвы ÑвлÑлиÑÑŒ передовыми Ð´Ð»Ñ Ñвоего времени[147]. Ð’ рамках программы была оÑущеÑтвлена Ð¿ÐµÑ€Ð²Ð°Ñ Ð¼ÑÐ³ÐºÐ°Ñ Ð¿Ð¾Ñадка ÑпуÑкаемого аппарата на поверхноÑÑ‚ÑŒ МарÑа («МарÑ-3», 2 Ð´ÐµÐºÐ°Ð±Ñ€Ñ 1971 года) и Ð¿ÐµÑ€Ð²Ð°Ñ Ð¿Ð¾Ð¿Ñ‹Ñ‚ÐºÐ° передачи Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ Ñ Ð¿Ð¾Ð²ÐµÑ€Ñ…Ð½Ð¾Ñти. + +СССР оÑущеÑтвил также программу «ФобоÑ» — две автоматичеÑкие межпланетные Ñтанции, предназначенные Ð´Ð»Ñ Ð¸ÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа и его Ñпутника ФобоÑа. + +ÐŸÐµÑ€Ð²Ð°Ñ ÐМС «ФобоÑ-1» была запущена 7 июлÑ, а втораÑ, «ФобоÑ-2» — 12 Ð¸ÑŽÐ»Ñ 1988 года[148]. ОÑÐ½Ð¾Ð²Ð½Ð°Ñ Ð·Ð°Ð´Ð°Ñ‡Ð° — доÑтавка на поверхноÑÑ‚ÑŒ ФобоÑа ÑпуÑкаемых аппаратов (ПрОП-Ф и ДÐС) Ð´Ð»Ñ Ð¸Ð·ÑƒÑ‡ÐµÐ½Ð¸Ñ Ñпутника МарÑа — оÑталаÑÑŒ невыполненной. Однако, неÑÐ¼Ð¾Ñ‚Ñ€Ñ Ð½Ð° потерю ÑвÑзи Ñ Ð¾Ð±Ð¾Ð¸Ð¼Ð¸ КÐ, иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ ÐœÐ°Ñ€Ñа, ФобоÑа и околомарÑианÑкого проÑтранÑтва, выполненные в течение 57 дней на Ñтапе орбитального Ð´Ð²Ð¸Ð¶ÐµÐ½Ð¸Ñ Â«Ð¤Ð¾Ð±Ð¾Ñа-2» вокруг МарÑа, позволили получить новые научные результаты о тепловых характериÑтиках ФобоÑа, плазменном окружении МарÑа, взаимодейÑтвии его Ñ Ñолнечным ветром. +ÐмериканÑкие иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² XX веке[править | править вики-текÑÑ‚] +Ð¤Ð¾Ñ‚Ð¾Ð³Ñ€Ð°Ñ„Ð¸Ñ Ñ€Ð°Ð¹Ð¾Ð½Ð° КидониÑ, ÑÐ´ÐµÐ»Ð°Ð½Ð½Ð°Ñ Ñтанцией «Викинг-1» в 1976 году. + +Ð’ 1964 году в СШРбыл оÑущеÑтвлён первый удачный запуÑк к МарÑу в рамках программы «Маринер». «Маринер-4» оÑущеÑтвил первое иÑÑледование Ñ Ð¿Ñ€Ð¾Ð»Ñ‘Ñ‚Ð½Ð¾Ð¹ траектории и Ñделал первые Ñнимки поверхноÑти[149]. «Маринер-6» и «Маринер-7», запущенные в 1969 году, произвели Ñ Ð¿Ñ€Ð¾Ð»Ñ‘Ñ‚Ð½Ð¾Ð¹ траектории первое иÑÑледование ÑоÑтава атмоÑферы Ñ Ð¿Ñ€Ð¸Ð¼ÐµÐ½ÐµÐ½Ð¸ÐµÐ¼ ÑпектроÑкопичеÑких методик и определение температуры поверхноÑти по измерениÑм инфракраÑного излучениÑ. Ð’ 1971 году «Маринер-9» Ñтал первым иÑкуÑÑтвенным Ñпутником МарÑа и оÑущеÑтвил первое картографирование поверхноÑти. + +Ð¡Ð»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ð¿Ñ€Ð¾Ð³Ñ€Ð°Ð¼Ð¼Ð° СШР— «Викинг» — включала запуÑк в 1975 году двух идентичных коÑмичеÑких аппаратов — «Викинг-1» и «Викинг-2», которые провели иÑÑÐ»ÐµÐ´Ð¾Ð²Ð°Ð½Ð¸Ñ Ñ Ð¾ÐºÐ¾Ð»Ð¾Ð¼Ð°Ñ€ÑианÑкой орбиты и на поверхноÑти МарÑа, в чаÑтноÑти, поиÑк жизни в пробах грунта. Каждый «Викинг» ÑоÑтоÑл из орбитальной Ñтанции — иÑкуÑÑтвенного Ñпутника МарÑа — и ÑпуÑкаемого аппарата Ñ Ð°Ð²Ñ‚Ð¾Ð¼Ð°Ñ‚Ð¸Ñ‡ÐµÑкой марÑианÑкой Ñтанцией. ÐвтоматичеÑкие марÑианÑкие Ñтанции «Викингов» — первые коÑмичеÑкие аппараты, уÑпешно работавшие на поверхноÑти МарÑа и передавшие фотографии Ñ Ð¼ÐµÑта поÑадки. Жизнь не удалоÑÑŒ обнаружить. + +Mars Pathfinder — поÑадочный аппарат ÐÐСÐ, работавший на поверхноÑти в 1996—1997 годах. +Карта МарÑа +ОпиÑание Ð¸Ð·Ð¾Ð±Ñ€Ð°Ð¶ÐµÐ½Ð¸Ñ + +Спирит Спирит + +Mars rover msrds simulation.jpg Оппортьюнити + +МарÑопроходец Соджорнер + +Viking Lander model.jpg + +Викинг-1 + +Viking Lander model.jpg Викинг-2 + +Ð¤ÐµÐ½Ð¸ÐºÑ Ð¤ÐµÐ½Ð¸ÐºÑ + +Mars3 lander vsm.jpg МарÑ-3 + +КьюриоÑити КьюриоÑити + +Maquette EDM salon du Bourget 2013 DSC 0192.JPG + +Скиапарелли +Ð’ наше времÑ[править | править вики-текÑÑ‚] + + Соединённые Штаты Ðмерики Mars Global Surveyor — орбитальный аппарат ÐÐСÐ, оÑущеÑтвлÑвший картографирование поверхноÑти в 1999—2007 годах. + Соединённые Штаты Ðмерики «ФеникÑ» — поÑадочный аппарат ÐÐСÐ, работавший на поверхноÑти в 2008 году. + Соединённые Штаты Ðмерики «Спирит» — марÑоход, работавший на поверхноÑти в 2004—2010 годах. + +Ðа наÑтоÑщий момент (2016 год) на орбитах иÑкуÑÑтвенных Ñпутников МарÑа находÑÑ‚ÑÑ Ð½ÐµÑколько работающих ÐМС: + + Соединённые Штаты Ðмерики Â«ÐœÐ°Ñ€Ñ ÐžÐ´Ð¸ÑÑей» (Ñ 24 октÑÐ±Ñ€Ñ 2001 года), + Европа «МарÑ-ÑкÑпреÑÑ» (Ñ 25 Ð´ÐµÐºÐ°Ð±Ñ€Ñ 2003 года), + Соединённые Штаты Ðмерики «МарÑианÑкий разведывательный Ñпутник» (Ñ 10 марта 2006 года), + Соединённые Штаты Ðмерики «MAVEN» (Ñ 21/22 ÑентÑÐ±Ñ€Ñ 2014 года)[150][151], + Ð˜Ð½Ð´Ð¸Ñ Â«Mangalyaan» (c 24 ÑентÑÐ±Ñ€Ñ 2014 года)[152]. + Европа Â«Ð¢Ñ€ÐµÐ¹Ñ Ð“Ð°Ñ ÐžÑ€Ð±Ð¸Ñ‚ÐµÑ€Â» (Ñ 19 октÑÐ±Ñ€Ñ 2016 г.) + +Ðа поверхноÑти планеты работают марÑоходы: + + Соединённые Штаты Ðмерики «Оппортьюнити» (Ñ 25 ÑÐ½Ð²Ð°Ñ€Ñ 2004 года), + Соединённые Штаты Ðмерики «КьюриоÑити» (Mars Science Laboratory) (Ñ 6 авгуÑта 2012 года). + +Кроме того, к МарÑу летит + + РоÑÑÐ¸Ñ Ð•Ð²Ñ€Ð¾Ð¿Ð° ДеÑантный модуль Ñ Ð¿Ð¾Ñадочной платформой «ЭкзомарÑ» (Ñ 14 марта 2016 года). + +Ð’ культуре[править | править вики-текÑÑ‚] +ИллюÑÑ‚Ñ€Ð°Ñ†Ð¸Ñ Ð¼Ð°Ñ€ÑианÑкого треножника из французÑкого Ð¸Ð·Ð´Ð°Ð½Ð¸Ñ Â«Ð’Ð¾Ð¹Ð½Ñ‹ миров» 1906 года. +Кадр из фильма Я. Протазанова «ÐÑлита» (1924). +ОÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑтатьÑ: ÐœÐ°Ñ€Ñ Ð² культуре + +К Ñозданию фантаÑтичеÑких произведений о МарÑе пиÑателей подталкивали начавшиеÑÑ Ð² конце XIX века диÑкуÑÑии учёных о возможноÑти того, что на поверхноÑти МарÑа ÑущеÑтвует не проÑто жизнь, а Ñ€Ð°Ð·Ð²Ð¸Ñ‚Ð°Ñ Ñ†Ð¸Ð²Ð¸Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ[153]. Ð’ Ñто Ð²Ñ€ÐµÐ¼Ñ Ð±Ñ‹Ð» Ñоздан, например, знаменитый роман Г. УÑллÑа «Война миров», в котором марÑиане пыталиÑÑŒ покинуть Ñвою умирающую планету Ð´Ð»Ñ Ð·Ð°Ð²Ð¾ÐµÐ²Ð°Ð½Ð¸Ñ Ð—ÐµÐ¼Ð»Ð¸. Ð’ 1938 году в СШРрадиоверÑÐ¸Ñ Ñтого Ð¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð±Ñ‹Ð»Ð° предÑтавлена в виде новоÑтной радиопередачи, что поÑлужило причиной маÑÑовой паники, когда многие Ñлушатели по ошибке принÑли Ñтот «репортаж» за правду[154]. Ð’ 1966 году пиÑатели Ðркадий и Ð‘Ð¾Ñ€Ð¸Ñ Ð¡Ñ‚Ñ€ÑƒÐ³Ð°Ñ†ÐºÐ¸Ðµ напиÑали ÑатиричеÑкое «продолжение» данного Ð¿Ñ€Ð¾Ð¸Ð·Ð²ÐµÐ´ÐµÐ½Ð¸Ñ Ð¿Ð¾Ð´ названием «Второе нашеÑтвие марÑиан». + +Ð’ 1917—1964 годах вышло одиннадцать книг о БарÑуме. Так называлаÑÑŒ планета ÐœÐ°Ñ€Ñ Ð² фантаÑтичеÑком мире, Ñозданном Эдгаром РайÑом Берроузом. Ð’ его произведениÑÑ… планета была предÑтавлена как умирающаÑ, жители которой находÑÑ‚ÑÑ Ð² непрерывной войне вÑех Ñо вÑеми за Ñкудные природные реÑурÑÑ‹. Ð’ 1938 году К. Ð›ÑŒÑŽÐ¸Ñ Ð½Ð°Ð¿Ð¸Ñал роман «За пределы безмолвной планеты». + +Ð’ чиÑле важных произведений о МарÑе также Ñтоит отметить вышедший в 1950 году роман РÑÑ Ð‘Ñ€Ñдбери «МарÑианÑкие хроники», ÑоÑтоÑщий из отдельных Ñлабо ÑвÑзанных между Ñобой новелл, а также Ñ€Ñд примыкающих к Ñтому циклу раÑÑказов; роман повеÑтвует об Ñтапах оÑÐ²Ð¾ÐµÐ½Ð¸Ñ Ñ‡ÐµÐ»Ð¾Ð²ÐµÐºÐ¾Ð¼ МарÑа и контактах Ñ Ð³Ð¸Ð±Ð½ÑƒÑ‰ÐµÐ¹ древней марÑианÑкой цивилизацией. + +Ð’ вымышленной вÑеленной Warhammer 40,000 ÐœÐ°Ñ€Ñ ÑвлÑетÑÑ Ð³Ð»Ð°Ð²Ð½Ð¾Ð¹ цитаделью Adeptus Mechanicus, первым из миров-кузниц. Фабрики МарÑа, покрывающие вÑÑŽ поверхноÑÑ‚ÑŒ планеты, круглоÑуточно выпуÑкают оружие и боевую технику Ð´Ð»Ñ Ð±ÑƒÑˆÑƒÑŽÑ‰ÐµÐ¹ в Галактике войны. + +Примечательно, что Джонатан Свифт упомÑнул о Ñпутниках МарÑа за 150 лет до того, как они были реально открыты, в 19-й чаÑти Ñвоего романа «ПутешеÑÑ‚Ð²Ð¸Ñ Ð“ÑƒÐ»Ð»Ð¸Ð²ÐµÑ€Ð°Â»[155]. diff --git a/xpcom/tests/gtest/wikipedia/th.txt b/xpcom/tests/gtest/wikipedia/th.txt new file mode 100644 index 0000000000..3341946a13 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/th.txt @@ -0,0 +1,412 @@ +ดาวอังคาร (อังà¸à¸¤à¸©: Mars) เป็นดาวเคราะห์ลำดับที่สี่จาà¸à¸”วงอาทิตย์ เป็นดาวเคราะห์เล็à¸à¸—ี่สุดอันดับที่สองในระบบสุริยะรองจาà¸à¸”าวพุธ ในภาษาอังà¸à¸¤à¸©à¹„ด้ชื่อตามเทพเจ้าà¹à¸«à¹ˆà¸‡à¸ªà¸‡à¸„รามของโรมัน มัà¸à¹„ด้รับขนานนาม "ดาวà¹à¸”ง" เพราะมีออà¸à¹„ซด์ของเหล็à¸à¸”าษดื่นบนพื้นผิวทำให้มีสีออà¸à¹à¸”งเรื่อ[15] ดาวอังคารเป็นดาวเคราะห์หินที่มีบรรยาà¸à¸²à¸¨à¹€à¸šà¸²à¸šà¸²à¸‡ มีลัà¸à¸©à¸“ะพื้นผิวคล้ายคลึงà¸à¸±à¸šà¸—ั้งหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸™à¸”วงจันทร์ à¹à¸¥à¸°à¸ à¸¹à¹€à¸‚าไฟ หุบเขา ทะเลทราย ตลอดจนพิดน้ำà¹à¸‚็งขั้วดาวที่ปราà¸à¸à¸šà¸™à¹‚ลภคาบà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸­à¸šà¸•à¸±à¸§à¹€à¸­à¸‡à¹à¸¥à¸°à¸§à¸±à¸à¸ˆà¸±à¸à¸£à¸¤à¸”ูà¸à¸²à¸¥à¸‚องดาวอังคารà¸à¹‡à¸¡à¸µà¸„วามคล้ายคลึงà¸à¸±à¸šà¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸„วามเอียงà¸à¹ˆà¸­à¹ƒà¸«à¹‰à¹€à¸à¸´à¸”ฤดูà¸à¸²à¸¥à¸•à¹ˆà¸²à¸‡ ๆ ดาวอังคารเป็นที่ตั้งของโอลิมปัสมอนส์ ภูเขาไฟใหà¸à¹ˆà¸—ี่สุดบนดาวอังคารà¹à¸¥à¸°à¸ªà¸¹à¸‡à¸ªà¸¸à¸”อันดับสองในระบบสุริยะเท่าที่มีà¸à¸²à¸£à¸„้นพบ à¹à¸¥à¸°à¹€à¸›à¹‡à¸™à¸—ี่ตั้งของเวลส์มาริเนริส à¹à¸„นยอนขนาดใหà¸à¹ˆà¸­à¸±à¸™à¸”ับต้น ๆ ในระบบสุริยะ à¹à¸­à¹ˆà¸‡à¸šà¸­à¹€à¸£à¸µà¸¢à¸¥à¸´à¸ªà¸—ี่ราบเรียบในซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวปà¸à¸„ลุมà¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 40 ของพื้นที่ทั้งหมดà¹à¸¥à¸°à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸¥à¸±à¸à¸©à¸“ะà¸à¸²à¸£à¸–ูà¸à¸­à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸Šà¸™à¸„รั้งใหà¸à¹ˆ[16][17] ดาวอังคารมีดาวบริวารสองดวง คือ โฟบอสà¹à¸¥à¸°à¸”ีมอสซึ่งต่างà¸à¹‡à¸¡à¸µà¸‚นาดเล็à¸à¹à¸¥à¸°à¸¡à¸µà¸£à¸¹à¸›à¸£à¹ˆà¸²à¸‡à¸šà¸´à¸”เบี้ยว ทั้งคู่อาจเป็นดาวเคราะห์น้อยที่ถูà¸à¸ˆà¸±à¸šà¹„ว้[18][19] คล้ายà¸à¸±à¸šà¸—รอยของดาวอังคาร เช่น 5261 ยูเรà¸à¸² + +à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸à¸²à¸£à¸šà¸´à¸™à¸œà¹ˆà¸²à¸™à¸”าวอังคารที่สำเร็จครั้งà¹à¸£à¸à¸‚อง มาริเนอร์ 4 เมื่อ 1965 หลายคนคาดว่ามีน้ำในรูปของเหลวบนพื้นผิวดาวอังคาร à¹à¸™à¸§à¸„ิดนี้อาศัยผลต่างเป็นคาบที่สังเà¸à¸•à¹„ด้ของรอยมืดà¹à¸¥à¸°à¸£à¸­à¸¢à¸ªà¸§à¹ˆà¸²à¸‡ โดยเฉพาะในละติจูดขั้วดาวซึ่งดูเป็นทะเลà¹à¸¥à¸°à¸—วีป บางคนà¹à¸›à¸¥à¸„วามรอยมืดริ้วลายขนานเป็นร่องทดน้ำสำหรับน้ำในรูปของเหลว ภายหลัง มีà¸à¸²à¸£à¸­à¸˜à¸´à¸šà¸²à¸¢à¸§à¹ˆà¸²à¸ à¸¹à¸¡à¸´à¸›à¸£à¸°à¹€à¸—ศเส้นตรงเหล่านั้นเป็นภาพลวงตา à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸—างธรณีวิทยาที่ภารà¸à¸´à¸ˆà¹„ร้คนบังคับรวบรวมชี้ว่า ครั้งหนึ่งดาวอังคารเคยมีน้ำปริมาณมาà¸à¸›à¸à¸„ลุมบนพื้นผิว ณ ช่วงใดช่วงหนึ่งในระยะต้น ๆ ของอายุ[20] ในปี 2005 เรดาร์เผยว่ามีน้ำà¹à¸‚็งน้ำ (water ice) ปริมาณมาà¸à¸‚ั้วทั้งสองของดาว[21] à¹à¸¥à¸°à¸—ี่ละติจูดà¸à¸¥à¸²à¸‡[22][23] ยานสำรวจภาคพื้นดาวอังคารสปิริต พบตัวอย่างสารประà¸à¸­à¸šà¹€à¸„มีที่มีโมเลà¸à¸¸à¸¥à¸™à¹‰à¸³à¹€à¸¡à¸·à¹ˆà¸­à¹€à¸”ือนมีนาคม 2007 ส่วนลงจอดฟีนิà¸à¸‹à¹Œ พบตัวอย่างน้ำà¹à¸‚็งน้ำโดยตรงในดินส่วนตื้นของดาวอังคารเมื่อวันที่ 31 à¸à¸£à¸à¸Žà¸²à¸„ม 2008[24] + +มียานอวà¸à¸²à¸¨à¸—ี่à¸à¸³à¸¥à¸±à¸‡à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸‡à¸²à¸™à¸­à¸¢à¸¹à¹ˆà¹€à¸ˆà¹‡à¸”ลำ ห้าลำอยู่ในวงโคจร ได้à¹à¸à¹ˆ 2001 มาร์สโอดิสซี มาร์สเอ็à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª มาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ เมเว็น à¹à¸¥à¸°à¸¡à¸²à¸£à¹Œà¸ªà¸­à¸­à¸£à¹Œà¸šà¸´à¹€à¸•à¸­à¸£à¹Œà¸¡à¸´à¸Šà¸Šà¸±à¸™ à¹à¸¥à¸°à¸ªà¸­à¸‡à¸¥à¸³à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ ได้à¹à¸à¹ˆ ยานสำรวจภาคพื้นดาวอังคารออปพอร์ทูนิตี à¹à¸¥à¸°à¸¢à¸²à¸™à¸¡à¸²à¸£à¹Œà¸ªà¹„ซà¹à¸­à¸™à¸‹à¹Œà¹à¸¥à¸šà¸­à¸£à¸²à¸—อรีคิวริออซิตี à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹‚ดย มาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ เปิดเผยว่ามีความเป็นไปได้ที่จะมีน้ำไหลในช่วงเดือนที่ร้อนที่สุดบนดาวอังคาร[25] ในปี 2013 ยานคิวริออซิตี ของนาซาค้นพบว่าดินของดาวอังคารมีน้ำเป็นองค์ประà¸à¸­à¸šà¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸£à¹‰à¸­à¸¢à¸¥à¸° 1.5 ถึง 3 โดยมวล à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸™à¹‰à¸³à¸™à¸±à¹‰à¸™à¸ˆà¸°à¸•à¸´à¸”อยู่à¸à¸±à¸šà¸ªà¸²à¸£à¸›à¸£à¸°à¸à¸­à¸šà¸­à¸·à¹ˆà¸™ ทำให้ไม่สามารถเข้าถึงได้โดยอิสระ[26] + +à¸à¸³à¸¥à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¸ªà¸·à¸šà¸„้นเพื่อประเมินศัà¸à¸¢à¸ à¸²à¸žà¸„วามสามารถอยู่อาศัยได้ในอดีตของดาวอังคาร ตลอดจนความเป็นไปได้ที่จะมีสิ่งมีชีวิตหลงเหลืออยู่ มีà¸à¸²à¸£à¸ªà¸·à¸šà¸„้นบริเวณนั้นโดยส่วนลงจอด ไวà¸à¸´à¸‡ โรเวอร์ สปิริต à¹à¸¥à¸°à¸­à¸­à¸›à¸žà¸­à¸£à¹Œà¸—ูนิตี ส่วนลงจอดฟีนิà¸à¸‹à¹Œ à¹à¸¥à¸°à¹‚รเวอร์ คิวริออซิตี[27][28] มีà¸à¸²à¸£à¸§à¸²à¸‡à¹à¸œà¸™à¸ à¸²à¸£à¸à¸´à¸ˆà¸—างชีวดาราศาสตร์ไว้à¹à¸¥à¹‰à¸§ ซึ่งรวม มาร์ส 2020 à¹à¸¥à¸°à¹€à¸­à¹‡à¸à¹‚ซมาร์สโรเวอร์ [29][30] + +ดาวอังคารสามารถมองเห็นได้ด้วยตาเปล่าจาà¸à¹‚ลà¸à¹‚ดยง่ายซึ่งจะปราà¸à¸à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¹€à¸›à¹‡à¸™à¸ªà¸µà¸­à¸­à¸à¹à¸”ง มีความส่องสว่างปราà¸à¸à¹„ด้ถึง −2.91[6] ซึ่งเป็นรองเพียงดาวพฤหัสบดี ดาวศุà¸à¸£à¹Œ ดวงจันทร์ à¹à¸¥à¸°à¸”วงอาทิตย์ à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ภาคพื้นดินโดยทั่วไปมีขีดจำà¸à¸±à¸”à¸à¸²à¸£à¸¡à¸­à¸‡à¹€à¸«à¹‡à¸™à¸£à¸²à¸¢à¸¥à¸°à¹€à¸­à¸µà¸¢à¸”ของภูมิประเทศขนาดประมาณ 300 à¸à¸´à¹‚ลเมตรเมื่อโลà¸à¹à¸¥à¸°à¸”าวอังคารเข้าใà¸à¸¥à¹‰à¸à¸±à¸™à¸¡à¸²à¸à¸—ี่สุดอันเป็นผลจาà¸à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸‚องโลà¸[31] + +ลัà¸à¸©à¸“ะทางà¸à¸²à¸¢à¸ à¸²à¸ž[à¹à¸à¹‰] +โลà¸à¹€à¸—ียบà¸à¸±à¸šà¸”าวอังคารโลà¸à¹€à¸—ียบà¸à¸±à¸šà¸”าวอังคาร +ไฟล์:Mars.ogvPlay media +ภาพเคลื่อนไหว (00:40) à¹à¸ªà¸”งภูมิประเทศสำคัภ+ไฟล์:GMM-3 Mars Gravity.webmPlay media +วีดีโอ (01:28) à¹à¸ªà¸”งให้เห็นสนามà¹à¸£à¸‡à¹‚น้มถ่วงของดาวอังคาร. + +ดาวอังคารมีขนาดเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ครึ่งหนึ่งของโลภà¹à¸¥à¸°à¸¡à¸µà¸žà¸·à¹‰à¸™à¸—ี่ผิวน้อยà¸à¸§à¹ˆà¸²à¸žà¸·à¹‰à¸™à¸—ี่ผิวดินทั้งหมดของโลà¸à¸£à¸§à¸¡à¸à¸±à¸™à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸­à¸¢[6] ดาวอังคารมีความหนาà¹à¸™à¹ˆà¸™à¸™à¹‰à¸­à¸¢à¸à¸§à¹ˆà¸²à¹‚ลภมีปริมาตรประมาณร้อยละ 15 ของโลภà¹à¸¥à¸°à¸¡à¸µà¸¡à¸§à¸¥à¸›à¸£à¸°à¸¡à¸²à¸“ร้อยละ 11 ของมวลของโลภถึงà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸”าวอังคารจะมีขนาดใหà¸à¹ˆà¸à¸§à¹ˆà¸²à¹à¸¥à¸°à¸¡à¸µà¸¡à¸§à¸¥à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸”าวพุธà¸à¹‡à¸•à¸²à¸¡ à¹à¸•à¹ˆà¸”าวพุธมีความหนาà¹à¸™à¹ˆà¸™à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸² เป็นผลให้à¹à¸£à¸‡à¹‚น้มถ่วงบริเวณพื้นผิวดาวเคราะห์ทั้งสองนั้นà¹à¸—บจะเท่าà¸à¸±à¸™ โดยดาวอังคารมีà¹à¸£à¸‡à¸”ึงโน้มถ่วงสูงà¸à¸§à¹ˆà¸²à¹€à¸žà¸µà¸¢à¸‡à¹„ม่ถึงร้อยละหนึ่ง ลัà¸à¸©à¸“ะปราà¸à¸à¸ªà¸µà¹à¸”งปนส้มของพื้นผิวดาวอังคารมีสาเหตุมาจาà¸à¹„อเอิร์น(III) ออà¸à¹„ซด์ รู้จัà¸à¸à¸±à¸™à¹ƒà¸™à¸Šà¸·à¹ˆà¸­à¸ªà¸²à¸¡à¸±à¸à¸„ือฮีมาไทต์หรือสนิมเหล็à¸[32] อาจมองเห็นคล้ายà¸à¸±à¸šà¸šà¸±à¸•à¹€à¸•à¸­à¸£à¹Œà¸ªà¸à¸­à¸•à¸Šà¹Œ[33] à¹à¸¥à¸°à¸ªà¸µà¸­à¸·à¹ˆà¸™ ๆ ที่ปราà¸à¸à¸—ั่วไปตามพื้นผิวนั้นมีได้ทั้งสีทอง สีน้ำตาล สีน้ำตาลอ่อน หรือสีออà¸à¹€à¸‚ียวขึ้นอยู่à¸à¸±à¸šà¹à¸£à¹ˆà¸­à¸‡à¸„์ประà¸à¸­à¸š[33] +โครงสร้างภายใน[à¹à¸à¹‰] + +เช่นเดียวà¸à¸±à¸™à¸à¸±à¸šà¹‚ลภดาวอังคารมีà¸à¸²à¸£à¹à¸¢à¸à¸Šà¸±à¹‰à¸™à¸­à¸‡à¸„์ประà¸à¸­à¸šà¸­à¸­à¸à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¹à¸à¹ˆà¸™à¹‚ลหะความหนาà¹à¸™à¹ˆà¸™à¸ªà¸¹à¸‡à¸‹à¸¶à¹ˆà¸‡à¸–ูà¸à¸«à¹ˆà¸­à¸«à¸¸à¹‰à¸¡à¸­à¸¢à¸¹à¹ˆà¸ à¸²à¸¢à¹ƒà¸•à¹‰à¸ªà¹ˆà¸§à¸™à¸›à¸£à¸°à¸à¸­à¸šà¸­à¸·à¹ˆà¸™ ๆ ที่มีความหนาà¹à¸™à¹ˆà¸™à¸™à¹‰à¸­à¸¢à¸à¸§à¹ˆà¸²[34] à¹à¸šà¸šà¸ˆà¸³à¸¥à¸­à¸‡à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸‚องโครงสร้างภายในà¹à¸ªà¸”งรัศมีอาณาบริเวณของà¹à¸à¹ˆà¸™à¸”าวอยู่ที่ประมาณ 1,794±65 à¸à¸´à¹‚ลเมตร (1,115±40 ไมล์) มีองค์ประà¸à¸­à¸šà¸«à¸¥à¸±à¸à¹€à¸›à¹‡à¸™à¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¸™à¸´à¸à¹€à¸à¸´à¸¥ โดยมีà¸à¸³à¸¡à¸°à¸–ันรวมอยู่ด้วยประมาณร้อยละ 16-17[35] คาดว่าà¹à¸à¹ˆà¸™à¹„อเอิร์น(II) ซัลไฟด์นั้นมีธาตุเบาเป็นองค์ประà¸à¸­à¸šà¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹à¸à¹ˆà¸™à¸‚องโลà¸à¸–ึงสองเท่า[36] à¹à¸à¹ˆà¸™à¸”าวล้อมรอบไปด้วยเนื้อดาวซิลิเà¸à¸•à¸‹à¸¶à¹ˆà¸‡à¸›à¸£à¸°à¸à¸­à¸šà¸‚ึ้นเป็นโครงสร้างทางธรณีสัณà¸à¸²à¸™à¹à¸¥à¸°à¸ à¸¹à¹€à¸‚าไฟต่าง ๆ บนดาวเคราะห์ซึ่งในปัจจุบันเหมือนจะสงบนิ่ง นอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸‹à¸´à¸¥à¸´à¸à¸­à¸™à¹à¸¥à¸°à¸­à¸­à¸à¸‹à¸´à¹€à¸ˆà¸™ ธาตุที่มีมาà¸à¸—ี่สุดในเปลือà¸à¸œà¸´à¸§à¸‚องดาวอังคารได้à¹à¸à¹ˆ เหล็ภà¹à¸¡à¸à¸™à¸µà¹€à¸‹à¸µà¸¢à¸¡ อะลูมิเนียม à¹à¸„ลเซียม à¹à¸¥à¸°à¹‚พà¹à¸—สเซียม ความหนาเฉลี่ยของเปลือà¸à¸”าวอยู่ที่ประมาณ 50 à¸à¸´à¹‚ลเมตร (31 ไมล์) มีความหนาสูงสุดที่ประมาณ 125 à¸à¸´à¹‚ลเมตร (78 ไมล์)[36] เปลือà¸à¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸¡à¸µà¸„วามหนาเฉลี่ย 40 à¸à¸´à¹‚ลเมตร (25 ไมล์) ถือว่ามีความหนาเพียงหนึ่งในสามของเปลือà¸à¸”าวอังคารเมื่อเปรียบสัมพัทธ์à¸à¸±à¸šà¸‚นาดของดาวเคราะห์ทั้งคู่ ยานส่วนลงจอดอินไซต์ตามà¹à¸œà¸™à¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¹ƒà¸™à¸›à¸µ 2016 (พ.ศ. 2559) จะมีà¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸„รื่องมือตรวจวัดความไหวสะเทือนเพื่อให้ได้à¹à¸šà¸šà¸ˆà¸³à¸¥à¸­à¸‡à¹‚ครงสร้างภายในดาวที่ชัดเจนมาà¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้น[37] +ธรณีวิทยาพื้นผิว[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ธรณีวิทยาดาวอังคาร + +ดาวอังคารเป็นดาวเคราะห์หินประà¸à¸­à¸šà¸‚ึ้นจาà¸à¹à¸£à¹ˆà¸Šà¸™à¸´à¸”ต่าง ๆ ที่มีซิลิà¸à¸­à¸™ ออà¸à¸‹à¸´à¹€à¸ˆà¸™ โลหะ ตลอดจนธาตุอื่น ๆ อีà¸à¸«à¸¥à¸²à¸¢à¸Šà¸™à¸´à¸”เป็นองค์ประà¸à¸­à¸šà¸£à¸§à¸¡à¸à¸±à¸™à¹€à¸‚้าเป็นหิน พื้นผิวของดาวอังคารมีหินบะซอลต์ชนิดโทเลอิทิà¸à¹€à¸›à¹‡à¸™à¸­à¸‡à¸„์ประà¸à¸­à¸šà¸«à¸¥à¸±à¸[38] à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸«à¸¥à¸²à¸¢à¸ªà¹ˆà¸§à¸™à¹€à¸›à¹‡à¸™à¸«à¸´à¸™à¸Šà¸™à¸´à¸”ที่มีซิลิà¸à¸²à¸ªà¸¹à¸‡à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸«à¸´à¸™à¸šà¸°à¸‹à¸­à¸¥à¸•à¹Œà¸—ั่วไปà¹à¸¥à¸°à¸­à¸²à¸ˆà¸¡à¸µà¸„วามคล้ายคลึงà¸à¸±à¸šà¸«à¸´à¸™à¹à¸­à¸™à¸”ีไซต์บนโลà¸à¸«à¸£à¸·à¸­à¹à¸à¹‰à¸§à¸‹à¸´à¸¥à¸´à¹€à¸à¸• ภูมิภาคที่มีอัตราส่วนสะท้อนต่ำà¹à¸ªà¸”งà¸à¸²à¸£à¸¡à¸µà¹€à¸Ÿà¸¥à¸”์สปาร์à¸à¸¥à¸¸à¹ˆà¸¡à¹€à¸žà¸¥à¸ˆà¸´à¹‚อเคลสหนาà¹à¸™à¹ˆà¸™ ในขณะที่ภูมิภาคที่มีอัตราส่วนสะท้อนต่ำทางตอนเหนือเผยให้เห็นà¸à¸²à¸£à¸¡à¸µà¹à¸œà¹ˆà¸™à¸‹à¸´à¸¥à¸´à¹€à¸à¸•à¹à¸¥à¸°à¹à¸à¹‰à¸§à¸Šà¸™à¸´à¸”ที่มีซิลิà¸à¸­à¸™à¸ªà¸¹à¸‡à¸”้วยความหนาà¹à¸™à¹ˆà¸™à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸²à¸›à¸à¸•à¸´ ในหลายส่วนของภูมิภาคที่ราบสูงตอนใต้ตรวจพบไพรอà¸à¸‹à¸µà¸™à¸Šà¸™à¸´à¸”à¹à¸„ลเซียมสูงรวมอยู่เป็นปริมาณมาภนอà¸à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¸žà¸šà¸®à¸µà¸¡à¸²à¹„ทต์à¹à¸¥à¸°à¹‚อลิวีนหนาà¹à¸™à¹ˆà¸™à¹ƒà¸™à¸ à¸¹à¸¡à¸´à¸ à¸²à¸„จำเพาะบางà¹à¸«à¹ˆà¸‡[39] พื้นที่ผิวส่วนใหà¸à¹ˆà¸–ูà¸à¸›à¸à¸„ลุมด้วยชั้นหนาของเม็ดà¸à¸¸à¹ˆà¸™à¹„อเอิร์น(III) ออà¸à¹„ซด์ละเอียด[40][41] +à¹à¸œà¸™à¸—ี่ธรณีวิทยาของดาวอังคาร (USGS; 14 à¸à¸£à¸à¸Žà¸²à¸„ม 2014) (à¹à¸œà¸™à¸—ี่เต็ม / วิดีโอ)[42][43][44] + +ถึงà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸”าวอังคารจะไม่มีหลัà¸à¸à¸²à¸™à¸‚องโครงสร้างสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸£à¸°à¸”ับครอบคลุมทั่วทั้งดาวในปัจจุบัน[45] à¹à¸•à¹ˆà¸œà¸¥à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹à¸ªà¸”งให้ทราบว่าหลายส่วนของเปลือà¸à¸”าวถูà¸à¸à¸£à¸°à¸—ำด้วยอำนาจà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¸à¸²à¸£à¸žà¸¥à¸´à¸à¸œà¸±à¸™à¸ªà¸¥à¸±à¸šà¸‚ั้วของสนามไดโพลเคยปราà¸à¸à¸¡à¸²à¹à¸¥à¹‰à¸§à¹ƒà¸™à¸­à¸”ีต เพราะในทางบรรพวิทยาà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸ à¹à¸£à¹ˆà¸—ี่มีความไวต่อà¹à¸£à¸‡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸™à¸±à¹‰à¸™à¸¢à¹ˆà¸­à¸¡à¹à¸ªà¸”งคุณสมบัติเช่นเดียวà¸à¸±à¸™à¸à¸±à¸šà¹à¸–บสลับที่พบบนพื้นมหาสมุทรของโลภทฤษฎีหนึ่งที่มีà¸à¸²à¸£à¸•à¸µà¸žà¸´à¸¡à¸žà¹Œà¹ƒà¸™à¸›à¸µ 1999 (พ.ศ. 2542) à¹à¸¥à¸°à¸¡à¸µà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸ªà¸­à¸šà¸­à¸µà¸à¸„รั้งในเดือนตุลาคม ปี 2005 (พ.ศ. 2548) (โดยอาศัยข้อมูลจาà¸à¸¡à¸²à¸£à¹Œà¸ªà¹‚à¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œ) ชี้ว่าà¹à¸™à¸§à¹à¸–บต่าง ๆ ที่เà¸à¸´à¸”ขึ้นà¹à¸ªà¸”งถึงà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸à¸²à¸£à¹à¸›à¸£à¸ªà¸±à¸“à¸à¸²à¸™à¹à¸œà¹ˆà¸™à¸˜à¸£à¸“ีภาคบนดาวอังคารเมื่อเวลาà¸à¸§à¹ˆà¸²à¸ªà¸µà¹ˆà¸žà¸±à¸™à¸¥à¹‰à¸²à¸™à¸›à¸µà¸à¹ˆà¸­à¸™ à¸à¹ˆà¸­à¸™à¸—ี่ไดนาโมของดาวเคราะห์จะหยุดลงเป็นผลให้สนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸‚องดาวจางหายไป[46] + +ในช่วงà¸à¸²à¸£à¸à¹ˆà¸­à¸à¸³à¹€à¸™à¸´à¸”ระบบสุริยะ ดาวอังคารได้ถือà¸à¸³à¹€à¸™à¸´à¸”ขึ้นจาà¸à¸œà¸¥à¸‚องà¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸ªà¸¸à¹ˆà¸¡à¸‚องมวลที่พอà¸à¸žà¸¹à¸™à¸‚ึ้นà¹à¸¢à¸à¸­à¸­à¸à¸ˆà¸²à¸à¸ˆà¸²à¸™à¸”าวเคราะห์à¸à¹ˆà¸­à¸™à¹€à¸à¸´à¸”ที่โคจรรอบดวงอาทิตย์ ดาวอังคารจึงมีคุณลัà¸à¸©à¸“ะทางเคมีที่จำเพาะพิเศษหลายประà¸à¸²à¸£à¸•à¸²à¸¡à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¹ƒà¸™à¸£à¸°à¸šà¸šà¸ªà¸¸à¸£à¸´à¸¢à¸° ธาตุต่าง ๆ ที่มีจุดเดือดค่อนข้างต่ำตัวอย่างเช่นคลอรีน ฟอสฟอรัส à¹à¸¥à¸°à¸à¸³à¸¡à¸°à¸–ัน จะพบเป็นปà¸à¸•à¸´à¸šà¸™à¸”าวอังคารในระดับที่มาà¸à¸à¸§à¹ˆà¸²à¹‚ลภเป็นไปได้ว่าธาตุเหล่านี้ถูà¸à¸‚ับออà¸à¸¡à¸²à¸ˆà¸²à¸à¸šà¸£à¸´à¹€à¸§à¸“ใà¸à¸¥à¹‰à¸”วงอาทิตย์โดยลมสุริยะอันทรงพลังในช่วงต้นของอายุขัย[47] + +หลังà¸à¸²à¸£à¸à¹ˆà¸­à¸à¸³à¹€à¸™à¸´à¸”ดาวเคราะห์à¹à¸¥à¹‰à¸§ ทั้งหมดล้วนตà¸à¹€à¸›à¹‡à¸™à¹€à¸«à¸¢à¸·à¹ˆà¸­à¸‚อง "à¸à¸²à¸£à¸£à¸°à¸”มชนหนัà¸à¸„รั้งสุดท้าย" à¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 60 ของพื้นที่ผิวดาวอังคารà¹à¸ªà¸”งบันทึà¸à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์à¸à¸²à¸£à¸£à¸°à¸”มชนจาà¸à¸¢à¸¸à¸„นั้น[48][49][50] ในขณะที่เป็นไปได้ว่าพื้นที่ผิวส่วนที่เหลืออีà¸à¸¡à¸²à¸à¸¡à¸²à¸¢à¸§à¸²à¸‡à¸•à¸±à¸§à¸­à¸¢à¸¹à¹ˆà¸ à¸²à¸¢à¹ƒà¸•à¹‰à¹à¸­à¹ˆà¸‡à¸‚นาดมโหฬารซึ่งà¸à¹‡à¹€à¸à¸´à¸”ขึ้นจาà¸à¹€à¸«à¸•à¸¸à¸à¸²à¸£à¸“์ดังà¸à¸¥à¹ˆà¸²à¸§ มีหลัà¸à¸à¸²à¸™à¸‚องà¹à¸­à¹ˆà¸‡à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚นาดมหึมาในบริเวณซีà¸à¹‚ลà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคารซึ่งà¹à¸œà¹ˆà¸‚ยายà¸à¸§à¹‰à¸²à¸‡à¸£à¸²à¸§ 8,500 à¸à¸´à¹‚ลเมตร à¹à¸¥à¸°à¸¢à¸²à¸§à¸£à¹ˆà¸§à¸¡ 10,600 à¸à¸´à¹‚ลเมตร (5,300 x 6,600 ไมล์) หรือมีขนาดใหà¸à¹ˆà¹€à¸›à¹‡à¸™à¸ªà¸µà¹ˆà¹€à¸—่าของà¹à¸­à¹ˆà¸‡à¹„อต์เค็น-ขั้วใต้ของดวงจันทร์ ทำให้เป็นà¹à¸­à¹ˆà¸‡à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸—ี่มีขนาดใหà¸à¹ˆà¸—ี่สุดเท่าที่มีà¸à¸²à¸£à¸„้นพบ[16][17] ทฤษฎีนี้เสนอว่าดาวอังคารถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยวัตถุขนาดเท่าดาวพลูโตเมื่อประมาณสี่พันล้านปีà¸à¹ˆà¸­à¸™ à¹à¸¥à¸°à¸„าดว่าเหตุà¸à¸²à¸£à¸“์นี้เองเป็นสาเหตุทำให้ดาวอังคารมีซีà¸à¸”าวà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸±à¸™à¹€à¸›à¹‡à¸™à¸ªà¸­à¸‡à¸¥à¸±à¸à¸©à¸“ะอย่างชัดเจน เà¸à¸´à¸”à¹à¸­à¹ˆà¸‡à¸šà¸­à¹€à¸£à¸µà¸¢à¸¥à¸´à¸ªà¸­à¸±à¸™à¸£à¸²à¸šà¹€à¸£à¸µà¸¢à¸šà¸›à¸à¸„ลุมพื้นที่à¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 40 ทางซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวเคราะห์[51][52] +ภาพรังสรรค์โดยศิลปินà¹à¸ªà¸”งภาพของดาวอังคารว่าน่าจะเป็นอย่างไรเมื่อสี่พันล้านปีà¸à¹ˆà¸­à¸™[53] + +ประวัติศาสตร์ธรณีวิทยาของดาวอังคารสามารถà¹à¸šà¹ˆà¸‡à¸­à¸­à¸à¹„ด้เป็นหลายช่วงเวลา à¹à¸•à¹ˆà¸ªà¸³à¸«à¸£à¸±à¸šà¸Šà¹ˆà¸§à¸‡à¹€à¸§à¸¥à¸²à¸«à¸¥à¸±à¸à¹à¸¥à¹‰à¸§à¸ªà¸²à¸¡à¸²à¸£à¸–à¹à¸šà¹ˆà¸‡à¹„ด้เป็นสามยุคด้วยà¸à¸±à¸™[54][55] + + ยุคโนอาเคียน (ตั้งชื่อตาม โนอาคิสเทร์รา หรือà¹à¸œà¹ˆà¸™à¸”ินของโนอาห์): เป็นช่วงà¸à¸³à¹€à¸™à¸´à¸”พื้นผิวดาวอังคารที่เà¸à¹ˆà¸²à¹à¸à¹ˆà¸—ี่สุดเท่าที่ปราà¸à¸ อยู่ในช่วงเวลาประมาณ 4.5 พันล้านปีà¸à¹ˆà¸­à¸™à¸ˆà¸™à¸–ึง 3.5 พันล้านปีที่ผ่านมา พื้นผิวยุคโนอาเคียนเต็มไปด้วยริ้วรอยจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚นาดใหà¸à¹ˆà¸„รั้งà¹à¸¥à¹‰à¸§à¸„รั้งเล่า ส่วนโป่งธาร์ซิส ที่ราบสูงภูเขาไฟที่คาดว่าเà¸à¸´à¸”ขึ้นในระหว่างยุคนี้พร้อมด้วยà¸à¸²à¸£à¸—่วมท้นอย่างà¸à¸§à¹‰à¸²à¸‡à¸‚วางของน้ำของเหลวในช่วงปลายยุค + ยุคเฮสเพียเรียน (ตั้งขื่อตาม เฮสเพียเรียนเพลนัม หรือที่ราบสูงตะวันตà¸): ราว 3.5 พันล้านปีà¸à¹ˆà¸­à¸™ จนถึงช่วงเวลาประมาณ 2.9 - 3.3 พันล้านปีที่ผ่านมา เป็นยุคที่มีรอยปราà¸à¸à¸Šà¸±à¸”เจนของà¸à¸²à¸£à¹€à¸à¸´à¸”ที่ราบลาวาขนาดใหà¸à¹ˆ + ยุคà¹à¸­à¸¡à¸°à¹‚ซเนียน (ตั้งขื่อตาม à¹à¸­à¸¡à¸°à¹‚ซนิสเพลนิเชีย หรือที่ราบà¹à¸­à¸¡à¸°à¸‹à¸­à¸™): นับตั้งà¹à¸•à¹ˆ 2.9 - 3.3 พันล้านปีà¸à¹ˆà¸­à¸™à¸ˆà¸™à¸–ึงปัจจุบัน พิ้นผิวยุคนี้มีหลุมจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸™à¹‰à¸­à¸¢à¹à¸•à¹ˆà¸„่อนข้างหลาà¸à¸«à¸¥à¸²à¸¢ ภูเขาไฟโอลิมปัสเà¸à¸´à¸”ขึ้นในยุคนี้ร่วมไปà¸à¸±à¸šà¸à¸²à¸£à¹„หลของลาวาอีà¸à¸«à¸¥à¸²à¸¢à¸—ี่บนดาวอังคาร + +à¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸—างธรณีวิทยาบางอย่างยังคงเà¸à¸´à¸”ขึ้นบนดาวอังคาร ที่หุบเขาอะธาบาสà¸à¸²à¸¡à¸µà¸£à¹ˆà¸­à¸‡à¸£à¸­à¸¢à¸à¸²à¸£à¹„หลของลาวาในลัà¸à¸©à¸“ะเป็นà¹à¸œà¹ˆà¸™à¸­à¸²à¸¢à¸¸à¸à¸§à¹ˆà¸² 200 ล้านปี ปราà¸à¸à¸£à¹ˆà¸­à¸‡à¸£à¸­à¸¢à¸à¸²à¸£à¹„หลของน้ำในพื้นผิวท่ามà¸à¸¥à¸²à¸‡à¸£à¸­à¸¢à¹€à¸¥à¸·à¹ˆà¸­à¸™à¸‹à¸¶à¹ˆà¸‡à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸²à¸£à¹ˆà¸­à¸‡à¹à¸¢à¸à¹€à¸‹à¸­à¸£à¹Œà¹€à¸šà¸­à¸£à¸±à¸ªà¸”้วยอายุน้อยà¸à¸§à¹ˆà¸² 20 ล้านปี บ่งชี้ว่าเป็นà¸à¸²à¸£à¸žà¸¥à¸¸à¹ˆà¸‡à¸‚ึ้นของภูเขาไฟเมื่อไม่นานมานี้เช่นà¸à¸±à¸™[56] วันที่ 19 à¸à¸¸à¸¡à¸ à¸²à¸žà¸±à¸™à¸˜à¹Œ 2008 (พ.ศ. 2551) ภาพจาà¸à¸¢à¸²à¸™à¸¡à¸²à¸£à¹Œà¸ªà¸£à¸µà¸„อนเนสเซนซ์ออร์บิเตอร์à¹à¸ªà¸”งให้เห็นหลัà¸à¸à¸²à¸™à¸‚องหิมะที่พังทลายลงมาจาà¸à¸«à¸™à¹‰à¸²à¸œà¸²à¸„วามสูง 700 เมตร[57] +ดิน[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดินดาวอังคาร +à¸à¸¸à¹ˆà¸™à¸—ี่มีซิลิà¸à¸²à¸›à¸£à¸´à¸¡à¸²à¸“สูง เผยให้เห็นโดยยานสำรวจดาวอังคารสปิริต + +ข้อมูลจาà¸à¸¢à¸²à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸‡à¸ˆà¸­à¸”ฟีนิà¸à¸‹à¹Œà¸—ี่ส่งà¸à¸¥à¸±à¸šà¸¡à¸²à¹à¸ªà¸”งว่าดินดาวอังคารมีความเป็นด่างเล็à¸à¸™à¹‰à¸­à¸¢à¹à¸¥à¸°à¸›à¸£à¸°à¸à¸­à¸šà¸”้วยธาตุต่าง ๆ อาทิเช่น à¹à¸¡à¸à¸™à¸µà¹€à¸‹à¸µà¸¢à¸¡ โซเดียม โพà¹à¸—สเซียม à¹à¸¥à¸°à¸„ลอรีน สารอาหารเหล่านี้สามารถพบได้ทั่วไปในสวนบนโลà¸à¹à¸¥à¸°à¸•à¹ˆà¸²à¸‡à¸à¹‡à¸ˆà¸³à¹€à¸›à¹‡à¸™à¸•à¹ˆà¸­à¸à¸²à¸£à¹€à¸ˆà¸£à¸´à¸à¹€à¸•à¸´à¸šà¹‚ตของพืช[58] à¸à¸²à¸£à¸—ดสอบโดนยานสำรวจเผยว่าดินดาวอังคารมีสมบัติเป็นด่างด้วยค่า พีเอชที่ 7.7 à¹à¸¥à¸°à¸¡à¸µà¹€à¸à¸¥à¸·à¸­à¹€à¸›à¸­à¸£à¹Œà¸„ลอเรตอยู่ราวร้อยละ 0.6[59][60][61][62] + +มีภูมิประเทศที่เป็นเส้นพาดขวางอยู่ทั่วไปบนดาวอังคารà¹à¸¥à¸°à¸—ี่เà¸à¸´à¸”ขึ้นใหม่ ๆ ปราà¸à¸à¸šà¹ˆà¸­à¸¢à¸„รั้งในบริเวณส่วนลาดที่สูงชันของหลุมตà¸à¸à¸£à¸°à¸—บ ร่องลึภà¹à¸¥à¸°à¸«à¸¸à¸šà¹€à¸«à¸§ รอยเส้นพาดจะมีสีคล้ำในช่วงà¹à¸£à¸à¹à¸¥à¹‰à¸§à¸„่อย ๆ จางลงเมื่อเวลาผ่านไป ในบางครั้งรอยเส้นเริ่มต้นในพื้นที่เล็ภๆ à¸à¹ˆà¸­à¸™à¸—ี่จะà¹à¸œà¹ˆà¸‚ยายà¸à¸§à¹‰à¸²à¸‡à¸­à¸­à¸à¹„ปได้เป็นหลายร้อยเมตร สามารถมองเห็นได้ตามขอบของหินขนาดใหà¸à¹ˆà¹à¸¥à¸°à¹€à¸„รื่องà¸à¸µà¸”ขวางต่าง ๆ ตามเส้นทางอีà¸à¸”้วย ทฤษฎีที่ได้รับà¸à¸²à¸£à¸¢à¸­à¸¡à¸£à¸±à¸šà¹‚ดยทั่วไปà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸²à¸£à¸­à¸¢à¹€à¸ªà¹‰à¸™à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸±à¹‰à¸™à¹€à¸›à¹‡à¸™à¸”ินชั้นล่างซึ่งมีสีคล้ำà¹à¸•à¹ˆà¸–ูà¸à¹€à¸›à¸´à¸”ออà¸à¸¡à¸²à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸±à¸‡à¸—ลายของà¸à¸¸à¹ˆà¸™à¸ªà¸µà¸ˆà¸²à¸‡à¸—างด้านบนหรือโดยพายุà¸à¸¸à¹ˆà¸™[63] มีà¸à¸²à¸£à¹€à¸ªà¸™à¸­à¸„ำอธิบายไปอีà¸à¸«à¸¥à¸²à¸¢à¹à¸™à¸§à¸—าง บางส่วนอธิบายว่าเà¸à¸µà¹ˆà¸¢à¸§à¸‚้องà¸à¸±à¸šà¸™à¹‰à¸³à¸«à¸£à¸·à¸­à¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งว่าเป็นà¸à¸²à¸£à¹€à¸ˆà¸£à¸´à¸à¹€à¸•à¸´à¸šà¹‚ตของสิ่งมีชีวิต[64][65] +อุทà¸à¸§à¸´à¸—ยา[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: น้ำบนดาวอังคาร +ภาพถ่ายà¸à¸³à¸¥à¸±à¸‡à¸‚ยายสูงถ่ายโดยยานออปพอร์ทูนิตี à¹à¸ªà¸”งà¸à¸²à¸£à¸žà¸­à¸à¸•à¸±à¸§à¸‚องฮีมาไทต์สีเทา ซึ่งบ่งชี้ว่าเคยมีน้ำในสถานะของเหลวปราà¸à¸à¹ƒà¸™à¸­à¸”ีต + +น้ำของเหลวนั้นไม่สามารถดำรงอยู่ได้บนดาวอังคารเนื่องจาà¸à¸„วามà¸à¸”อาà¸à¸²à¸¨à¸—ี่ต่ำมาà¸à¹€à¸žà¸µà¸¢à¸‡à¹à¸„่หนึ่งในร้อยของโลà¸[66] เว้นà¹à¸•à¹ˆà¸žà¸·à¹‰à¸™à¸—ี่ลุ่มต่ำบางบริเวณในช่วงเวลาเพียงสั้น ๆ[67][68] à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งที่ขั้วดาวทั้งคู่มีสภาพที่พอจะให้น้ำในปริมาณมาภๆ ได้[69][70] เฉพาะปริมาตรของน้ำà¹à¸‚็งขั้วใต้ของดาวหาà¸à¸¥à¸°à¸¥à¸²à¸¢à¸¥à¸‡à¸à¹‡à¸ˆà¸°à¹ƒà¸«à¹‰à¸™à¹‰à¸³à¹€à¸žà¸µà¸¢à¸‡à¸žà¸­à¸ªà¸³à¸«à¸£à¸±à¸šà¸›à¸à¸„ลุมพื้นผิวทั้งหมดของดาวเคราะห์ได้ด้วยความลึภ11 เมตร (36 ฟุต)[71] ชั้นดินเยือà¸à¹à¸‚็งคงตัวà¹à¸œà¹ˆà¸‚ยายจาà¸à¸‚ั้วดาวลงมาจนถึงประมาณละติจูดที่ 60 องศา[69] + +คาดว่าน้ำà¹à¸‚็งปริมาณมาà¸à¸–ูà¸à¸ˆà¸±à¸šà¹€à¸­à¸²à¹„ว้ภายในไครโอสเฟียร์หนาของดาวอังคาร ข้อมูลเรดาร์จาภมาร์สเอ็à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª à¹à¸¥à¸° มาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ เมื่อà¸à¸£à¸à¸Žà¸²à¸„ม 2005 (พ.ศ. 2548) à¹à¸ªà¸”งน้ำà¹à¸‚็งปริมาณมหาศาลที่ขั้วทั้งสองของดาว[21][72] à¹à¸¥à¸°à¹ƒà¸™à¹€à¸”ือนพฤศจิà¸à¸²à¸¢à¸™ 2008 (พ.ศ. 2551) พบในบริเวณละติจูดà¸à¸¥à¸²à¸‡[22] ยานส่วนลงจอดฟีนิà¸à¸‹à¹Œà¸žà¸šà¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡à¸™à¹‰à¸³à¹à¸‚็งโดยตรงในดินส่วนตื้นของดาวอังคารเมื่อวันที่ 31 à¸à¸£à¸à¸Žà¸²à¸„ม 2008[24] + +ลัà¸à¸©à¸“ะทางธรณีสัณà¸à¸²à¸™à¸—ี่มองเห็นบนดาวอังคารบ่งชี้อย่างหนัà¸à¹à¸™à¹ˆà¸™à¸§à¹ˆà¸²à¸¡à¸µà¸™à¹‰à¸³à¸‚องเหลวปราà¸à¸à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวเคราะห์ เส้นทางคดเคี้ยวขนาดใหà¸à¹ˆà¸—ี่โอบคลุมพื้นดินที่ถูà¸à¸à¸±à¸”เซาะหรือช่องทางà¸à¸²à¸£à¹„หลออà¸à¸™à¸±à¹‰à¸™à¸•à¸±à¸”ผ่านพื้นผิวโดยรอบà¸à¸§à¹ˆà¸² 25 à¹à¸«à¹ˆà¸‡ คาดว่าร่องรอยเหล่านี้เป็นบันทึà¸à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸¨à¸²à¸ªà¸•à¸£à¹Œà¸‚องà¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸à¸±à¸”เซาะระหว่างที่มีà¸à¸²à¸£à¸›à¸¥à¸”ปล่อยน้ำอย่างถล่มทลายออà¸à¸¡à¸²à¸ˆà¸²à¸à¸Šà¸±à¹‰à¸™à¸«à¸´à¸™à¸­à¸¸à¹‰à¸¡à¸™à¹‰à¸³à¹ƒà¸•à¹‰à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ อย่างไรà¸à¹‡à¸•à¸²à¸¡à¹‚ครงสร้างบางส่วนถูà¸à¸•à¸±à¹‰à¸‡à¸ªà¸¡à¸¡à¸•à¸´à¸à¸²à¸™à¸§à¹ˆà¸²à¹€à¸›à¹‡à¸™à¸œà¸¥à¸¡à¸²à¸ˆà¸²à¸à¸à¸²à¸£à¸à¸£à¸°à¸—ำของธารน้ำà¹à¸‚็งหรือลาวา[73][74] ตัวอย่างหนึ่งที่มีขนาดใหà¸à¹ˆà¸„ือ มาดดิมวัลลิส ซึ่งมีความยาว 700 à¸à¸´à¹‚ลเมตร (430 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸‚นาดใหà¸à¹ˆà¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¹à¸à¸£à¸™à¸”์à¹à¸„นยอนด้วยความà¸à¸§à¹‰à¸²à¸‡ 20 à¸à¸´à¹‚ลเมตร (12 ไมล์) à¹à¸¥à¸°à¸„วามลึภ2 à¸à¸´à¹‚ลเมตร (1.2 ไมล์) ในบางท้องที่ คาดว่าภูมิประเทศถูà¸à¸à¸±à¸”สร้างขึ้นมาโดยà¸à¸²à¸£à¹„หลของน้ำตั้งà¹à¸•à¹ˆà¸Šà¹ˆà¸§à¸‡à¸•à¹‰à¸™ ๆ ของประวัติศาสตร์ดาวอังคาร[75] ช่องทางà¸à¸²à¸£à¹„หลเหล่านี้ที่มีอายุน้อยที่สุดคาดว่าเพิ่งจะเà¸à¸´à¸”ขึ้นเมื่อเวลาเพียงไม่à¸à¸µà¹ˆà¸¥à¹‰à¸²à¸™à¸›à¸µà¸—ี่à¹à¸¥à¹‰à¸§[76] สำหรับที่อื่น ๆ โดยเฉพาะพื้นที่ที่เà¸à¹ˆà¸²à¹à¸à¹ˆà¸—ี่สุดบนผิวดาวอังคาร โครงสร้างระดับเล็à¸à¸¢à¹ˆà¸­à¸¢à¸•à¸¥à¸­à¸”จนเครือข่ายหุบเขาที่à¸à¸£à¸°à¸ˆà¸²à¸¢à¹€à¸›à¹‡à¸™à¸à¸´à¹ˆà¸‡à¸à¹‰à¸²à¸™à¸ªà¸²à¸‚าล้วนà¹à¸œà¹ˆà¸‚ยายพาดขวางเป็นสัดส่วนอย่างมีนัยสำคัà¸à¹ƒà¸™à¸ à¸²à¸„พื้นภูมิประเทศ รูปลัà¸à¸©à¸“ะของหุบเขาเหล่านี้รวมทั้งà¸à¸²à¸£à¸à¸£à¸°à¸ˆà¸²à¸¢à¸•à¸±à¸§à¹à¸ªà¸”งนัยอย่างเด่นชัดว่าถูà¸à¹€à¸‹à¸²à¸°à¸ªà¸£à¹‰à¸²à¸‡à¹‚ดยà¸à¸²à¸£à¹„หลบ่าซึ่งเป็นผลลัพธ์มาจาà¸à¸à¸™à¸«à¸£à¸·à¸­à¸«à¸´à¸¡à¸°à¸—ี่ตà¸à¸¥à¸‡à¸¡à¸²à¹€à¸¡à¸·à¹ˆà¸­à¸¢à¸¸à¸„à¹à¸£à¸à¸‚องประวัติศาสตร์ดาวอังคาร à¸à¸²à¸£à¹„หลของน้ำใต้ผิวดินà¹à¸¥à¸°à¸à¸²à¸£à¸œà¸¸à¸”เซาะของน้ำบาดาลอาจà¹à¸ªà¸”งบทบาทย่อยสำคัà¸à¹ƒà¸™à¸«à¸¥à¸²à¸¢à¹€à¸„รือข่าย à¹à¸•à¹ˆà¸«à¸¢à¸²à¸”น้ำฟ้าน่าจะเป็นสาเหตุหลัà¸à¸‚องริ้วร่องเà¸à¸·à¸­à¸šà¸—ั้งหมดในà¹à¸•à¹ˆà¸¥à¸°à¸à¸£à¸“ี[77] + +ร่วมไปà¸à¸±à¸šà¸œà¸™à¸±à¸‡à¸‚องหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸«à¸£à¸·à¸­à¸«à¸¸à¸šà¹€à¸‚าลึภมีลัà¸à¸©à¸“ะภูมิประเทศนับพันที่ปราà¸à¸à¸„ล้ายคลึงà¸à¸±à¸šà¹‚ตรà¸à¸«à¹‰à¸§à¸¢à¸šà¸™à¸žà¸·à¹‰à¸™à¸”ิน ห้วยต่าง ๆ นี้มัà¸à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸žà¸·à¹‰à¸™à¸—ี่ราบสูงทางซีà¸à¹ƒà¸•à¹‰à¸‚องดาวà¹à¸¥à¸°à¹€à¸œà¸Šà¸´à¸à¸à¸±à¸šà¹€à¸ªà¹‰à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£ ทั้งหมดชี้ไปในà¹à¸™à¸§à¸‚ั้วดาวที่ละติจูด 30 องศา นัà¸à¸§à¸´à¸ˆà¸±à¸¢à¸ˆà¸³à¸™à¸§à¸™à¸«à¸™à¸¶à¹ˆà¸‡à¹€à¸ªà¸™à¸­à¸§à¹ˆà¸²à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸à¹ˆà¸­à¸à¸³à¹€à¸™à¸´à¸”เà¸à¸µà¹ˆà¸¢à¸§à¸‚้องà¸à¸±à¸šà¸™à¹‰à¸³à¸‚องเหลวซึ่งอาจมาจาà¸à¸™à¹‰à¸³à¹à¸‚็งที่ละลาย[78][79] à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸ˆà¸°à¸¡à¸µà¸­à¸µà¸à¸«à¸¥à¸²à¸¢à¸„นà¹à¸¢à¹‰à¸‡à¸§à¹ˆà¸²à¸à¸¥à¹„à¸à¹ƒà¸™à¸à¸²à¸£à¹€à¸à¸´à¸”เà¸à¸µà¹ˆà¸¢à¸§à¸‚้องà¸à¸±à¸šà¸„าร์บอนไดออà¸à¹„ซด์เยือà¸à¹à¸‚็งหรือà¸à¸²à¸£à¹€à¸„ลื่อนที่ของà¸à¸¸à¹ˆà¸™à¹à¸«à¹‰à¸‡[80][81] ไม่ปราà¸à¸à¸§à¹ˆà¸²à¸¡à¸µà¹‚ตรà¸à¸«à¹‰à¸§à¸¢à¸—ี่ถูà¸à¸à¸£à¹ˆà¸­à¸™à¸—ำลายบางส่วนโดยà¸à¸²à¸£à¸œà¸¸à¸à¸£à¹ˆà¸­à¸™à¸•à¸²à¸¡à¸ªà¸ à¸²à¸žà¸­à¸²à¸à¸²à¸¨ à¹à¸¥à¸°à¸à¹‡à¸ªà¸±à¸‡à¹€à¸à¸•à¹„ม่พบในหลุมจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸—ั้งหลายที่มีความเด่นชัด จึงเป็นเครื่องชี้ว่าภูมิประเทศดังà¸à¸¥à¹ˆà¸²à¸§à¸¢à¸±à¸‡à¸¡à¸µà¸­à¸²à¸¢à¸¸à¸™à¹‰à¸­à¸¢à¹à¸¥à¸°à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¹„ด้ว่ายังคงเà¸à¸´à¸”ขึ้นในปัจจุบัน[79] + +ลัà¸à¸©à¸“ะทางธรณีวิทยาอื่นอีà¸à¸«à¸¥à¸²à¸¢à¸›à¸£à¸°à¸à¸²à¸£ เช่น ดินดอนสามเหลี่ยมปาà¸à¹à¸¡à¹ˆà¸™à¹‰à¸³ à¹à¸¥à¸°à¸•à¸°à¸à¸­à¸™à¸™à¹‰à¸³à¸žà¸²à¸£à¸¹à¸›à¸žà¸±à¸”ที่ถูà¸à¹€à¸à¹‡à¸šà¸£à¸±à¸à¸©à¸²à¹„ว้ในหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸•à¹ˆà¸²à¸‡ ๆ เป็นพยานหลัà¸à¸à¸²à¸™à¸—ี่เสริมให้ทราบว่ามีสภาพà¹à¸§à¸”ล้อมอุ่น-ชื้น ณ บางช่วงเวลาหรือหลายช่วงเวลาในประวัติศาสตร์ยุคต้นของดาวอังคาร[82] สภาวะà¹à¸§à¸”ล้อมเช่นนี้เป็นสิ่งที่จำเป็นสำหรับà¸à¸²à¸£à¹€à¸à¸´à¸”มีอย่างà¸à¸§à¹‰à¸²à¸‡à¸‚วางของทะเลสาบหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸—ี่ข้ามผ่านเป็นสัดส่วนขนาดใหà¸à¹ˆà¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ à¹à¸¥à¸°à¸™à¸±à¸šà¸§à¹ˆà¸²à¸¢à¸±à¸‡à¹€à¸›à¹‡à¸™à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸­à¸´à¸ªà¸£à¸°à¸—ั้งในทางà¹à¸£à¹ˆà¸§à¸´à¸—ยา ตะà¸à¸­à¸™à¸§à¸´à¸—ยา à¹à¸¥à¸°à¸˜à¸£à¸“ีสัณà¸à¸²à¸™à¸§à¸´à¸—ยาอีà¸à¸”้วย[83] +ส่วนประà¸à¸­à¸šà¸‚องหินบริเวณ "เยลโลไนฟ์เบย์" - หินเวนมีà¹à¸„ลเซียมà¹à¸¥à¸°à¸à¸³à¸¡à¸°à¸–ันมาà¸à¸à¸§à¹ˆà¸²à¸”ินที่ถูà¸à¸žà¸²à¸¡à¸² - ผลจาà¸à¹€à¸­à¸žà¸µà¹€à¸­à¸à¸‹à¹Œà¹€à¸­à¸ª - คิวริออซิตี (มีนาคม 2013) + +หลัà¸à¸à¸²à¸™à¸™à¸­à¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸™à¸µà¹‰à¸—ี่ยืนยันà¸à¸²à¸£à¸—ี่ครั้งหนึ่งเคยมีน้ำของเหลวปราà¸à¸à¸šà¸™à¸œà¸´à¸§à¸”าวอังคารมาจาà¸à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¹à¸£à¹ˆà¸—ี่มีความจำเพาะ เช่น ฮีมาไทต์ à¹à¸¥à¸°à¹€à¸à¸­à¹„ทต์ ซึ่งทั้งคู่บางครั้งจะà¸à¹ˆà¸­à¸•à¸±à¸§à¹ƒà¸™à¸—ี่ที่มีน้ำ[84] ในปี 2004 (พ.ศ. 2547) ยานออปพอร์ทูนิตี ตรวจพบà¹à¸£à¹ˆà¸ˆà¸²à¹‚รไซต์ซึ่งà¸à¹ˆà¸­à¸•à¸±à¸§à¸‚ึ้นเฉพาะเมื่อมีน้ำในสภาพเป็นà¸à¸£à¸” เป็นเครื่องพิสูจน์ว่าครั้งหนึ่งเคยมีน้ำอยู่บนดาวอังคาร[85] หลัà¸à¸à¸²à¸™à¹€à¸žà¸´à¹ˆà¸¡à¹€à¸•à¸´à¸¡à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸™à¹‰à¸³à¸‚องเหลวเมื่อไม่นานมานี้มาจาà¸à¸à¸²à¸£à¸„้นพบà¹à¸£à¹ˆà¸¢à¸´à¸›à¸‹à¸±à¸¡à¸šà¸™à¸žà¸·à¹‰à¸™à¸”ินโดยยานสำรวจออปพอร์ทูนิตีของนาซา เมื่อธันวาคม 2011 (พ.ศ. 2554)[86][87] นอà¸à¸ˆà¸²à¸à¸™à¸µà¹‰ ฟรานซิส à¹à¸¡à¸„คับบิน หัวหน้าà¸à¹ˆà¸²à¸¢à¸¨à¸¶à¸à¸©à¸² นัà¸à¸§à¸´à¸—ยาศาสตร์ดาวเคราะห์ที่มหาวิทยาลัยนิวเม็à¸à¸‹à¸´à¹‚à¸à¹ƒà¸™à¹à¸­à¸¥à¸šà¸¹à¹€à¸„อร์คี ตรวจสอบลัà¸à¸©à¸“ะไฮดรอà¸à¹„ซด์ในผลึà¸à¹à¸£à¹ˆà¸ˆà¸²à¸à¸”าวอังคาร à¹à¸–ลงว่าน้ำในà¹à¸¡à¸™à¹€à¸—ิลส่วนบนของดาวอังคารมีปริมาณเท่าà¸à¸±à¸šà¸«à¸£à¸·à¸­à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¸—ี่โลà¸à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¸—ี่ระดับ 50 - 300 ส่วนในล้านส่วน ซึ่งมาà¸à¹€à¸žà¸µà¸¢à¸‡à¸žà¸­à¸—ี่จะครอบคลุมพื้นผิวทั้งหมดของดาวได้ด้วยความลึภ200 ถึง 1,000 เมตร (660 ถึง 3,280 ฟุต)[88] + +เมื่อวันที่ 18 มีนาคม 2013 (พ.ศ. 2556) นาซารายงานหลัà¸à¸à¸²à¸™à¸ˆà¸²à¸à¹€à¸„รื่องตรวจวัดบนยานสำรวจคิวริออซิตี ของà¹à¸£à¹ˆà¸—ี่เà¸à¸´à¸”ขึ้นโดยมีน้ำเป็นองค์ประà¸à¸­à¸š อย่างเช่นไฮเดรตของà¹à¸„ลเซียมซัลเฟต ในตัวอย่างหินหลายชนิดรวมทั้งชิ้นส่วนที่à¹à¸•à¸à¸­à¸­à¸à¸¡à¸²à¸‚องหิน "ทินทินา" à¹à¸¥à¸°à¸«à¸´à¸™ "ซัตตันอินเลียร์" เช่นเดียวà¸à¸±à¸šà¹€à¸§à¸™à¹à¸¥à¸°à¹‚นดูลในหินอื่น ๆ เช่นหิน "นอร์" à¹à¸¥à¸°à¸«à¸´à¸™ "เวอนิà¸à¹€à¸"[89][90][91] à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์โดยใช้เครื่องมือดีเอเอ็นของยานสำรวจภาคพื้นให้หลัà¸à¸à¸²à¸™à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸™à¹‰à¸³à¹ƒà¸•à¹‰à¸œà¸´à¸§à¸”ินว่ามีปริมาณà¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 4 ลึà¸à¸¥à¸‡à¹„ปจนถึงระดับ 60 เซนติเมตร (24 นิ้ว) ในเส้นทางเคลื่อนผ่านของยานจาà¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸ˆà¸¸à¸”ลงจอดà¹à¸šà¸£à¸”บูรี ไปจนถึงพื้นที่ เยลโลไนฟ์เบย์ ในบริเวณภูมิภาคเà¸à¸¥à¹€à¸™à¸ [89] + +นัà¸à¸§à¸´à¸ˆà¸±à¸¢à¸šà¸²à¸‡à¸ªà¹ˆà¸§à¸™à¹€à¸Šà¸·à¹ˆà¸­à¸§à¹ˆà¸²à¸ªà¹ˆà¸§à¸™à¹ƒà¸«à¸à¹ˆà¸‚องพิ้นที่ราบต่ำทางตอนเหนือของดาวเคยถูà¸à¸¡à¸«à¸²à¸ªà¸¡à¸¸à¸—รปà¸à¸„ลุมด้วยความลึà¸à¸«à¸¥à¸²à¸¢à¸£à¹‰à¸­à¸¢à¹€à¸¡à¸•à¸£ ทั้งนี้ยังอยู่ในระหว่างà¸à¸²à¸£à¹‚ต้à¹à¸¢à¹‰à¸‡[92] ในเดือนมีนาคม 2015 (พ.ศ. 2558) นัà¸à¸§à¸´à¸—ยาศาสตร์ระบุว่ามหาสมุทรดังà¸à¸¥à¹ˆà¸²à¸§à¸­à¸²à¸ˆà¸¡à¸µà¸‚นาดราวมหาสมุทรอาร์à¸à¸•à¸´à¸à¸‚องโลภà¸à¸²à¸£à¸§à¸´à¸™à¸´à¸ˆà¸‰à¸±à¸¢à¸™à¸µà¹‰à¹„ด้มาจาà¸à¸à¸²à¸£à¸›à¸£à¸°à¹€à¸¡à¸´à¸™à¸­à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸™à¹‰à¸³à¹à¸¥à¸°à¸”ิวเทอเรียมในบรรยาà¸à¸²à¸¨à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸‚องดาวอังคารเทียบà¸à¸±à¸™à¸à¸±à¸šà¸­à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸—ี่พบบนโลภปริมาณดิวเทอเรียมที่พบบนดาวอังคารมีมาà¸à¸à¸§à¹ˆà¸²à¸—ี่ดำรงอยู่บนโลà¸à¸–ึงà¹à¸›à¸”เท่า บ่งชี้ว่าดาวอังคารครั้งโบราณà¸à¸²à¸¥à¸¡à¸µà¸™à¹‰à¸³à¹€à¸›à¹‡à¸™à¸›à¸£à¸´à¸¡à¸²à¸“มาà¸à¸­à¸¢à¹ˆà¸²à¸‡à¸¡à¸µà¸™à¸±à¸¢à¸ªà¸³à¸„ัภผลสำรวจจาà¸à¸¢à¸²à¸™à¸„ิวริออซิตี มาพบในภายหลังว่ามีดิวเทอเรียมในอัตราส่วนสูงในหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¹€à¸à¸¥ อย่างไรà¸à¹‡à¸•à¸²à¸¡à¸„่าที่ได้ยังไม่สูงพอที่จะสนับสนุนว่าเคยมีมหาสมุทรอยู่ นัà¸à¸§à¸´à¸—ยาศาสตร์รายอื่น ๆ เตือนว่าà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¹ƒà¸«à¸¡à¹ˆà¸™à¸µà¹‰à¸¢à¸±à¸‡à¹„ม่ได้รับà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™ à¹à¸¥à¸°à¸Šà¸µà¹‰à¸›à¸£à¸°à¹€à¸”็นว่าà¹à¸šà¸šà¸ˆà¸³à¸¥à¸­à¸‡à¸ à¸¹à¸¡à¸´à¸­à¸²à¸à¸²à¸¨à¸”าวอังคารยังไม่ได้à¹à¸ªà¸”งว่าดาวเคราะห์มีความอบอุ่นเพียงพอในอดีตที่ผ่านมาที่จะเอื้อให้น้ำคงอยู่ในรูปของเหลวได้[93] +à¹à¸œà¹ˆà¸™à¸‚ั้วโลà¸[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: น้ำà¹à¸‚็งขั้วโลà¸à¸”าวอังคาร +à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วเหนือช่วงต้นฤดูร้อน 1999 (พ.ศ. 2542) +à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วใต้ในช่วงฤดูร้อน 2000 (พ.ศ. 2543) + +ดาวอังคารมีà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งถาวรอยู่ที่ขั้วทั้งสอง เมื่อถึงฤดูหนาวของà¹à¸•à¹ˆà¸¥à¸°à¸‚ั้วพื้นที่โดยรอบà¸à¹‡à¸ˆà¸°à¸•à¸à¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸„วามมืดอย่างต่อเนื่อง à¸à¸²à¸£à¹€à¸¢à¸·à¸­à¸à¹€à¸¢à¹‡à¸™à¸¥à¸‡à¸‚องพื้นผิวเป็นสาเหตุให้เà¸à¸´à¸”à¸à¸²à¸£à¹€à¸¢à¸·à¸­à¸à¹à¸‚็งสะสมของบรรยาà¸à¸²à¸¨à¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 25 - 30 ลงมาเป็นà¹à¸œà¹ˆà¸™ CO2 เยือà¸à¹à¸‚็ง (น้ำà¹à¸‚็งà¹à¸«à¹‰à¸‡)[94] เมื่อà¹à¸•à¹ˆà¸¥à¸°à¸‚ั้วà¸à¸¥à¸±à¸šà¸¡à¸²à¹„ด้รับà¹à¸ªà¸‡à¹à¸”ดอีà¸à¸„รั้ง CO2 เยือà¸à¹à¸‚็งà¸à¹‡à¸ˆà¸°à¸£à¸°à¹€à¸«à¸´à¸” เà¸à¸´à¸”เป็นลมขนาดมหึมาà¸à¸§à¸²à¸”ซัดไปทั่วบริเวณขั้วด้วยอัตราเร็วถึง 400 à¸à¸´à¹‚ลเมตร/ชั่วโมง (250 ไมล์/ชั่วโมง) ปราà¸à¸à¸à¸²à¸£à¸“์ตามฤดูà¸à¸²à¸¥à¸™à¸µà¹‰à¸Šà¹ˆà¸§à¸¢à¹€à¸„ลื่อนย้ายà¸à¸¸à¹ˆà¸™à¹à¸¥à¸°à¹„อน้ำปริมาณมหาศาลให้ลอยสูงขึ้นคล้ายà¸à¸±à¸šà¹€à¸¡à¸†à¹€à¸‹à¸­à¸£à¹Œà¸£à¸±à¸ªà¹€à¸¢à¸·à¸­à¸à¹à¸‚็งขนาดใหà¸à¹ˆà¸šà¸™à¹‚ลภยานสำรวจออปพอร์ทูนิตี ถ่ายภาพเมฆที่เป็นน้ำเยือà¸à¹à¸‚็งนี้ได้ในปี 2004 (พ.ศ. 2547)[95] + +à¹à¸œà¹ˆà¸™à¸—ี่ขั้วโลà¸à¸—ั้งสองมีองค์ประà¸à¸­à¸šà¸«à¸¥à¸±à¸à¸à¸§à¹ˆà¸²à¸£à¹‰à¸­à¸¢à¸¥à¸° 70 เป็นน้ำเยือà¸à¹à¸‚็ง สำหรับคาร์บอนไดออà¸à¹„ซด์เยือà¸à¹à¸‚็งจะสะสมตัวเป็นชั้นที่บางà¸à¸§à¹ˆà¸²à¹€à¸¡à¸·à¹ˆà¸­à¹€à¸—ียบà¸à¸±à¸™à¹‚ดยหนาประมาณหนึ่งเมตรบนà¹à¸œà¹ˆà¸™à¸‚ั้วเหนือเฉพาะในช่วงฤดูหนาวเท่านั้น ในขณะที่à¹à¸œà¹ˆà¸™à¸‚ั้วใต้เป็นà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¹à¸«à¹‰à¸‡à¸„งตัวปà¸à¸„ลุมด้วยความหนาประมาณà¹à¸›à¸”เมตร à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¹à¸«à¹‰à¸‡à¸„งตัวที่ปà¸à¸„ลุมยังขั้วใต้นี้เà¸à¸¥à¸·à¹ˆà¸­à¸™à¸à¸¥à¹ˆà¸™à¹„ปด้วยหลุมตื้น ๆ พื้นเรียบขอบโค้งเว้าไม่à¹à¸™à¹ˆà¸™à¸­à¸™à¸«à¸£à¸·à¸­à¸¥à¸±à¸à¸©à¸“ะภูมิประเทศà¹à¸šà¸šà¹€à¸™à¸¢à¹à¸‚็งสวิส ภาพถ่ายซ้ำยังสถานที่เดิมà¹à¸ªà¸”งให้เห็นà¸à¸²à¸£à¸‚ยายตัวของรอยเหล่านี้ได้หลายเมตรต่อปี บอà¸à¹ƒà¸«à¹‰à¸—ราบว่าà¹à¸œà¹ˆà¸™ CO2 คงตัวที่ปà¸à¸„ลุมขั้วใต้เบื้องบนà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งจาà¸à¸™à¹‰à¸³à¸™à¸±à¹‰à¸™à¸¡à¸µà¸à¸²à¸£à¸ªà¸¥à¸²à¸¢à¸•à¸±à¸§à¹„ปตามเวลา[96] à¹à¸œà¹ˆà¸™à¸›à¸à¸„ลุมขั้วเหนือมีขนาดเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 1,000 à¸à¸´à¹‚ลเมตร (620 ไมล์) ระหว่างฤดูร้อนของซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคาร[97] à¹à¸¥à¸°à¸¡à¸µà¸›à¸£à¸´à¸¡à¸²à¸•à¸£à¸™à¹‰à¸³à¹à¸‚็งประมาณ 1.6 ล้านลูà¸à¸šà¸²à¸¨à¸à¹Œà¸à¸´à¹‚ลเมตร (380,000 ลูà¸à¸šà¸²à¸¨à¸à¹Œà¹„มล์) ซึ่งหาà¸à¸à¸£à¸°à¸ˆà¸²à¸¢à¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡à¸ªà¸¡à¹ˆà¸³à¹€à¸ªà¸¡à¸­à¸—ั่วทั้งà¹à¸œà¹ˆà¸™à¸à¹‡à¸ˆà¸°à¸¡à¸µà¸„วามหนาถึง 2 à¸à¸´à¹‚ลเมตร (1.2 ไมล์) [98] (เปรียบเทียบà¸à¸±à¸šà¸™à¹‰à¸³à¹à¸‚็งปริมาตร 2.85 ล้านลูà¸à¸šà¸²à¸¨à¸à¹Œà¸à¸´à¹‚ลเมตร (680,000 ลูà¸à¸šà¸²à¸¨à¸à¹Œà¹„มล์) ของà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¸à¸£à¸µà¸™à¹à¸¥à¸™à¸”์) à¹à¸œà¹ˆà¸™à¸Šà¸±à¹‰à¸§à¹ƒà¸•à¹‰à¸¡à¸µà¹€à¸ªà¹‰à¸™à¸œà¹ˆà¸²à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡ 350 à¸à¸´à¹‚ลเมตร (220 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸„วามหนา 3 à¸à¸´à¹‚ลเมตร (1.9 ไมล์)[99] ปริมาตรรวมของน้ำà¹à¸‚็งในà¹à¸œà¹ˆà¸™à¸‚ั้วใต้รวมทั้งที่เà¸à¹‡à¸šà¸ªà¸°à¸ªà¸¡à¹ƒà¸™à¸Šà¸±à¹‰à¸™à¸šà¸£à¸´à¹€à¸§à¸“ใà¸à¸¥à¹‰à¹€à¸„ียงประมาณว่ามีอยู่à¸à¸§à¹ˆà¸² 1.6 ล้านลูà¸à¸šà¸²à¸¨à¸à¹Œà¸à¸´à¹‚ลเมตร[100] à¹à¸œà¹ˆà¸™à¸‚ั้วโลà¸à¸—ั้งคู่มีร่องรูปเà¸à¸¥à¸µà¸¢à¸§à¸›à¸£à¸²à¸à¸ ตามข้อมูลà¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์จาà¸à¸Šà¸²à¹€à¸£à¸”หรือเรดาร์สำรวจส่วนตื้นของดาวอังคารผ่านน้ำà¹à¸‚็ง à¹à¸ªà¸”งว่าร่องดังà¸à¸¥à¹ˆà¸²à¸§à¹€à¸›à¹‡à¸™à¸œà¸¥à¸ˆà¸²à¸à¸¥à¸¡à¸žà¸±à¸”ลาดลงซี่งหมุนเป็นเà¸à¸¥à¸µà¸¢à¸§à¹€à¸™à¸·à¹ˆà¸­à¸‡à¸ˆà¸²à¸à¸œà¸¥à¸à¸£à¸°à¸—บโคริโอลิส[101][102] + +à¸à¸²à¸£à¹€à¸¢à¸·à¸­à¸à¹à¸‚็งตามฤดูà¸à¸²à¸¥à¹ƒà¸™à¸šà¸²à¸‡à¸—้องที่ใà¸à¸¥à¹‰à¸à¸±à¸šà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วใต้ทำให้เà¸à¸´à¸”ชั้นใสของà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งà¹à¸«à¹‰à¸‡à¸«à¸™à¸²à¸›à¸£à¸°à¸¡à¸²à¸“หนึ่งเมตรเหนือพื้นดิน เมื่อถึงฤดูใบไม้ผลิ à¹à¸ªà¸‡à¸­à¸²à¸—ิตย์ทำให้ใต้พื้นผิวอุ่นขึ้น ความดันจาภCO2 ระเหิดบริเวณข้างใต้à¹à¸œà¹ˆà¸™à¸ˆà¸°à¸”ัน ยภà¹à¸¥à¸°à¸ªà¸¸à¸”ท้ายทำให้à¹à¸œà¹ˆà¸™à¹à¸•à¸à¸­à¸­à¸ ซึ่งนำไปสู่à¸à¸²à¸£à¸›à¸°à¸—ุà¹à¸šà¸šà¹„à¸à¹€à¸‹à¸­à¸£à¹Œà¸‚องà¹à¸à¹Šà¸ª CO2 ผสมà¸à¸±à¸šà¸—รายบะซอลต์สีคล้ำหรือà¸à¸¸à¹ˆà¸™ à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸™à¸µà¹‰à¹€à¸à¸´à¸”ขึ้นเร็ว สังเà¸à¸•à¸ˆà¸²à¸à¸­à¸§à¸à¸²à¸¨à¹„ด้ในเวลาเพียงไม่à¸à¸µà¹ˆà¸§à¸±à¸™à¸«à¸£à¸·à¸­à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸«à¸¥à¸²à¸¢à¸ªà¸±à¸›à¸”าห์ถึงหลายเดือน อัตราà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸„่อนข้างจะไม่ปà¸à¸•à¸´à¹ƒà¸™à¸—างธรณีวิทยาโดยเฉพาะà¸à¸±à¸šà¸”าวอังคาร à¹à¸à¹Šà¸ªà¸—ี่เคลื่อนไหลไปข้างใต้à¹à¸œà¹ˆà¸™à¸ˆà¸™à¸–ึงตำà¹à¸«à¸™à¹ˆà¸‡à¹„à¸à¹€à¸‹à¸­à¸£à¹Œà¸ˆà¸°à¸à¸±à¸”สลัà¸à¸£à¸¹à¸›à¹à¸šà¸šà¸„ล้ายใยà¹à¸¡à¸‡à¸¡à¸¸à¸¡à¸à¸£à¸°à¸ˆà¸²à¸¢à¸­à¸­à¸à¹€à¸›à¹‡à¸™à¸£à¸±à¸¨à¸¡à¸µà¸•à¸²à¸¡à¸Šà¹ˆà¸­à¸‡à¸—างที่ผ่านใต้น้ำà¹à¸‚็ง à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸—ี่เà¸à¸´à¸”ขึ้นเหมือนà¸à¸±à¸šà¸ à¸²à¸„ตรงข้ามของโครงข่ายà¸à¸²à¸£à¸à¸±à¸”เซาะจาà¸à¸™à¹‰à¸³à¸—ี่ระบายลงหลุมที่ดึงจุà¸à¸­à¸¸à¸”ออà¸à¹„ป[103][104][105][106] +ภูมิศาสตร์à¹à¸¥à¸°à¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¸ à¸¹à¸¡à¸´à¸›à¸£à¸°à¹€à¸—ศพื้นผิว[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ภูมิศาสตร์ดาวอังคาร +à¹à¸œà¸™à¸—ี่ภูมิประเทศจาà¸à¹‚มลา à¹à¸ªà¸”งพื้นที่มีระดับสูง (สีà¹à¸”งà¹à¸¥à¸°à¸ªà¸µà¸ªà¹‰à¸¡) เป็นพื้นที่ส่วนใหà¸à¹ˆà¹ƒà¸™à¸‹à¸µà¸à¹‚ลà¸à¹ƒà¸•à¹‰à¸‚องดาวอังคาร ที่ราบลุ่ม (สีฟ้า) ทางตอนเหนือ ที่ราบสูงภูเขาไฟà¸à¸³à¸«à¸™à¸”ขอบเขตที่ราบทางเหนือในบางบริเวณ ในขณะที่พื้นที่สูงมีà¹à¸­à¹ˆà¸‡à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚นาดใหà¸à¹ˆà¸«à¸¥à¸²à¸¢à¹à¸«à¹ˆà¸‡ + +à¹à¸¡à¹‰à¸§à¹ˆà¸²à¹‚ยฮันน์ ไฮน์ริภฟอน เมดเลอร์ à¹à¸¥à¸°à¸§à¸´à¸¥à¹€à¸®à¸¥à¹Œà¸¡ เบียร์จะเป็นที่จดจำอย่างดียิ่งว่าเป็นผู้วาดà¹à¸œà¸™à¸—ี่ดวงจันทร์à¹à¸•à¹ˆà¸žà¸§à¸à¹€à¸‚าà¸à¹‡à¹€à¸›à¹‡à¸™ "นัà¸à¸§à¸²à¸”à¹à¸œà¸™à¸—ี่ดาวอังคาร" อันดับà¹à¸£à¸ พวà¸à¹€à¸‚าเริ่มโดยà¸à¸³à¸«à¸™à¸”ภูมิประเทศพื้นผิวดาวอังคารส่วนใหà¸à¹ˆà¹ƒà¸«à¹‰à¹€à¸›à¹‡à¸™à¸«à¸¥à¸±à¸à¸à¸²à¸™à¸¡à¸±à¹ˆà¸™à¸„ง à¹à¸¥à¸°à¹‚ดยà¸à¸²à¸£à¸™à¸µà¹‰à¸ˆà¸¶à¸‡à¸ªà¸²à¸¡à¸²à¸£à¸–วัดคาบà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸­à¸šà¸•à¸±à¸§à¹€à¸­à¸‡à¸‚องดาวอังคารได้อย่างà¹à¸¡à¹ˆà¸™à¸¢à¸³à¸¡à¸²à¸à¸‚ึ้น ในปี 1840 (พ.ศ. 2383) เมดเลอร์รวบรวมผลà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸•à¸¥à¸­à¸”สิบปีของเขาà¹à¸¥à¹‰à¸§à¸§à¸²à¸”à¹à¸œà¸™à¸—ี่ดาวอังคารขึ้นเป็นครั้งà¹à¸£à¸ à¹à¸—นที่จะมีà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¹ƒà¸«à¹‰à¸à¸±à¸šà¸ˆà¸¸à¸”สังเà¸à¸•à¸•à¹ˆà¸²à¸‡ ๆ อันหลายหลาà¸à¸™à¸±à¹‰à¸™ เบียร์à¹à¸¥à¸°à¹€à¸¡à¸”เลอร์à¸à¸¥à¸±à¸šà¹ƒà¸Šà¹‰à¸§à¸´à¸˜à¸µà¸‡à¹ˆà¸²à¸¢ ๆ โดยระบุด้วยตัวอัà¸à¸©à¸£ เมอริเดียนเบย์ (ไซนัสเมอริเดียนี) ถูà¸à¹€à¸£à¸µà¸¢à¸à¹€à¸›à¹‡à¸™à¸ à¸¹à¸¡à¸´à¸›à¸£à¸°à¹€à¸—ศ "a"[107] + +ปัจจุบันนี้ภูมิประเทศบนดาวอังคารได้รับà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¸ˆà¸²à¸à¸«à¸¥à¸²à¸¢à¹à¸«à¸¥à¹ˆà¸‡à¸—ี่มา ภูมิประเทศที่เห็นโดดเด่นจะตั้งชื่อตามเทววิทยาคลาสสิภหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸—ี่ใหà¸à¹ˆà¸à¸§à¹ˆà¸² 60 à¸à¸´à¹‚ลเมตรตั้งชื่อตามชื่อของนัà¸à¸§à¸´à¸—ยาศาสตร์ นัà¸à¹€à¸‚ียน à¹à¸¥à¸°à¸šà¸¸à¸„คลอื่นใดที่มีบทบาทช่วยเหลือสนับสนุนในà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸”าวอังคารซึ่งได้ล่วงลับไปà¹à¸¥à¹‰à¸§ หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸—ี่เล็à¸à¸à¸§à¹ˆà¸² 60 à¸à¸´à¹‚ลเมตรลงมา ตั้งชื่อตามชื่อเมืองหรือหมู่บ้านบนโลà¸à¸‹à¸¶à¹ˆà¸‡à¸ˆà¸°à¸•à¹‰à¸­à¸‡à¸¡à¸µà¸›à¸£à¸°à¸Šà¸²à¸à¸£à¸™à¹‰à¸­à¸¢à¸à¸§à¹ˆà¸² 100,000 คน หุบเขาขนาดใหà¸à¹ˆà¹„ด้ชื่อมาจาภคำ "ดาวอังคาร" หรือ ดาวฤà¸à¸©à¹Œ" ในภาษาต่าง ๆ นานา ส่วนหุบเขาขนาดเล็à¸à¸™à¸±à¹‰à¸™à¹„ด้ชื่อจาà¸à¸Šà¸·à¹ˆà¸­à¸‚องà¹à¸¡à¹ˆà¸™à¹‰à¸³[108] + +ภูมิประเทศที่มีความโดดเด่นขนาดใหà¸à¹ˆà¸¢à¸±à¸‡à¸„งมีชื่อเรียà¸à¹€à¸”ิมอยู่หลายชื่อ à¹à¸•à¹ˆà¸à¹‡à¸¡à¸±à¸à¸¡à¸µà¸à¸²à¸£à¸›à¸£à¸±à¸šà¸›à¸£à¸¸à¸‡à¹€à¸žà¸·à¹ˆà¸­à¹ƒà¸«à¹‰à¸ªà¸°à¸—้อนองค์ความรู้ใหม่เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸˜à¸£à¸£à¸¡à¸Šà¸²à¸•à¸´à¸‚องภูมิประเทศนั้น ตัวอย่างเช่น นิà¸à¸‹à¹Œà¹‚อลิมปิà¸à¸² (หิมะà¹à¸«à¹ˆà¸‡à¹‚อลิมปัส) à¸à¸¥à¸²à¸¢à¸¡à¸²à¹€à¸›à¹‡à¸™ โอลิมปัสมอนส์ (ภูเขาโอลิมปัส)[109] พื้นผิวดาวอังคารที่มองเห็นจาà¸à¹‚ลà¸à¹à¸šà¹ˆà¸‡à¸­à¸­à¸à¹„ด้เป็นสองà¸à¸¥à¸¸à¹ˆà¸¡à¸žà¸·à¹‰à¸™à¸—ี่จาà¸à¸„วามà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸‚องà¸à¸²à¸£à¸ªà¸°à¸—้อนà¹à¸ªà¸‡ ที่ราบสีจางที่ปà¸à¸„ลุมด้วยà¸à¸¸à¹ˆà¸™à¹à¸¥à¸°à¸—รายอันอุดมไปด้วยออà¸à¹„ซด์ของเหล็à¸à¸‹à¸¶à¹ˆà¸‡à¸¡à¸µà¸ªà¸µà¹à¸”งนั้น ครั้งหนึ่งเคยคิดà¸à¸±à¸™à¸§à¹ˆà¸²à¹€à¸›à¹‡à¸™ "ทวีป" ของดาวอังคาร จึงมีà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¸—ำนอง อะเรเบียเทร์รา (à¹à¸œà¹ˆà¸™à¸”ินà¹à¸«à¹ˆà¸‡à¸­à¸²à¸£à¸°à¹€à¸šà¸µà¸¢) หรืออย่าง à¹à¸­à¸¡à¸°à¹‚ซนิสเพลนิเชีย (ที่ราบà¹à¸­à¸¡à¸°à¸‹à¸­à¸™) ภูมิประเทศคล้ำถูà¸à¸„ิดว่าเป็นทะเล ดังนั้นจึงตั้งชื่ออย่าง à¹à¸¡à¸£à¹Œà¹€à¸­à¸£à¸´à¹€à¸•à¸£à¸µà¸¢à¸¡ (ทะเลà¹à¸”ง) à¹à¸¡à¸£à¹Œà¹„ซเรนัม à¹à¸¥à¸°à¸­à¸­à¹‚รรีไซนัส ภูมิประเทศมืดคล้ำที่มีขนาดใหà¸à¹ˆà¸—ี่สุดที่มองเห็นจาà¸à¹‚ลà¸à¸„ือ เซียทิสเมเจอร์เพลนัม[110] à¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งคงตัวทางขั้วเหนือได้ชื่อว่า เพลนัมบอเรียม ในขณะที่à¹à¸œà¹ˆà¸™à¸—างขั้วใต้เรียà¸à¸§à¹ˆà¸² เพลนัมออสเทรล + +เส้นศูนย์สูตรของดาวอังคารถูà¸à¸à¸³à¸«à¸™à¸”โดยà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸‚องดาว à¹à¸•à¹ˆà¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸‚องเมริเดียนà¹à¸£à¸à¹€à¸›à¹‡à¸™à¸ªà¸´à¹ˆà¸‡à¸—ี่ถูà¸à¸£à¸°à¸šà¸¸à¸‚ึ้นเอง ดังเช่นตำà¹à¸«à¸™à¹ˆà¸‡à¸à¸£à¸µà¸™à¸´à¸Šà¸‚องโลภคือต้องเลือà¸à¸à¸³à¸«à¸™à¸”จุดชี้ขาดขึ้นมา เมดเลอร์à¹à¸¥à¸°à¹€à¸šà¸µà¸¢à¸£à¹Œà¹„ด้เลือà¸à¹€à¸ªà¹‰à¸™à¹€à¸¡à¸­à¸£à¸´à¹€à¸”ียนในปี 1830 (พ.ศ. 2373) สำหรับà¹à¸œà¸™à¸—ี่à¹à¸£à¸à¸‚องดาวอังคาร ต่อมาภายหลังยานอวà¸à¸²à¸¨à¸¡à¸²à¸£à¸´à¹€à¸™à¸­à¸£à¹Œ 9 ได้ให้ภาพดาวอังคารมาà¸à¸¡à¸²à¸¢à¹ƒà¸™à¸›à¸µ 1972 (พ.ศ. 2515) หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸‚นาดเล็à¸à¸‹à¸¶à¹ˆà¸‡à¹„ด้ชื่อภายหลังว่า à¹à¸­à¸£à¸µ-0 ในบริเวณ ไซนัสเมอริเดียนี ("อ่าวตรงà¸à¸¥à¸²à¸‡" หรือ "อ่าวเมอริเดียน") ได้ถูà¸à¹€à¸¥à¸·à¸­à¸à¹€à¸›à¹‡à¸™à¸ˆà¸¸à¸”นิยามลองจิจูดที่ 0.0 องศา เพื่อให้พ้องตรงà¸à¸±à¸™à¸à¸±à¸šà¹€à¸ªà¹‰à¸™à¸—ี่ได้à¸à¸³à¸«à¸™à¸”ไว้เดิม[111] + +เพราะดาวอังคารไม่มีมหาสมุทรดังนั้นจึงไม่มี "ระดับน้ำทะเล" พื้นผิวที่มีระดับà¸à¸²à¸£à¸¢à¸à¸•à¸±à¸§à¹€à¸›à¹‡à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ˆà¸¶à¸‡à¸–ูà¸à¹€à¸¥à¸·à¸­à¸à¹ƒà¸Šà¹‰à¹€à¸›à¹‡à¸™à¸£à¸°à¸”ับอ้างอิงà¹à¸—นซึ่งเรียà¸à¸§à¹ˆà¸² à¹à¸­à¸£à¸µà¸­à¸­à¸¢à¸”์ [112] ของดาวอังคาร เปรียบดังจีออยด์บนพื้นผิวโลภระดับความสูงที่มีค่าเท่าà¸à¸±à¸šà¸¨à¸¹à¸™à¸¢à¹Œà¸–ูà¸à¸à¸³à¸«à¸™à¸” ณ ความสูงที่มีความดันบรรยาà¸à¸²à¸¨à¹€à¸—่าà¸à¸±à¸š 610.5 ปาสà¸à¸²à¸¥ (6.105 มิลลิบาร์)[113] ค่าความดันนี้สอดคล้องà¸à¸±à¸šà¸ˆà¸¸à¸”สามสถานะของน้ำà¹à¸¥à¸°à¸¡à¸µà¸„่าประมาณร้อยละ 0.6 ของความดันพื้นผิวที่ระดับน้ำทะเลบนโลภ(0.006 บรรยาà¸à¸²à¸¨)[114] ในทางปà¸à¸´à¸šà¸±à¸•à¸´ ณ ปัจจุบัน พื้นผิวนี้ถูà¸à¸à¸³à¸«à¸™à¸”โดยตรงจาà¸à¸”าวเทียมตรวจวัดความโน้มถ่วง +à¹à¸œà¸™à¸—ี่สี่มุมดาวอังคาร[à¹à¸à¹‰] + +ภาพอิมเมจà¹à¸¡à¸žà¸”ังต่อไปนี้ของดาวอังคารà¹à¸šà¹ˆà¸‡à¸­à¸­à¸à¹€à¸›à¹‡à¸™à¹à¸œà¸™à¸—ี่สี่มุมจำนวน 30 ชิ้น à¸à¸³à¸«à¸™à¸”โดยองค์à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸˜à¸£à¸“ีวิทยาสหรัà¸à¸­à¹€à¸¡à¸£à¸´à¸à¸²[115][116] à¹à¸œà¸™à¸—ี่à¹à¸•à¹ˆà¸¥à¸°à¸Šà¸´à¹‰à¸™à¸¡à¸µà¸à¸²à¸£à¸à¸³à¸à¸±à¸šà¸•à¸±à¸§à¹€à¸¥à¸‚พร้อมอัà¸à¸©à¸£à¸™à¸³à¸«à¸™à¹‰à¸² "MC" ย่อมาจาภ"Mars Chart" หรือà¹à¸œà¸™à¸ à¸²à¸žà¸”าวอังคาร[117] ด้านบนคือà¹à¸œà¸™à¸—ี่ตอนเหนือสุด ตำà¹à¸«à¸™à¹ˆà¸‡ 0°N 180°W / 0°N 180°W อยู่ทางซ้ายสุดเหนือเส้นศูนย์สูตร ภาพà¹à¸œà¸™à¸—ี่ได้มาจาà¸à¸¡à¸²à¸£à¹Œà¸ªà¹‚à¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œ +Mars Quad Map +เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ à¸²à¸žà¸™à¸µà¹‰ +0°N 180°W / 0°N 180°W +0°N 0°W / 0°N -0°E +90°N 0°W / 90°N -0°E +MC-01 + +à¹à¸¡à¸£à¹Œà¸šà¸­à¹€à¸£à¸µà¸¢à¸¡ +MC-02 + +ไดอะเครีย +MC-03 + +อาร์คาเดีย +MC-04 + +à¹à¸¡à¸£à¹Œà¹à¸­à¸‹à¸´à¹€à¸”เลียม +MC-05 + +อิสมีเนียสลาคัส +MC-06 + +เคเซียส +MC-07 + +ซีเบรเนีย +MC-08 + +à¹à¸­à¸¡à¸°à¹‚ซนิส +MC-09 + +ธาร์ซิส +MC-10 + +ลูนีเพลัส +MC-11 + +ออà¸à¹€à¸‹à¸µà¸¢à¹€à¸žà¸¥à¸±à¸ª +MC-12 + +อะเรเบีย +MC-13 + +เซียทิสเมเจอร์ +MC-14 + +อะเมนเธส +MC-15 + +อิลีเซียม +MC-16 + +เมมโนเนีย +MC-17 + +ฟีนีซิส +MC-18 + +โคเพรตส์ +MC-19 + +มาร์à¸à¸²à¸£à¸´à¸•à¸´à¹€à¸Ÿà¸­à¸£à¹Œ +MC-20 + +ซาบีอัส +MC-21 + +ไออาพีเจีย +MC-22 + +ทีร์รีนัม +MC-23 + +อีโอลิส +MC-24 + +à¹à¸Ÿà¸˜à¸­à¸™à¸•à¸´à¸ª +MC-25 + +ธอเมเซีย +MC-26 + +อาร์จีเร +MC-27 + +โนอาคิส +MC-28 + +เฮลลาส +MC-29 + +เอริเดเนีย +MC-30 + +à¹à¸¡à¸£à¹Œà¸­à¸­à¸ªà¹€à¸—รล + + +ภูมิประเทศจาà¸à¸à¸²à¸£à¸–ูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™[à¹à¸à¹‰] +หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸­à¸™à¹€à¸™à¸§à¸´à¸¥à¸¥à¹Œà¹à¸¥à¸°à¸à¸²à¸™à¸Šà¹ˆà¸§à¸¢à¸¥à¸‡à¸ˆà¸­à¸”ของยานโรเวอร์สปิริต + +ภูมิประเทศของดาวอังคารมีà¸à¸²à¸£à¹à¸¢à¸à¸­à¸­à¸à¹€à¸›à¹‡à¸™à¸ªà¸­à¸‡à¸¥à¸±à¸à¸©à¸“ะอย่างโดดเด่นคือ พื้นที่ราบà¹à¸šà¸™à¸ˆà¸²à¸à¸à¸²à¸£à¹„หลของลาวาทางซีà¸à¹€à¸«à¸™à¸·à¸­à¸‹à¸¶à¹ˆà¸‡à¸œà¸´à¸”à¹à¸œà¸à¹€à¸”่นชัดจาà¸à¸—ี่ราบสูงอันอุดมไปด้วยหลุมเล็à¸à¸«à¸¥à¸¸à¸¡à¸™à¹‰à¸­à¸¢à¸ˆà¸²à¸à¸à¸²à¸£à¸–ูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸¡à¸²à¹à¸•à¹ˆà¸„รั้งโบราณà¸à¸²à¸¥à¸—างซีà¸à¹ƒà¸•à¹‰ à¸à¸²à¸£à¸§à¸´à¸ˆà¸±à¸¢à¹ƒà¸™à¸›à¸µ 2008 (พ.ศ. 2551) à¹à¸ªà¸”งหลัà¸à¸à¸²à¸™à¹‚น้มเอียงไปยังทฤษฎีที่เสนอขึ้นในปี 1980 (พ.ศ. 2523) ซึ่งà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸² ราวสี่พันล้านปีà¸à¹ˆà¸­à¸™ ซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคารถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยวัตถุขนาดใหà¸à¹ˆà¸£à¸²à¸§à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¸ªà¸´à¸šà¸–ึงสองในสามของดวงจันทร์ของโลภถ้าทฤษฎีนี้เป็นจริงย่อมทำให้ซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคารเป็นตำà¹à¸«à¸™à¹ˆà¸‡à¸‚องหลุมà¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸”้วยขนาดยาว 10,600 à¸à¸´à¹‚ลเมตร à¹à¸¥à¸°à¸à¸§à¹‰à¸²à¸‡ 8,500 à¸à¸´à¹‚ลเมตร (6,600 x 5,300 ไมล์) หรือโดยคร่าว ๆ à¹à¸¥à¹‰à¸§à¹€à¸—่าà¸à¸±à¸šà¸žà¸·à¹‰à¸™à¸—ี่ของยุโรป เอเชีย à¹à¸¥à¸°à¸­à¸­à¸ªà¹€à¸•à¸£à¹€à¸¥à¸µà¸¢à¸—ั้งหมดรวมà¸à¸±à¸™ มีขนาดใหà¸à¹ˆà¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¹à¸­à¹ˆà¸‡à¹„อต์เค็น-ขั้วใต้ของดวงจันทร์à¹à¸¥à¸°à¹€à¸›à¹‡à¸™à¸«à¸¥à¸¸à¸¡à¸•à¸à¸à¸£à¸°à¸—บที่ใหà¸à¹ˆà¸—ี่สุดในระบบสุริยะ[16][17] +รอยจาà¸à¸”าวเคราะห์น้อยพุ่งชนดาวอังคารที่ใหม่มาภ3°20′N 219°23′E / 3.34°N 219.38°E - ซ้าย-à¸à¹ˆà¸­à¸™/27 มีนาคม & ขวา-หลัง/28 มีนาคม 2012 (MRO)[118] + +ดาวอังคารมีรอยตำหนิของหลุมจาà¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸¡à¸²à¸à¸¡à¸²à¸¢ เฉพาะที่มีเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸¡à¸²à¸à¸à¸§à¹ˆà¸² 5 à¸à¸´à¹‚ลเมตร (3.1 ไมล์) ขึ้นไป พบว่ามีจำนวนรวมà¸à¸§à¹ˆà¸² 43,000 à¹à¸«à¹ˆà¸‡[119] หลุมใหà¸à¹ˆà¸—ี่สุดที่มีà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¹à¸¥à¹‰à¸§à¸„ือà¹à¸­à¹ˆà¸‡à¸•à¸à¸à¸£à¸°à¸—บเฮลลาส ภูมิประเทศอัลเบโดจางมองเห็นได้ชัดเจนจาà¸à¹‚ลà¸[120] จาà¸à¸à¸²à¸£à¸—ี่ดาวอังคารมีมวลน้อย ความน่าจะเป็นที่จะถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸ˆà¸²à¸à¸§à¸±à¸•à¸–ุต่าง ๆ จึงอยู่ราวครึ่งหนึ่งของโลภà¹à¸•à¹ˆà¸”้วยตำà¹à¸«à¸™à¹ˆà¸‡à¸‚องดาวอังคารซึ่งใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¹à¸–บดาวเคราะห์น้อย ฉะนั้นจึงมีโอà¸à¸²à¸ªà¸¡à¸²à¸à¸‚ึ้นที่จะโดนจู่โจมโดยวัตถุมาà¸à¸¡à¸²à¸¢à¸ˆà¸²à¸à¹à¸–บดังà¸à¸¥à¹ˆà¸²à¸§ ดาวอังคารยังคล้ายว่าจะถูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยดาวหางคาบสั้นอยู่บ่อยครั้งอีà¸à¸”้วย อย่างเช่นà¸à¸¥à¸¸à¹ˆà¸¡à¸—ี่อยู่ในวงโคจรของดาวพฤหัสบดี[121] นอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸™à¸µà¹‰ หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸—ี่พบบนดาวอังคารเมื่อเทียบà¸à¸±à¸™à¹à¸¥à¹‰à¸§à¸¢à¸±à¸‡à¸™à¹‰à¸­à¸¢à¸à¸§à¹ˆà¸²à¸—ี่พบบนดวงจันทร์ค่อนข้างมาภเพราะบรรยาà¸à¸²à¸¨à¸‚องดาวอังคารสามารถปà¸à¸›à¹‰à¸­à¸‡à¸•à¹‰à¸²à¸™à¸—านต่ออุà¸à¸à¸²à¸šà¸²à¸•à¸‚นาดเล็à¸à¹„ด้ หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸²à¸‡à¹à¸«à¹ˆà¸‡à¸¡à¸µà¸¥à¸±à¸à¸©à¸“ะทางสัณà¸à¸²à¸™à¸§à¸´à¸—ยาที่à¹à¸ªà¸”งว่าพื้นบริเวณนั้นเปียà¸à¸Šà¸·à¹‰à¸™à¸ à¸²à¸¢à¸«à¸¥à¸±à¸‡à¸ˆà¸²à¸à¸—ี่อุà¸à¸à¸²à¸šà¸²à¸•à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹à¸¥à¹‰à¸§[122] +ภูเขาไฟ[à¹à¸à¹‰] +ภาพโอลิมปัสมอนส์จาà¸à¸¢à¸²à¸™à¹„วà¸à¸´à¸‡à¸­à¸­à¸£à¹Œà¸šà¸´à¹€à¸•à¸­à¸£à¹Œ +ดูบทความหลัà¸à¸—ี่: ภูเขาไฟบนดาวอังคาร + +ภูเขาไฟรูปโล่โอลิมปัสมอนส์ (เมาท์โอลิมปัส) เป็นภูเขาไฟที่ดับà¹à¸¥à¹‰à¸§à¹ƒà¸™à¸šà¸£à¸´à¹€à¸§à¸“ธาร์ซิส พื้นที่ราบสูงà¸à¸§à¹‰à¸²à¸‡à¹ƒà¸«à¸à¹ˆà¸‹à¸¶à¹ˆà¸‡à¸¢à¸±à¸‡à¸¡à¸µà¸ à¸¹à¹€à¸‚าไฟขนาดใหà¸à¹ˆà¸­à¸·à¹ˆà¸™à¸­à¸µà¸à¸«à¸¥à¸²à¸¢à¸¥à¸¹à¸ โอลิมปัสมอนส์มีความสูงโดยประมาณà¸à¸§à¹ˆà¸²à¸ªà¸²à¸¡à¹€à¸—่าของความสูงของเขาเอเวอเรสต์ซึ่งเทียบà¸à¸±à¸™à¹à¸¥à¹‰à¸§à¸ªà¸¹à¸‡à¹€à¸žà¸µà¸¢à¸‡ 8.8 à¸à¸´à¹‚ลเมตร (5.5 ไมล์)[123] ทำให้ภูเขาไฟลูà¸à¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¹€à¸‚าที่สูงที่สุดหรือสูงเป็นอันดับสองในระบบสุริยะขึ้นอยู่à¸à¸±à¸šà¸§à¸´à¸˜à¸µà¸à¸²à¸£à¸§à¸±à¸”ซึ่งà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸±à¸™à¸­à¸­à¸à¹„ป ทำให้ได้ค่าตัวเลขตั้งà¹à¸•à¹ˆ 21 ถึง 27 à¸à¸´à¹‚ลเมตร (13 ถึง 17 ไมล์)[124][125] +ตำà¹à¸«à¸™à¹ˆà¸‡à¸˜à¸£à¸“ีภาค[à¹à¸à¹‰] +เวลส์มาริเนริส (2001 Mars Odyssey) + +เวลส์มาริเนริส (เป็นรูปละตินของ หุบเขามาริเนอร์ หรือรู้จัà¸à¹ƒà¸™à¸Šà¸·à¹ˆà¸­ อะà¸à¸²à¸˜à¸²à¸”ีมอน ในà¹à¸œà¸™à¸—ี่คลองเà¸à¹ˆà¸²) เป็นหุบเขาขนาดใหà¸à¹ˆ มีความยาวร่วม 4,000 à¸à¸´à¹‚ลเมตร (2,500 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸„วามลึà¸à¹„ด้มาà¸à¸–ึง 7 à¸à¸´à¹‚ลเมตร (4.3 ไมล์) ความยาวของเวลส์มาริเนริสเทียบเท่าà¸à¸±à¸šà¸„วามยาวของทวีปยุโรปà¹à¸¥à¸°à¸—อดยาวà¸à¸´à¸™à¸£à¸°à¸¢à¸°à¸—างà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¸«à¹‰à¸²à¸‚องเส้นรอบวงของดาวอังคาร หาà¸à¹€à¸—ียบà¸à¸±à¸™à¹à¸¥à¹‰à¸§ à¹à¸à¸£à¸™à¸”์à¹à¸„นยอนบนโลà¸à¸¡à¸µà¸„วามยาวเพียง 446 à¸à¸´à¹‚ลเมตร (277 ไมล์) à¹à¸¥à¸°à¸¡à¸µà¸„วามลึà¸à¹€à¸žà¸µà¸¢à¸‡à¹€à¸à¸·à¸­à¸š 2 à¸à¸´à¹‚ลเมตร (1.2 ไมล์) เท่านั้น เวลส์มาริเนริสà¸à¸³à¹€à¸™à¸´à¸”ขึ้นจาà¸à¸à¸²à¸£à¸›à¸¹à¸”นูนขึ้นของพื้นที่ธาร์ซิสจนเป็นสาเหตุให้เปลือà¸à¸”าวเคราะห์ในพื้นที่เวลส์มาริเนริสà¹à¸•à¸à¸—ลายออภมีà¸à¸²à¸£à¹€à¸ªà¸™à¸­à¹ƒà¸™à¸›à¸µ 2012 (พ.ศ. 2555) ว่าเวลส์มาริเนริสไม่ได้เป็นเพียงà¹à¸„่à¸à¸£à¸²à¹€à¸šà¸™à¹à¸•à¹ˆà¸¢à¸±à¸‡à¹€à¸›à¹‡à¸™à¸‚อบเขตระหว่างà¹à¸œà¹ˆà¸™à¹€à¸›à¸¥à¸·à¸­à¸à¸”าวที่ปราà¸à¸à¸à¸²à¸£à¹€à¸„ลื่อนตัวà¹à¸šà¸šà¹€à¸¥à¸·à¹ˆà¸­à¸™à¸œà¹ˆà¸²à¸™à¸à¸±à¸™à¸à¸§à¹ˆà¸² 150 à¸à¸´à¹‚ลเมตร (93 ไมล์) ทำให้ดาวอังคารเป็นดาวเคราะห์ที่อาจจะมีà¸à¸²à¸£à¸§à¸²à¸‡à¸•à¸±à¸§à¸‚องà¹à¸œà¹ˆà¸™à¸˜à¸£à¸“ีภาคเป็นสองà¹à¸œà¹ˆà¸™[126][127] +หลุมโพรง[à¹à¸à¹‰] + +ภาพจาà¸à¹€à¸˜à¸¡à¸´à¸ªà¸«à¸£à¸·à¸­à¸£à¸°à¸šà¸šà¸–่ายภาพจาà¸à¸à¸²à¸£à¸›à¸¥à¹ˆà¸­à¸¢à¸„วามร้อนซึ่งอยู่บนยาน 2001 มาร์สโอดิสซีของนาซา ได้เผยให้เห็นถึงปาà¸à¸—างเข้าถ้ำที่เป็นไปได้เจ็ดà¹à¸«à¹ˆà¸‡à¸šà¸£à¸´à¹€à¸§à¸“ด้านข้างของภูเขาไฟอาร์เซียมอนส์[128] มีà¸à¸²à¸£à¸•à¸±à¹‰à¸‡à¸Šà¸·à¹ˆà¸­à¸–้ำเหล่านี้ตามชื่อของคนรัà¸à¹à¸•à¹ˆà¸¥à¸°à¸„นของบรรดาผู้คนพบซึ่งเรียà¸à¸£à¸§à¸¡ ๆ à¸à¸±à¸™à¸§à¹ˆà¸² "น้องสาวทั้งเจ็ด"[129] ปาà¸à¸—างเข้าถ้ำมีความà¸à¸§à¹‰à¸²à¸‡à¸§à¸±à¸”ได้ตั้งà¹à¸•à¹ˆ 100 ไปจนถึง 252 เมตร (328 ถึง 827 ฟุต) à¹à¸¥à¸°à¹€à¸Šà¸·à¹ˆà¸­à¸§à¹ˆà¸²à¸¡à¸µà¸„วามลึà¸à¸­à¸¢à¹ˆà¸²à¸‡à¸™à¹‰à¸­à¸¢ 73 ถึง 96 เมตร (240 ถึง 315 ฟุต) เนื่องจาà¸à¹à¸ªà¸‡à¹„ม่สามารถส่องลงถึงพื้นของเà¸à¸·à¸­à¸šà¸—ุà¸à¸–้ำ จึงเป็นไปได้ว่าตัวถ้ำอาจทอดยาวลึà¸à¹€à¸‚้าไปมาà¸à¸à¸§à¹ˆà¸²à¸„่าขั้นต่ำที่ประเมินไว้à¹à¸¥à¸°à¸­à¸²à¸ˆà¸‚ยายà¸à¸§à¹‰à¸²à¸‡à¸­à¸­à¸à¹ƒà¸•à¹‰à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ มีเฉพาะ "เดนา" เท่านั้นที่เป็นข้อยà¸à¹€à¸§à¹‰à¸™à¹€à¸žà¸£à¸²à¸°à¸ªà¸²à¸¡à¸²à¸£à¸–มองเห็นพื้นถ้ำà¹à¸¥à¸°à¸§à¸±à¸”ความลึà¸à¹„ด้เท่าà¸à¸±à¸š 130 เมตร (430 ฟุต) ภายในถ้ำโพรงเหล่านี้น่าจะเป็นบริเวณที่ปลอดภัยจาà¸à¸­à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸‚นาดเล็ภรังสีอัลตราไวโอเลต เปลวสุริยะ à¹à¸¥à¸°à¸­à¸™à¸¸à¸ à¸²à¸„พลังงานสูงต่าง ๆ ที่à¸à¸£à¸°à¸«à¸™à¹ˆà¸³à¸Šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸‚องดาวเคราะห์[130] +บรรยาà¸à¸²à¸¨[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: บรรยาà¸à¸²à¸¨à¸‚องดาวอังคาร +บรรยาà¸à¸²à¸¨à¸—ี่หลุดหนีไปจาà¸à¸”าวอังคาร (คาร์บอน ออà¸à¸‹à¸´à¹€à¸ˆà¸™ à¹à¸¥à¸°à¹„ฮโดรเจน) โดยเมเว็นในรังสียูวี[131] + +ดาวอังคารสูà¸à¹€à¸ªà¸µà¸¢à¹à¸¡à¹‡à¸à¸™à¸µà¹‚ตสเฟียร์ไปเมื่อสี่พันล้านปีà¸à¹ˆà¸­à¸™[132] อาจเพราะà¸à¸²à¸£à¸Šà¸™à¸¡à¸²à¸à¸¡à¸²à¸¢à¸«à¸¥à¸²à¸¢à¸„รั้งโดยดาวเคราะห์น้อย[133] ทำให้ลมสุริยะมีปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸à¸£à¸°à¸—บโดยตรงà¸à¸±à¸šà¹„อโอโนสเฟียร์ของดาวอังคาร ลดความหนาà¹à¸™à¹ˆà¸™à¸‚องบรรยาà¸à¸²à¸¨à¸¥à¸‡à¹„ปเรื่อย ๆ โดยปอà¸à¹€à¸›à¸¥à¸·à¹‰à¸­à¸‡à¸­à¸°à¸•à¸­à¸¡à¸ˆà¸²à¸à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸Šà¸±à¹‰à¸™à¸™à¸­à¸à¹ƒà¸«à¹‰à¸«à¸¥à¸¸à¸”ลอยออà¸à¹„ป ทั้งมาร์สโà¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œà¹à¸¥à¸°à¸¡à¸²à¸£à¹Œà¸ªà¹€à¸­à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ªà¸•à¹ˆà¸²à¸‡à¸à¹‡à¸•à¸£à¸§à¸ˆà¸žà¸šà¸­à¸™à¸¸à¸ à¸²à¸„ของบรรยาà¸à¸²à¸¨à¸—ี่à¹à¸•à¸à¸•à¸±à¸§à¹€à¸›à¹‡à¸™à¸›à¸£à¸°à¸ˆà¸¸à¸¥à¸²à¸à¹€à¸›à¹‡à¸™à¸«à¸²à¸‡à¸¢à¸²à¸§à¹ƒà¸™à¸«à¹‰à¸§à¸‡à¸­à¸§à¸à¸²à¸¨à¹€à¸šà¸·à¹‰à¸­à¸‡à¸«à¸¥à¸±à¸‡à¸”าวอังคาร[132][134] à¹à¸¥à¸°à¸à¸²à¸£à¸ªà¸¹à¸à¹€à¸ªà¸µà¸¢à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¹„ปนี้à¸à¸³à¸¥à¸±à¸‡à¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¹‚ดยยานเมเว็น เมื่อเทียบà¸à¸±à¸šà¹‚ลà¸à¹à¸¥à¹‰à¸§à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸‚องดาวอังคารเบาบางà¸à¸§à¹ˆà¸²à¸¡à¸²à¸ ความà¸à¸”อาà¸à¸²à¸¨à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ ณ ปัจจุบันอยู่ในช่วงตั้งà¹à¸•à¹ˆà¸™à¹‰à¸­à¸¢à¸ªà¸¸à¸”ที่ 30 ปาสà¸à¸²à¸¥ (0.030 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥) บนยอดโอลิมปัสมอนส์ ไปจนถึง 1,155 ปาสà¸à¸²à¸¥ (1.155 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥) ในเฮลลาสเพลนิเชีย โดยมีความà¸à¸”อาà¸à¸²à¸¨à¹€à¸‰à¸¥à¸µà¹ˆà¸¢à¸—ี่ระดับพื้นผิวเท่าà¸à¸±à¸š 600 ปาสà¸à¸²à¸¥ (0.60 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥)[135] ความหนาà¹à¸™à¹ˆà¸™à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸ªà¸¹à¸‡à¸ªà¸¸à¸”บนดาวอังคารมีค่าเทียบเท่าà¸à¸±à¸šà¸„วามดัน ณ จุดที่สูง 35 à¸à¸´à¹‚ลเมตร (22 ไมล์)[136] เหนือพื้นผิวโลภเป็นผลให้ความหนาà¹à¸™à¹ˆà¸™à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸„ิดเป็นเพียงร้อยละ 0.6 ของโลà¸à¹€à¸—่านั้น (101.3 à¸à¸´à¹‚ลปาสà¸à¸²à¸¥) มีมาตราความสูงของบรรยาà¸à¸²à¸¨à¸—ี่ประมาณ 10.8 à¸à¸´à¹‚ลเมตร (6.7 ไมล์)[137] ซึ่งสูงà¸à¸§à¹ˆà¸²à¹‚ลภ(6 à¸à¸´à¹‚ลเมตร (3.7 ไมล์)) เพราะความโน้มถ่วงที่พื้นผิวดาวอังคารมีค่าเพียงร้อยละ 38 ของโลภรวมถึงผลชดเชยจาà¸à¸—ั้งà¸à¸²à¸£à¸¡à¸µà¸­à¸¸à¸“หภูมิต่ำà¹à¸¥à¸°à¸™à¹‰à¸³à¸«à¸™à¸±à¸à¹‚มเลà¸à¸¸à¸¥à¸ªà¸¹à¸‡à¸à¸§à¹ˆà¸²à¸„่าเฉลี่ยร้อยละ 50 ของบรรยาà¸à¸²à¸¨à¸”าวอังคาร +บรรยาà¸à¸²à¸¨à¸—ี่เบาบางของดาวอังคาร มองเห็นจาà¸à¸‚อบฟ้า + +บรรยาà¸à¸²à¸¨à¸‚องดาวอังคารประà¸à¸­à¸šà¸”้วยคาร์บอนไดออà¸à¹„ซด์ร้อยละ 96 อาร์à¸à¸­à¸™à¸£à¹‰à¸­à¸¢à¸¥à¸° 1.93 à¹à¸¥à¸°à¹„นโตรเจนร้อยละ 1.89 ร่วมไปà¸à¸±à¸šà¸­à¸­à¸à¸‹à¸´à¹€à¸ˆà¸™à¹à¸¥à¸°à¸™à¹‰à¸³à¹ƒà¸™à¸›à¸£à¸´à¸¡à¸²à¸“เล็à¸à¸™à¹‰à¸­à¸¢[6][138] บรรยาà¸à¸²à¸¨à¸¡à¸µà¸à¸¸à¹ˆà¸™à¸„่อนข้างมาà¸à¹‚ดยเป็นอนุภาคขนาดเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 1.5 ไมโครเมตร ซึ่งทำให้ท้องฟ้าของดาวอังคารดูเป็นสีน้ำตาลปนเหลืองเมื่อมองจาà¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§[139] + +มีà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¸¡à¸µà¹€à¸—นในบรรยาà¸à¸²à¸¨à¸‚องดาวอังคารโดยมีเศษส่วนโมลที่ประมาณ 30 ส่วนในพันล้านส่วน[14][140] พบปราà¸à¸à¹ƒà¸™à¸à¸²à¸£à¸žà¸¥à¸¹à¸¡à¸‚องà¹à¸à¹Šà¸ªà¹à¸¥à¸°à¸ à¸²à¸§à¸°à¸à¸²à¸£à¸“์à¹à¸ªà¸”งไปในทางว่ามีà¸à¸²à¸£à¸›à¸¥à¸”ปล่อยมีเทนออà¸à¸¡à¸²à¸ˆà¸²à¸à¹à¸–บท้องที่เฉพาะบางà¹à¸«à¹ˆà¸‡ ในช่วงà¸à¸¥à¸²à¸‡à¸¤à¸”ูร้อนของซีà¸à¹€à¸«à¸™à¸·à¸­ à¸à¸²à¸£à¸žà¸¥à¸¹à¸¡à¸«à¸¥à¸±à¸à¸¡à¸µà¸›à¸£à¸´à¸¡à¸²à¸“มีเทนอยู่ถึง 19,000 เมตริà¸à¸•à¸±à¸™ คาดà¸à¸²à¸£à¸“์ว่าà¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”มีà¸à¸³à¸¥à¸±à¸‡à¸à¸²à¸£à¸›à¸¥à¸”ปล่อยราว 0.6 à¸à¸´à¹‚ลà¸à¸£à¸±à¸¡à¸•à¹ˆà¸­à¸§à¸´à¸™à¸²à¸—ี[141][142] ข้อมูลที่พบชี้ว่าน่าจะมีบริเวณท้องที่ที่เป็นà¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”สองà¹à¸«à¹ˆà¸‡ ศูนย์à¸à¸¥à¸²à¸‡à¹à¸«à¹ˆà¸‡à¹à¸£à¸à¸­à¸¢à¸¹à¹ˆà¹ƒà¸à¸¥à¹‰ 30°N 260°W / 30°N 260°W à¹à¸¥à¸°à¹à¸«à¹ˆà¸‡à¸—ี่สองใà¸à¸¥à¹‰ 0°N 310°W / 0°N 310°W[141] ประมาณà¸à¸²à¸£à¸§à¹ˆà¸²à¸”าวอังคารจะต้องมีà¸à¸²à¸£à¸œà¸¥à¸´à¸•à¸¡à¸µà¹€à¸—นปริมาณ 270 ตันต่อปี[141][143] + +มีเทนสามารถอยู่ในบรรยาà¸à¸²à¸¨à¸”าวอังคารได้เพียงเฉพาะช่วงเวลาจำà¸à¸±à¸”ระยะหนึ่งเท่านั้นà¸à¹ˆà¸­à¸™à¸—ี่จะถูà¸à¸—ำลาย ประมาณว่ามีช่วงชีวิตยืนยาวได้ตั้งà¹à¸•à¹ˆ 0.6 ถึง 4 ปี[141][144] à¸à¸²à¸£à¸—ี่มีมีเทนดำรงอยู่ทั้ง ๆ ที่เป็นสารที่ช่วงชีวิตสั้นเช่นนี้จึงบ่งชี้ว่าจะต้องมีà¹à¸«à¸¥à¹ˆà¸‡à¸œà¸¥à¸´à¸•à¹à¸à¹Šà¸ªà¸”ังà¸à¸¥à¹ˆà¸²à¸§à¸—ี่ยังดำเนินà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™ ทั้งà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚องภูเขาไฟ à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹‚ดยดาวหาง à¹à¸¥à¸°à¸à¸²à¸£à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¸‚องสิ่งมีชีวิตพวà¸à¸ˆà¸¸à¸¥à¸Šà¸µà¸žà¸—ี่สร้างมีเทนล้วนเป็นà¹à¸«à¸¥à¹ˆà¸‡à¸œà¸¥à¸´à¸•à¸—ี่เป็นไปได้ นอà¸à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸¡à¸µà¹€à¸—นยังสามารถผลิตขึ้นได้โดยà¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸—ี่ไม่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้องà¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸² เซอร์เพนทิไนเซชัน [b] (à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¹€à¸‹à¸­à¸£à¹Œà¹€à¸žà¸™à¸—ีน) โดยอาศัยน้ำ คาร์บอนไดออà¸à¹„ซด์ à¹à¸¥à¸°à¹à¸£à¹ˆà¹‚อลิวีนซึ่งต่างà¸à¹‡à¸žà¸šà¹„ด้ทั่วไปบนดาวอังคาร[145] +à¹à¸«à¸¥à¹ˆà¸‡à¸œà¸¥à¸´à¸•à¹à¸¥à¸°à¸à¸±à¸à¹€à¸à¹‡à¸šà¸¡à¸µà¹€à¸—น (CH4) ที่มีศัà¸à¸¢à¸ à¸²à¸žà¸šà¸™à¸”าวอังคาร + +ยานสำรวจภาคพื้นคิวริออซิตี ซึ่งลงจอดบนดาวอังคารในเดือนสิงหาคม 2012 (พ.ศ. 2555) นั้นมีความสามารถตรวจวัดเพื่อà¹à¸¢à¸à¹à¸¢à¸°à¸„วามà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸‚องมีเทนที่ได้จาà¸à¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”ที่ต่างà¸à¸±à¸™à¸­à¸­à¸à¸ˆà¸²à¸à¸à¸±à¸™à¹„ด้[146] à¹à¸•à¹ˆà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸à¸²à¸£à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸ à¸²à¸£à¸à¸´à¸ˆà¸™à¸±à¹‰à¸™à¸ˆà¸°à¸Šà¸µà¹‰à¸‚าดได้จริง ๆ ว่าสิ่งมีชีวิตขนาดเล็à¸à¸ˆà¸´à¹‹à¸§à¸šà¸™à¸”าวอังคารเป็นผู้ให้à¸à¸³à¹€à¸™à¸´à¸”มีเทน บรรดาสิ่งมีชีวิตเหล่านั้นà¸à¹‡à¹€à¸«à¸¡à¸·à¸­à¸™à¸ˆà¸°à¸­à¸¢à¸¹à¹ˆà¸•à¹ˆà¸³à¸¥à¸‡à¹„ปเบื้องล่างพื้นผิวนอà¸à¹€à¸«à¸™à¸·à¸­à¸‚อบเขตที่ตัวยานจะเข้าถึง[147] à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”à¹à¸£à¸à¹‚ดยเครื่องวัดสเปà¸à¸•à¸£à¸±à¸¡à¹€à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹à¸šà¸šà¸›à¸£à¸±à¸šà¹„ด้à¹à¸ªà¸”งข้อมูลว่ามีมีเทนต่ำà¸à¸§à¹ˆà¸² 5 ส่วนในพันล้านส่วน ณ จุดที่ทำà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”ในตำà¹à¸«à¸™à¹ˆà¸‡à¸¥à¸‡à¸ˆà¸­à¸”[148][149][150][151] เมื่อ 19 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 2013 (พ.ศ. 2556) นัà¸à¸§à¸´à¸—ยาศาสตร์นาซาได้เผยผลà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸„ืบหน้าจาà¸à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸”โดยคิวริออซิตี ว่า ตรวจไม่พบมีเทนในบรรยาà¸à¸²à¸¨à¹ƒà¸™à¸„่าà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸§à¸±à¸” 0.18±0.67 ส่วนในพันล้านส่วนปริมาตร สอดคล้องà¸à¸±à¸šà¸‚อบเขตบนที่เฉพาะ 1.3 ส่วนในพันล้านส่วนปริมาตร (ขอบเขตความเชื่อมั่นร้อยละ 95) à¹à¸¥à¸°à¸ˆà¸²à¸à¸œà¸¥à¸¥à¸±à¸žà¸˜à¹Œà¸™à¸µà¹‰à¸—ำให้สรุปได้ว่าความเป็นไปได้ที่จะมีà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚องจุลชีพที่สร้างมีเทนบนดาวอังคารในปัจจุบันนั้นลดลง[152][153][154] + +ยานมาร์สออร์บิเตอร์มิชชันของอินเดียมีปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸„้นหามีเทนในบรรยาà¸à¸²à¸¨[155] ในขณะที่เอ็à¸à¹‚ซมาร์สเทรซà¹à¸à¹Šà¸ªà¸­à¸­à¸£à¹Œà¸šà¸´à¹€à¸•à¸­à¸£à¹Œà¸¡à¸µà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸‚ึ้นปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹ƒà¸™à¸›à¸µ 2016 (พ.ศ. 2559) เพื่อศีà¸à¸©à¸²à¹ƒà¸«à¹‰à¹€à¸‚้าใจมาà¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้นเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸¡à¸µà¹€à¸—นรวมไปถึงสารที่ได้จาà¸à¸à¸²à¸£à¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¸‚องมีเทนด้วย เช่น ฟอร์มาลดีไฮด์ à¹à¸¥à¸°à¹€à¸¡à¸—านอล[156] + +ในวันที่ 16 ธันวาคม 2014 (พ.ศ. 2557) นาซารายงานว่ายานโรเวอร์คิวริออซิตี ตรวจพบปริมาณมีเทนในบรรยาà¸à¸²à¸¨à¸”าวอังคารเพิ่มสูงนับสิบเท่าในเฉพาะถิ่น ตัวอย่างที่ตรวจวัดได้ถือว่าสูงเป็นสิบเท่าในรอบ 20 เดือน à¹à¸ªà¸”งà¸à¸²à¸£à¹€à¸žà¸´à¹ˆà¸¡à¸‚ึ้นในปลายปี 2013 à¹à¸¥à¸°à¸•à¹‰à¸™à¸›à¸µ 2014 โดยมีค่าเฉลี่ยของมีเทนเป็น 7 ส่วนในพันล้านส่วนในบรรยาà¸à¸²à¸¨ ซึ่งในเวลาà¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸«à¸£à¸·à¸­à¸«à¸¥à¸±à¸‡à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸„่าเฉลี่ยที่วัดได้อยู่ประมาณหนึ่งในสิบของค่าดังà¸à¸¥à¹ˆà¸²à¸§[157][158] + +มีà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¹à¸­à¸¡à¹‚มเนียอย่างคร่าว ๆ บนดาวอังคารà¹à¸¥à¹‰à¸§à¹€à¸Šà¹ˆà¸™à¸à¸±à¸™à¹‚ดยยานดาวเทียมมาร์สเอ็à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª à¹à¸•à¹ˆà¸”้วยความที่เป็นสารช่วงชีวิตค่อนข้างสั้นจึงไม่เป็นที่à¹à¸™à¹ˆà¸Šà¸±à¸”ว่าถูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸¡à¸²à¸ˆà¸²à¸à¸­à¸°à¹„ร[159] à¹à¸­à¸¡à¹‚มเนียนั้นไม่เสถียรในบรรยาà¸à¸²à¸¨à¸‚องดาวอังคาร à¹à¸¥à¸°à¸ˆà¸°à¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¹„ปในเวลาเพียงไม่à¸à¸µà¹ˆà¸Šà¸±à¹ˆà¸§à¹‚มง à¹à¸«à¸¥à¹ˆà¸‡à¸à¸³à¹€à¸™à¸´à¸”หนึ่งที่น่าจะเป็นไปได้คือà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸‚องภูเขาไฟ[159] +ออโรรา[à¹à¸à¹‰] + +ในปี 1994 (พ.ศ. 2537) ยานอวà¸à¸²à¸¨à¸¡à¸²à¸£à¹Œà¸ªà¹€à¸­à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ªà¸‚ององค์à¸à¸²à¸£à¸­à¸§à¸à¸²à¸¨à¸¢à¸¸à¹‚รปพบà¸à¸²à¸£à¹€à¸£à¸·à¸­à¸‡à¹à¸ªà¸‡à¸­à¸±à¸¥à¸•à¸£à¸²à¹„วโอเลตจาภ"ร่มà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸" ในซีà¸à¹ƒà¸•à¹‰à¸‚องดาว ดาวอังคารไม่มีสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸—ี่ครอบคลุมทั้งดาวซึ่งจะนำทางอนุภาคมีประจุทั้งหลายให้เข้าสู่ชั้นบรรยาà¸à¸²à¸¨ ดาวอังคารมีสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸£à¸¹à¸›à¸£à¹ˆà¸¡à¸­à¸¢à¸¹à¹ˆà¸«à¸¥à¸²à¸¢à¹à¸«à¹ˆà¸‡ ส่วนใหà¸à¹ˆà¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸‹à¸µà¸à¹ƒà¸•à¹‰à¸‹à¸¶à¹ˆà¸‡à¹€à¸›à¹‡à¸™à¸‹à¸²à¸à¸«à¸¥à¸‡à¹€à¸«à¸¥à¸·à¸­à¸‚องสนามซึ่งเคยครอบคลุมทั้งพิภพดาวà¹à¸•à¹ˆà¹€à¸ªà¸·à¹ˆà¸­à¸¡à¸ªà¸¥à¸²à¸¢à¹„ปเมื่อหลายพันล้านปีà¸à¹ˆà¸­à¸™ + +ในปลายเดือนธันวาคม 2014 (พ.ศ. 2557) ยานอวà¸à¸²à¸¨à¹€à¸¡à¹€à¸§à¹‡à¸™à¸‚องนาซาตรวจพบหลัà¸à¸à¸²à¸™à¸à¸²à¸£à¹à¸œà¹ˆà¸à¸£à¸°à¸ˆà¸²à¸¢à¹€à¸›à¹‡à¸™à¸šà¸£à¸´à¹€à¸§à¸“à¸à¸§à¹‰à¸²à¸‡à¸‚องออโรราบนซีà¸à¹€à¸«à¸™à¸·à¸­à¸‚องดาวอังคาร à¹à¸¥à¸°à¸—อดต่ำลงถึงละติจูดประมาณ 20-30 องศาเหนือจาà¸à¹€à¸ªà¹‰à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£à¸”าวอังคาร ในขณะที่ออโรราบนโลà¸à¸­à¸¢à¸¹à¹ˆà¹ƒà¸™à¸£à¸°à¸¢à¸°à¸ªà¸¹à¸‡ 100 ถึง 500 à¸à¸´à¹‚ลเมตรจาà¸à¸œà¸´à¸§à¸”าวเคราะห์ à¹à¸•à¹ˆà¸šà¸™à¸”าวอังคารอนุภาคที่à¸à¹ˆà¸­à¹ƒà¸«à¹‰à¹€à¸à¸´à¸”ออโรราทะลวงผ่านบรรยาà¸à¸²à¸¨à¸‚องดาวเข้ามาสร้างออโรราขึ้นในระดับต่ำà¸à¸§à¹ˆà¸² 100 à¸à¸´à¹‚ลเมตรจาà¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§ สนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹ƒà¸™à¸¥à¸¡à¸ªà¸¸à¸£à¸´à¸¢à¸°à¹‚อบคลุมดาวอังคาร เข้าสู่บรรยาà¸à¸²à¸¨ à¹à¸¥à¸°à¸­à¸™à¸¸à¸ à¸²à¸„มีประจุตามเส้นà¹à¸£à¸‡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¸‚องลมสุริยะเข้าสู่บรรยาà¸à¸²à¸¨à¸—ำให้ออโรราเà¸à¸´à¸”ขึ้นภายนอà¸à¸£à¹ˆà¸¡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸[160] + +วันที่ 18 มีนาคม 2015 (พ.ศ. 2558) นาซารายงานà¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¸­à¸­à¹‚รราที่ยังไม่เป็นที่เข้าใจà¹à¸™à¹ˆà¸Šà¸±à¸” à¹à¸¥à¸°à¹€à¸¡à¸†à¸à¸¸à¹ˆà¸™à¸—ี่ยังไมมีคำอธิบายภายในบรรยาà¸à¸²à¸¨à¸‚องดาวอังคาร[161] +ภูมิอาà¸à¸²à¸¨[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ภูมิอาà¸à¸²à¸¨à¸‚องดาวอังคาร +18 พฤศจิà¸à¸²à¸¢à¸™ 2012 +25 พฤศจิà¸à¸²à¸¢à¸™ 2012 +พายุà¸à¸¸à¹ˆà¸™à¸šà¸™à¸”าวอังคาร ยานออปพอร์ทูนิตีà¹à¸¥à¸°à¸¢à¸²à¸™à¸„ิวริออซิตี มีเครื่องหมายà¸à¸³à¸à¸±à¸š + +จาà¸à¸”าวเคราะห์ทั้งหมดในระบบสุริยะ ฤดูà¸à¸²à¸¥à¸‚องดาวอังคารมีความใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุด เนื่องจาà¸à¸„วามเอียงของà¹à¸à¸™à¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸‚องดาวทั้งสองที่คล้ายคลึงà¸à¸±à¸™ ระยะเวลาของà¹à¸•à¹ˆà¸¥à¸°à¸¤à¸”ูà¸à¸²à¸¥à¸šà¸™à¸”าวอังคารมีความยาวประมาณสองเท่าของฤดูà¸à¸²à¸¥à¸šà¸™à¹‚ลภเพราะดาวอังคารมีระยะห่างจาà¸à¸”วงอาทิตย์มาà¸à¸à¸§à¹ˆà¸² หนึ่งปีของดาวอังคารจึงยาวนานร่วมสองปีของโลภอุณหภูมิบนพื้นผิวดาวอังคารผันà¹à¸›à¸£à¸ˆà¸²à¸à¸„่าต่ำสุดที่ประมาณ -143 องศาเซลเซียส (-225 องศาฟาเรนไฮต์) ที่บริเวณà¹à¸œà¹ˆà¸™à¸‚ั้วดาวในฤดูหนาว[8] จนถึงค่าสูงสุดที่ประมาณ 35 องศาเซลเซียส (95 องศาฟาเรนไฮต์) ในฤดูร้อนบริเวณศูนย์สูตร[9] à¸à¸²à¸£à¸¡à¸µà¸Šà¹ˆà¸§à¸‡à¸­à¸¸à¸“หภูมิที่à¸à¸§à¹‰à¸²à¸‡à¸¡à¸²à¸à¹€à¸Šà¹ˆà¸™à¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¸œà¸¥à¸¡à¸²à¸ˆà¸²à¸à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ี่เบาบางจนไม่สามารถà¸à¸±à¸à¹€à¸à¹‡à¸šà¸„วามร้อนจาà¸à¸”วงอาทิตย์ได้มาà¸à¸™à¸±à¸ à¸à¸²à¸£à¸¡à¸µà¸„วามà¸à¸”อาà¸à¸²à¸¨à¸—ี่ต่ำ à¹à¸¥à¸°à¸à¸²à¸£à¸—ี่มีค่าความเฉื่อยความร้อนต่ำของดินบนดาวอังคาร[162] ระยะห่างจาà¸à¸”วงอาทิตย์ถึงดาวอังคารคิดเป็น 1.52 เท่าเมื่อเทียบà¸à¸±à¸šà¸£à¸°à¸¢à¸°à¸ˆà¸²à¸à¸”วงอาทิตย์ถึงโลภทำให้ดาวอังคารได้รับà¹à¸ªà¸‡à¸ˆà¸²à¸à¸”วงอาทิตย์เพียงร้อยละ 43 ต่อหน่วยพื้นที่เมื่อเทียบà¸à¸±à¸šà¹‚ลà¸[163] + +ถ้าหาà¸à¸”าวอังคารมีวงโคจรà¹à¸šà¸šà¹€à¸”ียวà¸à¸±à¸šà¹‚ลà¸à¹à¸•à¹ˆà¸¥à¸°à¸¤à¸”ูà¸à¸²à¸¥à¸‚องดาวอังคารà¸à¹‡à¸ˆà¸°à¹€à¸«à¸¡à¸·à¸­à¸™à¹‚ลภà¹à¸•à¹ˆà¸à¸²à¸£à¸¡à¸µà¸„วามเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องวงโคจรมาà¸à¸à¸§à¹ˆà¸²à¹€à¸¡à¸·à¹ˆà¸­à¹€à¸›à¸£à¸µà¸¢à¸šà¸à¸±à¸™à¸™à¸µà¹‰à¹€à¸­à¸‡à¸—ี่ส่งผลà¸à¸£à¸°à¸—บสำคัภดาวอังคารเข้าใà¸à¸¥à¹‰à¸ˆà¸¸à¸”ใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดเมื่อเป็นฤดูร้อนในดาวซีà¸à¹ƒà¸•à¹‰à¸‹à¸¶à¹ˆà¸‡à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸­à¸à¹‡à¸ˆà¸°à¹€à¸›à¹‡à¸™à¸¤à¸”ูหนาว à¹à¸¥à¸°à¹€à¸‚้าใà¸à¸¥à¹‰à¸ˆà¸¸à¸”ไà¸à¸¥à¸”วงอาทิตย์ที่สุดเมื่อเป็นฤดูหนาวในดาวซีà¸à¹ƒà¸•à¹‰à¸‹à¸¶à¹ˆà¸‡à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸­à¸à¹‡à¸ˆà¸°à¹€à¸›à¹‡à¸™à¸¤à¸”ูร้อน ผลที่ตามมาคือฤดูà¸à¸²à¸¥à¹ƒà¸™à¸”าวซีà¸à¹ƒà¸•à¹‰à¸ˆà¸°à¸£à¸¸à¸™à¹à¸£à¸‡à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹à¸¥à¸°à¸¤à¸”ูà¸à¸²à¸¥à¹ƒà¸™à¸”าวซีà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸°à¸­à¹ˆà¸­à¸™à¹€à¸šà¸²à¸à¸§à¹ˆà¸²à¸­à¸µà¸à¸‹à¸µà¸à¸«à¸™à¸¶à¹ˆà¸‡à¹ƒà¸™à¹à¸•à¹ˆà¸¥à¸°à¸à¸£à¸“ี อุณหภูมิในฤดูร้อนของดาวซีà¸à¹ƒà¸•à¹‰à¸ªà¸²à¸¡à¸²à¸£à¸–อุ่นได้มาà¸à¸à¸§à¹ˆà¸²à¸­à¸¸à¸“หภูมิในฤดูร้อนของดาวซีà¸à¹€à¸«à¸™à¸·à¸­à¹„ด้ถึง 30 เคลวิน (30 องศาเซลเซียส หรือ 54 องศาฟาเรนไฮต์)[164] + +ดาวอังคารมีพายุà¸à¸¸à¹ˆà¸™à¸—ี่ใหà¸à¹ˆà¸—ี่สุดในระบบสุริยะ มีได้ตั้งà¹à¸•à¹ˆà¸žà¸²à¸¢à¸¸à¹ƒà¸™à¸žà¸·à¹‰à¸™à¸—ี่เล็ภๆ ไปจนถึงพายุขนาดมโหฬารที่ครอบคลุมทั่วทั้งดาวเคราะห์ พายุเหล่านี้มัà¸à¸ˆà¸°à¹€à¸à¸´à¸”ขึ้นเมื่อดาวอังคารเข้าใà¸à¸¥à¹‰à¸”วงอาทิตย์à¹à¸¥à¸°à¹à¸ªà¸”งให้เห็นà¸à¸²à¸£à¹€à¸žà¸´à¹ˆà¸¡à¸‚ึ้นของอุณหภูมิบนดาว[165] +วงโคจรà¹à¸¥à¸°à¸à¸²à¸£à¸«à¸¡à¸¸à¸™[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: วงโคจรของดาวอังคาร +ดาวอังคารห่างจาà¸à¸”วงอาทิตย์ประมาณ 230 ล้านà¸à¸´à¹‚ลเมตร (143 ล้านไมล์) คาบà¸à¸²à¸£à¹‚คจรเท่าà¸à¸±à¸š 687 วัน (โลà¸) à¹à¸ªà¸”งด้วยวงสีà¹à¸”ง สีน้ำเงินคือวงโคจรโลภ+ +ดาวอังคารไà¸à¸¥à¸ˆà¸²à¸à¸”วงอาทิตย์ด้วยระยะทางเฉลี่ย 230 ล้านà¸à¸´à¹‚ลเมตรโดยประมาณ (143 ล้านไมล์, 1.5 หน่วยดาราศาสตร์) à¹à¸¥à¸°à¸¡à¸µà¸„าบà¸à¸²à¸£à¹‚คจรเท่าà¸à¸±à¸š 687 วันของโลภหนึ่งวันสุริยะบนดาวอังคารยาวà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¸§à¸±à¸™à¸‚องโลà¸à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸­à¸¢à¸„ือเท่าà¸à¸±à¸š 24 ชั่วโมง 39 นาที 35.244 วินาที หนึ่งปีของดาวอังคารเท่าà¸à¸±à¸š 1.8809 ปีของโลภหรือ 1 ปี 320 วัน à¸à¸±à¸šà¸­à¸µà¸ 18.2 ชั่วโมง[6] + +ดาวอังคารมีความเอียงของà¹à¸à¸™à¹€à¸—่าà¸à¸±à¸š 25.19 องศา สัมพัทธ์à¸à¸±à¸šà¸£à¸°à¸™à¸²à¸šà¸à¸²à¸£à¹‚คจรซึ่งคล้ายคลึงà¸à¸±à¸šà¸„วามเอียงของà¹à¸à¸™à¹‚ลà¸[6] เป็นผลให้ดาวอังคารมีฤดูà¸à¸²à¸¥à¸„ล้ายโลà¸à¹à¸¡à¹‰à¸§à¹ˆà¸²à¹à¸•à¹ˆà¸¥à¸°à¸¤à¸”ูบนดาวอังคารจะยาวเà¸à¸·à¸­à¸šà¸ªà¸­à¸‡à¹€à¸—่าเพราะคาบà¸à¸²à¸£à¹‚คจรที่ยาวนานà¸à¸§à¹ˆà¸² ณ ปัจจุบัน ขั้วเหนือของดาวอังคารมีà¸à¸²à¸£à¸§à¸²à¸‡à¸•à¸±à¸§à¸Šà¸µà¹‰à¹„ปใà¸à¸¥à¹‰à¸à¸±à¸šà¸”าวฤà¸à¸©à¹Œà¹€à¸”เนบ[11] ดาวอังคารผ่านจุดไà¸à¸¥à¸”วงอาทิตย์ที่สุดในเดือนà¸à¸¸à¸¡à¸ à¸²à¸žà¸±à¸™à¸˜à¹Œ 2012 (พ.ศ. 2555)[166][167] ผ่านจุดใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดในเดือนมà¸à¸£à¸²à¸„ม 2013 (พ.ศ. 2556)[166] จุดไà¸à¸¥à¸”วงอาทิตย์ที่สุดถัดไปคือมà¸à¸£à¸²à¸„ม 2014 (พ.ศ. 2557)[166] à¹à¸¥à¸°à¸ˆà¸¸à¸”ใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดถัดไปคือธันวาคมปีเดียวà¸à¸±à¸™[166] + +ดาวอังคารมีความเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องวงโคจรค่อนข้างเด่นชัดที่ประมาณ 0.09 เมื่อเทียบà¸à¸±à¸šà¸”าวเคราะห์อื่นอีà¸à¹€à¸ˆà¹‡à¸”ดวงในระบบสุริยะà¹à¸¥à¹‰à¸§ มีเพียงดาวพุธเท่านั้นที่มีความเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องวงโคจรมาà¸à¸à¸§à¹ˆà¸² เป็นที่ทราบว่าในอดีตดาวอังคารมีวงโคจรที่à¸à¸¥à¸¡à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹ƒà¸™à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™à¸¡à¸²à¸ ที่ขณะหนึ่งเมื่อ 1.35 ล้านปีà¸à¹ˆà¸­à¸™ ดาวอังคารมีความเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸—ี่ราว 0.002 ซึ่งน้อยยิ่งà¸à¸§à¹ˆà¸²à¹‚ลà¸à¹ƒà¸™à¸•à¸­à¸™à¸™à¸µà¹‰[168] วัà¸à¸ˆà¸±à¸à¸£à¸„วามเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องดาวอังคารอยู่ที่ 96,000 ปีโลภเทียบà¸à¸±à¸šà¹‚ลà¸à¸—ี่วัà¸à¸ˆà¸±à¸à¸£à¹€à¸”ียวà¸à¸±à¸™à¸­à¸¢à¸¹à¹ˆà¸—ี่ 100,000 ปี[169] ดาวอังคารยังมีวัà¸à¸ˆà¸±à¸à¸£à¸„วามเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸­à¸µà¸à¹à¸šà¸šà¸«à¸™à¸¶à¹ˆà¸‡à¸—ี่à¸à¸´à¸™à¹€à¸§à¸¥à¸²à¸¢à¸²à¸§à¸™à¸²à¸™à¸à¸§à¹ˆà¸²à¸™à¸µà¹‰à¸”้วยคาบราว 2.2 ล้านปีโลภซึ่งมีความสำคัà¸à¸šà¸”บังà¸à¸£à¸²à¸Ÿà¸§à¸±à¸à¸ˆà¸±à¸à¸£ 96,000 ปี นับจาภ35,000 ปีที่ผ่านมา วงโคจรของดาวอังคารมีความเยื้องศูนย์à¸à¸¥à¸²à¸‡à¹€à¸žà¸´à¹ˆà¸¡à¸‚ึ้นทีละน้อยเพราะผลà¸à¸£à¸°à¸—บเชิงโน้มถ่วงจาà¸à¸”าวเคราะห์ดวงอื่น ๆ ระยะที่ใà¸à¸¥à¹‰à¸—ี่สุดระหว่างโลà¸à¹à¸¥à¸°à¸”าวอังคารจะลดลงอย่างค่อยเป็นค่อยไปต่อเนื่องตลอดระยะเวลา 25,000 ปีข้างหน้า[170] +à¸à¸²à¸£à¸„้นหาสิ่งมีชีวิต[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: สิ่งมีชีวิตบนดาวอังคาร à¹à¸¥à¸° à¸à¸²à¸£à¸—ดลองทางชีววิทยาโดยยานส่วนลงจอดไวà¸à¸´à¸‡ +ยานส่วนลงจอดไวà¸à¸´à¸‡ 1 - à¹à¸‚นสุ่มตัวอย่างสร้างร่องลึภตัà¸à¸§à¸±à¸ªà¸”ุเพื่อทำà¸à¸²à¸£à¸—ดสอบ (คริสเพลนิเชีย) + +ตามความเข้าใจในปัจจุบันเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸„วามสามารถอยู่อาศัยได้ของดาวเคราะห์ หรือความสามารถที่โลà¸à¹ƒà¸”โลà¸à¸«à¸™à¸¶à¹ˆà¸‡à¸¡à¸µà¸ à¸²à¸§à¸°à¸à¸²à¸£à¸“์ทางสิ่งà¹à¸§à¸”ล้อมเจริà¸à¸žà¸±à¸’นาขึ้นจนชีวิตอุบัติขึ้นได้ เช่นดาวเคราะห์ที่เอื้อให้มีน้ำของเหลวอยู่บนพื้นผิว เà¸à¸“ฑ์ที่ต้องà¸à¸²à¸£à¹‚ดยมาà¸à¸„ือวงโคจรของดาวเคราะห์นั้นต้องอยู่ในเขตอาศัยได้ ซึ่งในà¸à¸£à¸“ีของดวงอาทิตย์คือตั้งà¹à¸•à¹ˆà¹à¸–บพ้นจาà¸à¸”าวศุà¸à¸£à¹Œà¸­à¸­à¸à¹„ปจนถึงระยะประมาณà¸à¸¶à¹ˆà¸‡à¹à¸à¸™à¹€à¸­à¸à¸‚องดาวอังคาร[171] ระหว่างà¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุด ดาวอังคารได้ล่วงเข้าไปในเขตนี้ à¹à¸•à¹ˆà¸”้วยความที่มีบรรยาà¸à¸²à¸¨à¹€à¸šà¸²à¸šà¸²à¸‡ ความà¸à¸”อาà¸à¸²à¸¨à¸—ี่ต่ำเป็นอุปสรรคไม่ให้น้ำของเหลวปà¸à¸„ลุมภูมิประเทศเป็นบริเวณà¸à¸§à¹‰à¸²à¸‡à¹„ด้ในช่วงระยะเวลาที่นานพอ à¸à¸²à¸£à¹„หลของน้ำของเหลวในอดีตเป็นเครื่องพิสูจน์ว่าดาวอังคารมีศัà¸à¸¢à¸ à¸²à¸žà¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£à¸­à¸¢à¸¹à¹ˆà¸­à¸²à¸¨à¸±à¸¢à¸‚องสิ่งมีชีวิต หลัà¸à¸à¸²à¸™à¸—ี่พบใหม่บางประà¸à¸²à¸£à¸Šà¸µà¹‰à¸§à¹ˆà¸²à¸™à¹‰à¸³à¸šà¸™à¸œà¸´à¸§à¸”าวอังคารนั้นอาจจะเค็มเà¸à¸´à¸™à¹„ปà¹à¸¥à¸°à¸¡à¸µà¸„วามเป็นà¸à¸£à¸”มาà¸à¹€à¸à¸´à¸™à¹„ปที่จะค้ำจุนสิ่งมีชีวิตบà¸à¹‚ดยทั่ว ๆ ไปได้[172] + +à¸à¸²à¸£à¸›à¸£à¸²à¸¨à¸ˆà¸²à¸à¸ªà¸™à¸²à¸¡à¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ี่เบาบางอย่างยิ่งของดาวอังคารเป็นปัà¸à¸«à¸²à¸—ี่ท้าทาย ดาวเคราะห์เองมีà¸à¸²à¸£à¸–่ายเทความร้อนผ่านพื้นผิวที่ต่ำ à¸à¸²à¸£à¸›à¹‰à¸­à¸‡à¸à¸±à¸™à¸­à¸±à¸™à¸¢à¹ˆà¸³à¹à¸¢à¹ˆà¸•à¹ˆà¸­à¸à¸²à¸£à¸à¸£à¸°à¸«à¸™à¹ˆà¸³à¹‚จมตีของลมสุริยะ à¹à¸¥à¸°à¸„วามอ่อนด้อยของความดันบรรยาà¸à¸²à¸¨à¸ˆà¸™à¹„ม่อาจà¸à¸”น้ำลงมาให้อยู่ในสภาพของเหลวเพราะน้ำà¹à¸‚็งจะระเหิดไปจนหมด à¸à¸¥à¹ˆà¸²à¸§à¹„ด้ว่าดาวอังคารนั้นจวนที่จะตายหรือไม่à¸à¹‡à¸­à¸²à¸ˆà¸ˆà¸°à¸•à¸²à¸¢à¹„ปà¹à¸¥à¹‰à¸§à¹ƒà¸™à¸—างธรณีวิทยา เพราะà¸à¸²à¸£à¸ˆà¸šà¸ªà¸´à¹‰à¸™à¸¥à¸‡à¸‚องà¸à¸´à¸ˆà¸à¸£à¸£à¸¡à¸ à¸¹à¹€à¸‚าไฟย่อมเป็นที่ประจัà¸à¸©à¹Œà¸§à¹ˆà¸²à¸à¸²à¸£à¹à¸›à¸£à¹ƒà¸Šà¹‰à¹ƒà¸«à¸¡à¹ˆà¸‚องà¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸à¸•à¸¥à¸­à¸”จนองค์ประà¸à¸­à¸šà¹€à¸„มีต่าง ๆ ระหว่างพื้นผิวà¸à¸±à¸šà¸šà¸£à¸´à¹€à¸§à¸“ภายในดาวเคราะห์นั้นย่อมต้องจบสิ้นไปด้วย[173] +หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸­à¸±à¸¥à¸à¸² - ตรวจพบà¸à¸²à¸£à¸—ับถมของอุลà¸à¸¡à¸“ี (จุดสีเขียว), ตำà¹à¸«à¸™à¹ˆà¸‡à¸—ี่เป็นไปได้ว่าจะมีสิ่งมีชีวิตโบราณถูà¸à¹€à¸à¹‡à¸šà¸£à¸±à¸à¸©à¸²à¹„ว้[174] + +หลัà¸à¸à¸²à¸™à¸šà¹ˆà¸‡à¸šà¸­à¸à¸§à¹ˆà¸²à¸„รั้งหนึ่งดาวอังคารมีความเป็นมิตรต่อà¸à¸²à¸£à¸­à¸¢à¸¹à¹ˆà¸­à¸²à¸¨à¸±à¸¢à¸¡à¸²à¸à¸à¸§à¹ˆà¸²à¹ƒà¸™à¸—ุà¸à¸§à¸±à¸™à¸™à¸µà¹‰à¸­à¸¢à¹ˆà¸²à¸‡à¸¡à¸²à¸ à¹à¸•à¹ˆà¸ˆà¸°à¸¡à¸µà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸”ำรงสืบต่อมาบนดาวหรือไม่นั้นยังไม่มีคำตอบที่à¹à¸™à¹ˆà¸Šà¸±à¸” ยานสำรวจไวà¸à¸´à¸‡à¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¸à¸¥à¸²à¸‡à¸—ศวรรษ 1970 (พ.ศ. 2513-) มีอุปà¸à¸£à¸“์ที่ออà¸à¹à¸šà¸šà¸¡à¸²à¹€à¸žà¸·à¹ˆà¸­à¸•à¸£à¸§à¸ˆà¸«à¸²à¸ˆà¸¸à¸¥à¸´à¸™à¸—รีย์ต่าง ๆ ในดินของดาวอังคาร ณ บริเวณลงจอดของà¹à¸•à¹ˆà¸¥à¸°à¸¢à¸²à¸™à¹à¸¥à¸°à¸•à¹ˆà¸²à¸‡à¸à¹‡à¹„ด้ผลลัพธ์เป็นบวภรวมถึงà¸à¸²à¸£à¸œà¸¥à¸´à¸• CO2 เพิ่มขึ้นเป็นครั้งคราวเมื่อได้สัมผัสà¸à¸±à¸šà¸™à¹‰à¸³à¹à¸¥à¸°à¸ªà¸²à¸£à¸­à¸²à¸«à¸²à¸£ สัà¸à¸à¸²à¸“ของสิ่งมีชีวิตเหล่านี้ภายหลังได้ถูà¸à¹‚ต้à¹à¸¢à¹‰à¸‡à¹‚ดยนัà¸à¸§à¸´à¸—ยาศาสตร์จำนวนหนึ่ง ผลที่ได้ยังคงเป็นที่อภิปรายถà¸à¹€à¸–ียงเรื่อยมา โดยนัà¸à¸§à¸´à¸—ยาศาสตร์นาซา à¸à¸´à¸¥à¹€à¸šà¸´à¸£à¹Œà¸• เลวิน ยืนยันว่ายานไวà¸à¸´à¸‡à¸­à¸²à¸ˆà¸•à¸£à¸§à¸ˆà¸žà¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸• à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์ข้อมูลจาà¸à¹„วà¸à¸´à¸‡à¸‹à¹‰à¸³à¸­à¸µà¸à¸„รั้งภายใต้องค์ความรู้ใหม่ในปัจจุบันเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸ªà¸¸à¸”ขั้วรูปà¹à¸šà¸šà¸•à¹ˆà¸²à¸‡ ๆ ชี้ว่า à¸à¸²à¸£à¸—ดสอบโดยไวà¸à¸´à¸‡à¹„ม่ได้ละเอียดซับซ้อนเพียงพอที่จะตรวจหารูปà¹à¸šà¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹€à¸Šà¹ˆà¸™à¸™à¸µà¹‰ ตัวà¸à¸²à¸£à¸—ดสอบเองยังอาจà¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งฆ่าสิ่งมีชีวิต (ตามสมมติà¸à¸²à¸™) เหล่านั้นไปเสียด้วยซ้ำ[175] ปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸—ดสอบโดยยานส่วนลงจอดฟีนิà¸à¸‹à¹Œà¹à¸ªà¸”งให้ทราบว่าดินมีค่าพีเอชเป็นด่าง ประà¸à¸­à¸šà¸”้วยà¹à¸¡à¸à¸™à¸µà¹€à¸‹à¸µà¸¢à¸¡ โซเดียม โพà¹à¸—สเซียม à¹à¸¥à¸°à¸„ลอไรด์[176] ลำพังสารอาหารในดินอาจสามารถเà¸à¸·à¹‰à¸­à¸«à¸™à¸¸à¸™à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¹„ด้ à¹à¸•à¹ˆà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸¢à¸±à¸‡à¸„งต้องได้รับà¸à¸²à¸£à¸›à¹‰à¸­à¸‡à¸à¸±à¸™à¸ˆà¸²à¸à¹à¸ªà¸‡à¸­à¸±à¸¥à¸•à¸£à¸²à¹„วโอเลตอันà¹à¸£à¸‡à¸à¸¥à¹‰à¸²[177] à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์ล่าสุดเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸­à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸”าวอังคาร EETA79001 พบ ClO4- 0.6 ส่วนในล้านส่วน ClO3- 1.4 ส่วนในล้านส่วน à¹à¸¥à¸° NO3- 16 ส่วนในล้านส่วน เà¸à¸·à¸­à¸šà¸—ั้งหมดน่าจะมีที่มาจาà¸à¸”าวอังคารโดยตรง à¸à¸²à¸£à¸¡à¸µ ClO3- ชี้ว่าน่าจะมีสารประà¸à¸­à¸šà¸­à¸­à¸à¸‹à¸´à¹€à¸ˆà¸™-คลอรีนที่สภาพออà¸à¸‹à¸´à¹„ดซ์สูงชนิดอื่น อย่างเช่น ClO2- หรือ ClO ด้วย ทั้งสองถูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸‚ึ้นโดยปà¸à¸´à¸à¸´à¸£à¸´à¸¢à¸²à¸­à¸­à¸à¸‹à¸´à¹€à¸”ชันของคลอรีนโดยรังสียูวี à¹à¸¥à¸°à¸à¸²à¸£à¹à¸•à¸à¸ªà¸¥à¸²à¸¢ ClO4- ด้วยรังสีเอà¸à¸‹à¹Œ ด้วยเหตุนี้รูปà¹à¸šà¸šà¸­à¸´à¸™à¸—รีย์หรือสิ่งมีชีวิตที่ทนทายาดà¹à¸¥à¸°à¹„ด้รับà¸à¸²à¸£à¸›à¹‰à¸­à¸‡à¸à¸±à¸™à¸­à¸¢à¹ˆà¸²à¸‡à¸”ี (ใต้พื้นผิว) เท่านั้นที่อาจอยู่รอดมาได้[178] + +นอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸™à¸µà¹‰ à¸à¸²à¸£à¸§à¸´à¹€à¸„ราะห์ใหม่จาà¸à¸‚้อมูลของห้องปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹€à¸„มีเปียà¸à¸‚องยานฟีนิà¸à¸‹à¹Œ à¹à¸ªà¸”งให้เห็นว่า Ca(ClO4)2 ในดินตรงที่ยานฟีนิà¸à¸‹à¹Œà¸­à¸¢à¸¹à¹ˆà¹„ม่ได้มีปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸à¸±à¸šà¸™à¹‰à¸³à¸‚องเหลวไม่ว่าจะรูปà¹à¸šà¸šà¹ƒà¸” ๆ อาจเป็นระยะเวลายาวนานถึง 600 ล้านปี เพราะหาà¸à¸§à¹ˆà¸²à¸¡à¸µà¸™à¹‰à¸³ สาร Ca(ClO4)2 ซึ่งละลายได้ดีมาà¸à¹€à¸¡à¸·à¹ˆà¸­à¹„ด้สัมผัสà¸à¸±à¸šà¸™à¹‰à¸³à¸‚องเหลวย่อมเปลี่ยนไปเà¸à¸´à¸”เฉพาะ CaSO4 ขึ้น ผลที่ได้จึงเป็นเครื่องบ่งบอà¸à¸–ึงà¸à¸²à¸£à¸¡à¸µà¸ªà¸ à¸²à¸žà¹à¸§à¸”ล้อมà¹à¸«à¹‰à¸‡à¹à¸¥à¹‰à¸‡à¸­à¸¢à¹ˆà¸²à¸‡à¸ªà¸²à¸«à¸±à¸ªà¹‚ดยมีน้ำเล็à¸à¸™à¹‰à¸­à¸¢à¸«à¸£à¸·à¸­à¹„ม่มีเลยที่จะมาปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸”้วย[179] + +นัà¸à¸§à¸´à¸—ยาศาสตร์บางส่วนเสนอว่าเม็ดคาร์บอเนตเล็ภๆ ที่พบในอุà¸à¸à¸²à¸šà¸²à¸•à¹€à¸­à¹à¸­à¸¥à¹€à¸­à¸Š 84001ซึ่งคาดว่ามาจาà¸à¸”าวอังคารนั้น อาจเป็นซาà¸à¸ˆà¸¸à¸¥à¸Šà¸µà¸žà¸”ึà¸à¸”ำบรรพ์ที่หลงเหลืออยู่บนดาวอังคารเมื่อà¸à¹‰à¸­à¸™à¸­à¸¸à¸à¸à¸²à¸šà¸²à¸•à¸£à¸°à¹€à¸šà¸´à¸”à¸à¸£à¸°à¹€à¸”็นออà¸à¸¡à¸²à¸ˆà¸²à¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวอังคารโดยà¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚องดาวตà¸à¹€à¸¡à¸·à¹ˆà¸­à¸£à¸²à¸§ 15 ล้านปีà¸à¹ˆà¸­à¸™ ข้อเสนอดังà¸à¸¥à¹ˆà¸²à¸§à¸¢à¸±à¸‡à¸„งเป็นที่เคลือบà¹à¸„ลง à¹à¸¥à¸°à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¹€à¸ªà¸™à¸­à¸§à¹ˆà¸²à¸£à¸¹à¸›à¹à¸šà¸šà¸—ี่เห็นอาจมีต้นà¸à¸³à¹€à¸™à¸´à¸”à¹à¸šà¸šà¸­à¸™à¸´à¸™à¸—รีย์ที่พิเศษออà¸à¹„ปà¸à¹‡à¹„ด้[180] + +à¸à¸²à¸£à¸•à¸£à¸§à¸ˆà¸žà¸šà¸—ั้งมีเทนà¹à¸¥à¸°à¸Ÿà¸­à¸£à¹Œà¸¡à¸²à¸¥à¸”ีไฮด์ปริมาณเล็à¸à¸™à¹‰à¸­à¸¢à¹‚ดยยานโคจรรอบดาวอังคารล้วนถูà¸à¸™à¸³à¹„ปอ้างเป็นหลัà¸à¸à¸²à¸™à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸„วามเป็นไปได้ว่ามีสิ่งมีชีวิต เนื่องจาà¸à¸ªà¸²à¸£à¸›à¸£à¸°à¸à¸­à¸šà¹€à¸„มีทั้งคู่จะà¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¹„ปอย่างรวดเร็วในบรรยาà¸à¸²à¸¨à¸‚องดาวอังคาร[181][182] ในอีà¸à¸—างหนึ่ง สารเหล่านี้อาจมีà¸à¸²à¸£à¸œà¸¥à¸´à¸•à¸—ดà¹à¸—นโดยภูเขาไฟหรือà¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸—างธรณีวิทยาอื่น เช่น à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¹€à¸‹à¸­à¸£à¹Œà¹€à¸žà¸™à¸—ีน[145] + +อุลà¸à¸¡à¸“ีซึ่งเà¸à¸´à¸”ขึ้นภายหลังดาวตà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¹ƒà¸™à¸à¸£à¸“ีของโลà¸à¸™à¸±à¹‰à¸™à¸ªà¸²à¸¡à¸²à¸£à¸–เà¸à¹‡à¸šà¸£à¹ˆà¸­à¸‡à¸£à¸­à¸¢à¸‚องสิ่งมีชีวิตไว้ได้ มีรายงานà¸à¸²à¸£à¸žà¸šà¸­à¸¸à¸¥à¸à¸¡à¸“ีบนพื้นผิวของหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸™à¸”าวอังคาร[183][184] ในทำนองเดียวà¸à¸±à¸™ à¹à¸à¹‰à¸§à¸—ี่พบในหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸šà¸™à¸”าวอังคารà¸à¹‡à¸­à¸²à¸ˆà¹€à¸à¹‡à¸šà¸£à¸±à¸à¸©à¸²à¸£à¹ˆà¸­à¸‡à¸£à¸­à¸‡à¸šà¸²à¸‡à¸­à¸¢à¹ˆà¸²à¸‡à¸‚องสิ่งมีชีวิตไว้หาà¸à¸ªà¸–านที่นั้นเคยมีสิ่งมีชีวิตอยู่[185][186][187] +ความสามารถอยู่อาศัยได้[à¹à¸à¹‰] +ดูเพิ่มเติมที่: ความสามารถอยู่อาศัยได้ของดาวเคราะห์ + +ศูนย์à¸à¸²à¸£à¸šà¸´à¸™à¹à¸¥à¸°à¸­à¸§à¸à¸²à¸¨à¹€à¸¢à¸­à¸£à¸¡à¸±à¸™à¸„้นพบว่าไลเคนของโลà¸à¸ªà¸²à¸¡à¸²à¸£à¸–อยู่รอดได้ในสภาพà¹à¸§à¸”ล้อมดาวอังคารจำลอง ทำให้à¸à¸²à¸£à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¸‚องสิ่งมีชีวิตบนดาวอังคารเป็นเรื่องน่าเชื่อถือมาà¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้นตามที่นัà¸à¸§à¸´à¸ˆà¸±à¸¢ ทิลà¹à¸¡à¸™ สปอห์น รายงาน[188] เงื่อนไขด้านอุณหภูมิ ความà¸à¸”อาà¸à¸²à¸¨ à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸ à¹à¸¥à¸°à¹à¸ªà¸‡à¸ˆà¸³à¸¥à¸­à¸‡à¸‚ึ้นโดยอาศัยข้อมูลจาà¸à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร[188] เครื่องมือตรวจวัดสภาพà¹à¸§à¸”ล้อมหรือเรมส์ออà¸à¹à¸šà¸šà¸¡à¸²à¹€à¸žà¸·à¹ˆà¸­à¸ªà¸·à¸šà¸„้นเบาะà¹à¸ªà¹ƒà¸«à¸¡à¹ˆ ๆ เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸„ุณลัà¸à¸©à¸“ะà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¹€à¸§à¸µà¸¢à¸™à¸—ั่วไปบนดาวอังคาร ระบบสภาพอาà¸à¸²à¸¨à¹ƒà¸™à¸£à¸°à¸”ับเล็ภวัà¸à¸ˆà¸±à¸à¸£à¸­à¸¸à¸—à¸à¸§à¸´à¸—ยาท้องถิ่น ศัà¸à¸¢à¸ à¸²à¸žà¹ƒà¸™à¸à¸²à¸£à¸—ำลายล้างของรังสียูวี à¹à¸¥à¸°à¸„วามสามารถอยู่อาศัยได้ใต้พื้นผิวซึ่งวางอยู่บนปà¸à¸´à¸ªà¸±à¸¡à¸žà¸±à¸™à¸˜à¹Œà¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸žà¸·à¹‰à¸™à¸”ินà¸à¸±à¸šà¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨[189][190] เครื่องมือนี้เป็นส่วนหนึ่งของยาน คิวริออซิตี (มาร์สไซà¹à¸­à¸™à¸‹à¹Œà¹à¸¥à¸šà¸­à¸£à¸²à¸—อรี (MSL) หรือ ห้องปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸§à¸´à¸—ยาศาสตร์ดาวอังคาร) ซึ่งลงจอดบนดาวอังคารเมื่อเดือนสิงหาคม 2012 (พ.ศ. 2555) +à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆ[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร +ทัศนียภาพของหลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸à¸¹à¹€à¸‹à¸Ÿ ตำà¹à¸«à¸™à¹ˆà¸‡à¸—ี่ยานสปิริตโรเวอร์ สำรวจหินบะซอลต์ภูเขาไฟ + +นอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸ˆà¸²à¸à¹‚ลภส่วนหนึ่งของข้อมูลใหม่ ๆ ของดาวอังคารได้มาจาà¸à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¹€à¸ˆà¹‡à¸”ลำที่ยังอยู่ในระหว่างà¸à¸²à¸£à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸ à¸²à¸£à¸à¸´à¸ˆà¸—ั้งบนà¹à¸¥à¸°à¹‚คจรเหนือดาวอังคาร ประà¸à¸­à¸šà¸”้วยยานในวงโคจรห้าลำà¹à¸¥à¸°à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นอีà¸à¸ªà¸­à¸‡à¸¥à¸³ ได้à¹à¸à¹ˆ 2001 มาร์สโอดิสซี [191] มาร์สเอ็à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª มาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ เมเว็น มาร์สออร์บิเตอร์มิชชัน ออปพอร์ทูนิตี à¹à¸¥à¸°à¸„ิวริออซิตี + +มีà¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸­à¸§à¸à¸²à¸¨à¹„ร้คนบังคับหลายสิบลำทั้งที่โคจรรอบ ยานส่วนลงจอด à¹à¸¥à¸°à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นไปยังดาวอังคารโดยสหภาพโซเวียต สหรัà¸à¸­à¹€à¸¡à¸£à¸´à¸à¸² ยุโรป à¹à¸¥à¸° อินเดีย เพื่อศึà¸à¸©à¸²à¸ªà¸ à¸²à¸žà¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸‚องดาว ภูมิอาà¸à¸²à¸¨ à¹à¸¥à¸°à¸˜à¸£à¸“ีวิทยา สาธารณะชนทั่วไปสามารถขอดูรูปภาพดาวอังคารได้ผ่านทางโปรà¹à¸à¸£à¸¡à¹„ฮวิช + +ยานคิวริออซิตี จาà¸à¸ à¸²à¸£à¸à¸´à¸ˆà¸¡à¸²à¸£à¹Œà¸ªà¹„ซà¹à¸­à¸™à¸‹à¹Œà¹à¸¥à¸šà¸­à¸£à¸²à¸—อรีซึ่งส่งขึ้นสู่อวà¸à¸²à¸¨à¹€à¸¡à¸·à¹ˆà¸­ 26 พฤศจิà¸à¸²à¸¢à¸™ 2011 (พ.ศ. 2554) à¹à¸¥à¸°à¹„ปถึงดาวอังคารวันที่ 6 สิงหาคม 2012 (พ.ศ. 2555) ตามเวลาสาà¸à¸¥ มีขนาดใหà¸à¹ˆà¹à¸¥à¸°à¸¥à¹‰à¸³à¸«à¸™à¹‰à¸²à¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸à¸§à¹ˆà¸²à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นดาวอังคารรุ่นà¸à¹ˆà¸­à¸™ โดยสามารถเคลื่อนที่ด้วยอัตราเร็ว 90 เมตร (300 ฟุต) ต่อชั่วโมง[192] à¸à¸²à¸£à¸—ดลองประà¸à¸­à¸šà¸”้วยà¸à¸²à¸£à¹ƒà¸Šà¹‰à¹€à¸¥à¹€à¸‹à¸­à¸£à¹Œà¸—ดสอบตัวอย่างเพื่อหาองค์ประà¸à¸­à¸šà¸—างเคมี สามารถประเมินสรุปหินต่าง ๆ ที่พบว่ามีองค์ประà¸à¸­à¸šà¸­à¸¢à¹ˆà¸²à¸‡à¹„รได้ที่ระยะห่าง 7 เมตร (23 ฟุต)[193] วันที่ 10 à¸à¸¸à¸¡à¸ à¸²à¸žà¸±à¸™à¸˜à¹Œ 2013 (พ.ศ. 2556) ยานคิวริออซิตี ได้มีà¸à¸²à¸£à¹€à¸à¹‡à¸šà¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡à¸«à¸´à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸¶à¸à¸‹à¸¶à¹ˆà¸‡à¸–ือเป็นà¸à¸²à¸£à¹€à¸ˆà¸²à¸°à¸¨à¸¶à¸à¸©à¸²à¸•à¸±à¸§à¸­à¸¢à¹ˆà¸²à¸‡à¸«à¸´à¸™à¸šà¸™à¸”าวเคราะห์ดวงอื่นเป็นครั้งà¹à¸£à¸à¹‚ดยà¸à¸²à¸£à¹€à¸ˆà¸²à¸°à¸”้วยสว่านบนยาน[194] + +วันที่ 24 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 2014 (พ.ศ. 2557) ยานมาร์สออร์บิเตอร์มิชชัน (มงคลยาน หรือ เอ็มโอเอ็ม) ซึ่งส่งขึ้นสู่อวà¸à¸²à¸¨à¹‚ดยองค์à¸à¸²à¸£à¸§à¸´à¸ˆà¸±à¸¢à¸­à¸§à¸à¸²à¸¨à¸­à¸´à¸™à¹€à¸”ียได้เข้าสู่วงโคจรดาวอังคาร โครงà¸à¸²à¸£à¹€à¸£à¸´à¹ˆà¸¡à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸ˆà¸²à¸à¹‚ลà¸à¹€à¸¡à¸·à¹ˆà¸­ 5 พฤศจิà¸à¸²à¸¢à¸™ 2013 โดยมีเป้าหมายเพื่อศึà¸à¸©à¸²à¸§à¸´à¹€à¸„ราะห์บรรยาà¸à¸²à¸¨à¹à¸¥à¸°à¸¥à¸±à¸à¸©à¸“ะภูมิประเทศของดาวอังคาร ยานมาร์สออร์บิเตอร์มิชชันใช้วงโคจรส่งเฮาห์à¹à¸¡à¸™à¸™à¹Œà¹€à¸žà¸·à¹ˆà¸­à¸«à¸¥à¸¸à¸”ออà¸à¸ˆà¸²à¸à¸­à¸´à¸—ธิพลโน้มถ่วงของโลภà¹à¸¥à¸°à¹€à¸«à¸§à¸µà¹ˆà¸¢à¸‡à¹„ปสู่เส้นทางยาวไà¸à¸¥à¹€à¸à¹‰à¸²à¹€à¸”ือนสู่ดาวอังคาร ภารà¸à¸´à¸ˆà¸™à¸µà¹‰à¹€à¸›à¹‡à¸™à¸ à¸²à¸£à¸à¸´à¸ˆà¹€à¸”ินทางสู่ดาวเคราะห์อื่นโดยเอเชียที่ประสบความสำเร็จเป็นครั้งà¹à¸£à¸[195] +อนาคต[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร § เส้นเวลาà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร + +มีà¹à¸œà¸™à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸‡à¸ˆà¸­à¸”อินไซต์ในเดือนมีนาคม 2016 (พ.ศ. 2559) ร่วมไปà¸à¸±à¸šà¸„ิวบ์à¹à¸‹à¸•à¸„ู่à¹à¸à¸”ซึ่งจะบินผ่านดาวอังคารà¹à¸¥à¸°à¸„อยช่วยเชื่อมโยงà¸à¸±à¸šà¸ à¸²à¸„พื้นดิน ยานส่วนลงจอดà¹à¸¥à¸°à¸„ิวบ์à¹à¸‹à¸•à¸—ั้งคู่มีà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¹„ปถึงดาวอังคารในเดือนà¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 2016[196] + +องค์à¸à¸²à¸£à¸­à¸§à¸à¸²à¸¨à¸¢à¸¸à¹‚รปโดยความร่วมมือà¸à¸±à¸šà¸£à¸­à¸ªà¸„อสมอสจะมีà¸à¸²à¸£à¸ªà¹ˆà¸‡à¹€à¸­à¹‡à¸à¹‚ซมาร์สเทรซà¹à¸à¹Šà¸ªà¸­à¸­à¸£à¹Œà¸šà¸´à¹€à¸•à¸­à¸£à¹Œà¸à¸±à¸šà¸¢à¸²à¸™à¸ªà¹ˆà¸§à¸™à¸¥à¸‡à¸ˆà¸­à¸”สเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µ ในปี 2016 à¹à¸¥à¸°à¸ªà¹ˆà¸‡à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นเอ็à¸à¹‚ซมาร์สในปี 2018 (พ.ศ. 2561) นาซามีà¹à¸œà¸™à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¡à¸²à¸£à¹Œà¸ª 2020 ยานสำรวจภาคพื้นชีววิทยาดาราศาสตร์ในปี 2020 (พ.ศ. 2563) + +ยานโคจรมาร์สโฮปของสหรัà¸à¸­à¸²à¸«à¸£à¸±à¸šà¹€à¸­à¸¡à¸´à¹€à¸£à¸•à¸ªà¹Œà¸¡à¸µà¸à¸³à¸«à¸™à¸”à¸à¸²à¸£à¸ªà¹ˆà¸‡à¹ƒà¸™à¸›à¸µ 2020 ซึ่งจะไปถึงวงโคจรของดาวอังคารในปี 2021 (พ.ศ. 2564) ยานจะทำà¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸—ั่วทั้งหมดของดาวอังคาร[197] + +มีà¸à¸²à¸£à¹€à¸ªà¸™à¸­à¹à¸œà¸™à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸¡à¸™à¸¸à¸©à¸¢à¹Œà¸ªà¸¹à¹ˆà¸”าวอังคารหลายต่อหลายครั้งตลอดช่วงศตวรรษที่ 20 ต่อเนื่องมาจนถึงศตวรรษที่ 21 à¹à¸•à¹ˆà¸¢à¸±à¸‡à¹„ม่มีà¹à¸œà¸™à¹ƒà¸”ที่ดำเนินà¸à¸²à¸£à¸ˆà¸£à¸´à¸‡à¸­à¸¢à¹ˆà¸²à¸‡à¹€à¸£à¹‡à¸§à¸—ี่สุดà¸à¹ˆà¸­à¸™à¸›à¸µ 2025 (พ.ศ. 2568) +ดาราศาสตร์บนดาวอังคาร[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดาราศาสตร์บนดาวอังคาร +โฟบอสผ่านหน้าดวงอาทิตย์ (ออปพอร์ทูนิตี, 10 มีนาคม 2004) +à¸à¸²à¸£à¹€à¸à¹‰à¸²à¸•à¸´à¸”ตามจุดมืดดวงอาทิตย์จาà¸à¸”าวอังคาร + +ด้วยà¸à¸²à¸£à¸—ี่มีทั้งยานอวà¸à¸²à¸¨à¹ƒà¸™à¸§à¸‡à¹‚คจร ยานส่วนลงจอด à¹à¸¥à¸°à¸¢à¸²à¸™à¸ªà¸³à¸£à¸§à¸ˆà¸ à¸²à¸„พื้นมาà¸à¸¡à¸²à¸¢à¸«à¸¥à¸²à¸¢à¸¥à¸³ ทำให้à¸à¸²à¸£à¸¨à¸¶à¸à¸©à¸²à¸”าราศาสตร์จาà¸à¸”าวอังคารในปัจจุบันเป็นเรื่องที่เป็นไปได้ à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸”าวบริวารโฟบอสของดาวอังคารจะปราà¸à¸à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¸”้วยขนาดเชิงมุมประมาณหนึ่งในสามของดวงจันทร์เต็มดวงที่มองเห็นจาà¸à¹‚ลภà¹à¸•à¹ˆà¸ªà¸³à¸«à¸£à¸±à¸šà¸”าวบริวารดีมอสà¹à¸¥à¹‰à¸§à¸à¸¥à¸±à¸šà¸›à¸£à¸²à¸à¸à¸„ล้ายà¸à¸±à¸šà¸”าวทั่วไปมาà¸à¸™à¹‰à¸­à¸¢à¹à¸¥à¹‰à¸§à¹à¸•à¹ˆà¸à¸£à¸“ีà¹à¸¥à¸°à¸¡à¸­à¸‡à¹€à¸«à¹‡à¸™à¸ªà¸§à¹ˆà¸²à¸‡à¸à¸§à¹ˆà¸²à¸”าวศุà¸à¸£à¹Œà¹€à¸¡à¸·à¹ˆà¸­à¸¡à¸­à¸‡à¸ˆà¸²à¸à¹‚ลà¸à¹€à¸žà¸µà¸¢à¸‡à¹€à¸¥à¹‡à¸à¸™à¹‰à¸­à¸¢[198] + +มีปราà¸à¸à¸à¸²à¸£à¸“์หลายอย่างที่รู้จัà¸à¸à¸±à¸™à¸šà¸™à¹‚ลà¸à¸‹à¸¶à¹ˆà¸‡à¸ªà¸±à¸‡à¹€à¸à¸•à¸žà¸šà¸šà¸™à¸”าวอังคาร เช่น ดาวตภà¹à¸¥à¸°à¸­à¸­à¹‚รรา[199] ปราà¸à¸à¸à¸²à¸£à¸“์โลà¸à¹€à¸„ลื่อนผ่านหน้าดวงอาทิตย์มองเห็นจาà¸à¸”าวอังคารจะเà¸à¸´à¸”ขึ้นในวันที่ 10 พฤศจิà¸à¸²à¸¢à¸™ 2084 (พ.ศ. 2627)[200] นอà¸à¸ˆà¸²à¸à¸™à¸±à¹‰à¸™à¸¢à¸±à¸‡à¸¡à¸µà¸à¸²à¸£à¹€à¸„ลื่อนผ่านโดยดาวพุธ à¸à¸²à¸£à¹€à¸„ลื่อนผ่านโดยดาวศุà¸à¸£à¹Œ ตลอดจนดาวบริวารโฟบอสà¹à¸¥à¸°à¸”ีมอสซึ่งมีขนาดเชิงมุมค่อนข้างเล็à¸à¸—ำให้อย่างมาà¸à¸—ี่สุดเà¸à¸´à¸”เป็น "สุริยุปราคา" บางส่วนเมื่อดาวทั้งสองเคลื่อนผ่าน (ดู à¸à¸²à¸£à¹€à¸„ลื่อนผ่านของดีมอสจาà¸à¸”าวอังคาร)[201][202] + +วันที่ 19 ตุลาคม 2014 (พ.ศ. 2557) ดาวหางไซดิงสปริงผ่านเฉียดใà¸à¸¥à¹‰à¸”าวอังคารอย่างมาภจนโคม่าอาจครอบคลุมดาวอังคาร[203][204][205][206][207][208] +à¸à¸²à¸£à¸Šà¸¡[à¹à¸à¹‰] +ภาพเคลื่อนไหวà¹à¸ªà¸”งà¸à¸²à¸£à¹€à¸„ลื่อนถอยหลังปราà¸à¸à¸‚องดาวอังคารในปี 2003 เมื่อมองจาà¸à¹‚ลภ+ +เนื่องจาà¸à¸„วามเยื้องศูนย์à¸à¸¥à¸²à¸‡à¸‚องวงโคจรดาวอังคาร เมื่อดาวอังคารอยู่ในตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามà¸à¸±à¸šà¸”วงอาทิตย์จะมีความส่องสว่างปราà¸à¸à¹„ด้ตั้งà¹à¸•à¹ˆ -2.91[6] จนถึง -1.4 ความสว่างน้อยที่สุดของดาวอังคารคือ +1.6 เà¸à¸´à¸”ขึ้นเมื่อดาวอยู่ด้านเดียวà¸à¸±à¸™à¸à¸±à¸šà¸”วงอาทิตย์[10] ดาวอังคารมัà¸à¸›à¸£à¸²à¸à¸à¸Šà¸±à¸”ว่ามีสีเหลือง สีส้ม หรือสีà¹à¸”ง à¹à¸•à¹ˆà¸ªà¸µà¸•à¸²à¸¡à¸ˆà¸£à¸´à¸‡à¸‚องดาวอังคารนั้นใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¸ªà¸µà¸‚องบัตเตอร์สà¸à¸­à¸•à¸Šà¹Œ สีà¹à¸”งที่มองเห็นนั้นเป็นเพียงà¸à¸¸à¹ˆà¸™à¹ƒà¸™à¸šà¸£à¸£à¸¢à¸²à¸à¸²à¸¨à¸‚องดาวเคราะห์ ยานสำรวจภาคพื้นสปิริต ของนาซาได้ทำà¸à¸²à¸£à¸–่ายภาพภูมิทัศน์โคลนสีเขียวอมน้ำตาลร่วมà¸à¸±à¸šà¸«à¸´à¸™à¸ªà¸µà¸™à¹‰à¸³à¹€à¸‡à¸´à¸™à¸›à¸™à¹€à¸—าà¹à¸¥à¸°à¸«à¸¢à¹ˆà¸­à¸¡à¸—รายสีà¹à¸”งจาง ๆ เอาไว้[209] ขณะที่อยู่ห่างออà¸à¹„ปจาà¸à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุด จะมีระยะทางมาà¸à¸à¸§à¹ˆà¸²à¸•à¸­à¸™à¸—ี่อยู่ใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดมาà¸à¸à¸§à¹ˆà¸²à¹€à¸ˆà¹‡à¸”เท่า เมื่อถึงตำà¹à¸«à¸™à¹ˆà¸‡à¸—ี่ไม่เหมาะสมสำหรับà¸à¸²à¸£à¸Šà¸¡ ดาวอังคารà¸à¹‡à¸ˆà¸°à¸–ูà¸à¸šà¸”บังโดยความเจิดจ้าของดวงอาทิตย์ได้เป็นเวลานานà¸à¸§à¹ˆà¸²à¸«à¸™à¸¶à¹ˆà¸‡à¹€à¸”ือน สำหรับเวลาที่เหมาะสมที่สุดในà¸à¸²à¸£à¸Šà¸¡à¹€à¸à¸´à¸”ขึ้นทุภๆ ช่วง 15 - 17 ปี à¹à¸¥à¸°à¸¡à¸±à¸à¹€à¸à¸´à¸”ขึ้นระหว่างปลายเดือนà¸à¸£à¸à¸Žà¸²à¸„มถึงปลายเดือนà¸à¸±à¸™à¸¢à¸²à¸¢à¸™ เป็นจุดที่สามารถมองเห็นรายละเอียดพื้นผิวดาวอังคารได้ค่อนข้างมาà¸à¸”้วยà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ สำหรับส่วนที่สังเà¸à¸•à¹€à¸«à¹‡à¸™à¹„ด้ง่ายà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸ˆà¸°à¹ƒà¸Šà¹‰à¸à¸¥à¹‰à¸­à¸‡à¸à¸³à¸¥à¸±à¸‡à¸‚ยายต่ำคือà¹à¸œà¹ˆà¸™à¸™à¹‰à¸³à¹à¸‚็งขั้วดาว[210] + +เมื่อดาวอังคารเข้ามายังตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามดวงอาทิตย์ à¸à¹‡à¸ˆà¸°à¹€à¸£à¸´à¹ˆà¸¡à¸Šà¹ˆà¸§à¸‡à¹€à¸§à¸¥à¸²à¹à¸«à¹ˆà¸‡à¸à¸²à¸£à¹€à¸„ลื่อนถอยหลัง หมายความว่าดาวอังคารจะมองเห็นเสมือนเคลื่อนที่ย้อนทางà¸à¸¥à¸±à¸šà¸«à¸¥à¸±à¸‡à¹ƒà¸™à¸¥à¸±à¸à¸©à¸“ะเป็นวงเมื่อเทียบดาวฤà¸à¸©à¹Œà¸žà¸·à¹‰à¸™à¸«à¸¥à¸±à¸‡à¸•à¹ˆà¸²à¸‡ ๆ ระยะเวลาของà¸à¸²à¸£à¹€à¸„ลื่อนถอยหลังนี้ยาวได้จนถึงราว 72 วัน à¹à¸¥à¸°à¸”าวอังคารจะมีความสว่างเพิ่มขึ้นสูงสุดท่ามà¸à¸¥à¸²à¸‡à¸à¸²à¸£à¹€à¸„ลื่อนที่ดังà¸à¸¥à¹ˆà¸²à¸§[211] +à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸¡à¸²à¸à¸—ี่สุด[à¹à¸à¹‰] +สัมพัทธ์[à¹à¸à¹‰] + +ณ จุดที่เส้นลองจิจูดของดาวอังคารอยู่ในตำà¹à¸«à¸™à¹ˆà¸‡ 180 องศาจาà¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸‚องดวงอาทิตย์เมื่อโลà¸à¹€à¸›à¹‡à¸™à¸¨à¸¹à¸™à¸¢à¹Œà¸à¸¥à¸²à¸‡à¸™à¸±à¹‰à¸™à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸²à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้าม ซึ่งเป็นเวลาที่ใà¸à¸¥à¹‰à¹€à¸„ียงà¸à¸±à¸šà¸ˆà¸¸à¸”ที่เข้ามาใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุด เวลาà¸à¸²à¸£à¹€à¸à¸´à¸”ของตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้าม สามารถห่างจาà¸à¸ˆà¸¸à¸”เข้ามาใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดได้มาà¸à¸–ึง 8.5 วัน ระยะทางเข้าใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดผันà¹à¸›à¸£à¹„ด้ตั้งà¹à¸•à¹ˆà¸›à¸£à¸°à¸¡à¸²à¸“ 54[212] ถึง 103 ล้านà¸à¸´à¹‚ลเมตรขึ้นอยู่à¸à¸±à¸šà¸„วามรีของวงโคจรดาวเคราะห์ ซึ่งเป็นสาเหตุทำให้ขนาดเชิงมุมผันà¹à¸›à¸£à¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸à¸±à¸™[213] ดาวอังคารอยู่ในตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามเมื่อวันที่ 8 เมษายน 2014 (พ.ศ. 2557) ด้วยระยะทางประมาณ 93 ล้านà¸à¸´à¹‚ลเมตร[214] à¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามครั้งถัดไปของดาวอังคารจะเà¸à¸´à¸”ขึ้นในวันที่ 22 พฤษภาคม 2016 (พ.ศ. 2559) ด้วยระยะทาง 76 ล้านà¸à¸´à¹‚ลเมตร[214] ระยะเวลาเฉลี่ยระหว่างà¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามของดาวอังคารà¹à¸•à¹ˆà¸¥à¸°à¸„รั้งหรือคาบซินอดิà¸à¸„ือ 780 วัน โดยจำนวนวันที่เà¸à¸´à¸”จริงอาจยาวนานจาภ764 ถึง 812 วัน[215] +ดาวอังคารในตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามจาà¸à¸›à¸µ 2003-2018 มองจาà¸à¸”้านบนของสุริยวิถีโดยมีโลà¸à¸­à¸¢à¸¹à¹ˆà¸•à¸£à¸‡à¸à¸¥à¸²à¸‡ +ค่าที่à¹à¸™à¹ˆà¸™à¸­à¸™à¹ƒà¸à¸¥à¹‰à¹€à¸„ียงเวลาปัจจุบัน[à¹à¸à¹‰] + +ดาวอังคารเข้าใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸—ี่สุดà¹à¸¥à¸°à¸¡à¸µà¸„วามสว่างปราà¸à¸à¸ªà¸¹à¸‡à¸—ี่สุดในรอบเà¸à¸·à¸­à¸š 60,000 ปี ด้วยระยะทาง 55,758,006 à¸à¸´à¹‚ลเมตร (34,646,419 ไมล์, 0.37271925 หน่วยดาราศาสตร์) à¹à¸¥à¸°à¸¡à¸µà¸„วามส่องสว่าง -2.88 เมื่อวันที่ 27 สิงหาคม 2003 (พ.ศ. 2546) 9:51:13 ตามเวลาสาà¸à¸¥ à¸à¸²à¸£à¹€à¸à¸´à¸”ครั้งนี้ห่างจาà¸à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามของดาวอังคารหนึ่งวัน à¹à¸¥à¸°à¸›à¸£à¸°à¸¡à¸²à¸“สามวันจาà¸à¸ˆà¸¸à¸”ใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุด ทำให้มองเห็นจาà¸à¹‚ลà¸à¹„ด้ง่ายเป็นพิเศษ à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸¡à¸²à¸à¸ªà¸¸à¸”à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸™à¸µà¹‰à¸„าดว่าเà¸à¸´à¸”ขึ้นในวันที่ 12 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 57,617 ปีà¸à¹ˆà¸­à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š ครั้งต่อไปจะเà¸à¸´à¸”ขึ้นในปี 2287 (พ.ศ. 2830)[216] à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¹€à¸›à¹‡à¸™à¸›à¸£à¸°à¸§à¸±à¸•à¸´à¸à¸²à¸£à¸“์นี้จัดว่าใà¸à¸¥à¹‰à¸à¸§à¹ˆà¸²à¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¸¡à¸²à¸à¸—ี่สุดร่วมสมัยอื่นเพียงเล็à¸à¸™à¹‰à¸­à¸¢ ตัวอย่างเช่น ระยะใà¸à¸¥à¹‰à¸—ี่สุดเมื่อ 22 สิงหาคม 1924 (พ.ศ. 2467) ที่ 0.37285 หน่วยดาราศาสตร์ à¹à¸¥à¸°à¸£à¸°à¸¢à¸°à¹ƒà¸à¸¥à¹‰à¸—ี่สุดที่จะเà¸à¸´à¸”ขึ้นเมื่อ 24 สิงหาคม 2208 (พ.ศ. 2751) ที่ 0.37279 หน่วยดาราศาสตร์[169] +ประวัติศาสตร์à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ประวัติศาสตร์à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸”าวอังคาร + +จุดที่โดดเด่นในประวัติศาสตร์à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸”าวอังคารคือเมื่อดาวอังคารอยู่ในตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามใà¸à¸¥à¹‰à¸à¸±à¸šà¹‚ลà¸à¹à¸¥à¸°à¸—ำให้มองเห็นได้ง่ายที่สุดซึ่งเà¸à¸´à¸”ขึ้นในทุà¸à¸ªà¸­à¸‡à¸›à¸µ ที่เด่นชัดยิ่งขึ้นอีà¸à¸„ือà¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามขณะอยู่ใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดของดาวอังคารซึ่งเà¸à¸´à¸”ขึ้นทุภๆ 15 - 17 ปี นั่นหมายถึงà¸à¸²à¸£à¹€à¸‚้าใà¸à¸¥à¹‰à¹‚ลà¸à¸¡à¸²à¸à¸¢à¸´à¹ˆà¸‡à¸‚ึ้นด้วยจนทำให้เห็นความà¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸­à¸¢à¹ˆà¸²à¸‡à¸Šà¸±à¸”เจน +à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹ƒà¸™à¸¢à¸¸à¸„โบราณà¹à¸¥à¸°à¸¢à¸¸à¸„à¸à¸¥à¸²à¸‡[à¹à¸à¹‰] + +à¸à¸²à¸£à¸”ำรงอยู่ของดาวอังคารในà¸à¸²à¸™à¸°à¸§à¸±à¸•à¸–ุหนึ่งที่เคลื่อนผ่านท้องฟ้ายามiราตรีได้ถูà¸à¸šà¸±à¸™à¸—ึà¸à¹„ว้โดยนัà¸à¸”าราศาสตร์อียิปต์โบราณ à¹à¸¥à¸°à¹€à¸¡à¸·à¹ˆà¸­ 1534 ปีà¸à¹ˆà¸­à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š พวà¸à¹€à¸‚าà¸à¹‡à¸„ุ้นเคยดีà¹à¸¥à¹‰à¸§à¸à¸±à¸šà¸à¸²à¸£à¹€à¸„ลื่อนถอยหลังของดาวเคราะห์[217]ในยุคจัà¸à¸£à¸§à¸£à¸£à¸”ิบาบิโลเนียใหม่ นัà¸à¸”าราศาสตร์ชาวบาบิโลเนียได้มีà¸à¸²à¸£à¸šà¸±à¸™à¸—ึà¸à¸›à¸¹à¸¡à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸‚องดาวเคราะห์ต่าง ๆ ตลอดจนพฤติà¸à¸£à¸£à¸¡à¸‚องดาวเคราะห์ที่สังเà¸à¸•à¹„ด้เอาไว้อย่างเป็นระบบà¹à¸¥à¸°à¸ªà¸¡à¹ˆà¸³à¹€à¸ªà¸¡à¸­ สำหรับดาวอังคาร พวà¸à¹€à¸‚าทราบว่าดาวจะโคจรครบ 37 คาบซินอดิภหรือ 42 รอบจัà¸à¸£à¸£à¸²à¸¨à¸µà¹ƒà¸™à¸—ุภๆ 79 ปี พวà¸à¹€à¸‚ายังได้คิดค้นระเบียบวิธีทางคณิตศาสตร์ขึ้นมาเพื่อให้เà¸à¸´à¸”ความคลาดเคลื่อนเพียงเล็à¸à¸™à¹‰à¸­à¸¢à¹ƒà¸™à¸à¸²à¸£à¸—ำนายตำà¹à¸«à¸™à¹ˆà¸‡à¸‚องดาวเคราะห์ทั้งหลาย[218][219] + +ในศตวรรษที่สี่à¸à¹ˆà¸­à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š อาริสโตเติลตั้งข้อสังเà¸à¸•à¸§à¹ˆà¸²à¸”าวอังคารได้หายไปเบื้องหลังดวงจันทร์ระหว่างà¸à¸²à¸£à¸–ูà¸à¸šà¸”บัง บ่งบอà¸à¸§à¹ˆà¸²à¸”าวอังคารนั้นต้องอยู่ห่างไà¸à¸¥à¸­à¸­à¸à¹„ป[220] ทอเลมี ชาวà¸à¸£à¸µà¸à¸—ี่อาศัยในอะเล็à¸à¸‹à¸²à¸™à¹€à¸”รีย[221] ได้พยายามà¹à¸à¹‰à¹„ขปัà¸à¸«à¸²à¸à¸²à¸£à¹€à¸„ลื่อนไหวในวงโคจรของดาวอังคาร à¹à¸šà¸šà¸ˆà¸³à¸¥à¸­à¸‡à¸‚องทอเลมีà¹à¸¥à¸°à¸‡à¸²à¸™à¸—างดาราศาสตร์ที่เขารวบรวมขึ้น ปราà¸à¸à¸•à¹ˆà¸­à¸¡à¸²à¹€à¸›à¹‡à¸™à¸Šà¸¸à¸”หนังสือหลายเล่มรู้จัà¸à¸à¸±à¸™à¹ƒà¸™à¸Šà¸·à¹ˆà¸­à¸­à¸±à¸¥à¸¡à¸²à¹€à¸ˆà¸ªà¸•à¹Œ ซึ่งได้à¸à¸¥à¸²à¸¢à¸¡à¸²à¹€à¸›à¹‡à¸™à¸•à¸³à¸£à¸²à¸­à¸±à¸™à¸—รงอิทธิพลต่อดาราศาสตร์ตะวันตà¸à¸•à¸¥à¸­à¸”สิบสี่ศตวรรษถัดมา[222] งานนิพนธ์จาà¸à¸ªà¸¡à¸±à¸¢à¸ˆà¸µà¸™à¹‚บราณยืนยันว่าดาวอังคารเป็นที่รู้จัà¸à¹‚ดยนัà¸à¸”าราศาสตร์ชาวจีนไม่ช้าไปà¸à¸§à¹ˆà¸²à¸¨à¸•à¸§à¸£à¸£à¸©à¸—ี่สี่à¸à¹ˆà¸­à¸™à¸„ริสต์ศัà¸à¸£à¸²à¸Š[223] ในคริสต์ศตวรรษที่ห้า สุริยสิทธันต์ ตำราทางดาราศาสตร์อินเดีย มีà¸à¸²à¸£à¸›à¸£à¸°à¸¡à¸²à¸“เส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸‚องดาวอังคารไว้[c][224] ในวัฒนธรรมเอเชียตะวันออภมัà¸à¹€à¸£à¸µà¸¢à¸à¸”าวอังคารตามประเพณีว่า "ดาวไฟ" (ç«æ˜Ÿ) โดยวางอยู่บนหลัà¸à¸˜à¸²à¸•à¸¸à¸—ั้งห้า[225][226][227] + +ในช่วงคริสต์ศตวรรษที่สิบเจ็ด ไทโค บราเฮทำà¸à¸²à¸£à¸§à¸±à¸”พารัลà¹à¸¥à¸à¸‹à¹Œà¹ƒà¸™à¹à¸•à¹ˆà¸¥à¸°à¸§à¸±à¸™à¸‚องดาวอังคาร ซึ่งต่อมาโยฮันเนส เคปเลอร์ได้นำไปใช้คำนวณเบื้องต้นหาระยะทางสัมพัทธ์สู่ดาวเคราะห์[228] เมื่อà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์เป็นที่à¹à¸žà¸£à¹ˆà¸«à¸¥à¸²à¸¢ ได้มีà¸à¸²à¸£à¸§à¸±à¸”ค่าพารัลà¹à¸¥à¸à¸‹à¹Œà¸£à¸²à¸¢à¸§à¸±à¸™à¸‚องดาวอังคารซ้ำอีà¸à¸„รั้งเนื่องในความพยายามที่จะหาระยะทางที่à¹à¸¡à¹ˆà¸™à¸¢à¸³à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¹‚ลà¸à¸à¸±à¸šà¸”วงอาทิตย์ โจวันนี โดเมนีโภà¸à¸±à¸ªà¸‹à¸µà¸™à¸µà¹€à¸›à¹‡à¸™à¸œà¸¹à¹‰à¸”ำเนินà¸à¸²à¸£à¸”ังà¸à¸¥à¹ˆà¸²à¸§à¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลà¹à¸£à¸à¹ƒà¸™à¸›à¸µ 1672 (พ.ศ. 2215) à¸à¸²à¸£à¸§à¸±à¸”ค่าพารัลà¹à¸¥à¸à¸‹à¹Œà¹ƒà¸™à¸Šà¹ˆà¸§à¸‡à¹à¸£à¸ ๆ นั้นมีอุปสรรคสำคัà¸à¸ˆà¸²à¸à¸„ุณภาพของตัวเครื่องมือเอง[229] à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸›à¸£à¸²à¸à¸à¸à¸²à¸£à¸“์ดาวศุà¸à¸£à¹Œà¸šà¸”บังดาวอังคารเพียงครั้งเดียวเà¸à¸´à¸”ขึ้นในวันที่ 13 ตุลาคม 1590 (พ.ศ. 2133) โดยมิคาเอล à¹à¸¡à¸ªà¸•à¹Œà¸¥à¸´à¸™à¸—ี่ไฮเดลà¹à¸šà¸£à¹Œà¸[230] ในปี 1610 (พ.ศ. 2153) à¸à¸²à¸¥à¸´à¹€à¸¥à¹‚อ à¸à¸²à¸¥à¸´à¹€à¸¥à¸­à¸µà¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลà¹à¸£à¸à¸—ี่มองดูดาวอังคารผ่านà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์[231] บุคคลà¹à¸£à¸à¸—ี่วาดภาพดาวอังคารโดยà¹à¸ªà¸”งลัà¸à¸©à¸“ะภูมิประเทศต่าง ๆ ด้วยคือนัà¸à¸”าราศาสตร์ชาวดัตช์ คริสตียาน เฮยเคินส์[232] +"คลอง" ดาวอังคาร[à¹à¸à¹‰] +à¹à¸œà¸™à¸—ี่ดาวอังคารโดยโจวานนี สเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µ +ภาพร่างดาวอังคารจาà¸à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹‚ดยโลเวลล์ เวลาใดเวลาหนึ่งà¸à¹ˆà¸­à¸™à¸›à¸µ 1914 (ขั้วใต้อยู่ด้านบน) +à¹à¸œà¸™à¸—ี่ดาวอังคารจาà¸à¸à¸¥à¹‰à¸­à¸‡à¸®à¸±à¸šà¹€à¸šà¸´à¸¥ เห็นใà¸à¸¥à¹‰à¸•à¸³à¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามปี 1999 (ขั้วเหนืออยู่ด้านบน) +ดูบทความหลัà¸à¸—ี่: คลองบนดาวอังคาร + +เมื่อถึงคริสต์ศตวรรษที่สิบเà¸à¹‰à¸² à¸à¸³à¸¥à¸±à¸‡à¸‚ยายของà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ได้เพิ่มมาà¸à¸‚ึ้นจนถึงระดับที่พอจำà¹à¸™à¸à¹à¸¢à¸à¹à¸¢à¸°à¸£à¸²à¸¢à¸¥à¸°à¹€à¸­à¸µà¸¢à¸”ต่าง ๆ บนพื้นผิวได้ à¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามใà¸à¸¥à¹‰à¸”วงอาทิตย์ที่สุดของดาวอังคารเมื่อวันที่ 5 à¸à¸±à¸™à¸¢à¸²à¸¢à¸™ 1877 (พ.ศ. 2420) ปีนั้นเอง โจวานนี สเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µ นัà¸à¸”าราศาสตร์ชาวอิตาลี ใช้à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ขนาด 22 เซนติเมตร (8.7 นิ้ว) ในมิลานได้สร้างà¹à¸œà¸™à¸—ี่ดาวอังคารที่มีรายละเอียดปลีà¸à¸¢à¹ˆà¸­à¸¢à¸‚ึ้นเป็นฉบับà¹à¸£à¸ à¹à¸œà¸™à¸—ี่นี้มีเอà¸à¸¥à¸±à¸à¸©à¸“์โดดเด่นด้วยภูมิประเทศที่เขาเรียà¸à¸Šà¸·à¹ˆà¸­à¸§à¹ˆà¸² คานาลี ซึ่งได้รับà¸à¸²à¸£à¹€à¸›à¸´à¸”เผยต่อมาในภายหลังว่าเป็นเพียงภาพลวงตา รอยเส้นตรงยืดยาวบนพื้นผิวดาวอังคารที่ถูà¸à¸—ึà¸à¸—ัà¸à¹€à¸£à¸µà¸¢à¸à¸§à¹ˆà¸²à¸„านาลี เหล่านี้ โจวานนีได้ตั้งชื่อให้ตามอย่างชื่อà¹à¸¡à¹ˆà¸™à¹‰à¸³à¸—ี่มีชื่อเสียงเป็นที่รู้จัà¸à¸šà¸™à¹‚ลภศัพท์ที่เขาใช้มีความหมายว่า "ทางน้ำ" หรือ "ร่องน้ำ" ซึ่งนิยมà¹à¸›à¸¥à¸à¸±à¸™à¸­à¸¢à¹ˆà¸²à¸‡à¸œà¸´à¸” ๆ ในภาษาอังà¸à¸¤à¸©à¸§à¹ˆà¸² "คลอง"[233][234] + +จาà¸à¸­à¸´à¸—ธิพลของà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸² เพอร์ซิวัล โลเวลล์ นัà¸à¸•à¸°à¸§à¸±à¸™à¸­à¸­à¸à¸¨à¸¶à¸à¸©à¸²à¹„ด้ตั้งหอดูดาวขึ้นโดยมีà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ขนาด 30 à¹à¸¥à¸° 45 เซนติเมตร (12 à¹à¸¥à¸° 18 นิ้ว) หอดูดาวนี้ได้ใช้ในà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคารระหว่างโอà¸à¸²à¸ªà¸­à¸±à¸™à¸”ีที่ผ่านมาในปี 1894 (พ.ศ. 2437) ตลอดจนà¸à¸²à¸£à¹€à¸‚้าสู่ตำà¹à¸«à¸™à¹ˆà¸‡à¸•à¸£à¸‡à¸‚้ามที่ดีลดหลั่นลงมาหลังจาà¸à¸™à¸±à¹‰à¸™ เขาตีพิมพ์หนังสือหลายเล่มเรื่องดาวอังคารรวมไปถึงสิ่งมีชีวิตบนนั้นซึ่งส่งอิทธิพลอย่างใหà¸à¹ˆà¸«à¸¥à¸§à¸‡à¸•à¹ˆà¸­à¸ªà¸²à¸˜à¸²à¸£à¸“ะ[235] ยังมีà¸à¸²à¸£à¸žà¸š คานาลี โดยนัà¸à¸”าราศาสตร์คนอื่น ๆ เช่น อองรี โฌเซฟ เพร์โรà¹à¸•à¸‡ à¹à¸¥à¸°à¸«à¸¥à¸¸à¸¢à¸ªà¹Œ ตอลลง ที่เมืองนิสโดยใช้หนึ่งในà¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ที่ใหà¸à¹ˆà¸—ี่สุดในเวลานั้น[236][237] + +à¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹à¸›à¸¥à¸‡à¸•à¸²à¸¡à¸¤à¸”ูà¸à¸²à¸¥à¸­à¸±à¸™à¸›à¸£à¸°à¸à¸­à¸šà¸”้วยà¸à¸²à¸£à¸–อยร่นของà¹à¸œà¹ˆà¸™à¸‚ั้วดาวà¹à¸¥à¸°à¸à¸²à¸£à¹€à¸à¸´à¸”พื้นที่มืดในช่วงฤดูร้อนของดาวอังคาร เมื่อประจวบเข้าà¸à¸±à¸šà¸„ลองมาà¸à¸¡à¸²à¸¢à¸ˆà¸¶à¸‡à¸™à¸³à¹„ปสู่à¸à¸²à¸£à¸„าดเดาเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸šà¸™à¸”าวอังคาร à¹à¸¥à¸°à¸„วามเชื่อที่ยึดมั่นถือมั่นอย่างยาวนานว่าดาวอังคารมีผืนทะเลที่à¸à¸§à¹‰à¸²à¸‡à¹ƒà¸«à¸à¹ˆà¸à¸±à¸šà¸žà¸·à¸Šà¸™à¸²à¸™à¸²à¸žà¸±à¸™à¸˜à¸¸à¹Œ à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ในขณะนั้นยังไม่มีà¸à¸³à¸¥à¸±à¸‡à¸‚ยายถึงขั้นที่สามารถให้หลัà¸à¸à¸²à¸™à¸¢à¸·à¸™à¸¢à¸±à¸™à¸à¸²à¸£à¸„าดเดาใด ๆ ได้ เมื่อใช้à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ขนาดใหà¸à¹ˆà¸‚ึ้นà¸à¹‡à¸ˆà¸°à¸ªà¸±à¸‡à¹€à¸à¸•à¹€à¸«à¹‡à¸™ คานาลี ตรงยาวที่ขนาดเล็à¸à¸¥à¸‡ ระหว่างà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹ƒà¸™à¸›à¸µ 1909 (พ.ศ. 2452) โดยใช้à¸à¸¥à¹‰à¸­à¸‡à¹‚ทรทรรศน์ขนาด 84 เซนติเมตร (33 นิ้ว) à¹à¸Ÿà¸¥à¸¡à¸¡à¸²à¸£à¸´à¸¢à¸‡à¸ªà¸±à¸‡à¹€à¸à¸•à¸žà¸šà¸£à¸¹à¸›à¹à¸šà¸šà¸—ี่ไม่เป็นระเบียบà¹à¸•à¹ˆà¹„ม่เห็นคานาลี [238] + +à¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งบทความในทศวรรษ 1960 (พ.ศ. 2503-) ยังมีà¸à¸²à¸£à¸•à¸µà¸žà¸´à¸¡à¸žà¹Œà¹€à¸£à¸·à¹ˆà¸­à¸‡à¸Šà¸µà¸§à¸§à¸´à¸—ยาบนดาวอังคารโดยผลัà¸à¹„สคำอธิบายà¹à¸™à¸§à¸—างอื่นออà¸à¹„ป คงไว้à¹à¸•à¹ˆà¸§à¹ˆà¸²à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸šà¸™à¸™à¸±à¹‰à¸™à¸™à¸±à¹ˆà¸™à¹€à¸­à¸‡à¹€à¸›à¹‡à¸™à¹€à¸«à¸•à¸¸à¸‚องà¸à¸²à¸£à¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¸•à¸²à¸¡à¸¤à¸”ูà¸à¸²à¸¥à¸šà¸™à¸”าวอังคาร ภาวะà¸à¸²à¸£à¸“์โดยละเอียดทั้งเมà¹à¸—บอลิซึมà¹à¸¥à¸°à¸§à¸±à¸à¸ˆà¸±à¸à¸£à¸—างเคมีต่าง ๆ สำหรับระบบนิเวศที่ดำเนินได้จริงได้รับà¸à¸²à¸£à¸•à¸µà¸žà¸´à¸¡à¸žà¹Œ[239] +à¸à¸²à¸£à¹€à¸¢à¸·à¸­à¸™à¹‚ดยยานอวà¸à¸²à¸¨[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: à¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร + +ครั้นยานอวà¸à¸²à¸¨à¹„ปเยือนถึงดาวอังคารระหว่างปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸¡à¸²à¸£à¸´à¹€à¸™à¸­à¸£à¹Œà¸‚องนาซาในช่วงทศวรรษ 1960 à¹à¸¥à¸° 70 à¹à¸™à¸§à¸„ิดเดิม ๆ à¸à¹‡à¸žà¸´à¸™à¸²à¸¨à¹„ปà¹à¸šà¸šà¹„ม่มีชิ้นดี นอà¸à¸ˆà¸²à¸à¸™à¸µà¹‰à¸œà¸¥à¸à¸²à¸£à¸—ดลองตรวจหาสิ่งมีชีวิตโดยยานไวà¸à¸´à¸‡à¹ƒà¸™à¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸ à¸²à¸£à¸à¸´à¸ˆ ทำให้สมมติà¸à¸²à¸™à¸”าวเคราะห์มรณะที่ไม่น่าอยู่อย่างยิ่งà¸à¹‡à¹„ด้มาเป็นที่ยอมรับอย่างà¹à¸žà¸£à¹ˆà¸«à¸¥à¸²à¸¢[240] + +ข้อมูลจาà¸à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹‚ดยยานมาริเนอร์ 9 à¹à¸¥à¸°à¹„วà¸à¸´à¸‡à¹„ด้นำมาใช้สร้างà¹à¸œà¸™à¸—ี่ดาวอังคารที่ดียิ่งขึ้น à¹à¸¥à¸°à¸¢à¸´à¹ˆà¸‡à¸”ียิ่งขึ้นอย่างà¸à¹‰à¸²à¸§à¸à¸£à¸°à¹‚ดดด้วยปà¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¹‚ดยมาร์สโà¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œà¸‹à¸¶à¹ˆà¸‡à¸ªà¹ˆà¸‡à¸‚ึ้นในปี 1996 (พ.ศ. 2539) à¹à¸¥à¸°à¸”ำเนินงานต่อเนื่องจนà¸à¸£à¸°à¸—ั่งปลายปี 2006 (พ.ศ. 2549) ทำให้ได้à¹à¸œà¸™à¸—ี่à¹à¸ªà¸”งภูมิประเทศดาวอังคารที่ละเอียดลออครบถ้วนสมบูรณ์à¹à¸¡à¹‰à¸à¸£à¸°à¸—ั่งสนามà¹à¸¡à¹ˆà¹€à¸«à¸¥à¹‡à¸à¹à¸¥à¸°à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸à¸šà¸™à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸à¹‡à¹€à¸›à¹‡à¸™à¸—ี่รับทราบ[241] à¹à¸œà¸™à¸—ี่เหล่านี้สามารถเข้าถึงได้ทางออนไลน์ ตัวอย่างเช่น à¸à¸¹à¹€à¸à¸´à¸¥à¸¡à¸²à¸£à¹Œà¸ª สำหรับมาร์สรีคอนเนสเซนซ์ออร์บิเตอร์ à¹à¸¥à¸°à¸¡à¸²à¸£à¹Œà¸ªà¹€à¸­à¹‡à¸à¸‹à¹Œà¹€à¸žà¸£à¸ª ยังทำà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸•à¹ˆà¸­à¹€à¸™à¸·à¹ˆà¸­à¸‡à¸”้วยเครื่องไม้เครื่องมือใหม่ ๆ à¹à¸¥à¸°à¸Šà¹ˆà¸§à¸¢à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¸›à¸à¸´à¸šà¸±à¸•à¸´à¸à¸²à¸£à¸¥à¸‡à¸ˆà¸­à¸” นาซาได้เปิดให้เข้าใช้เครื่องมือทางออนไลน์คือ มาร์สเทร็ค ซึ่งให้ภาพปราà¸à¸à¸‚องดาวอังคารจาà¸à¸‚้อมูลà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸•à¸¥à¸­à¸” 50 ปี à¹à¸¥à¸° เอ็à¸à¸‹à¹Œà¸žà¸µà¹€à¸£à¸µà¸¢à¸™à¸‹à¹Œà¸„ิวริออซิตี ซึ่งให้ภาพจำลองà¸à¸²à¸£à¸—่องไปบนดาวอังคารà¹à¸šà¸šà¸ªà¸²à¸¡à¸¡à¸´à¸•à¸´à¸žà¸£à¹‰à¸­à¸¡à¸à¸±à¸šà¸¢à¸²à¸™à¸„ิวริออซิตี[242] +ในวัฒนธรรม[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดาวอังคารในวัฒนธรรม à¹à¸¥à¸° ดาวอังคารในบันเทิงคดี +Mars symbol.svg + +ดาวอังคารทางสาà¸à¸¥à¸™à¸´à¸¢à¸¡à¹„ด้ชื่อตามเทพเจ้าà¹à¸«à¹ˆà¸‡à¸ªà¸‡à¸„รามของโรมัน ในต่างวัฒนธรรม ดาวอังคารเป็นตัวà¹à¸—นของความเข้มà¹à¸‚็ง ความเป็นชาย à¹à¸¥à¸°à¸„วามเยาว์วัย มีสัà¸à¸¥à¸±à¸à¸©à¸“์เป็นรูปวงà¸à¸¥à¸¡à¸—ี่มีลูà¸à¸¨à¸£à¸Šà¸µà¹‰à¸­à¸­à¸à¸¡à¸²à¸ˆà¸²à¸à¸”้านขวาบน ซึ่งยังใช้เป็นสัà¸à¸¥à¸±à¸à¸©à¸“์à¹à¸—นเพศชายอีà¸à¸”้วย + +จà¸à¸à¸„วามล้มเหลวหลายต่อหลายครั้งของยาน-โครงà¸à¸²à¸£à¸ªà¸³à¸£à¸§à¸ˆà¸”าวอังคาร เป็นผลให้à¸à¸¥à¸¸à¹ˆà¸¡à¸§à¸±à¸’นธรรมนอà¸à¸à¸£à¸°à¹à¸ªà¸™à¸³à¹„ปเยาะเย้ยเสียดสีโดยà¸à¸¥à¹ˆà¸²à¸§à¹‚ทษตำหนิติเตียนว่าความล้มเหลวต่าง ๆ เป็นเพราะ "สามเหลี่ยมเบอร์มิวดา" ของโลà¸-ดาวอังคาร "คำสาปเทพอังคาร" หรือไม่à¸à¹‡ "ผีปอบมหาดาราจัà¸à¸£" ที่ได้เขมือบเอายานสำรวจดาวอังคารไป[243] +"ชาวดาวอังคาร" ผู้ทรงปัà¸à¸à¸²[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดาวอังคารในบันเทิงคดี + +ความคิดตามสมัยนิยมที่ว่าดาวอังคารเต็มไปด้วยชาวดาวอังคารผู้ทรงปัà¸à¸à¸²à¹€à¸‰à¸¥à¸µà¸¢à¸§à¸‰à¸¥à¸²à¸”ลงหลัà¸à¸›à¸±à¸à¸à¸²à¸™à¸­à¸¢à¸¹à¹ˆà¸­à¸²à¸¨à¸±à¸¢ ได้ปะทุขึ้นในช่วงปลายคริสต์ศตวรรษที่ 19 à¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¸žà¸š "คานาลี" ของสเà¸à¸µà¸¢à¸›à¸›à¸²à¹€à¸£à¸¥à¸¥à¸µà¹€à¸¡à¸·à¹ˆà¸­à¸›à¸£à¸°à¸ªà¸²à¸™à¹€à¸‚้าà¸à¸±à¸šà¸«à¸™à¸±à¸‡à¸ªà¸·à¸­à¸‚องเพอร์ซิวัล โลเวลล์ในประเด็นดังà¸à¸¥à¹ˆà¸²à¸§ ได้ผลัà¸à¸”ันà¹à¸™à¸§à¸„ิดมาตรà¸à¸²à¸™à¹€à¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸”าวอังคารว่าเป็นดาวเคราะห์ที่à¹à¸«à¹‰à¸‡à¹à¸¥à¹‰à¸‡ หนาวเย็น ใà¸à¸¥à¹‰à¸”ับสูภร่วมไปà¸à¸±à¸šà¸à¸²à¸£à¸¡à¸µà¸­à¸²à¸£à¸¢à¸˜à¸£à¸£à¸¡à¹‚บราณที่à¸à¹ˆà¸­à¸ªà¸£à¹‰à¸²à¸‡à¸‡à¸²à¸™à¸Šà¸¥à¸›à¸£à¸°à¸—านมาà¸à¸¡à¸²à¸¢à¹€à¸­à¸²à¹„ว้[244] +โฆษณาสบู่ในปี 1893 (พ.ศ. 2436) บนมโนคตินิยมว่าดาวอังคารมีคนอยู่อาศัย + +ด้วยหลายà¸à¸²à¸£à¸ªà¸±à¸‡à¹€à¸à¸•à¹à¸¥à¸°à¸–้อยà¹à¸–ลงโดยบุคคลผู้มีความโดดเด่นในสังคมได้ทำให้เà¸à¸´à¸”สิ่งที่เรียà¸à¸§à¹ˆà¸² "โรคคลั่งดาวอังคาร"[245] ในปี 1899 (พ.ศ. 2442) ขณะà¸à¸³à¸¥à¸±à¸‡à¸•à¸£à¸§à¸ˆà¸ªà¸­à¸šà¸„ลื่นวิทยุในบรรยาà¸à¸²à¸¨à¸”้วยเครื่องรับสัà¸à¸à¸²à¸“ของเขาในห้องทดลองโคโลราโดสปริงส์ นิโคลา เทสลา นัà¸à¸›à¸£à¸°à¸”ิษà¸à¹Œ ได้สังเà¸à¸•à¸žà¸šà¸ªà¸±à¸à¸à¸²à¸“ซ้ำ ๆ เขาสันนิษà¸à¸²à¸™à¹ƒà¸™à¸ à¸²à¸¢à¸«à¸¥à¸±à¸‡à¸§à¹ˆà¸²à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¸à¸²à¸£à¸•à¸´à¸”ต่อสื่อสารทางวิทยุมาจาà¸à¸”าวเคราะห์ดวงอื่น ซึ่งเป็นไปได้ว่าคือดาวอังคาร บทสัมภาษณ์ในปี 1901 (พ.ศ. 2444) เทสลาà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸²: + + มันเป็นบางครั้งภายหลังจาà¸à¸„วามคิดที่ได้ผุดวาบขึ้นมาในใจของผม à¸à¸²à¸£à¸£à¸šà¸à¸§à¸™à¸—ี่ผมสังเà¸à¸•à¸žà¸šà¸™à¸±à¹ˆà¸™à¸­à¸²à¸ˆà¹€à¸›à¹‡à¸™à¹„ด้ว่าคือà¸à¸²à¸£à¸„วบคุมทางปัà¸à¸à¸² à¹à¸¡à¹‰à¸§à¹ˆà¸²à¸œà¸¡à¸ˆà¸°à¹„ม่สามารถไขรหัสความหมายเหล่านั้นได้ มันเป็นไปไม่ได้เลยสำหรับผมที่จะคิดว่าสิ่งเหล่านั้นทั้งหมดเป็นเพียงอุบัติเหตุ ความรู้สึà¸à¸—ี่ทวีขึ้นอย่างมั่นคงในตัวผมà¸à¹‡à¸„ือผมเป็นบุคคลà¹à¸£à¸à¸—ี่ได้ยินà¸à¸²à¸£à¸›à¸à¸´à¸ªà¸±à¸™à¸–ารของดาวเคราะห์หนึ่งสู่ดาวเคราะห์อื่น[246] + +ทฤษฎีของเทสลาได้รับà¸à¸²à¸£à¸ªà¸™à¸±à¸šà¸ªà¸™à¸¸à¸™à¹‚ดยลอร์ดเคลวิน ผู้ซึ่งไปเยือนสหรัà¸à¸­à¹€à¸¡à¸£à¸´à¸à¸²à¹ƒà¸™à¸›à¸µ 1902 (พ.ศ. 2445) มีรายงานถึงคำพูดของเขาว่าเขาคิดว่าเทสลาจับสัà¸à¸à¸²à¸“ของชาวดาวอังคารที่ส่งมายังสหรัà¸à¸­à¹€à¸¡à¸£à¸´à¸à¸²à¹„ว้ได้[247] เคลวินปà¸à¸´à¹€à¸ªà¸˜ "อย่างหนัà¸à¹à¸™à¹ˆà¸™" ในรายงานฉบับนี้ไม่นานà¸à¹ˆà¸­à¸™à¸à¸²à¸£à¹€à¸”ินทางออà¸à¸ˆà¸²à¸à¸­à¹€à¸¡à¸£à¸´à¸à¸² เขาà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸² "อะไรที่ผมพูดไปจริง ๆ à¸à¹‡à¸„ือ ชนชาวดาวอังคาร ถ้าพวà¸à¹€à¸‚ามีอยู่ à¸à¹‡à¹„ม่ต้องสงสัยเลยว่าคงเห็นนิวยอร์ภเพราะไฟฟ้าจะเรืองà¹à¸ªà¸‡à¸­à¸­à¸à¸¡à¸²à¸ˆà¸™à¹€à¸«à¹‡à¸™à¹„ด้ชัด"[248] + +ในบทความของนิวยอร์à¸à¹„ทมส์ ในปี 1901 เอ็ดเวิร์ด ชาลส์ พิà¸à¹€à¸„อริง ผู้อำนวยà¸à¸²à¸£à¸«à¸­à¸”ูดาววิทยาลัยฮาร์วาร์ดà¸à¸¥à¹ˆà¸²à¸§à¸§à¹ˆà¸² พวà¸à¹€à¸‚าได้รับโทรเลขจาà¸à¸«à¸­à¸”ูดาวโลเวลล์ในรัà¸à¹à¸­à¸£à¸´à¹‚ซนาที่ดูเหมือนจะยืนยันว่าดาวอังคารได้พยายามติดต่อสื่อสารà¸à¸±à¸šà¹‚ลà¸[249] + + ในต้นเดือนธันวาคมปี 1900 (พ.ศ. 2443) เราได้รับโทรเลขจาà¸à¸«à¸­à¸”ูดาวโลเวลล์ในà¹à¸­à¸£à¸´à¹‚ซนาว่าเห็นลำของà¹à¸ªà¸‡à¸‰à¸²à¸¢à¸ªà¹ˆà¸‡à¸­à¸­à¸à¸ˆà¸²à¸à¸”าวอังคาร (หอดูดาวโลเวลล์มีความชำนาà¸à¹€à¸›à¹‡à¸™à¸žà¸´à¹€à¸¨à¸©à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸”าวอังคาร) เป็นเวลาเจ็ดสิบนาที ผมส่งต่อข้อเท็จจริงนี้ไปยังยุโรปà¹à¸¥à¸°à¸ªà¹ˆà¸‡à¸ªà¸³à¹€à¸™à¸²à¸ˆà¸±à¸”รูปà¹à¸šà¸šà¹ƒà¸«à¸¡à¹ˆà¸­à¸µà¸à¸«à¸¥à¸²à¸¢à¸Šà¸¸à¸”ไปทั่วประเทศ ผู้สังเà¸à¸•à¸žà¸šà¹€à¸›à¹‡à¸™à¸šà¸¸à¸„คลที่ละเอียดถี่ถ้วน เชื่อถือได้ à¹à¸¥à¸°à¹€à¸‚าà¸à¹‡à¹„ม่มีเหตุผลอะไรที่จะสงสัยว่าà¹à¸ªà¸‡à¸™à¸±à¹ˆà¸™à¸¡à¸µà¸­à¸¢à¸¹à¹ˆà¸ˆà¸£à¸´à¸‡ มันส่งมาจาà¸à¸ˆà¸¸à¸”ทางภูมิศาสตร์ที่รู้จัà¸à¸à¸±à¸™à¸”ีบนดาวอังคาร นั่นà¹à¸«à¸¥à¸°à¸„ือทั้งหมด ตอนนี้เรื่องได้ไปทั่วโลà¸à¹à¸¥à¹‰à¸§ ในยุโรปà¸à¹‡à¸¡à¸µà¸à¸²à¸£à¸à¸¥à¹ˆà¸²à¸§à¸à¸±à¸™à¸§à¹ˆà¸²à¸‰à¸±à¸™à¸à¹‡à¸¡à¸µà¸à¸²à¸£à¸•à¸´à¸”ต่อสื่อสารà¸à¸±à¸šà¸”าวอังคาร à¹à¸¥à¸°à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸žà¸´à¸ªà¸”ารเà¸à¸´à¸™à¸ˆà¸£à¸´à¸‡à¸ªà¸²à¸£à¸žà¸±à¸”อย่างà¸à¹‡à¸žà¸¸à¹ˆà¸‡à¸žà¸£à¸§à¸” ไม่ว่าà¹à¸ªà¸‡à¸™à¸±à¹ˆà¸™à¸ˆà¸°à¹€à¸›à¹‡à¸™à¸­à¸°à¹„ร พวà¸à¹€à¸£à¸²à¹„ม่มีทางล่วงรู้ ไม่ว่านั่นจะทรงปัà¸à¸à¸²à¸«à¸£à¸·à¸­à¹„ม่ ใครà¸à¹‡à¸•à¸­à¸šà¹„ม่ได้ มันเป็นเรื่องที่อธิบายไม่ได้โดยà¹à¸—้[249] + +ต่อมาภายหลังพิà¸à¹€à¸„อริงได้เสนอให้มีà¸à¸²à¸£à¸à¹ˆà¸­à¸ªà¸£à¹‰à¸²à¸‡à¸Šà¸¸à¸”à¸à¸£à¸°à¸ˆà¸à¹€à¸‡à¸²à¸ˆà¸³à¸™à¸§à¸™à¸¡à¸²à¸à¹ƒà¸™à¸£à¸±à¸à¹€à¸—à¸à¸‹à¸±à¸ªà¹‚ดยมุ่งหมายเพื่อส่งสัà¸à¸à¸²à¸“ถึงชาวดาวอังคาร[250] + +ในทศวรรษที่ผ่านมา à¹à¸œà¸™à¸—ี่พื้นผิวดาวอังคารความละเอียดสูงได้สำเร็จสมบูรณ์โดยมาร์สโà¸à¸¥à¸šà¸­à¸¥à¹€à¸‹à¸­à¸£à¹Œà¹€à¸§à¹€à¸¢à¸­à¸£à¹Œ เปิดเผยให้เห็นว่าไม่มีสิ่งประดิษà¸à¹Œà¹à¸›à¸¥à¸à¸›à¸¥à¸­à¸¡à¹ƒà¸” ๆ เลยที่à¹à¸ªà¸”งว่ามีสิ่งมีชีวิตที่ "ทรงปัà¸à¸à¸²" อยู่อาศัย à¹à¸•à¹ˆà¸à¸²à¸£à¸™à¸¶à¸à¸à¸±à¸™à¹ƒà¸™à¹à¸šà¸šà¸§à¸´à¸—ยาศาสตร์เทียมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸—รงปัà¸à¸à¸²à¸šà¸™à¸”าวอังคารยังดำเนินต่อไปจาà¸à¹€à¸«à¸¥à¹ˆà¸²à¸™à¸±à¸à¸§à¸´à¸ˆà¸²à¸£à¸“์ เช่น ริชาร์ด ซี. ฮอà¸à¹à¸¥à¸™à¸”์ à¸à¸²à¸£à¹‚ต้à¹à¸¢à¹‰à¸‡à¹€à¸£à¸·à¹ˆà¸­à¸‡ คานาลี ดั้งเดิม à¸à¸²à¸£à¸„าดà¸à¸±à¸™à¸šà¸²à¸‡à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸§à¸²à¸‡à¸­à¸¢à¸¹à¹ˆà¸šà¸™à¸¥à¸±à¸à¸©à¸“ะภูมิประเทศเล็ภๆ ที่เห็นรายละเอียดไม่ชัดà¹à¸•à¹ˆà¸™à¸¶à¸à¸„ิดเอาผ่านภาพที่ได้จาà¸à¸¢à¸²à¸™à¸­à¸§à¸à¸²à¸¨ อย่างเช่น 'พีระมิด' à¹à¸¥à¸° 'ใบหน้าบนดาวอังคาร' นัà¸à¸”าราศาสตร์ดาวเคราะห์ คาร์ล เซà¹à¸à¸™ เขียนไว้ว่า:: + + ดาวอังคารà¸à¸¥à¸²à¸¢à¸¡à¸²à¹€à¸›à¹‡à¸™à¸ªà¸¡à¸£à¸ à¸¹à¸¡à¸´à¹à¸«à¹ˆà¸‡à¹€à¸—พนิยายชนิดหนึ่งที่พวà¸à¹€à¸£à¸²à¸Šà¸²à¸§à¹‚ลà¸à¹„ด้ฉายออà¸à¸¡à¸²à¸‹à¸¶à¹ˆà¸‡à¸„วามหวังà¹à¸¥à¸°à¸„วามà¸à¸¥à¸±à¸§[234] + +ภาพประà¸à¸­à¸šà¸¡à¸²à¸£à¹Œà¹€à¸Šà¸µà¸¢à¸™à¸ªà¸²à¸¡à¸‚าจาà¸à¸«à¸™à¸±à¸‡à¸ªà¸·à¸­à¹€à¸”อะวอร์ออฟเดอะเวิลด์ส ของ เอช. จี. เวลส์ ฉบับà¸à¸£à¸±à¹ˆà¸‡à¹€à¸¨à¸ª ปี 1906 (พ.ศ. 2449) + +à¸à¸²à¸£à¸žà¸£à¸£à¸“นาเรื่องดาวอังคารในนิยายได้รับà¸à¸²à¸£à¸à¸£à¸°à¸•à¸¸à¹‰à¸™à¹€à¸ªà¸£à¸´à¸¡à¸”้วยโทนสีà¹à¸”งเร้าอารมณ์ ผนวà¸à¸à¸±à¸šà¸à¸²à¸£à¸„าดเดาตามà¹à¸šà¸šà¸§à¸´à¸—ยาศาสตร์ในสมัยคริสต์ศตวรรษที่สิบเà¸à¹‰à¸²à¸§à¹ˆà¸²à¸ à¸²à¸§à¸°à¸à¸²à¸£à¸“์ต่าง ๆ บนพื้นผิวดาวจะต้องเà¸à¸·à¹‰à¸­à¸«à¸™à¸¸à¸™à¹„ม่เฉพาะชีวิตเท่านั้นà¹à¸•à¹ˆà¸¢à¸±à¸‡à¹€à¸›à¹‡à¸™à¸ªà¸´à¹ˆà¸‡à¸¡à¸µà¸Šà¸µà¸§à¸´à¸•à¸—รงปัà¸à¸à¸²à¸­à¸µà¸à¸”้วย[251] นำไปสู่à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸ªà¸£à¸£à¸„์งานในà¸à¸²à¸™à¸šà¸—ดำเนินเรื่องของนิยายวิทยาศาสตร์จำนวนมาภหนึ่งในนั้นคือเรื่อง เดอะวอร์ออฟเดอะเวิลด์ส ของ เอช. จี. เวลส์ ซึ่งตีพิมพ์ในปี 1898 (พ.ศ. 2441) มีเนื้อหาว่าชาวดาวอังคารพยายามหลบหนีออà¸à¸ˆà¸²à¸à¸”าวเคราะห์ใà¸à¸¥à¹‰à¸•à¸²à¸¢à¸‚องพวà¸à¹€à¸‚าโดยà¸à¸²à¸£à¸¡à¸²à¸£à¸¸à¸à¸£à¸²à¸™à¹‚ลภต่อมาภายหลังได้มีà¸à¸²à¸£à¸—ำเดอะวอร์ออฟเดอะเวิลด์ส ฉบับวิทยุในอเมริà¸à¸² à¸à¸£à¸°à¸ˆà¸²à¸¢à¹€à¸ªà¸µà¸¢à¸‡à¹€à¸¡à¸·à¹ˆà¸­à¸§à¸±à¸™à¸—ี่ 30 ตุลาคม 1938 (พ.ศ. 2481) โดยออร์สัน เวลส์ซึ่งà¹à¸ªà¸”งในรูปà¸à¸²à¸£à¸£à¸²à¸¢à¸‡à¸²à¸™à¸‚่าวà¹à¸šà¸šà¸ªà¸” à¹à¸¥à¸°à¹€à¸›à¹‡à¸™à¸—ี่ลือà¸à¸£à¸°à¸‰à¹ˆà¸­à¸™à¸‚ึ้นมาทันทีเพราะไปทำให้สาธารณชนเà¸à¸´à¸”à¸à¸²à¸£à¸•à¸·à¹ˆà¸™à¸•à¸£à¸°à¸«à¸™à¸à¹€à¸¡à¸·à¹ˆà¸­à¸œà¸¹à¹‰à¸Ÿà¸±à¸‡à¸ˆà¸³à¸™à¸§à¸™à¸¡à¸²à¸à¹€à¸‚้าใจผิดไปว่าสิ่งที่พวà¸à¹€à¸‚าได้ยินเป็นเรื่องจริง[252] + +งานที่มีอิทธิพลประà¸à¸­à¸šà¸”้วย เดอะมาร์เชียนครอนิเคิลส์ ของ เรย์ à¹à¸šà¸£à¸”บูรี ซึ่งมีเนื้อหาว่านัà¸à¸ªà¸³à¸£à¸§à¸ˆà¸¡à¸™à¸¸à¸©à¸¢à¹Œà¹„ด้ทำลายอารยธรรมชาวดาวอังคารโดยบังเอิภนิยายชุด บาร์ซูม ของเอ็ดà¸à¸²à¸£à¹Œ ไรซ์ เบอร์โรห์ นวนิยายเรื่องเอาท์ออฟเดอะไซเลนต์à¹à¸žà¸¥à¹€à¸™à¹‡à¸• ของซี. เอส. ลิวอิส ในปี 1938[253] à¹à¸¥à¸°à¸­à¸µà¸à¸«à¸¥à¸²à¸¢à¸Šà¸´à¹‰à¸™à¸‡à¸²à¸™à¸‚องโรเบิร์ต เอ. ไฮน์ไลน์à¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸Šà¹ˆà¸§à¸‡à¸à¸¥à¸²à¸‡à¸„ริสต์ทศวรรษหà¸à¸ªà¸´à¸š[254] + +โจนาธาน สวิฟท์ได้มีà¸à¸²à¸£à¸­à¹‰à¸²à¸‡à¸­à¸´à¸‡à¸–ึงดวงจันทร์บริวารของดาวอังคารซึ่งเป็นเวลาà¸à¹ˆà¸­à¸™à¸«à¸™à¹‰à¸²à¸à¸²à¸£à¸„้นพบจริงโดยเอเสฟ ฮอลล์à¸à¸§à¹ˆà¸² 150 ปี โดยบรรยายรายละเอียดลัà¸à¸©à¸“ะวงโคจรของดาวเหล่านั้นได้ใà¸à¸¥à¹‰à¹€à¸„ียงเป็นเหตุเป็นผลในบทที่ 19 ในนวนิยายของเขาเรื่อง à¸à¸±à¸¥à¸¥à¸´à¹€à¸§à¸­à¸£à¹Œà¹à¸—รฟเวลส์[255] + +มาร์วินเดอะมาร์เชียน เป็นตัวà¸à¸²à¸£à¹Œà¸•à¸¹à¸™à¸¥à¸±à¸à¸©à¸“ะชาวดาวอังคารที่เฉลียวฉลาด เริ่มปราà¸à¸à¹ƒà¸™à¹‚ทรทัศน์เมื่อปี 1948 (พ.ศ. 2491) ในà¸à¸²à¸™à¸°à¸•à¸±à¸§à¸¥à¸°à¸„รหนึ่งในà¸à¸²à¸£à¹Œà¸•à¸¹à¸™à¸ à¸²à¸žà¹€à¸„ลื่อนไหวเรื่องลูนีทูนส์ของวอร์เนอร์บราเธอร์ส à¹à¸¥à¸°à¸¢à¸±à¸‡à¸”ำเนินต่อมาในà¸à¸²à¸™à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¸«à¸™à¸¶à¹ˆà¸‡à¸‚องวัฒนธรรมนิยมจนปัจจุบัน[256] + +หลังจาà¸à¸¢à¸²à¸™à¸­à¸§à¸à¸²à¸¨à¸¡à¸²à¸£à¸´à¹€à¸™à¸­à¸£à¹Œà¹à¸¥à¸°à¹„วà¸à¸´à¸‡à¹„ด้ส่งภาพดาวอังคารตามสภาพที่เป็นจริงมาà¸à¸¡à¸²à¸¢à¸à¸¥à¸±à¸šà¸¡à¸² ว่าเป็นโลà¸à¸—ี่à¹à¸¥à¹‰à¸‡à¸£à¹‰à¸²à¸‡ ไร้ซึ่งชีวิตอย่างชัดà¹à¸ˆà¹‰à¸‡ à¹à¸¥à¸°à¸›à¸£à¸²à¸¨à¸ˆà¸²à¸à¸„ลองใด ๆ à¹à¸™à¸§à¸„ิดดั้งเดิมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¸”าวอังคารà¸à¹‡à¸–ูà¸à¹‚ละทิ้ง นำมาสู่สมัยนิยมà¹à¸«à¹ˆà¸‡à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸£à¸²à¸§à¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸™à¸´à¸„มอยู่อาศัยของมนุษย์บนดาวอังคารà¹à¸šà¸šà¸ªà¸­à¸”คล้องเที่ยงตรงตามจริง เรื่องที่เป็นที่รู้จัà¸à¸à¸±à¸™à¸”ีที่สุดเรื่องหนึ่งในลัà¸à¸©à¸“ะนี้คือ มาร์สไตรโลจี ของคิม สà¹à¸•à¸™à¸¥à¸µà¸¢à¹Œ โรบินสัน อย่างไรà¸à¹‡à¸•à¸²à¸¡ à¸à¸²à¸£à¸„าดเดาà¹à¸šà¸šà¸§à¸´à¸—ยาศาสตร์เทียมเà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸šà¹ƒà¸šà¸«à¸™à¹‰à¸²à¸šà¸™à¸”าวอังคารตลอดจนจุดลึà¸à¸¥à¸±à¸šà¸™à¹ˆà¸²à¸žà¸´à¸¨à¸§à¸‡à¸­à¸·à¹ˆà¸™ ๆ ซึ่งยานสำรวจอวà¸à¸²à¸¨à¸ˆà¸±à¸šà¸ à¸²à¸žà¹„ด้ว่าเป็นร่องรอยของอารยธรรมโบราณ ยังเป็นà¹à¸™à¸§à¸—างยอดนิยมในบันเทิงคดีà¹à¸™à¸§à¸§à¸´à¸—ยาศาสตร์มาอย่างต่อเนื่อง โดยเฉพาะอย่างยิ่งในภาพยนตร์[257] +ดาวบริวาร[à¹à¸à¹‰] +ดูบทความหลัà¸à¸—ี่: ดาวบริวารของดาวอังคาร, โฟบอส à¹à¸¥à¸° ดีมอส +ภาพ ไฮไรส์ ปรับระดับสีของโฟบอสà¹à¸ªà¸”งชุดร่องที่ขนานà¸à¸±à¸™à¹€à¸›à¹‡à¸™à¸ªà¹ˆà¸§à¸™à¹ƒà¸«à¸à¹ˆ à¹à¸¥à¸°à¹‚ซ่หลุมอุà¸à¸à¸²à¸šà¸²à¸•à¸à¸±à¸šà¸«à¸¥à¸¸à¸¡à¸ªà¸•à¸´à¸à¸™à¸µà¸¢à¹Œà¸—างด้านขวา +ภาพไฮไรส์ ปรับระดับสีของดีมอส (ไม่ตามสัดส่วนà¸à¸±à¸šà¸£à¸¹à¸›à¸šà¸™) à¹à¸ªà¸”งผืนเรโà¸à¸¥à¸´à¸˜à¸£à¸²à¸šà¹€à¸£à¸µà¸¢à¸šà¸›à¸à¸„ลุมดาว + +ดาวอังคารมีดาวบริวารค่อนข้างเล็à¸à¸ªà¸­à¸‡à¸”วง ได้à¹à¸à¹ˆ โฟบอส (เส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 22 à¸à¸´à¹‚ลเมตร (14 ไมล์)) à¹à¸¥à¸° ดีมอส (เส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸›à¸£à¸°à¸¡à¸²à¸“ 12 à¸à¸´à¹‚ลเมตร (7.5 ไมล์)) โดยมีวงโคจรใà¸à¸¥à¹‰à¸à¸±à¸šà¸”าวเคราะห์à¹à¸¡à¹ˆ ทฤษฎีที่อธิบายว่าทั้งคู่เป็นดาวเคราะห์น้อยที่ถูà¸à¸ˆà¸±à¸šà¹€à¸­à¸²à¹„ว้เป็นที่นิยมมายาวนาน à¹à¸•à¹ˆà¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸³à¹€à¸™à¸´à¸”ที่มานั้นยังคลุมเครือ[258] ดาวบริวารทั้งสองถูà¸à¸„้นพบในปี 1877 (พ.ศ. 2420) โดยเอเสฟ ฮอลล์ ตั้งชื่อตามโฟบอส (ตระหนà¸/à¸à¸¥à¸±à¸§) à¹à¸¥à¸°à¸”ีมอส (สยอง/น่าขนลุà¸) ซึ่งเป็นเทพในตำนานà¸à¸£à¸µà¸ ร่วมไปà¸à¸±à¸šà¹€à¸—พà¹à¸­à¸£à¸µà¸ª เทพเจ้าà¹à¸«à¹ˆà¸‡à¸ªà¸‡à¸„รามบิดาของพวà¸à¹€à¸‚า ชื่อดาวอังคารว่า "มาร์ส" นั้นคือชื่อเทพà¹à¸­à¸£à¸µà¸ªà¸•à¸²à¸¡à¹à¸šà¸šà¹‚รมัน[259][260] ในà¸à¸£à¸µà¸à¸›à¸±à¸ˆà¸ˆà¸¸à¸šà¸±à¸™ ดาวอังคารยังคงใช้ชื่อตามอย่างโบราณว่า Ares (Aris: ΆÏης)[261] + +จาà¸à¸žà¸·à¹‰à¸™à¸œà¸´à¸§à¸”าวอังคาร à¸à¸²à¸£à¹€à¸„ลื่อนที่ของโฟบอสà¹à¸¥à¸°à¸”ีมอสจะปราà¸à¸à¹ƒà¸«à¹‰à¹€à¸«à¹‡à¸™à¹à¸•à¸à¸•à¹ˆà¸²à¸‡à¸­à¸­à¸à¹„ปจาà¸à¸”วงจันทร์ โฟบอสจะขึ้นทางทิศตะวันตภตà¸à¸—างทิศตะวันออภà¹à¸¥à¸°à¸à¸¥à¸±à¸šà¸¡à¸²à¸‚ึ้นอีà¸à¸„รั้งในเวลาเพียง 11 ชั่วโมง ส่วนดีมอสซึ่งอยู่นอà¸à¸§à¸‡à¹‚คจรพ้องคาบพอดี ระยะคาบà¸à¸²à¸£à¹‚คจรของดาวจึงไม่ตรงพอดีà¸à¸±à¸šà¸„าบà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸­à¸šà¸•à¸±à¸§à¹€à¸­à¸‡à¸‚องดาวเคราะห์à¹à¸¡à¹ˆ ดาวจะไม่ลอยค้างฟ้าในตำà¹à¸«à¸™à¹ˆà¸‡à¹€à¸”ิมà¹à¸•à¹ˆà¸ˆà¸°à¸‚ึ้นตามปà¸à¸•à¸´à¸—างทิศตะวันออà¸à¸­à¸¢à¹ˆà¸²à¸‡à¸Šà¹‰à¸² ๆ à¹à¸¡à¹‰à¸”ีมอสจะมีคาบà¸à¸²à¸£à¹‚คจรราว 30 ชั่วโมง à¹à¸•à¹ˆà¹ƒà¸Šà¹‰à¹€à¸§à¸¥à¸²à¸–ึง 2.7 วันระหว่างà¸à¸²à¸£à¸‚ึ้นจนตà¸à¸¥à¸±à¸šà¸Ÿà¹‰à¸²à¹„ปสำหรับผู้สังเà¸à¸•à¸—ี่ศูนย์สูตร ซึ่งà¸à¹‡à¸ˆà¸°à¸¥à¸±à¸šà¹„ปอย่างช้า ๆ คล้อยหลังà¸à¸²à¸£à¸«à¸¡à¸¸à¸™à¸£à¸­à¸šà¸•à¸±à¸§à¹€à¸­à¸‡à¸‚องดาวอังคาร[262] +วงโคจรของโฟบอสà¹à¸¥à¸°à¸”ีมอส (ตามสัดส่วน) + +เนื่องจาà¸à¸§à¸‡à¹‚คจรของโฟบอสต่ำà¸à¸§à¹ˆà¸²à¸£à¸°à¸”ับความสูงพ้องคาบ à¹à¸£à¸‡à¹„ทดัลจาà¸à¸”าวอังคารจึงดึงวงโคจรของดาวให้ต่ำลงไปเรื่อย ๆ ทีละน้อย อีà¸à¸›à¸£à¸°à¸¡à¸²à¸“ 50 ล้านปีข้างหน้า เป็นไปได้ว่าโฟบอสอาจพุ่งเข้าชนà¸à¸±à¸šà¸”าวอังคารหรือไม่à¸à¹‡à¹à¸•à¸à¸ªà¸¥à¸²à¸¢à¸­à¸­à¸à¸à¸¥à¸²à¸¢à¹€à¸›à¹‡à¸™à¹‚ครงสร้างวงà¹à¸«à¸§à¸™à¸£à¸­à¸šà¸”าวเคราะห์[262] + +à¸à¸³à¹€à¸™à¸´à¸”ของดาวบริวารทั้งสองนั้นยังไม่เป็นที่เข้าใจดีนัภà¸à¸²à¸£à¸¡à¸µà¸­à¸±à¸•à¸£à¸²à¸ªà¹ˆà¸§à¸™à¸ªà¸°à¸—้อนต่ำà¹à¸¥à¸°à¸¡à¸µà¸­à¸‡à¸„์ประà¸à¸­à¸šà¹à¸šà¸šà¸«à¸´à¸™à¸„อนไดรต์à¸à¸¥à¸¸à¹ˆà¸¡à¸„าร์บอเนเชียสทำให้มีความคล้ายคลึงà¸à¸±à¸šà¸”าวเคราะห์น้อยซึ่งช่วยสนับสนุนทฤษฎีà¸à¸²à¸£à¸ˆà¸±à¸šà¸¢à¸¶à¸” วงโคจรที่ไม่เสถียรของโฟบอสเหมือนจะชี้ให้เห็นว่าเป็นà¸à¸²à¸£à¸ˆà¸±à¸šà¹€à¸­à¸²à¹„ว้ที่ค่อนข้างใหม่ à¹à¸•à¹ˆà¸—ั้งคู่มีวงโคจรที่à¸à¸¥à¸¡à¹ƒà¸à¸¥à¹‰à¸à¸±à¸šà¸¨à¸¹à¸™à¸¢à¹Œà¸ªà¸¹à¸•à¸£à¸‹à¸¶à¹ˆà¸‡à¸ˆà¸±à¸”ว่าไม่ปà¸à¸•à¸´à¸ªà¸³à¸«à¸£à¸±à¸šà¸§à¸±à¸•à¸–ุที่ถูà¸à¸ˆà¸±à¸šà¹„ว้ได้à¹à¸¥à¸°à¸¢à¸±à¸‡à¸•à¹‰à¸­à¸‡à¸à¸²à¸£à¸žà¸¥à¸§à¸±à¸•à¸à¸²à¸£à¸¢à¸¶à¸”จับที่สลับซับซ้อน à¸à¸²à¸£à¸ˆà¸±à¸šà¸•à¸±à¸§à¸žà¸­à¸à¸žà¸¹à¸™à¸‚ึ้นตั้งà¹à¸•à¹ˆà¸Šà¹ˆà¸§à¸‡à¸•à¹‰à¸™à¸‚องประวัติศาสตร์ดาวอังคารยังเป็นà¸à¸£à¸“ีที่ถือว่าเป็นไปได้ ถ้าหาà¸à¸§à¹ˆà¸²à¸ˆà¸°à¹„ม่นับรวมลัà¸à¸©à¸“ะองค์ประà¸à¸­à¸šà¸‚องทั้งคู่ที่คล้ายคลึงà¸à¸±à¸šà¸”าวเคราะห์น้อยมาà¸à¸à¸§à¹ˆà¸²à¸—ี่จะเหมือนà¸à¸±à¸šà¸”าวอังคารซึ่งยังต้องรอà¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™ + +ความเป็นไปได้ในรูปà¹à¸šà¸šà¸—ี่สามคือà¸à¸²à¸£à¸¡à¸µà¸§à¸±à¸•à¸–ุที่สามเข้ามาเà¸à¸µà¹ˆà¸¢à¸§à¸‚้องหรือเป็นชนิดหนึ่งของà¸à¸²à¸£à¹à¸•à¸à¸à¸£à¸°à¸ˆà¸²à¸¢à¸­à¸­à¸à¸¡à¸²à¸ˆà¸²à¸à¸à¸²à¸£à¸žà¸¸à¹ˆà¸‡à¸Šà¸™[263] หลัà¸à¸à¸²à¸™à¸«à¸¥à¸²à¸¢à¸›à¸£à¸°à¸à¸²à¸£à¸—ี่ได้มาค่อนข้างใหม่พบว่าโครงสร้างภายในของโฟบอสมีความพรุนสูง[264] à¹à¸¥à¸°à¸Šà¸µà¹‰à¸§à¹ˆà¸²à¸­à¸‡à¸„์ประà¸à¸­à¸šà¸ à¸²à¸¢à¹ƒà¸™à¸ªà¹ˆà¸§à¸™à¹ƒà¸«à¸à¹ˆà¹€à¸›à¹‡à¸™à¸Ÿà¸´à¸¥à¹‚ลซิลิเà¸à¸•à¹à¸¥à¸°à¹à¸£à¹ˆà¸˜à¸²à¸•à¸¸à¸­à¸·à¹ˆà¸™ ๆ ที่ทราบว่ามีบนดาวอังคาร[265] ทำให้ประเด็นà¸à¸²à¸£à¸à¸³à¹€à¸™à¸´à¸”ของโฟบอสว่ามาจาà¸à¹€à¸¨à¸©à¸§à¸±à¸•à¸–ุที่à¸à¸£à¸°à¸ˆà¸²à¸¢à¸­à¸­à¸à¸¡à¸²à¸ à¸²à¸¢à¸«à¸¥à¸±à¸‡à¸à¸²à¸£à¸–ูà¸à¸žà¸¸à¹ˆà¸‡à¸Šà¸™à¸‚องดาวอังคารà¹à¸¥à¹‰à¸§à¹„ด้มารวมà¸à¸±à¸™à¹ƒà¸™à¸§à¸‡à¹‚คจรรอบดาวà¹à¸¡à¹ˆà¸™à¸±à¹‰à¸™à¸™à¹ˆà¸²à¹€à¸Šà¸·à¹ˆà¸­à¸–ือมาà¸à¸‚ึ้น[266] คล้ายà¸à¸±à¸™à¸à¸±à¸šà¸—ฤษฎีà¸à¸£à¸°à¹à¸ªà¸«à¸¥à¸±à¸à¹€à¸£à¸·à¹ˆà¸­à¸‡à¸à¸²à¸£à¸à¸³à¹€à¸™à¸´à¸”ดวงจันทร์ของโลภอย่างไรà¸à¹‡à¸•à¸²à¸¡ ค่าสเปà¸à¸•à¸£à¸±à¸¡à¸‚องà¹à¸ªà¸‡à¸—ี่มองเห็นได้ถึงช่วงใà¸à¸¥à¹‰à¸­à¸´à¸™à¸Ÿà¸£à¸²à¹€à¸£à¸” (VNIR) ของดาวบริวารทั้งสองของดาวอังคารมีความคล้ายคลึงà¸à¸±à¸šà¸—ี่วัดได้จาà¸à¹à¸–บดาวเคราะห์น้อยด้านนอภà¹à¸¥à¸°à¸¡à¸µà¸£à¸²à¸¢à¸‡à¸²à¸™à¸§à¹ˆà¸²à¸ªà¹€à¸›à¸à¸•à¸£à¸±à¸¡à¸£à¸±à¸‡à¸ªà¸µà¸­à¸´à¸™à¸Ÿà¸£à¸²à¹€à¸£à¸”ของโฟบอสไม่สอดคล้องà¸à¸±à¸šà¸„อนไดรต์ไม่ว่าจะà¸à¸¥à¸¸à¹ˆà¸¡à¹ƒà¸”[265] + +ดาวอังคารอาจจะมีดาวบริวารอื่นนอà¸à¹€à¸«à¸™à¸·à¸­à¸ˆà¸²à¸à¸™à¸µà¹‰à¹à¸•à¹ˆà¸¡à¸µà¸‚นาดเล็à¸à¸”้วยเส้นผ่าศูนย์à¸à¸¥à¸²à¸‡à¸£à¸²à¸§ 50 - 100 เมตร (160 ถึง 330 ฟุต) à¹à¸¥à¸°à¸„าดว่ามีวงà¹à¸«à¸§à¸™à¸à¸¸à¹ˆà¸™à¸­à¸¢à¸¹à¹ˆà¸£à¸°à¸«à¸§à¹ˆà¸²à¸‡à¹‚ฟบอสà¸à¸±à¸šà¸”ีมอส[19] diff --git a/xpcom/tests/gtest/wikipedia/tr.txt b/xpcom/tests/gtest/wikipedia/tr.txt new file mode 100644 index 0000000000..7c83510231 --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/tr.txt @@ -0,0 +1,245 @@ +Latince Mars veya Arapça Merih (Türkçe: Bakırsokum[1] ya da Sakıt[2]), GüneÅŸ Sistemi'nin GüneÅŸ'ten itibâren dördüncü gezegeni. Roma mitolojisindeki savaÅŸ tanrısı Mars'a ithâfen adlandırılmıştır. Yüzeyindeki yaygın demiroksitten dolayı kızılımsı bir görünüme sahip olduÄŸu için Kızıl Gezegen de denir. + +Ä°nce bir atmosferi olan Mars gerek Ay'daki gibi meteor kraterlerini, gerekse Dünya'daki gibi volkan, vadi, çöl ve kutup bölgelerini içeren çehresiyle bir yerbenzeri gezegendir. Ayrıca dönme periyodu ve mevsim dönemleri Dünya’nınkine çok benzer. 2 adet uydusu bulunmaktadır. + +Mars’taki Olimpos Dağı (Olympus Mons) adı verilen daÄŸ GüneÅŸ Sistemi’nde bilinen en yüksek daÄŸ ve Marineris Vadisi (Valles Marineris) adı verilen kanyon en büyük kanyondur. Ayrıca Haziran 2008’de Nature dergisinde yayımlanan üç makalede açıklandığı gibi, Mars’ın kuzey yarımküresinde 10.600 km. uzunluÄŸunda ve 8.500 km. geniÅŸliÄŸindeki dev bir meteor kraterinin varlığı saptanmıştır. Bu krater, bugüne kadar keÅŸfedilmiÅŸ en büyük meteor kraterinin (Ay'ın güney kutbu kısmındaki Atkien Havzası) dört misli büyüklüğündedir.[3][4] + +Mars, Dünya hariç tutulursa, halen GüneÅŸ Sistemi’ndeki gezegenler içinde sıvı su ve yaÅŸam içermesi en muhtemel gezegen olarak görülmektedir.[5] Mars Express ve Mars Reconnaissance Orbiter keÅŸif projelerinin radar verileri gerek kutuplarda (Temmuz 2005)[6] gerekse orta bölgelerde (Kasım 2008)[7] geniÅŸ miktarlarda su buzlarının var olduÄŸunu ortaya koymuÅŸ bulunmaktadır. 31 Temmuz 2008’de Phoenix Mars Lander adlı robotik uzay gemisi Mars toprağının sığ bölgelerindeki su buzlarından örnekler almayı baÅŸarmıştır.[8] + +Günümüzde, Mars, yörüngelerine oturmuÅŸ üç uzay gemisine evsahipliÄŸi yapmaktadır: Mars Odyssey, Mars Express ve Mars Reconnaissance Orbiter. Mars, Dünya hariç tutulursa, GüneÅŸ Sistemi’ndeki herhangi bir sıradan gezegenden ibaret deÄŸildir. Yüzeyi pek çok uzay aracına evsahipliÄŸi yapmıştır. Bu uzay araçlarıyla elde edilen jeolojik veriler ÅŸunu ortaya koymuÅŸtur ki, Mars önceden su konusunda geniÅŸ bir çeÅŸitliliÄŸe sahipti; hatta geçen on yıllık süre sırasında gayzer (kaynaç) türü su fışkırma veya akıntıları meydana gelmiÅŸti.[9] NASA’nın Mars Global Surveyor projesi kapsamında sürdürülen incelemeler Mars’ın güney kutbu buz bölgesinin geri çekilmiÅŸ olduÄŸunu ortaya koymuÅŸtur.[10] Bilim insanları, 2006'da Mars yörüngesine oturtulan "Mars Reconnaissance Orbiter" (Mars Yörünge KaÅŸifi) uydusundan alınan veriler sonucu, Mars'ta sıcak aylarda tuzlu su akıntılarının oluÅŸtuÄŸunu bildirmiÅŸlerdir.[11] + +Mars’ın 1877 yılında astronom Asaph Hall tarafından keÅŸfedilen Phobos ve Deimos adları verilmiÅŸ, düzensiz biçimli iki küçük uydusu vardır. Mars Dünya’dan çıplak gözle görülebilmektedir. "Görünür kadir"i −2,9’a[12] ulaşır ki bu, çıplak gözle çoÄŸu zaman Jüpiter Mars’tan daha parlak görünmesine karşın, ancak Venüs, Ay ve Güneş’çe aşılabilen bir parlaklıktır. + +Fiziksel özellikler[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Mars’ın yarıçapı Dünya’nınkinin yaklaşık yarısı kadardır. YoÄŸunluÄŸu Dünya’nınkinden daha az olup, hacmi Dünya’nın hacminin % 15’i, kütlesi ise Dünya’nınkinin % 11’i kadardır. Mars’ın Merkür’den daha büyük ve daha ağır olmasına karşılık, Merkür ondan daha yoÄŸundur. Bu yüzden Merkürün yüzeyindeki yerçekimi Mars’ınkinden daha fazladır. Mars, boyutu, kütlesi ve yüzeyindeki yerçekimi bakımından Dünya ile Ay arasında yer alır. Mars yüzeyinin kızıl-turuncu görünümü hematit ya da pas adıyla tanınan demiroksitten (Fe2O3) kaynaklanır.[13] +Jeoloji ("arkeoloji")[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Dört "yerbenzeri gezegen"in[14] boyutlarının mukayesesi: Soldan saÄŸa doÄŸru Merkür, Venüs, Dünya ve Mars. +Mars’ın üstteki topoÄŸrafik haritasında daha ziyade volkanik platolar (kırmızı) ve çarpışma havzaları (mavi) hakim görünmektedir. +Mars Pathfinder tarafından çekilmiÅŸ Mars’ın dağınık kaya oluÅŸumlu bir yüzey fotoÄŸrafı + +Uydu gözlemleri ile Mars meteorlarının incelenmesi Mars yüzeyinin esas olarak bazalttan oluÅŸtuÄŸunu göstermektedir. Bazı kanıtlar Mars yüzeyinin bir kısmının tipik bazalttan ziyade, yeryüzündeki andezit kayalarının benzeri olabilecek zengin silisyum oluÅŸumlarından meydana geldiÄŸini göstermektedir; fakat gözlemlerdeki veriler bunların silisli cam olduÄŸu ÅŸeklinde de yorumlanabilir. Her ne kadar Mars’ın asli manyetik alanı yoksa da, gözlemler gezegen kabuÄŸunun parçalarının vaktiyle iki kutuplu bir manyetik alanın etkisinde bulunmuÅŸ olduÄŸunu göstermektedir. Minerallerde gözlemlenen bu paleomanyetizm[15] yeryüzünün okyanus diplerinde bulunan tabakalarındakilere çok benzer özelliklere sahiptir. 1999’da ortaya atılan ve 2005’te Mars Global Surveyor verileriyle yeniden gözden geçirilen bir teoriye göre bu tabakalar, Mars’ta 4 milyar yıl önce, manyetik kutuplaÅŸmanın yani manyetik alanın henüz etkin olduÄŸu dönemde mevcut olan tektonik plakaların kanıtıdır.[16] + +Gezegenin iç yapısına iliÅŸkin güncel modellere göre, gezegen, esas olarak demir ve % 14-17 civarında sülfürden oluÅŸan, yarıçapı yaklaşık 1480 km. olan bir çekirdek bölgesi içerir. Bu demir sülfür (FeS) bileÅŸiÄŸi kısmen akışkandır. Çekirdek, günümüzde etkin olmadığı görülen, gezegendeki birçok tektonik ve volkanik oluÅŸumlardan oluÅŸmuÅŸ bir silikat mantosuyla çevrilidir. Gezegenin kabuÄŸunun ortalama kalınlığı 50 km. olup, azami kalınlığı 120 km. civarındadır.[17] Dünya’nın ortalama kalınlığı 40 km. olan kabuÄŸu, her iki gezegenin boyutları gözönüne alındığında Mars’ınkine göre üç misli daha ince kalır. + +Mars’ın temel jeolojik devirleri ÅŸunlardır: + + Nuh Devri: Devre bu ad, Mars’ın güney yarımküresindeki bir bölgenin Nuh’un Toprağı (Noachis Terra) olarak adlandırılması nedeniyle verilmiÅŸtir. Mars’ın en eski yüzey oluÅŸumuna iliÅŸkin devirdir, 3,8 milyar yıl öncesi ile 3,5 milyar yıl öncesi arasındaki dönemi kapsar. Nuh Devri yüzeyleri birçok büyük çarpma kraterleriyle oyulmuÅŸ haldedir. Tharsis volkanik plato bölgesinin bu devirdeki büyük bir sıvı su baskınıyla oluÅŸtuÄŸu sanılmaktadır. + Hesperian devri: 3,5 milyar yıl öncesi ile 1,8 milyar yıl öncesi arasındaki dönemi kapsar. Bu devir, geniÅŸ lav ovalarının oluÅŸumu ile nitelenir. + Amazon Devri: 1,8 milyar yıl öncesi ile günümüze kadarki dönemi kapsar. Amazon Devri bölgeleri, meteor çarpmalarıyla açılmış kraterleri pek içermez ve tamamen deÄŸiÅŸiktir. Ãœnlü Olimpos Dağı bu dönemdeki lav akıntılarıyla oluÅŸmuÅŸtur. + +19 Åžubat 2008’de Mars’ta muhteÅŸem bir çığ meydana geldi. Mars Reconnaissance Orbiter uzay gemisinin kamerasınca filme kaydedilen görüntülerde 700 m. yükseklikteki bir uçurumun tepesinden kopan buz bloklarının ardında toz bulutları bırakarak yuvarlanışları görülüyordu.[18] Son incelemeler ilk kez 1980’lerde ortaya atılmış bir teoriyi desteklemektedir: Bu teoriye göre 4 milyar önce Mars’a Plüton gezegeni boyutlarındaki bir meteor çarpmıştır. Gezegenin kuzey kutup bölgesini kapsadığı gibi, yaklaşık % 40’ını kapsayan Borealis basin adı verilen garip havzanın bu çarpmayla oluÅŸtuÄŸu sanılmaktadır.[19][20] +Toprak[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Haziran 2008’de Phoenix uzay gemisi tarafından gönderilen veriler Mars toprağının hafifçe alkalin olduÄŸunu ve hepsi de organik maddenin geliÅŸmesi için elzem olan magnezyum, sodyum, potasyum ve klorür içerdiÄŸini ortaya koydu. Bilim insanları Mars’ın kuzey kutbuna yakın toprağın kuÅŸkonmaz gibi bitkilerin yetiÅŸtirilebileceÄŸi bir bahçe oluÅŸturulması için elveriÅŸli olduÄŸu sonucuna vardı.[21] AÄŸustos 2008’de Phoenix uzay gemisi Dünya suyu ile Mars toprağının karıştırılması gibi basit kimya deneylerine baÅŸladı ve önceden Mars toprağı konusunda ortaya atılmış birçok teoriyi doÄŸrulayan bir keÅŸifte bulundu: Mars toprağında perklorat tuzlarının izlerini keÅŸfetti. Perklorat tuzlarının varlığı Mars toprağının daha da ilginç bulunmasını saÄŸlamıştı[22] Fakat perklorat tuzlarının varlığının Mars’a taşınan Dünya toprağından, çeÅŸitli örneklerden veya aletlerden kaynaklanmış olma olasılığı da vardı; bu yüzden, kaynağın Mars toprağı olup olmadığından iyice emin olunması için bu konuda daha fazla deneyler yapılması gerekmektedir.[23] +2005 yılı Kasım ayı sonunda Mars Exploration Rover A Spirit 'in Husband Hill'in zirvesinden inerken çektiÄŸi Marstan bir panoramik fotoÄŸraf. +Hidroloji[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Cerberus Fossae adı verilen yüzey yarıkları +Opportunity adlı uzay keÅŸif aracı (astromobil) tarafından çekilmiÅŸ, Mars yüzeyinde geçmiÅŸte sıvı su bulunduÄŸunu gösteren mikroskobik kaya oluÅŸumlarının fotoÄŸrafı +10 Eylül 2005'te Mars Global Surveyor sonda aracı tarafından alınmış bu fotoÄŸraf (saÄŸda) 30 AÄŸustos 1999'daki fotoÄŸrafta (solda) mevcut olmayan su buzuna benzer beyazımsı bir çökeltinin meydana geldiÄŸini, yani geçici de olsa, yüzeyde sıvı su akışının varlığını ortaya koymaktadır.[24][25] + +1965’te Mariner-4’le gerçekleÅŸtirilen ilk Mars alçak uçuÅŸuna kadar, gezegenin yüzeyinde sıvı su olup olmadığı çok tartışılmıştı. Bu tartışma özellikle kutup bölgelerindeki periyodik olarak deÄŸiÅŸim gösteren, deniz ve kıtaları andıran açık ve koyu renkli lekelerin gözlemlenmiÅŸ olmasından kaynaklanıyordu. Koyu renkli çizgiler bazı gözlemciler tarafından uzun zaman sıvı su içeren sulama kanalları olarak yorumlanmıştı. Bu düz çizgi oluÅŸumları sonraki dönemlerde gözlemlenemediÄŸinden optik illüzyonlar olarak yorumlandı. Kısa dönemlerde alçak irtifalarda olabilecek oluÅŸumlar hariç tutulursa, günümüzdeki atmosferik basınç altında Mars yüzeyinde sıvı su mevcut olamaz, ancak geçici sıvı su akışları olabilir.[24][25][26][27] Buna karşılık özellikle iki kutup bölgesinde geniÅŸ su buzları mevcuttur.[28] Mart 2007’de NASA, güney kutbu bölgesindeki su buzlarının erimeleri halinde suların gezegenin tüm yüzeyini kaplayacağını ve oluÅŸacak bu okyanusun derinliÄŸinin 11 m. olacağının hesaplandığını açıkladı.[29] Ayrıca gezegende kutuptan 60° enlemine kadar bir buz permafrost[30] mantosu uzanır.[28] Mars’ta kalın kriyosfer[31] tabakasının altında, büyük miktarlarda, sıkışık halde tutulmuÅŸ (yüzeye çıkamayan) su rezervlerinin bulunduÄŸu sanılmaktadır. Mars Express ve Mars Reconnaissance Orbiter’dan gelen radar verileri her iki kutupta (Temmuz 2005)[6] ve orta enlemlerde (Kasım 2008)[7] büyük miktarlarda su buzlarının bulunduÄŸunu ortaya koymuÅŸtur. Phoenix Mars Lander ise 31 Temmuz 2008’de Mars toprağındaki su buzlarından örnek parçalar almayı baÅŸarmıştır.[32] + +Mars tarihinin nispeten erken bir döneminde Valles Marineris Vadisi (4000 km.) oluÅŸtuÄŸunda su kanallarının oluÅŸmasına neden olan, serbest kalmış yeraltı sularının yol açtığı büyük bir sıvı su baskınının meydana geldiÄŸi sanılmaktadır. Bu su baskının biraz daha küçüğü de daha sonra Cerberus Fossae denilen büyük yüzey yarıklarının açıldığı dönemde, yani yaklaşık 5 milyon yıl önce meydana gelmiÅŸtir ki, Cerberus Palus bölgesindeki Elysium Planitia’da halen görülebilen donmuÅŸ denizin bu olayın bir sonucu olduÄŸu sanılmaktadır.[33] Bununla birlikte bölgenin buz akıntılarını[34] andıran lav akıntıları gölcüklerinin oluÅŸabileceÄŸi bir morfolojiye de sahip olduÄŸu gözden uzak tutulmamalıdır. Kısa zaman önce Mars Global Surveyor’daki Mars Orbiter’ın yüksek çözünürlüğe sahip kamerasıyla çekilen fotoÄŸraflar Mars yüzeyindeki sıvı suyun tarihi hakkında daha ayrıntılı bilgiler saÄŸlamıştır. Ä°lginçtir ki, bu verilerde Mars’ta dev kanalların, aÄŸacın dallanmasına benzeyen aÄŸ biçimli geniÅŸ yolların bulunmasına karşın su akışlarını gösteren daha küçük ölçekli damar ve oluÅŸumlara rastlanamamıştır. Bunun üzerine hava koÅŸullarının bu küçük izleri zamanla yok etmiÅŸ olabilecekleri (erozyon) düşünüldü. Mars Global Surveyor uzay gemisiyle edinilen yüksek çözünürlüklü veriler, kraterlerde ve kanyonların duvarları boyunca yüzlerce yarık bulunduÄŸunu ortaya koymuÅŸtur. AraÅŸtırmalar bu oluÅŸumların genç yaÅŸta olduÄŸunu göstermektedir. Dikkat çeken bir yarığın altı yıl arayla çekilen iki fotoÄŸrafı karşılaÅŸtırıldığında yarıkta yeni tortul çökeltilerinin biriktiÄŸi farkedilmiÅŸtir. NASA’nın Mars KeÅŸif Programı yetkili uzmanlarından Michael Meyer bu tür renkli tortul çökelti oluÅŸumlarına ancak güçlü bir sıvı su akışının yol açabileceÄŸi görüşündedir. + +"Mars Reconnaissance Orbiter" (Mars Yörünge KaÅŸifi) uydusundan alınan veriler sonucu, Mars'ta sıcak aylarda tuzlu su akıntılarının oluÅŸtuÄŸu belirlenmiÅŸtir.[11][35] + +Ä°ster yağıştan (yaÄŸmurdan), ister yeraltı su kaynaklarından, ister baÅŸka bir kaynaktan kaynaklansın, sonuç olarak Mars’ta su mevcuttur.[36] Öte yandan söz konusu çökelti oluÅŸumlarına donmuÅŸ karbondioksidin veya gezegen yüzeyindeki toz akımlarının neden olduÄŸunu ileri süren senaryolar da ortaya atılmıştır.[37][38] Mars yüzeyinde geçmiÅŸte sıvı suyun bulunduÄŸunun bir baÅŸka kanıtı da yüzeyde saptanan minerallerden gelmektedir: Hematit, goetit gibi mineraller genellikle suyun varlığını iÅŸaret eden minerallerdir (goetit serin topraklardaki yegane demir oksittir).[39] +CoÄŸrafya[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Globo de Marte - Valles Marineris.gifGlobo de Marte - Elysium Planitia.gifGlobo de Marte - Syrtis Major.gif + +Ay’ın haritasının yapılmasında ilk çalışmalarda bulunanlardan biri olan Johann Heinrich Mädler on yıl süren gözlemlerinden sonra, 1840’ta da ilk Mars haritasını çizdi. Ä°lk areografi uzmanları olan Mädler ve kendisiyle Ay haritasının yapımında da çalışmış arkadaşı Wilhelm Beer, Mars haritasındaki iÅŸaretlemelerde, isimler vererek belirlemek yerine, sade bir ÅŸekilde, harfler kullanmayı tercih ettiler.[40] +Mars’ın ve GüneÅŸ Sistemi’nin en yüksek dağı olan, 27.000 m. yükseklikteki Olimpos Dağı'nın (Olympus Mons) Mars’ın yörüngesinden çekilmiÅŸ fotoÄŸrafı + +Mars’taki coÄŸrafi oluÅŸumlara Dünya coÄŸrafyasından veya tarihsel ve mitolojik isimler verilmiÅŸtir. Mars’ın ekvatoru doÄŸal olarak kendi çevresinde dönmesiyle belirlenmiÅŸtir, baÅŸlangıç meridyeni ise Dünya’daki Greenwich meridyeni gibi keyfi olarak, 1830’da ilk Mars haritalarının yapımı çalışmasında Mädler and Beer tarafından belirlenmiÅŸtir. 1972’de Mariner 9 uzay aracının Mars’le ilgili yeterince veri toplamasından itibaren, Sinus Meridiani’deki (Meridian Bay), sonradan Airy-0 olarak adlandırılan küçük bir krater, eski belirlemeyle uyuÅŸacak tarzda 0.0° boylamı olarak seçildi (Beer ve Mädler tarafından “a†harfi ile iÅŸaretlenen boylam). + +Mars’ta deniz olmadığından Olimpos Dağı’nın yüksekliÄŸi “ortalama çekim yüzeyi†(Ä°ng. mean gravity surface) esas alınarak hesaplanmış ve yüksekliÄŸi 27 km. olarak saptanmıştır. (Bir baÅŸka deyiÅŸle, Mars’ta irtifalar atmosfer basıncının 610,5 Pa (6.105 mbar) olduÄŸu seviye esas alınarak hesaplanır. Bu da Dünya’daki deniz seviyesinde mevcut basıncın yaklaşık ‰ 6’sıdır.)[41] +Gezegen fotoÄŸrafının tam ortasındaki devasa kanal, Valles Marineris kanyon oluÅŸumunu göstermektedir. +Mars’taki 7 maÄŸaranın giriÅŸlerinin THEMIS tarafından çekilen fotoÄŸrafı: A-Dena, B-Chloe, C-Wendy, D-Annie, E-Abby (solda) ve Nikki F-Jeanne. + +Mars topoÄŸrafyası ilginç bir ikilem göstermesiyle dikkat çeker. Kuzey yarımkürenin lav akıntılarıyla düzleÅŸmiÅŸ ovalar içermesine karşın, güney yarımküre eski çarpışmalarla çukurlar ve kraterlerle oyulmuÅŸ haldeki bir daÄŸlık arazidir. 2008’de yapılan araÅŸtırma ve incelemeler 1980’de ortaya atılmış, Mars’ın kuzey yarımküresine dört milyar yıl önce Ay’ın boyutunun %6,6’sı büyüklükteki bir cismin çarpmış olduÄŸunu ileri süren teoriyi kanıtlar görünmektedir. Bu görüş doÄŸru olduÄŸu takdirde Mars’ın kuzey yarımküresinde 10.600 km. uzunluÄŸunda ve 8.500 km. geniÅŸliÄŸinde bir krater alanının açılmış olması gerekirdi ki, bu, Avrupa, Asya ve Avustralya toprakları bütününe denk bir alandır.[42][43] Mars’ın yüzeyi Dünya’dan görünüşle, farklı albedo’su olan iki tür alana ayrılır. Kızılımsı demiroksit içeren tuz ve kumla kaplı soluk ovalar geçmiÅŸte Mars kıtaları olarak yorumlanmış ve bunlara Arabistan Ãœlkesi (Arabia Terra), Amazon Ovası (Amazonis Planitia) gibi adlar verilmiÅŸtir. Koyu renkli oluÅŸumlar ise denizler olarak yorumlanmış ve bunlara Mare Erythraeum, Mare Sirenum ve Aurorae Sinus adları verilmiÅŸtir. Dünya’dan görünüşe göre en koyu renkli coÄŸrafi oluÅŸum Syrtis Major’dur.[44] +Mars'taki Victoria Krateri'nin bir görüntüsü. + +Everest’in üç misli yüksekliÄŸindeki Olimpos Dağı birçok büyük volkan içeren daÄŸlık Tharsis bölgesindeki, yumuÅŸak eÄŸimli bir sönmüş volkandır. Mars aynı zamanda çarpma kraterlerinin gözlemlendiÄŸi bir gezegendir; yarıçapı 5 km. ve daha büyük olabilen bu krater oluÅŸumlarının toplam sayısı 43.000 olarak belirlenmiÅŸtir.[45] En büyükleri hafif bir albedo oluÅŸumuna sahip, Dünya’dan kolayca görülebilen Hellas çarpma havzasıdır (Hellas Planitia).[46] Hacmi açısından, bir kozmik cismin Dünya’ya oranla daha küçük olan Mars’a çarpma olasılığı, Dünya’ya çarpma olasılığının yarısı kadardır. Bununla birlikte Mars’ın asteroit kuÅŸağına daha yakın olması, bu kuÅŸaktan gelen cisimlerle çarpışma olasılığını çok fazla arttırmaktadır. Mars aynı zamanda kısa periyotlu (yörüngeleri Jüpiter’e uzanan) kuyruklu yıldızların çarpmalarına (veya süpürmelerine) da maruz kalmaktadır. Bununla birlikte Ay’ın yüzeyi ile kıyaslandığında, atmosferi kendisine küçük meteorlara karşı koruma saÄŸladığından Mars yüzeyinde daha az krater görülür. Bazı kraterler meteor düştüğünde yerin nemli olduÄŸunu gösteren bir morfolojiye sahiptir. + +Valles Marineris adlı ünlü büyük kanyon 4.000 km uzunluÄŸunda ve 200 km geniÅŸliÄŸinde olup, 7 km'ye varan bir derinliÄŸe sahiptir. Yani uzunluÄŸu Avrupa’nın uzunluÄŸuna eÅŸ olup, gezegenin çevresinin beÅŸte biridir. Büyüklüğünün devasa boyutlarının anlaşılması amacıyla Dünya’daki Büyük Kanyon'un boyutları göz önüne getirilebilir. (Büyük Kanyon 446 km uzunluÄŸunda ve yaklaşık 2 km derinliÄŸindedir.) Bir baÅŸka geniÅŸ kanyon olan Ma'adim Vallis 700 km uzunluÄŸunda, 20 km geniÅŸliÄŸinde ve yer yer 2 km derinliÄŸindedir. Bu kanyonun geçmiÅŸte bir sıvı su baskınıyla oluÅŸtuÄŸu sanılmaktadır.[47] 2001 Mars Odyssey robotik uzay gemisindeki kısa adı THEMIS (Thermal Emission Imaging System) olan kamera sayesinde Arsia Mons volkanının yamaçlarında 7 muhtemel maÄŸara giriÅŸi saptanmıştır.[48] Bunlar günümüzde “yedi kızkardeÅŸler†adıyla bilinmektedirler.[49] MaÄŸara giriÅŸlerinin geniÅŸliklerinin 100 m ile 252 m arasında deÄŸiÅŸtiÄŸi sanılmakta ve ışık genellikle maÄŸaraların dibine kadar giremediÄŸinden bu maÄŸaraların yeraltında sanılandan daha derin ve geniÅŸ bir halde uzandıkları düşünülmektedir. Bunlar içinden tek istisna dibi görünen Dena adlı maÄŸaradır. Mars’ın kuzey kutbu dairesine Planum Boreum ve güney kutbu dairesine Planum Australe adı verilmiÅŸtir. +Atmosfer[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars gezegeninde en bol bulunan gazlar – (Curiosity rover, Ekim 2012). +Kuzey yarımkürenin yaz döneminde Mars atmosferinde saptanan metan gazı izleri-NASA +Mars’ın yörüngeden çekilmiÅŸ, ufukta görülebilen ince atmosferi. +Mars Pathfinder tarafından çekilmiÅŸ, Mars semalarındaki buz bulutlarının fotoÄŸrafı + +. + +Mars manyetosferini 4 milyar yıl önce kaybetmiÅŸtir. Böylece GüneÅŸ rüzgârları Mars’ın iyonosfer tabakasıyla doÄŸrudan etkileÅŸime girerek atmosferi ince halde tutmaktadır. Mars Global Surveyor ve Mars Express’in her ikisi de, iyonize atmosfer parçacıklarının uzaya sürüklendiklerini saptamışlardır.[50][51] Mars atmosferi günümüzde nispeten incedir. Yüzeydeki atmosfer basıncı gezegenin en yüksek kısmında saptanan 30 Pa (0.03 kPa) ile en derin kısmında saptanan 1,155 Pa (1.155 kPa) arasında deÄŸiÅŸmektedir. Yani ortalama yüzey basıncı 600 Pa’dır (0.6 kPa) ki, bu da Dünya yüzeyinden 35 km. yükseklikte rastlanan basınca eÅŸtir. Bir baÅŸka deyiÅŸle Dünya yüzey basıncının %1’inden daha düşük bir deÄŸerdir. Mars’taki düşük yerçekiminden dolayı da atmosferinin "ölçek irtifa"sı (Ä°ng. scale height)[52] Dünya’nınkinden (6 km.) daha yüksek olup, 11 km.’dir. Mars yüzeyinde yerçekimi Dünya yüzeyindeki yerçekiminin %38’i kadardır. + +Mars atmosferi % 95 karbondioksit, % 3 nitrojen, % 1,6 argondan oluÅŸmakla birlikte, oksijen ve su izleri de taşımaktadır.[53] 1,5 µm yarıçapındaki toz parçacıklarını içeren atmosferi tümüyle tozludur ki, bu, Mars yüzeyinden bakıldığında Mars gökyüzünün soluk bir turuncu-kahverengimsi renkte (Ä°ng. tawny) görülmesine neden olmaktadır.[54] + +Birçok araÅŸtırmacı Mars atmosferinde hacim itibariyle 30 ppb oranında metanın varlığını saptamışlardır.[55][56] Metan morötesi ışınlarla bozunan ve Mars’ınki gibi bir atmosferde[57] yaklaşık 340 yılda bozunacak kararsız bir gaz olduÄŸundan, bu, gezegende güncel veya kısa zaman öncesine dek mevcut bir gaz kaynağının varlığını göstermektedir. Buna da ancak volkanik etkinlik, kuyruklu yıldız çarpmaları ve metanojenik mikroorganizma türleri neden olabilir. Bununla birlikte kısa zaman önce metanın biyolojik olmayan bir süreçle de üretilebileceÄŸi görüşü ortaya atılmıştır.[58] + +Kutup bölgelerinde kışın sürekli bir karanlık ve yüzeyde dondurucu bir soÄŸuk hakim olur, bu da atmosferin % 25–30 civarındaki kısmının yoÄŸunlaÅŸmasına ve karbondioksitin “kuru buz†(Ä°ng. dry ice)[59] denilen halde katılaÅŸmasına yol açar.[60] Kutuplar kış mevsimi geçip yeniden GüneÅŸ ışıklarına maruz kalmaya baÅŸladığında, buzlaÅŸmış karbondioksit, hızı saatte 400 km.’ye ulaÅŸan müthiÅŸ rüzgarlar yaratarak uçmaya baÅŸlar. Bu mevsimlik deÄŸiÅŸimler, büyük miktarlarda toz ve su buharı taşırlar ve Dünya’dakine benzer kırağı ve "sirüs bulutları"nın (saçakbulut) oluÅŸmasına neden olurlar. Su-buzu bulutlarının fotoÄŸrafı Opportunity tarafından 2004’te çekilmiÅŸtir.[61] +Ä°klim[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars’ın Eylül 2001'deki toz fırtınasından önceki (solda) ve toz fırtınası sırasındaki (saÄŸda) görünümlerinin karşılaÅŸtırılması + +Gezegenler içinde mevsimleri Dünya’nınkilere en çok benzeyen gezegen, kendi çevresinde dönme ekseninin yörüngeye eÄŸikliÄŸinin Dünya’nınkine benzer olması nedeniyle, Mars’tır. Bununla birlikte Mars mevsimlerinin süreleri gezegenin Güneş’e daha uzak olması nedeniyle Dünya’nınkilerin iki mislidir ve “Mars yılıâ€nın süresi de iki Dünya yılı süresi kadardır. Mars’ın yüzey sıcaklıkları kutup kışı sırasındaki −140 °C (133 K) ile yaz sırasındaki 20 °C (293 K) arasında deÄŸiÅŸir.[62] Sıcaklık farklarının büyük olması, ince atmosferinin GüneÅŸ ısısını yeterince depolayamaması, atmosfer basıncının düşük olması ve toprağın ısı kapasitesinin (Ä°ng. thermal inertia) düşük olması gibi nedenlerden ileri gelir.[63] + +Mars Dünya’nınki gibi bir yörüngeye sahip olsaydı "eksen eÄŸikliÄŸi"nin de benzeÅŸmesi sayesinde, mevsimleri de Dünya’nınkilere daha benzer olacaktı. Bununla birlikte Mars yörüngesinin geniÅŸ eksantrikliÄŸi ilginç bir sonuç saÄŸlamaktadır. Mars, güney yarımkürede yaz, kuzey yarımkürede kış olduÄŸu zaman günberiye yakındır, güney yarımkürede kış, kuzey yarımkürede yaz olduÄŸu zaman da günöteye yakındır. Bunun sonucunda da güney yarımkürede mevsimlerin daha aşırı farklar göstermesine karşın kuzey yarımkürede mevsimler olması gerekenden daha yumuÅŸak geçerler. Böylece güneyde 30 °C ‘yi (303 K) bulan yaz sıcaklıkları kuzeydeki yaz sıcaklıklarına kıyasla biraz daha fazladır.[64] +Mars’ın kuzey kutbu buz bölgesi + +Mars aynı zamanda GüneÅŸ Sistemi’ndeki en büyük “toz fırtınalarıâ€na[65] sahne olan gezegendir. Bu toz fırtınaları mahalli bir bölgedeki küçük fırtınalar biçiminde olabildiÄŸi gibi, tüm gezegeni kaplar büyüklükteki dev fırtınalar biçiminde de olabilmektedir. Bunlar özellikle Mars Güneş’e en yakın konumuna geldiÄŸinde ve küresel sıcaklığın arttığı hallerde oluÅŸmaya eÄŸilimlidirler.[66] + +Kutup dairelerinin her ikisi de esas olarak su buzundan oluÅŸmaktadırlar. Ayrıca yüzeylerinde “kuru buz†da mevcuttur. KatılaÅŸan karbondioksit olan “kuru buz†(Ä°ng. dry ice) kuzey kutup dairesinde yalnızca kışın yaklaşık bir metre kalınlıkta bir ince tabaka oluÅŸturacak ÅŸekilde birikir; güney kutup dairesine ise bu tabaka kalıcıdır ve kalınlığı 8 m.’yi bulur.[67] Kuzey kutup dairesinin yarıçapı kuzey yarımkürenin yazı sırasında 1000 km. olup yaklaşık 1.6 milyon {\displaystyle km^{3}} buz içerir. (Grönland buz kitlesinin hacmi 2,85 milyon {\displaystyle km^{3}}’tür.) Bu buz tabakasının kalınlığı 2 km.’ye ulaşır. Güney kutbu dairesinin yarıçapı ise 350 km. olup, buradaki buz kalınlığı 3 km.’dir.[68] Buradaki buz kitlesinin hacminin de kuzeydeki kadar olduÄŸu sanılmaktadır.[69] Her iki kutup dairesinde de diferansiyel güneÅŸ ısısından kaynaklandığı sanılan, buzların uçması ve su buharının yoÄŸunlaÅŸması olaylarıyla etkileÅŸim içinde bulunan spiral oluÅŸumlar gözlemlenmiÅŸtir.[70][71] Her iki kutup dairesi de Mars mevsimlerinin ısı dalgalanmalarına baÄŸlı olarak küçülüp büyürler. +Evrim[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Mars’la ilgili son keÅŸifler gezegenin tarihi boyunca çeÅŸitli belirleyici anlar yaÅŸamış olduÄŸunu ortaya koymuÅŸtur. ÖrneÄŸin sıvı su izleri gezegenin atmosferinin vaktiyle bugünkünden daha kalın olduÄŸunu, Kuzey Havzası izleri de çok büyük kütleli bir cisimle büyük bir çarpışma geçirmiÅŸ olduÄŸunu ortaya koymaktadır. Gezegenin evrimiyle ilgili muhtemel açıklamalar ÅŸunlardır: + + GeçmiÅŸte büyük bir uydu iç kısmın üzerindeki gelgit etkisiyle kalıcı bir manyetik alanın oluÅŸmasını saÄŸlamış olabilir. Bu alan Mars atmosferini güneÅŸ rüzgarlarından korumuÅŸ ve yüzeyde sıvı su hareketlerinin meydana gelmesine olanak saÄŸlamış olmalıdır. + Bu çarpışma bir yarımküresinin kabuÄŸunun kalkmasına ve atmosfer tabakasının tahrip olmasına yol açmış olmalıdır. Mars’a geçmiÅŸte kuzey kutbu bölgesinden çarpan bu büyük cisim muhtemelen yörüngesi gelgit gücünün etkisiye bozulmuÅŸ bir uydusu olabilir. Düşen uydunun artık gelgit etkisi olmadığından manyetik alan zayıflamış ve yüzeye çarpan güneÅŸ rüzgarları atmosferin yeniden oluÅŸmasını engellemiÅŸ olmalıdır. + Gezegende belirli bir kararlılığı saÄŸlayan uydunun yokluÄŸu beÅŸ milyon yıllık dengenin yalpalaması ya da bozulması demekti. Dengedeki bu bozulma, kutup bölgelerinin düzenli olarak ısınmasına, buzların bir parça erimesiyle sıvı suların oluÅŸmasına ve dolayısıyla kutup dairesinde çizgilerin meydana gelmesine neden oldu. + +Yörünge ve kendi çevresinde dönüş[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars ve yörüngesi (kırmızı) ile asteroit kuÅŸağındaki cüce gezegen Ceres’in (sarı) mukayesesi (kuzey tutulum kutbundan görünüşle). Tutulumun güney yörünge parçaları koyu renkle gösterilmiÅŸtir. Günberi (q) ve günöte (Q) en yakın geçiÅŸ tarihleriyle belirtilmiÅŸtir. + +Mars’ın Güneş’ten ortalama uzaklığı yaklaşık 230.000.000 km. (1,5 AU), yörünge süresi ise 687 Dünya günüdür. Mars günü Dünya gününden biraz daha uzun olup, tam olarak 24 saat, 39 dakika ve 35,244 saniyedir. Bir Mars yılı 1.8809 Dünya yılıdır, yani Dünya zaman birimiyle tam olarak 1 yıl, 320 gün ve 18,2 saattir. + +Mars’ın eksen eÄŸikliÄŸi Dünya’nın eksen eÄŸikliÄŸine çok yakın olup, 25,19 derecedir. Dolayısıyla Mars’ta de Dünya’dakini andıran mevsimler meydana gelir. Fakat Mars mevsimlerinin süreleri Mars’ın yörünge süresinin uzunluÄŸundan dolayı, Dünya mevsimlerinin sürelerinin iki katıdır. Mars Mayıs 2008’de günöteye Nisan 2009’de günberiye geçmiÅŸtir. Bir sonraki günöte tarihi haziran 2010’dur. + +Mars’ın nispi olarak söylenebilecek yörünge eksantrikliÄŸi (eksenel kaçıklık, dışmerkezlik) 0,09'dur; GüneÅŸ Sistemi’nde yalnızca Merkür bundan daha büyük bir eksantrikliÄŸe sahiptir. Bununla birlikte Mars’ın geçmiÅŸte bugünkünden daha dairesel bir yörünge çizdiÄŸi bilinmektedir. 1,35 milyon Dünya yılı öncesinde Mars’ın eksantrikliÄŸi yaklaşık 0,002 idi, yani Dünya’nın bugünkü eksantrikliÄŸinden de daha azdı.[72] Mars’ın eksantriklik devresi 96.000 Dünya yılıdır.[73] Bununla birlikte Mars’ın 2,2 milyon yıllık bir eksantriklik devresi daha vardır. Son 35.000 yılda Mars’ın yörüngesinin eksantrikliÄŸi diÄŸer gezegenlerin çekimsel etkileri dolayısıyla artmıştır. Mars ve Dünya’nın birbirlerine en yaklaÅŸtıkları zamanlarda aralarında bulunan mesafe gelecek 25.000 yılda biraz daha azalacaktır.[74] +DoÄŸal uyduları[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Phobos deimos diff.jpg +Ä°sim Çap +(km) Kütle +(kg) Ortalama yörünge +yarıçapı (km) Yörünge süresi +(saat) +Phobos 22,2 (27 × 21.6 × 18.8) 1,08×1016 9 378 7,66 +Deimos 12,6 (10 × 12 × 16) 2×1015 23 400 30.35 + +Mars’ın düzensiz biçimli, iki küçük doÄŸal uydusu vardır. Kendilerine eski Yunan mitolojisindeki savaÅŸ ilahı Ares’e (Romalılar’da Mars) yardım eden çocuklarının adlarından esinlenerek Phobos ve Deimos adları verilmiÅŸ, gezegene çok yakın yörüngeler izleyen bu uydular muhtemelen bir Mars "Trojan asteroiti"[75] olan 5261 Eureka gibi, gezegenin çekim alanına kapılarak uydu haline gelmiÅŸ asteroitlerdir.[76] Fakat hava tabakası olmayan Mars’ın bu iki uyduya nasıl ve ne zaman sahip olduÄŸu tam olarak anlaşılmış deÄŸildir. Ãœstelik bu büyüklükteki asteroitler çok nadirdir, özellikle ikili olanları. Bu büyüklükteki asteroitlere asteroit kuÅŸağının dışında rastlanması durumu daha da garip kılmaktadır.[77] + +Her iki uydu da 1877’de Asaph Hall tarafından keÅŸfedilmiÅŸtir. Phobos ve Deimos’un hareketleri Mars yüzeyinden bizim ‘ay’ımızın Dünya’dan görünüşüne kıyasla çok farklı olarak görünür. Phobos 11 saatte bir, batıdan doÄŸar. Deimos ise, dolanım süresi 30 saat olmakla birlikte, 2,7 günde bir doÄŸar.[78] Her iki uydu da ekvatora yakın dairesel yörüngeler izlerler. Phobos ‘un yörüngesi Mars’tan kaynaklanan gelgit etkileri nedeniyle giderek küçülmektedir. Bu yüzden Phobos yaklaşık 50 milyon yıl içinde Mars’a çarpacaktır.[78] +YaÅŸam[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +ALH84001 adı verilen Mars meteoru, bakteri düzeyinde yaÅŸam belirtileri olduÄŸu ileri sürülen mikroskobik oluÅŸumlar göstermektedir. +Mars Global Surveyor (MGS) tarafından çekilmiÅŸ fotoÄŸrafta görülen, mahiyeti anlaşılamamış “koyu kumul lekeleriâ€. +“Koyu kumul lekeleriâ€nin Mars Global Surveyor tarafından çekilen yüksek çözünürlüğe sahip fotoÄŸrafında lekelerin yakın plandan görünümü. + +Evrende yaÅŸamın Dünya’daki koÅŸullara benzer koÅŸullar altında ortaya çıkabileceÄŸi varsayımından hareketle, günümüzde bir gezegenin yaÅŸanabilirlik (Ä°ng. planetary habitability)[79] ölçüsü, yani bir gezegende yaÅŸamın geliÅŸebilme ve sürebilmesinin ölçüsü yüzeyinde su bulunup bulunmamasıyla yakından ilgili görülmektedir. Bu da bir güneÅŸ sistemindeki gezegenin güneÅŸine uzaklığının gereken uygun uzaklıkta olup olmamasına baÄŸlıdır. Mars’ın yörüngesinin Dünya’nın yer aldığı bu uygun kuÅŸağın yarım astronomik birim kadar daha uzağında olması, ince bir atmosfere sahip bu gezegenin yüzeyinde suyun donmasına neden olmaktadır. Bununla birlikte gezegenin geçmiÅŸindeki sıvı su akışları Mars’ın yaÅŸanabilirlik potansiyeli taşıdığını ortaya koymaktadır. Verilere göre, Mars yüzeyindeki sular yaÅŸam için gerekenden çok daha tuzlu ve çok daha asitlidir.[80] + +Gezegenin manyetosferinin olmayışı ve son derece ince bir atmosfere sahip oluÅŸu büyük bir handikaptır. Yüzeyindeki ısı tranferi (Ä°ng. heat transfer)[81] pek büyük deÄŸildir, meteorlara ve güneÅŸ rüzgarlarına karşı savunması hemen hemen yok gibidir ve suyu sıvı halde tutacak atmosfer basıncı yetersizdir (dolayısıyla su gaz haline geçer). Verilere göre gezegen geçmiÅŸte günümüzdeki haline kıyasla daha yaÅŸanabilir haldeydi. Bütün bu olumsuzluklara raÄŸmen Mars’ta organizmaların olmadığı ya da hiç yaÅŸamamış olduÄŸu söylenemez. Nitekim 1970’lerdeki Viking Programı sırasında Mars toprağındaki mikroorganizmaların saptanması amacıyla Mars’tan getirilen örneklerde bazı pozitif görünen sonuçlar elde edildi. Fakat bu sonuçlar birçok bilim insanının katıldığı bir tartışmaya yol açtı ve kesin bir sonuca ulaşılamadı. Buna karşılık Viking Programı’yla edinilen verilerden yararlanan profesör Gilbert Levin,[82] Rafaël Navarro-González[83] ve Ronalds Paepe yeni bir taksonomik sistem hazırladılar ve bu sistemde Mars’taki yaÅŸam türü Gillevinia straata[84] adı altında ele alındı.[85][86][87] + +Sonraki yıllarda Phoenix Mars Lander tarafından yürütülen deneyler Mars toprağında sodyum, potasyum ve klorür içeren bir alkali bulunduÄŸunu gösterdi.[88] Bu besleyici toprak yaÅŸamı taşımaya gayet elveriÅŸliydi, fakat unutulmaması gereken bir sorun daha vardı: YaÅŸamın yoÄŸun morötesi ışınlardan korunabilmesi. + +Nihayet Johnson Uzay Merkezi Laboratuvarı’nda[89] Mars kökenli ALH84001 meteoru üzerinde organik bileÅŸimler saptandı; varılan sonuca göre bunlar Mars üzerindeki ilk yaÅŸam türleriydi.[90][91][92][93] Öte yandan Mars yörüngesindeki uzay gemileri kısa zaman önce düşük miktarlarda metan ve formaldehit saptadılar ki, bunlar da yaÅŸamın varlığını ima eden iÅŸaretler olarak yorumlandılar; zira bu kimyasal bileÅŸimler Mars atmosferinde hızla çözünmektedirler.[94][95] + +Mars’ta biyolojik kökenli oldukları ileri sürülen oluÅŸumlardan en tanınmışları “koyu kumul lekeleri†adıyla bilinen oluÅŸumlardır.[96] Ä°lk kez Mars Global Surveyor tarafından 1998-1999 yıllarında gönderilen fotoÄŸraflarla keÅŸfedilen “koyu kumul lekeleri†Mars’ın özellikle güney kutup bölgesinde (60°-80°enlemleri arasında) görülebilen, buz tabakasının üzerinde veya altında beliren, mahiyeti henüz anlaşılamamış oluÅŸumlardır. Mars ilkbaharının baÅŸlarında belirmekte ve kış baÅŸlarında yok olmaktadırlar. Bunların kış boyunca buz tabakasının altında kalan fotosentetik koloniler, yani fotosentez yapan ve yakın çevrelerini ısıtan mikroorganizmalar oldukları ileri sürülmektedir.[97][98][99][100][101][102] + +28 Eylül 2015'te Mars'ta sıvı halde tuzlu su bulunduÄŸu açıklanmıştır. Tuzlu suyun bulunması ile birlikte, bilim adamları Mars'ta yaÅŸam bulma olasılığının da arttığını ifade etmiÅŸlerdir.[103] +KeÅŸif[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Mars’a günümüze dek, gezegenin yüzeyini, iklimini ve jeolojisini incelemek üzere, ABD, Avrupa ülkeleri, Japonya ve SSCB tarafından düzinelerce uzay gemisi (Ä°ng. spacecraft), uydu/yörünge aracı (Ä°ng. orbiter), iniÅŸ aracı/uzay gemisi (Ä°ng. lander) ve sonda/uzay keÅŸif aracı (Ä°ng. rover) gibi çeÅŸitli uzay araçları gönderilmiÅŸtir. Fakat bu uzay gemisi gönderme denemelerinin yaklaşık üçte ikisi araçlar ya görevlerini tamamlayamadan ya da görevlerine daha baÅŸlayamadan bilinen veya bilinmeyen nedenlerle baÅŸarısızlıkla sonuçlanmıştır. +Tamamlanmış keÅŸif projeleri[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Phoenix'in temsilî iniÅŸi + +Görevini tamamlama konusunda ilk baÅŸarı 1964’te NASA tarafından gönderilen Mariner-4’ten gelmiÅŸtir. Yüzeye ilk baÅŸarılı iniÅŸler ise SSCB’nin Mars Probe Projesi kapsamında 1971’de fırlattığı Mars-2 ve Mars-3 tarafından gerçekleÅŸtirilmiÅŸ, fakat her iki araçla irtibat, iniÅŸlerinden kısa bir süre sonra kesilmiÅŸtir. Sonraki yıllarda NASA Viking Projesi'ni baÅŸlattı ve 1975’te her biri birer "iniÅŸ aracı" taşıyan iki "uydu aracı" fırlatıldı. Her iki araç 1976’da baÅŸarıyla iniÅŸ yaptılar. Gezegende Viking-1 altı yıl, Viking-2 ise üç yıl kaldı. Bunlar Mars’ın ilk renkli fotoÄŸraflarını gönderdiler[104]; gezegenin yüzeyinin haritasının çıkarılması amacıyla gönderdikleri fotoÄŸraflara günümüzde bile zaman zaman baÅŸvurulmaktadır. + +Sovyetler 1988’de Mars’a gezegeni ve doÄŸal uydularını incelemek üzere Phobos-1 ve Phobos-2 adlı sonda araçları gönderdiler. Phobos-1’le irtibat Mars yolundayken kesilmiÅŸ olmasına karşın, Phobos-2 fotoÄŸraflar göndermede baÅŸarılı oldu. Fakat Phobos-2 de tam Phobos adlı doÄŸal uydunun yüzeyine iki iniÅŸ aracını salmak üzereyken baÅŸarısızlığa uÄŸradı. + +Mars Observer uydusunun 1992’deki baÅŸarısızlığından sonra NASA tarafından 1996’da Mars Global Surveyor fırlatıldı. Görevinde tümüyle baÅŸarılı oldu. Harita çıkarma görevini 2001’de tamamladı. Kasım 2006’da üçüncü uzatılmış görevi sırasında sonda aracıyla irtibat kesildi, uzayda 10 yıl çalışır halde kalmayı baÅŸardı. NASA Surveyor’ın fırlatılmasından bir ay sonra da Mars Pathfinder’ı fırlattı. Bu, robotik bir keÅŸif aracı olan Sojourner’ı taşıyordu. 1997 yazında Mars’taki Ares Vallis bölgesine iniÅŸ yaptı. Bu proje de baÅŸarıyla sonuçlandı.[105] + +Mars’la ilgili son tamamlanmış görevde 4 AÄŸustos 2007’de fırlatılan iniÅŸ yeteneÄŸine sahip Phoenix uzay gemisi kullanılmıştır. Araç 25 Mayıs 2007’de Mars’ın kuzey kutbu bölgesine iniÅŸ yaptı.[106] 2,5 m.’ye uzanan robot koluyla Mars toprağını bir metre kazabilecek kapasitede olup, mikroskobik bir kamerayla donatılmıştı. Bu mikroskobik kamera insan saçının binde biri kadar inceliÄŸi ayırt edebilecek bir hassasiyete sahipti. 15 Haziran 2008’de indiÄŸi yerde su buzlarını keÅŸfetti.[107][108] Görevini 10 Kasım 2008’de tamamladı. + +Rusya ve Çin’in ortak projesi olan Phobos-Grunt projesiyle Mars'ın uydusu Phobos’tan örnekler toplanarak analiz için Dünya'ya geri getirilmesi planlanıyordu. Ancak bu sonda 9 Kasım 2011’de fırlatılmasının ardından bozulmuÅŸ, 15 Ocak 2012'de dünyaya düşerek imha olmuÅŸtur. +Sürdürülen keÅŸif projeleri[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Spirit adlı sonda aracı + + 2001’de NASA Eylül 2010’a kadar görevini sürdürmesi planlanan ve halen görevini baÅŸarıyla yerine getiren Mars Odyssey uydu aracını fırlattı.[109] Aracın Gamma Işını Spektrometresi,[110] regolit[111] incelemesi sırasında ilginç miktarlarda hidrojen tespit etti.[112] + 2003’te Avrupa Uzay Ajansı (ESA), Mars Express Projesi kapsamında Mars Express Orbiter adlı uydu aracı ile Beagle-2 adlı iniÅŸ aracını fırlattı. Beagle-2 iniÅŸ sırasında baÅŸarısızlığa uÄŸradı ve Åžubat 2004’te kaybolduÄŸuna dair bir açıklama yapıldı. + ESA’nın Beagle-2’yi fırlattığı yıl NASA Spirit ve Opportunity adlarında iki keÅŸif sondasını fırlattı. Her ikisi de görevini baÅŸarıyla yerine getirdi. Bu çalışmalarla Mars’ta en azından geçmiÅŸte sıvı suyun bulunduÄŸu kesinlik kazanmıştır. 2004 yılında Mars'ın kuzey kutbuna inen ve görev süresi 6 yıl olarak belirtilen Opportunity, bilim adamlarını ÅŸaşırtan bir performansla halen bütün fonksiyonlarını en üst düzeyde kullanarak Mars görevine devam etmektedir.[113] + 2004’te Planetary Fourier Spectrometer ekibi Mars atmosferinde metan saptadıklarını açıkladı. + NASA 12 AÄŸustos 2005’te Mars Reconnaissance Orbiter sonda aracını fırlattı, 10 Mart 2006’da yörüngeye oturan araç iki yıl boyunca bilimsel incelemelerde bulundu. Araç aynı zamanda gelecekteki projeler için en uygun iniÅŸ platformlarını bulmak üzere Mars toprağı ve iklimini incelemekle görevlidir. + ESA Haziran 2006’da Mars’ta “kutup ışıkları†olayını saptadıklarını açıkladı.[114] Vesta ve Ceres’i incelemek üzere gönderilen Dawn uzay gemisinin Åžubat 2009’da Mars’a ulaÅŸtı. + Mars Bilim Laboratuvarı adlı keÅŸif sondası (curiosity) "merak" Kasım 2011'de fırlatıldı. Sonda 2012 AÄŸustos'unda Mars'ın Gale kraterine yumuÅŸak iniÅŸ yaptı. Yapması planlanan deneyler arasında kayalar üzerinde kimyasal lazer yardımıyla uzaktan (azami 13 m.) çalışarak örnekler toplaması da bulunmaktadır. + +Gelecekte uygulanacak projeler[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + + ESA 2013’te Mars toprağındaki organik molekülleri araÅŸtırmak üzere ExoMars adlı sonda aracını fırlatmayı planlamıştır.[115] + 15 Eylül 2008’de NASA Mars atmosferi hakkında bilgi edinilmesini saÄŸlamak üzere 2013’te MAVEN adı verilen robot programını uygulamaya koyacağını açıklamıştır.[116] + Fin-Rus ortak projesi olan MetNet projesinde, gezegenin atmosferik, fiziksel ve meteorolojik yapısını yeterince inceleyebilmek için yaygın bir gözlem ağı kurmak üzere on kadar küçük taşıtın Mars yüzeyine gönderilmesi planlanmıştır.[117] Öncü görevinde bulunacak ilk iniÅŸ araçlarının 2009’da ya da 2011’de fırlatılması planlanmıştır.[118] + NASA ve Lockheed Martin ÅŸirketi önce Ay’a (2020’de), ardından Mars’a insan taşıması planlanan Orion adı verilen uzay gemisi için çalışmalara baÅŸladılar. 28 Eylül 2007’de NASA baÅŸkanı Michael D. Griffin NASA tarafından Mars’a yollanacak ilk insanın 2037’de Mars’ta olacağını açıkladı.[119] + Nasa, Marsa insan göndermek için ADEPT adında 1704 derece ısıya dayanıklı materyal geliÅŸtiriyor. Bu materyalin Mars atmosferine giriÅŸ için ve Mars'ta kurulacak seranın yapımında kullanılacağı açıklandı.[120] + ESA Mars’a insan göndermelerinin 2030 ve 2035 yılları arasında yer alacağını umduklarını açıkladı.[121] + Mars One adlı proje ile de insan bulunduran araçlarla koloni kurulması ön görülüyor.[122] + +Mars’ta astronomi gözlemleri[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars’tan Güneş’in batışı manzarası. FotoÄŸraf Gusev Krateri’nden Spirit adlı uzay keÅŸif sonda aracınca 19 Mayıs 2005’te çekilmiÅŸtir. + +ÇeÅŸitli uydu araçlarının, iniÅŸ araçlarının ve sonda araçlarının Mars’taki varlıkları sayesinde günümüzde astronomi araÅŸtırmaları Mars gökyüzünden de yapılabilmektedir. Bunun pek çok yönden avantajları bulunmaktadır. Mars’ın doÄŸal uydularından Phobos doÄŸal olarak Mars’tan daha iyi gözlemlenebilmektedir (dolunayın üçte biri açısal çapta görülür). DiÄŸer doÄŸal uydu olan Deimos ise Mars yüzeyinden az çok bir yıldızı andırır tarzda görünür; Dünya’dan Venüs’ün görünüşüne kıyasla, Venüs’ten hafifçe daha parlaktır.[123] + +Öte yandan Dünya’da iyi bilinen, kutup ışıkları, meteorlar gibi birçok fenomen artık Mars yüzeyinde de gözlemlenebilmektedir.[124] Dünya’nın Mars ile GüneÅŸ arasına girecek ve GüneÅŸ üzerinde bir leke oluÅŸturacak ÅŸekilde GüneÅŸ önünden geçiÅŸi Mars’tan 10 Kasım 2084’te izlenebilecektir. Mars’tan aynı ÅŸekilde Merkür ve Venüs’ün transit geçiÅŸi (bir kütlenin baÅŸka bir kütlenin önünden geçmesinin gözlemlenebildiÄŸi geçiÅŸ) ve Deimos’un kısmi güneÅŸ tutulmaları izlenebilir. +Görünüş ve "karşı konum"lar[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars’ın 2003 yılında küçük bir teleskoptan görünüşü esas alınarak hazırlanmış dönme hareketi. +Mars’ın 2003’te Dünya’dan görünüşle hazırlanan görünürdeki “geri devimâ€i(tersinir hareketi) +Dünya'yı merkez alan, tutulum üzerinden 2003-2018 Mars "karşı konum"larının görünüşü + +Çıplak gözle bakıldığında Mars genellikle, farklı olarak sarı, turuncu ya da kırmızımsı renklerde görünür. Parlaklığı da, yörüngesindeki yolculuÄŸu sırasındaki konumlarına baÄŸlı olarak, Dünya’dan görünen diÄŸer gezegenlerden daha fazla deÄŸiÅŸiklik gösterir. Görünürdeki kadiri “ kavuÅŸum konumuâ€ndaki +1,8’den “günberi karşı konumuâ€ndaki −2,9 aralığında deÄŸiÅŸir.[12] Kimi konumlarında güneÅŸin güçlü ışığından dolayı görünmez hale gelir. 32 yılda iki kez – Temmuz ve Eylül sonunda – en uygun konuma gelir. Mars teleskopla bakıldığında bolca yüzey ayrıntısı sunan bir gezegendir, özellikle kutuplardaki buzul bölgeleri elveriÅŸsiz koÅŸullarda bile belirgin olarak görülürler.[125] + +Mars’ın Dünya’ya en yakın olduÄŸu konum karşı konum olarak bilinir. “KavuÅŸum dönümü†(Ä°ng. synodic period) olarak bilinen iki “karşı konum†arasındaki süre Mars için 780 gündür. Yörünge eksantriklikleri nedeniyle bu sürede 8,5 güne varan oynamalar olabilir. Dünya’ya en yaklaÅŸtığı zamanlarda Dünya ile Mars arasındaki en kısa uzaklık, gezegenlerin eliptik yörüngelerine baÄŸlı olarak 55.000.000 km. ile 100.000.000 km. arasında deÄŸiÅŸir.[12] Önümüzdeki Mars “karşı konumâ€u 29 Ocak 2010’da meydana gelecektir. Mars “karşı konum†pozisyonuna yaklaÅŸtığında “geri devim “ (tersinir hareket, Ä°ng. retrograde motion) periyodu baÅŸlar. + +27 AÄŸustos 2003 günü, saat 9:51:13’de (UT) Mars son 60.000 yıl boyunca Dünya’ya en yaklaÅŸtığı konuma geldi. Bu konumunda Dünya ile arasındaki uzaklık 55.758.006 km. (0,372719 AU) idi. Bu olay Mars’ın karşı konumundan bir gün, günberisinden yaklaşık üç gün farkla meydana geldiÄŸinden Mars Dünya’dan kolaylıkla izlenebildi. Yapılan hesaplamalara göre, Mars’ın Dünya’ya bu denli yaklaÅŸmasının söz konusu olduÄŸu son tarih M.Ö. 57.617 yılıdır, bir sonraki tarih ise 2287 yılıdır. 24 AÄŸustos 2208’deki yakınlaÅŸmada ise iki gezegen arasındaki uzaklığın yalnızca 0.372254 AU olacağı hesaplanmıştır. +Gözlem tarihi[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Mars’ın eski uygarlıklarla baÅŸlayan gözlem tarihinin özellikle, her iki yılda bir meydana gelen, gezegenin Dünya’ya yaklaÅŸtığı ve dolayısıyla görünürlüğünün arttığı “karşı konumâ€larına dayandığı görülür. Ayrıca tarihsel kayıtlarda her 15-17 yılda bir meydana gelen, farkedilebilen günberi “karşı konumâ€larına da yer verildiÄŸi görülmektedir, çünkü Mars günberiye yaklaÅŸtığında Dünya’ya da yaklaÅŸmaktadır. + +Batı tarihinde Mars gözlemlerine iliÅŸkin pek fazla kayıt olduÄŸu söylenemez. Aristo Mars gözlemlerini tarif eden ilk yazarlardan biri olmuÅŸtur. Mars’ın 3 Ekim 1590’da Venüs’çe “örtülmeâ€si (Ä°ng. Occultation) Heidelberg’te M. Möstlin tarafından kaydedilmiÅŸtir.[126] Nihayet 1609’da Mars Galile tarafından gözlemlendi. Bu aynı zamanda Mars’ın bir teleskop aracılığıyla yapılan ilk gözlemiydi. +Mars kanalları[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Giovanni Schiaparelli tarafından yapılan Mars haritası. + +19. yy.’da teleskobun yaygınlaÅŸması gök cisimlerinin tanımlanmasında belirli bir düzeye gelinmesini saÄŸladı. 5 Eylül 1877’de Mars’ın bir günberi karşı konumu meydana geldi. O yıl Ä°talyan astronom Giovanni Schiaparelli Milano’da ilk ayrıntılı Mars haritalarını çıkarmak üzere 22 cm.’lik bir teleskop kullandı. Gözlemlerinde Mars yüzeyinde kendisinin “kanallar†adını verdiÄŸi, günümüzde kimilerince “optik illüzyon†olarak açıklanan birtakım oluÅŸumlar saptadı ve bunları hazırladığı Mars haritalarına iÅŸaretledi. Mars yüzeyinde gözlemlediÄŸi bu uzun doÄŸrusal hatlara Dünya’daki ünlü nehirlerin adlarını verdi.[127][128] + +Bu gözlemlerden etkilenen ÅŸarkiyatçı Percival Lowell 300 mm. 450 mm.’lik teleskoplara sahip bir gözlemevi kurdu. Gözlemevi Mars’ın keÅŸfine ağırlık verdi. Mars’ın pozisyonları bakımından 1894 yılı son uygun fırsattı. Lowell Mars ve Mars’ta yaÅŸam üzerine kamuda büyük bir yankı uyandıran kitaplar yayımladı. “Kanallar†dönemin en büyük teleskoplarını kullanan Henri Joseph Perrotin ve Louis Thollon gibi baÅŸka astronomlarca da saptanmıştı. Sonraki yıllarda daha büyük teleskoplarla yapılan gözlemler sonucunda, boyları önceden belirtildiÄŸi kadar uzun olmamakla doÄŸrusal kanalların bulunduÄŸu doÄŸrulandı. +Lowell tarafından 1914’ten önce yapılan gözlemlere göre hazırlanmış Mars kanalları + +Fakat daha sonra, 1909’da Flammarion, 840 mm.’lik bir teleskopla yaptığı gözlemler sonucunda, düzensiz bazı izler gözlemlemekle birlikte sözü edilen kanallara rastlamadığını açıkladı.[129] 1960’lı yıllara gelindiÄŸinde farklı yaÅŸam biçimleri olan Marslılar hakkında çeÅŸitli senaryolar içeren bir sürü makale ve kitap yayımlanmış bulunuyordu.[130] NASA’nın Mariner Projesi kapsamında gönderdiÄŸi uzay gemisinin Mars’a ulaÅŸmasından sonra bu tür senaryolar azalmış ve ÅŸekil deÄŸiÅŸtirmiÅŸtir. ÖrneÄŸin bu kez, Marslılar’in bir baÅŸka boyutta ya da frekansta oldukları, gezegenlerine inilse de algılanamayacakları yönünde yeni senaryolar üretildi. +Kültürde[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Mars’ın çeÅŸitli adları[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Bu gezegene Batı kültüründe Mars adının verilmesi, eski Yunan mitolojisinde savaÅŸ ilahı olan Ares’e Roma mitolojisinde tekabül eden ilahın adının Mars olmasından ileri gelir.[131] Mars’a çeÅŸitli dillerde verilmiÅŸ adlardan bazıları ÅŸunlardır: + + Babil mitolojisinde Mars, gezegenin kızılımsı görünüşünden olsa gerek, ateÅŸ ve yıkım ilahı Nergal’in adıyla ifade edilirdi.[132] + Eski Yunanlar Nergal’i Ares’e denk tuttukları zaman bu gezegene Areos aster (ἌÏεως ἀστἡÏ), yani “Ares’in yıldızı†adını verdiler.[133] Daha sonra Ares ile Mars ilahlarının özdeÅŸ kılınmasıyla gezegen Romalılar’da stella Martis, yani “Mars’ın yıldızı†ya da kısaca Mars adını aldı. Eski Yunanlarda Mars’ın bir baÅŸka adı “ateÅŸli, ateÅŸten †anlamına gelen Pyroeis (ΠυÏόεις) idi.[134] + Mars’a Hindu mitolojisinde Mangala (मंगल),[135][136][137] Sanskrit dilinde ise, Koç Burcu ve Akrep Burcu iÅŸaretlerine sahip olan ve okült bilimleri öğreten savaÅŸ ilahından dolayı Angaraka denir.[138] + Mars eski Mısırlılar’da “kızıl Horusâ€[139] anlamında "Ḥr DÅ¡r";;;; adını almıştır. + Ä°branicede Mars’a “kızaran†anlamında Ma'adim (מ×די×) denir ki, Mars’taki en büyük kanyona da bu ad (Ma'adim Vallis) verilmiÅŸtir.[140] + Gezegenin Arapça’daki adı El-Mirrih’tir, oradan da Türkçe’ye Merih olarak geçmiÅŸtir. Eski Türkler’de Mars’a Sakit adı verilirdi. + Urdu ve Acem dillerinde de Merih (مریخ) olarak telaffuz edilir. Merih teriminin etimolojik kökeni bilinmemektedir. Eski Ä°ranlılar’da ise gezegene "iman ilahı"yla ilgili olarak Bahram (بهرام.) adı verilirdi.[141] + Çin, Japon, Kore ve Vietnam kültürlerinde Mars eski Çin mitolosindeki beÅŸ unsurdan ateÅŸle iliÅŸkilendirilerek “ateÅŸ yıldızı†( ç«æ˜Ÿ) adıyla geçer.[142] + +Mars symbol.svg + +Mars’ın sembolü astrolojik sembolünden yararlanılarak hazırlanmış bir sembol olup, bir daire ve küçük bir oktan oluÅŸur. Bu, aslında, savaÅŸ ilahı Mars’ın kalkan ve mızrağının stilize bir temsilidir. Biyolojide de eril cinsiyeti göstermede kullanılan bu sembol, simyada karakteristik rengi kırmızı olan Mars’ın hükmettiÄŸi demir elementini simgeler; Mars da kırmızımsı rengini demiroksite borçludur.[143][144] +“Zeki Marslılarâ€[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] +Cydonia bölgesinde insan suratına benzetilen, doÄŸal olmayabileceÄŸi iddia edilen oluÅŸum. Viking-1,1976 +Ä°nsan suratına benzetilen oluÅŸum yakınlarında doÄŸal olmayabileceÄŸi iddia edilen piramit benzeri oluÅŸumlar. + +Mars’ta zeki bir yaÅŸam olabileceÄŸi konusunda 19.yy.’da ve 20. yy.’da, özellikle Mars’ın modern uzay araçlarınca incelenmesinden önce çeÅŸitli iddialarda bulunulmuÅŸtur. Bu iddialardan bazıları şöyle özetlenebilir: + + 19. yy. sonlarındaki popüler görüşe göre Mars zeki Marslılar’ca meskundu. Schiaparelli’nin gözlemlediÄŸi kanallar ile Percival Lowell’ın kitapları insanlarda ÅŸu kavramın doÄŸmasına neden olmuÅŸtu: Mars soÄŸuk, çorak bir gezegen olmakla birlikte burada sulama çalışmaları yapan eski uygarlıklar mevcuttu.[145] + 1899 Colorado Springs Laboratuvarı’nda alıcılarını kullanarak atmosferdeki radyo gürültülerini incelemeye çalışan mucit Nikola Tesla, sonradan bir baÅŸka gezegenden, muhtemelen Mars’tan gelmekte olduÄŸunu iddia ettiÄŸi, tekrarlanan sinyaller saptadı. Bu düşüncesini 1901’deki bir konuÅŸmasında açıkladı.[146] Lord Kelvin, Tesla’nın Dünya-dışı yaÅŸamla ilgili varsayımlarını önceleri desteklemiÅŸse de, sonradan reddetmiÅŸtir.[147][148] + 1901’de ise Harvard College Gözlemevi müdürü Edward Charles Pickering, New York Times’taki bir makalede Arizona’daki Lowell Gözlemevi’nden Mars’tan irtibat kurma giriÅŸimlerinin olduÄŸunu doÄŸrulayıcı bir telgraf almış bulunduÄŸunu açıkladı.[149] + 20. yy.’ın son çeyreÄŸinde Mars’a giden uzay araçları Mars’ta “zeki yaÅŸam†ürünü olabilecek ikamet yapıları olmadığını ortaya koyduÄŸunda, bu kez, Marslılar konusunda yeni görüşler ileri sürüldü. Bunlardan bazılarına göre, Marslılar farklı bir boyutta yaÅŸamaktaydılar, kimilerine göre de Mars’ta saptanan insan suratı ve piramitler biçimindeki oluÅŸumlar doÄŸal oluÅŸumlar deÄŸildiler.[150][151][152][153] + +Bilimkurgu[deÄŸiÅŸtir | kaynağı deÄŸiÅŸtir] + +Bilimkurguda Mars kızıl renkte temsil edilmiÅŸ ve ilk zamanlardaki bilimsel spekülasyonlar doÄŸrultusunda zeki canlılarca meskun olarak canlandırılmıştı. Marslılar’a iliÅŸkin ilk bilimkurgu senaryoları içinde en tanınmışı H.G. Wells’in 1898’de yayımlanan, ölmekte olan gezegenlerinden kaçan Marslılar’ın Dünya’yı istila etmesini konu alan Dünyalar Savaşı'dır. Kitap, 1938’de radyoya, daha sonra sinemaya uyarlandı.[154] Mars ya da Marslılar’a iliÅŸkin diÄŸer bilimkurgu eserlerinin arasında ÅŸunlar sayılabilir: + + The Martian Chronicles (hikâye), Ray Bradbury. + Barsoom (hikâyeler serisi), Edgar Rice Burroughs. + Marvin the Martian (çizgi film), Warner Brothers. + Gulliver'in Gezileri, Jonathan Swift. + Mars Trilogy (hikâyeler serisi), Kim Stanley Robinson. + We Can Remember It for You Wholesale (hikâye), Philip K. Dick. (Bu hikâyeden uyarlanan Total Recall isimli film de bulunmaktadır.) + Babylon 5 (televizyon dizisi). + GerçeÄŸe ÇaÄŸrı (sinema filmi). + Marslı (bilim kurgu romanı), Andy Weir. + diff --git a/xpcom/tests/gtest/wikipedia/vi.txt b/xpcom/tests/gtest/wikipedia/vi.txt new file mode 100644 index 0000000000..48270cc62d --- /dev/null +++ b/xpcom/tests/gtest/wikipedia/vi.txt @@ -0,0 +1,333 @@ + + +Sao Há»a còn gá»i là: Há»a Tinh, (Tiếng Anh: Mars) là hành tinh thứ tÆ° tính từ Mặt Trá»i trong Thái DÆ°Æ¡ng Hệ. Äôi khi hành tinh này còn được gá»i tên là Há»a Tinh. Nó thÆ°á»ng được gá»i vá»›i tên khác là "Hành tinh Äá»", do sắt ôxít có mặt rất nhiá»u trên bá» mặt hành tinh làm cho bá» mặt nó hiện lên vá»›i màu đỠđặc trÆ°ng.[14] Sao Há»a là má»™t hành tinh đất đá vá»›i má»™t khí quyển má»ng, vá»›i những đặc Ä‘iểm trên bá» mặt có nét giống vá»›i cả các hố va chạm trên Mặt Trăng và các núi lá»­a, thung lÅ©ng, sa mạc và chá»m băng ở cá»±c trên Trái Äất. Chu kỳ tá»± quay và sá»± tuần hoàn của các mùa trên Há»a Tinh khá giống vá»›i của Trái Äất do sá»± nghiêng của trục quay tạo ra. Trên Sao Há»a có ngá»n núi Olympus Mons, ngá»n núi cao nhất trong Hệ Mặt Trá»i, và hẻm núi Valles Marineris, hẻm núi dài và rá»™ng nhất trong Thái DÆ°Æ¡ng Hệ. Lòng chảo Borealis bằng phẳng trên bán cầu bắc bao phủ tá»›i 40% diện tích bá» mặt hành tinh Ä‘á» và có thể là má»™t hố va chạm khổng lồ trong quá khứ.[15][16] + +Cho đến khi tàu Mariner 4 lần đầu tiên bay ngang qua Sao Há»a vào năm 1965, đã có nhiá»u suy Ä‘oán vá» sá»± có mặt của nÆ°á»›c lá»ng trên bá» mặt hành tinh này. Chúng dá»±a trên những quan sát vá» sá»± biến đổi chu kỳ vá» Ä‘á»™ sáng và tối của những nÆ¡i trên bá» mặt hành tinh, đặc biệt tại những vÄ© Ä‘á»™ vùng cá»±c, nÆ¡i có đặc Ä‘iểm của biển và lục địa; những Ä‘Æ°á»ng kẻ sá»c dài và tối ban đầu được cho là những kênh tÆ°á»›i tiêu chứa nÆ°á»›c lá»ng. Những Ä‘Æ°á»ng sá»c thẳng này sau đó được giải thích nhÆ° là những ảo ảnh quang há»c, mặc dù các chứng cứ địa chất thu thập bởi các tàu thăm dò không gian cho thấy Sao Há»a có khả năng đã từng có nÆ°á»›c lá»ng bao phủ trên diện rá»™ng ở bá» mặt của nó.[17] Năm 2005, dữ liệu từ tín hiệu radar cho thấy sá»± có mặt của má»™t lượng lá»›n nÆ°á»›c đóng băng ở hai cá»±c,[18] và tại các vÅ©ng vÄ© Ä‘á»™ trung bình.[19][20] Robot tá»± hành Spirit đã lấy được mẫu các hợp chất hóa há»c chứa phân tá»­ nÆ°á»›c vào tháng 3 năm 2007. Tàu đổ bá»™ Phoenix đã trá»±c tiếp lấy được mẫu nÆ°á»›c đóng băng trong lá»›p đất nông trên bá» mặt vào ngày 31 tháng 7 năm 2008.[21] + +Sao Há»a có hai vệ tinh, Phobos và Deimos, chúng là các vệ tinh nhá» và dị hình. Äây có thể là các tiểu hành tinh bị Há»a Tinh bắt được, tÆ°Æ¡ng tá»± nhÆ° 5261 Eureka-má»™t tiểu hành tinh Trojan của Há»a Tinh. Hiện nay có ba tàu quỹ đạo còn hoạt Ä‘á»™ng Ä‘ang bay quanh Sao Há»a: Mars Odyssey, Mars Express, và Mars Reconnaissance Orbiter. Trên bá» mặt nó có robot tá»± hành thám hiểm Sao Há»a (Mars Exploration Rover) Opportunity còn hoạt Ä‘á»™ng và cặp song sinh vá»›i nó robot tá»± hành Spirit đã ngừng hoạt Ä‘á»™ng, cùng vá»›i đó là những tàu đổ bá»™ và robot tá»± hành trong quá khứ-cả thành công lẫn không thành công. Tàu đổ bá»™ Phoenix đã hoàn thành phi vụ của nó vào năm 2008. Những quan sát bởi tàu quỹ đạo đã ngừng hoạt Ä‘á»™ng của NASA Mars Global Surveyor chỉ ra chứng cứ vá» sá»± dịch chuyển thu nhá» và mở rá»™ng của chá»m băng cá»±c bắc theo các mùa.[22] Tàu quỹ đạo Mars Reconnaissance Orbiter của NASA đã thu nhận được các bức ảnh cho thấy khả năng có nÆ°á»›c chảy trong những tháng nóng nhất trên Há»a Tinh.[23] + +Sao Há»a có thể dá»… dàng nhìn từ Trái Äất bằng mắt thÆ°á»ng. Cấp sao biểu kiến của nó đạt giá trị −3,0[7] chỉ đứng sau so vá»›i Sao Má»™c, Sao Kim, Mặt Trăng, và Mặt Trá»i. + +Äặc tính vật lý[sá»­a | sá»­a mã nguồn] +So sánh kích cỡ của Trái Äất và Sao Há»a. + +Bán kính của Sao Há»a xấp xỉ bằng má»™t ná»­a bán kính của Trái Äất. Tá»· trá»ng của nó nhá» hÆ¡n của Trái Äất, vá»›i thể tích chỉ bằng 15% thể tích Trái Äất và khối lượng chỉ bằng 11%. Diện tích bá» mặt của hành tinh Ä‘á» chỉ hÆ¡i nhá» hÆ¡n tổng diện tích đất liá»n trên Trái Äất.[6] Trong khi Sao Há»a có Ä‘Æ°á»ng kính và khối lượng lá»›n hÆ¡n Sao Thủy, nhÆ°ng Sao Thủy lại có tá»· trá»ng cao hÆ¡n. Äiá»u này làm cho hai hành tinh có giá trị gia tốc hấp dẫn tại bá» mặt gần bằng nhau-của Sao Há»a chỉ lá»›n hÆ¡n có 1%. Sao Há»a cÅ©ng là hành tinh có giá trị kích thÆ°á»›c, khối lượng và gia tốc hấp dẫn bá» mặt ở giữa khi so vá»›i Trái Äất và Mặt Trăng (Mặt Trăng có Ä‘Æ°á»ng kính bằng má»™t ná»­a của Sao Há»a, trong khi Trái Äất có Ä‘Æ°á»ng kính gấp đôi Há»a Tinh; Trái Äất có khối lượng gấp chín lần khối lượng Sao Há»a trong khi Mặt Trăng có khối lượng chỉ bằng má»™t phần chín so vá»›i Há»a Tinh). Màu sắc vàng cam của bá» mặt Sao Há»a là do lá»›p phủ chứa sắt(III) ôxít, thÆ°á»ng được gá»i là hematit, hay rỉ sét.[24] +Äịa chất[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Äịa chất trên Sao Há»a + +Minh há»a cấu trúc bên trong Sao Há»a + +Dá»±a trên những quan sát từ các tàu quỹ đạo và kết quả phân tích mẫu thiên thạch Sao Há»a, các nhà khoa há»c nhận thấy bá» mặt Sao Há»a có thành phần chủ yếu từ đá bazan. Má»™t số chứng cứ cho thấy có nÆ¡i trên bá» mặt Sao Há»a giàu silic hÆ¡n bazan, và có thể giống vá»›i đá andesit ở trên Trái Äất; những chứng cứ này cÅ©ng có thể được giải thích bởi sá»± có mặt của silic Ä‘iôxít (silica glass). Äa phần bá» mặt của hành tinh được bao phủ má»™t lá»›p bụi mịn, dày của sắt(III) ôxít.[25][26] + +Mặc dù Há»a Tinh không còn thấy sá»± hoạt Ä‘á»™ng của má»™t từ trÆ°á»ng trên toàn cầu,[27] các quan sát cÅ©ng chỉ ra là nhiá»u phần trên lá»›p vá» hành tinh bị từ hóa, và sá»± đảo ngược cá»±c từ luân phiên đã xảy ra trong quá khứ. Những đặc Ä‘iểm cổ từ há»c đối vá»›i những khoáng chất dá»… bị từ hóa này có tính chất rất giống vá»›i những dải vằn từ luân phiên nhau trên ná»n đại dÆ°Æ¡ng của Trái Äất. Má»™t lý thuyết được công bố năm 1999 và được tái kiểm tra vào tháng 10 năm 2005 (nhá» những dữ liệu từ tàu Mars Global Surveyor), theo đó những dải này thể hiện hoạt Ä‘á»™ng kiến tạo mảng trên Sao Há»a khoảng 4 tá»· năm trÆ°á»›c, trÆ°á»›c khi sá»± vận Ä‘á»™ng dynamo của hành tinh bị suy giảm và dẫn đến sá»± mất hoàn toàn của từ trÆ°á»ng toàn cầu bao quanh hành tinh Ä‘á».[28] + +Những mô hình hiện tại vá» cấu trúc bên trong hành tinh cho rằng vùng lõi Há»a Tinh có bán kính khoảng 1.480 km, vá»›i thành phần chủ yếu là sắt vá»›i khoảng 14–17% lÆ°u huỳnh. Lõi sắt sunfit này có trạng thái lá»ng má»™t phần, vá»›i sá»± tập trung các nguyên tố nhẹ hÆ¡n cao gấp hai lần so vá»›i lõi của Trái Äất. Lõi được bao quanh bởi má»™t lá»›p phủ silicat, lá»›p này hình thành lên sá»± kiến tạo và đặc Ä‘iểm núi lá»­a của hành tinh, nhÆ°ng hiện nay những hoạt Ä‘á»™ng này đã ngừng hẳn. Chiá»u dày trung bình của lá»›p vá» Sao Há»a vào khoảng 50 km, vá»›i chiá»u dày lá»›n nhất bằng 125 km.[29] Lá»›p vá» Trái Äất vá»›i chiá»u dày trung bình 40 km, chỉ dày bằng má»™t phần ba so vá»›i Sao Há»a khi so vá»›i tỉ lệ Ä‘Æ°á»ng kính của hai hành tinh. + +Trong thá»i gian hình thành hệ Mặt Trá»i, Sao Há»a được tạo ra từ Ä‘Ä©a tiá»n hành tinh quay quanh Mặt Trá»i do kết quả của các quá trình ngẫu nhiên của sá»± vận Ä‘á»™ng Ä‘Ä©a tiá»n hành tinh. Trên Há»a Tinh có nhiá»u đặc trÆ°ng hóa há»c khác biệt do vị trí của nó trong hệ Mặt Trá»i. Trên hành tinh này, các nguyên tố vá»›i Ä‘iểm sôi tÆ°Æ¡ng đối thấp nhÆ° clo, phốt pho và lÆ°u huỳnh có mặt nhiá»u hÆ¡n so vá»›i trên Trái Äất; các nguyên tố này có lẽ đã bị đẩy khá»i những vùng gần Mặt Trá»i bởi gió Mặt Trá»i trong giai Ä‘oạn hình thành Thái DÆ°Æ¡ng hệ.[30] + +Ngay sau khi hình thành lên các hành tinh, tất cả chúng Ä‘á»u chịu những đợt bắn phá lá»›n của các thiên thạch ("Late Heavy Bombardment"). Khoảng 60% bá» mặt Sao Há»a còn để lại chứng tích những đợt va chạm trong thá»i kỳ này.[31][32][33] Phần bá» mặt còn lại có lẽ thuá»™c vá» má»™t lòng chảo va chạm rá»™ng lá»›n hình thành trong thá»i gian đó—chứng tích của má»™t lòng chảo va chạm khổng lồ ở bán cầu bắc Sao Há»a, dài khoảng 10.600 km và rá»™ng 8.500 km, hay gần bốn lần lá»›n hÆ¡n lòng chảo cá»±c nam Aitken của Mặt Trăng, lòng chảo lá»›n nhất từng được phát hiện.[15][16] Các nhà thiên văn cho rằng trong thá»i kỳ này Sao Há»a đã bị va chạm vá»›i má»™t thiên thể kích cỡ tÆ°Æ¡ng Ä‘Æ°Æ¡ng vá»›i Pluto cách nay bốn tá»· năm. Sá»± kiện này được cho là nguyên nhân gây nên sá»± khác biệt vỠđịa hình giữa bán cầu bắc và bán cầu nam của Há»a Tinh, tạo nên lòng chảo Borealis bao phủ 40% diện tích bá» mặt hành tinh.[34][35] + +Lịch sá»­ địa chất của Sao Há»a có thể tách ra thành nhiá»u chu kỳ, nhÆ°ng bao gồm ba giai Ä‘oạn lá»›n sau:[36][37] + + Ká»· Noachis (đặt tên theo Noachis Terra): Giai Ä‘oạn hình thành những bá» mặt cổ nhất hiện còn tồn tại trên Sao Há»a, cách nay từ 4,5 tá»· năm đến 3,5 tá»· năm trÆ°á»›c. Bá» mặt hành tinh ở thá»i kỳ Noachis đã bị cày xá»›i bởi rất nhiá»u cú va chạm lá»›n. Cao nguyên Tharsis, cao nguyên núi lá»­a, được cho là đã hình thành trong thá»i kỳ này. Cuối thá»i kỳ này bá» mặt hành tinh bị bao phủ bởi những trận lụt lá»›n. + Ká»· Hesperia (đặt tên theo Hesperia Planum): 3,5 tá»· năm đến 2,9–3,3 tá»· năm trÆ°á»›c. Ká»· Hesperia đánh dấu bởi sá»± hình thành và mở rá»™ng của các đồng bằng nham thạch núi lá»­a. + Ká»· Amazon (đặt tên theo Amazonis Planitia): thá»i gian từ 2,9–3,3 tá»· năm trÆ°á»›c cho đến ngày nay. Bá» mặt hành tinh trong ká»· Amazon chịu má»™t số ít các hố va chạm thiên thạch, nhÆ°ng đặc tính của các hố va chạm cÅ©ng khá Ä‘a dạng. Ngá»n núi Olympus Mons hình thành trong ká»· này, cùng vá»›i các dòng nham thạch ở khắp nÆ¡i trên Sao Há»a. + +Má»™t số hoạt Ä‘á»™ng địa chất vẫn còn diá»…n ra trên Há»a Tinh. Trong thung lÅ©ng Athabasca (Athabasca Valles) có những vỉa dung nham niên đại tá»›i 200 triệu năm (Mya). NÆ°á»›c chảy trong những địa hào (graben) dá»c ở vùng Cerberus diá»…n ra ở thá»i Ä‘iểm 20 Mya, ám chỉ những sá»± xâm thá»±c của mac ma núi lá»­a trong thá»i gian gần đây.[38] Ngày 19 tháng 2 năm 2008, ảnh chụp từ tàu Mars Reconnaissance Orbiter cho thấy chứng cứ vá» vụ sạt lở đất đá từ má»™t vách núi cao 700 m.[39] +Äất[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Äất trên Sao Há»a + +Tàu đổ bá»™ Phoenix gá»­i dữ liệu vá» cho thấy đất trên Sao Há»a có tính kiá»m yếu và chứa các nguyên tố nhÆ° magiê, natri, kali và clo. Những dưỡng chất này được tìm thấy trong đất canh tác trên Trái Äất, và cần thiết cho sá»± phát triển của thá»±c vật.[40] Các thí nghiệm thá»±c hiện bởi tàu đổ bá»™ cho thấy đất của Há»a Tinh có Ä‘á»™ bazÆ¡ pH bằng 8,3, và chứa dấu vết của muối peclorat.[41][42] +Khe đất hẹp (streak) ở vùng Tharsis Tholus, chụp bởi thiết bị Hirise. Nó nằm ở giữa bên trái bức ảnh này (hình mÅ©i kim). Tharsis Tholus nằm ở ngay bên phải ngoài bức ảnh. + +Khe đất hẹp (streak) thÆ°á»ng xuất hiện trên khắp bá» mặt Sao Há»a và những cái má»›i thÆ°á»ng xuất hiện trên các sÆ°á»n dốc của các hố va chạm, trÅ©ng hẹp và thung lÅ©ng. Khe đất hẹp ban đầu có màu tối và theo thá»i gian nó bị nhạt màu dần. Thỉnh thoảng các rãnh này có mặt ở trong má»™t vùng nhá» hẹp sau đó chúng bắt đầu kéo dài ra hàng trăm mét. Khe rãnh hẹp cÅ©ng đã được quan sát thấy ở cạnh của các tảng đá lá»›n và những chÆ°á»›ng ngại vật trên Ä‘Æ°á»ng lan rá»™ng của chúng. Các lý thuyết Ä‘a số cho rằng những khe rãnh hẹp có màu tối do chúng nằm ở dÆ°á»›i những lá»›p đất sau đó bị lá»™ ra bá» mặt do tác Ä‘á»™ng của quá trình sạt nở đất của bụi sáng màu hay các trận bão bụi.[43] Má»™t số cách giải thích khác lại trá»±c tiếp hÆ¡n khi cho rằng có sá»± tham gia của nÆ°á»›c hay thậm chí là sá»± sinh trưởng của các tổ chức sống.[44][45] +Thủy văn[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: NÆ°á»›c trên Sao Há»a + +Ảnh vi mô chụp bởi robot Opportunity vá» dạng kết hạch màu xám của khoáng vật hematit, ám chỉ sá»± tồn tại trong quá khứ của nÆ°á»›c lá»ng +Khe xói má»›i hình thành trong hố ở vùng Centauri Montes + +NÆ°á»›c lá»ng không thể tồn tại trên bá» mặt Sao Há»a do áp suất khí quyển của nó hiện nay rất thấp, ngoại trừ những nÆ¡i có cao Ä‘á»™ thấp nhất trong má»™t chu kỳ ngắn.[46][47] Hai mÅ© băng ở các cá»±c dÆ°á»ng nhÆ° chứa má»™t lượng lá»›n nÆ°á»›c.[48][49] Thể tích của nÆ°á»›c băng ở mÅ© băng cá»±c nam nếu bị tan chảy có thể đủ bao phủ toàn bá»™ bá» mặt hành tinh Ä‘á» vá»›i Ä‘á»™ dày 11 mét.[50] Lá»›p manti của tầng băng vÄ©nh cá»­u mở rá»™ng từ vùng cá»±c đến vÄ© Ä‘á»™ khoảng 60°.[48] + +Má»™t lượng lá»›n nÆ°á»›c băng được cho là nằm ẩn dÆ°á»›i băng quyển dày của Sao Há»a. Dữ liệu radar từ tàu Mars Express và Mars Reconnaissance Orbiter đã chỉ ra trữ lượng lá»›n nÆ°á»›c băng ở hai cá»±c (tháng 7 năm 2005)[18][51] và ở những vùng vÄ© Ä‘á»™ trung bình (tháng 11 năm 2008).[19] Tàu đổ bá»™ Phoenix đã trá»±c tiếp lấy được mẫu nÆ°á»›c băng trong lá»›p đất nông vào ngày 31 tháng 7 năm 2008.[21] + +Äịa mạo trên Sao Há»a gợi ra má»™t cách mạnh mẽ rằng nÆ°á»›c lá»ng đã từng có thá»i Ä‘iểm tồn tại trên bá» mặt hành tinh này. Cụ thể, những mạng lÆ°á»›i thÆ°a khổng lồ phân tán trên bá» mặt, gá»i là thung lÅ©ng chảy thoát (outflow channels), xuất hiện ở 25 vị trí trên bá» mặt hành tinh. Äây được cho là dấu tích của sá»± xâm thá»±c diá»…n ra trong thá»i kỳ đại hồng thủy giải phóng nÆ°á»›c lÅ© từ tầng chứa nÆ°á»›c dÆ°á»›i bá» mặt, mặc dù má»™t vài đặc Ä‘iểm cấu trúc này được giả thuyết là do kết quả của tác Ä‘á»™ng từ băng hà hoặc dung nham núi lá»­a.[52][53] Những con kênh trẻ nhất có thể hình thành trong thá»i gian gần đây vá»›i chỉ vài triệu năm tuổi.[54] Ở những nÆ¡i khác, đặc biệt là những vùng cổ nhất trên bá» mặt Há»a Tinh, ở tá»· lệ nhá» hÆ¡n, những mạng lÆ°á»›i thung lÅ©ng (networks of valleys) hình cây trải rá»™ng trên má»™t tá»· lệ diện tích lá»›n của cảnh quan địa hình. Những thung lÅ©ng đặc trÆ°ng này và sá»± phân bố của chúng hàm ý mạnh mẽ rằng chúng hình thành từ các dòng chảy mặt, kết quả của những trận mÆ°a hay tuyết rÆ¡i trong giai Ä‘oạn sá»›m của lịch sá»­ Sao Há»a. Sá»± vận Ä‘á»™ng của dòng nÆ°á»›c ngầm và sá»± thoát của nó (groundwater sapping) có thể đóng má»™t vai trò phụ quan trá»ng trong má»™t số mạng lÆ°á»›i, nhÆ°ng có lẽ lượng mÆ°a là nguyên nhân gây ra những khe rãnh trong má»i trÆ°á»ng hợp.[55] + +CÅ©ng có hàng nghìn đặc Ä‘iểm dá»c các hố va chạm và hẻm vá»±c giống vá»›i các khe xói (gully) trên Trái Äất. Những khe xói này có xu hÆ°á»›ng xuất hiện trên các cao nguyên ở bán cầu nam và gần xích đạo. Má»™t số nhà khoa há»c Ä‘á» xuất là quá trình hình thành của chúng đòi há»i có sá»± tham gia của nÆ°á»›c lá»ng, có lẽ từ sá»± tan chảy của băng,[56][57] mặc dù những ngÆ°á»i khác lại cho rằng cÆ¡ chế hình thành có sá»± tham gia của lá»›p băng cacbon Ä‘iôxít vÄ©nh cá»­u hoặc do sá»± chuyển Ä‘á»™ng của bụi khô.[58][59] Không má»™t phần biến dạng nào của các khe xói được hình thành bởi quá trình phong hóa và không có má»™t hố va chạm nào xuất hiện trên những khe xói, Ä‘iá»u này chứng tá» những đặc Ä‘iểm này còn rất trẻ, thậm chí các khe xói được hình thành chỉ trong những ngày gần đây.[57] + +Những đặc trÆ°ng địa chất khác, nhÆ° châu thổ và quạt bồi tích (alluvial fans) tồn tại trong các miệng hố lá»›n, cÅ©ng là bằng chứng mạnh vá» những Ä‘iá»u kiện nóng hÆ¡n, ẩm Æ°á»›t hÆ¡n trên bá» mặt trong má»™t số thá»i Ä‘iểm ở giai Ä‘oạn lịch sá»­ ban đầu của Sao Há»a.[60] Những Ä‘iá»u kiện này cÅ©ng yêu cầu cần sá»± xuất hiện của những hồ nÆ°á»›c lá»›n phân bố trên diện rá»™ng của bá» mặt hành tinh, mà ở những hồ này cÅ©ng có những bằng chứng Ä‘á»™c lập vá» khoáng vật há»c, trầm tích há»c và địa mạo há»c.[61] Má»™t số nhà khoa há»c thậm chí còn lập luận rằng ở má»™t số thá»i Ä‘iểm trong quá khứ, nhiá»u đồng bằng thấp ở bán cầu bắc của hành tinh đã thá»±c sá»± bị bao phủ bởi những đại dÆ°Æ¡ng sâu hàng trăm mét, mặc dù vậy vấn Ä‘á» này vẫn còn nhiá»u tranh luận.[62] + +CÅ©ng có thêm các dữ kiện khác vá» nÆ°á»›c lá»ng đã từng tồn tại trên bá» mặt Há»a Tinh nhá» việc xác định được những chất khoáng đặc biệt nhÆ° hematit và goethit, cả hai đôi khi được hình thành trong sá»± có mặt của nÆ°á»›c lá»ng.[63] Má»™t số chứng cứ trÆ°á»›c đây được cho là ám chỉ sá»± tồn tại của các đại dÆ°Æ¡ng và các con sông cổ đã bị phủ nhận bởi việc nghiên cứu từ các bức ảnh Ä‘á»™ phân giải cao từ tàu Mars Reconnaissance Orbiter.[64] Năm 2004, robot Opportunity phát hiện khoáng chất jarosit. Khoáng chất này chỉ hình thành trong môi trÆ°á»ng nÆ°á»›c a xít, đây cÅ©ng là biểu hiện của việc nÆ°á»›c lá»ng đã từng tồn tại trên Sao Há»a.[65] +Chá»m băng ở các cá»±c[sá»­a | sá»­a mã nguồn] +Tàu Viking chụp chá»m băng cá»±c bắc Sao Há»a +Chá»m băng cá»±c nam chụp bởi Mars Global Surveyor năm 2000 + +Sao Há»a có hai chá»m băng vÄ©nh cá»­u ở các cá»±c. Khi mùa đông tràn đến má»™t cá»±c, chá»m băng liên tục nằm trong bóng tối, bá» mặt bị đông lạnh và gây ra sá»± tích tụ của 25–30% bầu khí quyển thành những phiến băng khô CO2.[66] Khi vùng cá»±c chuyển sang mùa hè, các chá»m băng bị ánh sáng Mặt Trá»i chiếu liên tục, băng khô CO2 thăng hoa, dẫn đến những cÆ¡n gió khổng lồ quét qua vùng cá»±c vá»›i tốc Ä‘á»™ 400 km/h. Những hoạt Ä‘á»™ng theo mùa này đã vận chuyển lượng lá»›n bụi và hÆ¡i nÆ°á»›c, tạo ra những đám mây ti lá»›n, băng giá giống nhÆ° trên Trái Äất. Những đám mây băng giá này đã được robot Opportunity chụp vào năm 2004.[67] + +Hai chá»m băng ở cá»±c chứa chủ yếu nÆ°á»›c đóng băng. Cacbon Ä‘iôxít đóng băng thành má»™t lá»›p tÆ°Æ¡ng đối má»ng dày khoảng 1 mét trên bá» mặt chá»m băng cá»±c bắc chỉ trong thá»i gian mùa đông, trong khi chá»m băng cá»±c nam có lá»›p băng khô cacbon Ä‘iôxít vÄ©nh cá»­u dày tá»›i 8 mét.[68] Chá»m băng cá»±c bắc có Ä‘Æ°á»ng kính khoảng 1.000 kilômét trong thá»i gian mùa hè ở bán cầu bắc Sao Há»a,[69] và chứa khoảng 1,6 triệu km khối băng, và nếu trải Ä‘á»u ra thì chá»m băng này dày khoảng 2 km.[70] (Lá»›p phủ băng ở Greenland có thể tích khoảng 2,85 triệu km3.) Chá»m băng cá»±c nam có Ä‘Æ°á»ng kính khoảng 350 km và dày tá»›i 3 km.[71] Tổng thể tích của chá»m băng cá»±c nam cá»™ng vá»›i lượng băng tàng trữ ở những lá»›p kế tiếp Æ°á»›c lượng vào khoảng 1,6 triệu km3.[72] Cả hai cá»±c có những rãnh băng hà hình xoắn ốc, mà các nhà khoa há»c cho là được hình thành từ sá»± nhận được lượng nhiệt Mặt Trá»i khác nhau theo từng nÆ¡i, kết hợp vá»›i sá»± thăng hoa của băng và tích tụ của hÆ¡i nÆ°á»›c.[73][74] + +Sá»± đóng băng theo mùa ở má»™t số vùng gần chá»m băng cá»±c nam làm hình thành má»™t lá»›p băng khô (hoặc tấm) trong suốt dày 1 mét trên bá» mặt. Khi mùa xuân đến, những vùng này ấm dần lên, áp suất được tạo ra ở dÆ°á»›i lá»›p băng khô do sá»± thăng hoa của CO2, đẩy lá»›p này căng lên và cuối cùng phá bung nó ra. Äiá»u này dẫn đến sá»± hình thành những mạch phun trên Sao Há»a ở cá»±c nam (Martian geyser) chứa há»—n hợp khí CO2 vá»›i bụi hoặc cát bazan Ä‘en. Quá trình này diá»…n ra nhanh chóng, được quan sát từ các tàu quỹ đạo trong không gian vá»›i tốc Ä‘á»™ thay đổi chỉ diá»…n ra trong vài ngày, tuần hoặc tháng, má»™t tốc Ä‘á»™ rất nhanh so vá»›i các hiện tượng địa chất khác—đặc biệt đối vá»›i Sao Há»a. Ãnh sáng Mặt Trá»i xuyên qua lá»›p băng khô trong suốt, làm ấm lá»›p vật liệu tối ở bên dÆ°á»›i, tạo ra áp suất đẩy khí lên tá»›i 161 km/h qua những vị trí băng má»ng. Bên dÆ°á»›i những phiến băng, khí cÅ©ng làm xói mòn ná»n đất, giật những hạt cát lá»ng lẻo và tạo ra những hình thù giống mạng nhện bên dÆ°á»›i lá»›p băng.[75][76][77][78] +Äịa lý[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Äịa lý trên Sao Há»a + +Cao nguyên núi lá»­a (Ä‘á») và lòng chảo va chạm (xanh) chiếm phần lá»›n trong bản đồ địa hình của Sao Há»a + +Mặc dù được công nhận nhiá»u là những ngÆ°á»i đã vẽ bản đồ Mặt Trăng, Johann Heinrich Mädler và Wilhelm Beer cÅ©ng là hai ngÆ°á»i đầu tiên vẽ bản đồ Sao Há»a. HỠđã nhận ra rằng hầu hết đặc Ä‘iểm trên bá» mặt Sao Há»a là vÄ©nh cá»­u và nhỠđó đã xác định được má»™t cách chính xác chu kỳ tá»± quay của hành tinh này. Năm 1840, Mädler kết hợp 10 năm quan sát để vẽ ra tấm bản đồ địa hình đầu tiên trên Há»a Tinh. Tuy không đặt tên cho những vị trí đặc trÆ°ng, Beer và Mädler Ä‘Æ¡n giản chỉ gán chữ cho chúng; ví dụ Vịnh Meridiani (Sinus Meridiani) được đặt tên vá»›i chữ "a."[79] + +Ngày nay, các đặc Ä‘iểm trên Sao Há»a được đặt tên theo nhiá»u nguồn khác nhau. Những đặc Ä‘iểm theo suất phản chiếu quang há»c được đặt tên trong thần thoại. Các hố lá»›n hÆ¡n 60 km được đặt tên để tưởng nhá»› những nhà khoa há»c và văn chÆ°Æ¡ng và những ngÆ°á»i đã đóng góp cho việc nghiên cứu Há»a Tinh. Những hố nhá» hÆ¡n 60 km được đặt tên theo các thị trấn và ngôi làng trên Trái Äất vá»›i dân số nhá» hÆ¡n 100.000 ngÆ°á»i. Những thung lÅ©ng lá»›n được đặt tên theo từ "Sao Há»a" và các ngôi sao trong nghÄ©a của các ngôn ngữ khác nhau, những thung lÅ©ng nhỠđược đặt tên theo các con sông.[80] + +Những đặc Ä‘iểm có suất phản chiếu hình há»c lá»›n (albedo) mang nhiá»u tên gá»i cÅ©, nhÆ°ng thÆ°á»ng được thay đổi để phản ánh những hiểu biết má»›i vá» bản chất của đặc Ä‘iểm. Ví dụ, tên gá»i Nix Olympica (tuyết ở ngá»n Olympus) được đổi thành Olympus Mons (núi Olympus).[81] Bá» mặt Sao Há»a khi quan sát từ Trái Äất được chia ra thành loại vùng, vá»›i suất phản chiếu hình há»c khác nhau. Những đồng bằng nhạt màu bao phủ bởi bụi và cát trong màu Ä‘á» của sắt ôxít từng được cho là các 'lục địa' và đặt tên nhÆ° Arabia Terra (vùng đất Ả Rập) hay Amazonis Planitia (đồng bằng Amazon). Những vùng tối màu được coi là các biển, nhÆ° Mare Erythraeum (biển Erythraeum), Mare Sirenum và Aurorae Sinus. Vùng tối lá»›n nhất khi nhìn từ Trái Äất là Syrtis Major Planum.[82] Chá»m băng vÄ©nh cá»­u cá»±c bắc được đặt tên là Planum Boreum, và Planum Australe. + +Xích đạo của Sao Há»a được xác định bởi sá»± tá»± quay của nó, nhÆ°ng vị trí của kinh tuyến gốc được quy Æ°á»›c cụ thể, nhÆ° kinh tuyến Greenwich của Trái Äất. Bằng cách lá»±a chá»n má»™t Ä‘iểm bất kỳ, năm 1830 Mädler và Beer đã chá»n lấy má»™t Ä‘Æ°á»ng trong bản đồ đầu tiên của há» vá» hành tinh Ä‘á». Sau khi tàu Mariner 9 cung cấp thêm những bức ảnh vá» bá» mặt Sao Há»a năm 1972, má»™t miệng hố nhá» (sau này gá»i là Airy-0), nằm trong Sinus Meridiani ("vịnh Kinh Tuyến"), được chá»n làm định nghÄ©a cho kinh Ä‘á»™ 0,0° để phù hợp vá»›i lá»±a chá»n ban đầu của hai ông.[83] + +Do Sao Há»a không có đại dÆ°Æ¡ng và vì vậy không có 'má»±c nÆ°á»›c biển', nên các nhà khoa há»c phải lá»±a chá»n má»™t bá» mặt có cao Ä‘á»™ bằng 0, tÆ°Æ¡ng tá»± nhÆ° má»±c nÆ°á»›c biển, làm bá» mặt tham chiếu; mặt này được gá»i là areoid [84] của Sao Há»a, tÆ°Æ¡ng tá»± nhÆ° geoid của Trái Äất. Cao Ä‘á»™ 0 được xác định tại Ä‘á»™ cao mà ở đó áp suất khí quyển Há»a Tinh bằng 610,5 Pa (6,105 mbar).[85] Ãp suất này tÆ°Æ¡ng ứng vá»›i Ä‘iểm ba trạng thái của nÆ°á»›c, và bằng khoảng 0,6% áp suất tại má»±c nÆ°á»›c biển trên Trái Äất (0,006 atm).[86] Ngày nay, mặt geoid hay areoid được xác định má»™t cách chính xác nhá» những vệ tinh khảo sát trÆ°á»ng hấp dẫn của Trái Äất và Sao Há»a. +Ảnh màu gần đúng vá» miệng hố Victoria, chụp bởi robot tá»± hành Opportunity. Nó được chụp trong thá»i gian ba tuần từ 16 tháng 10 – 6 tháng 11, 2006. +Äịa hình va chạm[sá»­a | sá»­a mã nguồn] + +Äịa hình Sao Há»a có hai Ä‘iểm khác biệt rõ rệt: những vùng đồng bằng bắc bán cầu bằng phẳng do tác Ä‘á»™ng của dòng chảy dung nham ngược hẳn vá»›i vùng cao nguyên, những hố va chạm cổ ở bán cầu nam. Má»™t nghiên cứu năm 2008 cho thấy chứng cứ ủng há»™ lý thuyết Ä‘á» xuất năm 1980 rằng, khoảng bốn tá»· năm trÆ°á»›c, bán cầu bắc của Sao Há»a đã bị má»™t thiên thể kích cỡ má»™t phần mÆ°á»i đến má»™t phần ba Mặt Trăng đâm vào. Nếu Ä‘iá»u này đúng, bán cầu bắc Sao Há»a sẽ có má»™t hố va chạm vá»›i chiá»u dài tá»›i 10.600 km và rá»™ng tá»›i 8.500 km, hay gần bằng diện tích của châu Âu, châu à và lục địa Australia cá»™ng lại, và hố va chạm này sẽ vượt qua lòng chảo cá»±c nam Aitken, được coi là lòng chảo va chạm lá»›n nhất trong hệ Mặt Trá»i hiện nay.[15][16] + +Bá» mặt Sao Há»a có rất nhiá»u hố va chạm: có khoảng 43.000 hố vá»›i Ä‘Æ°á»ng kính lá»›n hÆ¡n hoặc bằng 5 km đã được phát hiện.[87] Hố lá»›n nhất được công nhận là lòng chảo va chạm Hellas, vá»›i đặc trÆ°ng suất phản chiếu hình há»c có thể nhìn thấy rõ từ Trái Äất.[88] Do Sao Há»a có kích thÆ°á»›c và khối lượng nhá» hÆ¡n, nên xác suất để má»™t vật thể va chạm vào Há»a Tinh bằng khoảng má»™t ná»­a so vá»›i Trái Äất. Sao Há»a nằm gần vành Ä‘ai tiểu hành tinh hÆ¡n, nên khả năng nó bị những vật thể từ nÆ¡i này va chạm vào là cao hÆ¡n. Hành tinh Ä‘á» cÅ©ng bị các sao chổi chu kỳ ngắn va vào vá»›i khả năng lá»›n, do những sao chổi này nằm gần bên trong quỹ đạo của Sao Má»™c.[89] Mặc dù vậy, hố va chạm trên Sao Há»a vẫn ít hÆ¡n nhiá»u so vá»›i trên Mặt Trăng, do bầu khí quyển má»ng của nó cÅ©ng có tác dụng bảo vệ những thiên thạch nhá» chạm tá»›i bá» mặt. Má»™t số hố va chạm có hình thái gợi ra rằng chúng bị ẩm Æ°á»›t sau má»™t thá»i gian thiên thạch va chạm xuống bá» mặt.[90] +Những vùng kiến tạo[sá»­a | sá»­a mã nguồn] +Ảnh chụp núi lá»­a Olympus Mons, núi cao nhất trong hệ Mặt Trá»i + +Núi lá»­a hình khiên Olympus Mons có chiá»u cao tá»›i 27 km và là ngá»n núi cao nhất trong hệ Mặt Trá»i.[91] Nó là ngá»n núi lá»­a đã tắt nằm trong vùng cao nguyên rá»™ng lá»›n Tharsis, vùng này cÅ©ng chứa má»™t vài ngá»n núi lá»­a lá»›n khác. Olympus Mons cao gấp ba lần núi Everest, vá»›i chiá»u cao trên 8,8 km. CÅ©ng chú ý rằng, ngoài những hoạt Ä‘á»™ng kiến tạo, kích thÆ°á»›c của hành tinh cÅ©ng giá»›i hạn cho chiá»u cao của những ngá»n núi trên bá» mặt của nó.[92] + +Hẻm vá»±c lá»›n Valles Marineris (tiếng Latin của thung lÅ©ng Mariner, hay còn gá»i là Agathadaemon trong những tấm bản đồ kênh đào Sao Há»a cÅ©), có chiá»u dài tá»›i 4.000 km và Ä‘á»™ sâu khoảng 7 km. Chiá»u dài của Valles Marineris tÆ°Æ¡ng Ä‘Æ°Æ¡ng vá»›i chiá»u dài của châu Âu và chiếm tá»›i má»™t phần năm chu vi của Sao Há»a. Hẻm núi Grand Canyon trên Trái Äất có chiá»u dài 446 km và sâu gần 2 km. Valles Marineris được hình thành là do sá»± trồi lên của vùng cao nguyên Tharsis làm cho lá»›p vá» hành tinh ở vùng Valles Marineris bị tách giãn và sụt xuống. Má»™t hẻm vá»±c lá»›n khác là Ma'adim Vallis (Ma'adim trong tiếng Hebrew là Sao Há»a). Nó dài 700 km và bá» rá»™ng cÅ©ng lá»›n hÆ¡n Grand Canyon vá»›i chiá»u rá»™ng 20 km và Ä‘á»™ sâu 2 km ở má»™t số vị trí. Trong quá khứ Ma'adim Vallis có thể đã bị ngập bởi nÆ°á»›c lÅ©.[93] +Hang Ä‘á»™ng[sá»­a | sá»­a mã nguồn] +Ảnh chụp từ thiết bị THEMIS vá» má»™t số hang trên bá» mặt Há»a Tinh. Những hang này được đặt tên không chính thức là (A) Dena, (B) Chloe, (C) Wendy, (D) Annie, (E) Abby (trái) và Nikki, và (F) Jeanne. + +Ảnh chụp từ thiết bị THEMIS trên tàu Mars Odyssey của NASA cho thấy khả năng có tá»›i 7 cá»­a hang Ä‘á»™ng trên sÆ°á»n núi lá»­a Arsia Mons.[94] Những hang này được đặt tên tạm thá»i theo những ngÆ°á»i phát hiện ra nó đôi khi còn được gá»i là "bảy chị em."[95] Cá»­a vào hang có bá» rá»™ng từ 100 m tá»›i 252 m và chiá»u sâu ít nhất từ 73 m tá»›i 96 m. Bởi vì ánh sáng không thể chiếu tá»›i đáy của hầu hết các hang, do vậy các nhà thiên văn cho rằng thá»±c tế chúng có chiá»u sâu lá»›n hÆ¡n và rá»™ng hÆ¡n ở trong hang so vá»›i giá trị Æ°á»›c lượng. Hang "Dena" là má»™t ngoại lệ; có thể quan sát thấy đáy của nó và vì vậy chiá»u sâu của nó bằng 130 m. Bên trong những hang này có thể giúp tránh khá»i tác Ä‘á»™ng từ những thiên thạch nhá», bức xạ cá»±c tím, gió Mặt Trá»i và những tia vÅ© trụ năng lượng cao bắn phá xuống hành tinh Ä‘á».[96] +Khí quyển[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Khí quyển Sao Há»a + +Bầu khí quyển má»ng manh của Há»a Tinh, nhìn từ chân trá»i trong bức ảnh chụp từ quỹ đạo thấp. + +Sao Há»a đã mất từ quyển của nó từ 4 tá»· năm trÆ°á»›c,[97] do vậy gió Mặt Trá»i tÆ°Æ¡ng tác trá»±c tiếp đến tầng Ä‘iện li của hành tinh, làm giảm mật Ä‘á»™ khí quyển do dần dần tÆ°á»›c Ä‘i các nguyên tá»­ ở lá»›p ngoài cùng của bầu khí quyển. Cả hai tàu Mars Global Surveyor và Mars Express đã thu nhận được những hạt bị ion hóa từ khí quyển khi chúng Ä‘ang thoát vào không gian.[97][98] So vá»›i Trái Äất, khí quyển của Sao Há»a khá loãng. Ãp suất khí quyển tại bá» mặt thay đổi từ 30 Pa (0,030 kPa) ở ngá»n Olympus Mons tá»›i 1.155 Pa (1,155 kPa) ở lòng chảo Hellas Planitia, và áp suất trung bình bằng 600 Pa (0,600 kPa).[99] Ãp suất lá»›n nhất trên Há»a Tinh bằng vá»›i áp suất ở những Ä‘iểm có Ä‘á»™ cao 35 km[100] trên bá» mặt Trái Äất. Con số này nhá» hÆ¡n 1% áp suất trung bình tại bá» mặt Trái Äất (101,3 kPa). Tỉ lệ Ä‘á»™ cao (scale height) của khí quyển Sao Há»a bằng 10,8 km,[101] lá»›n hÆ¡n của Trái Äất (bằng 6 km) bởi vì gia tốc hấp dẫn bá» mặt Sao Há»a chỉ bằng 38% của Trái Äất, và nhiệt Ä‘á»™ trung bình trong khí quyển Sao Há»a thấp hÆ¡n đồng thá»i khối lượng trung bình của các phân tá»­ cao hÆ¡n 50% so vá»›i trên Trái Äất. + +Bầu khí quyển Sao Há»a chứa 95% cacbon Ä‘iôxít, 3% nitÆ¡, 1,6% argon và chứa dấu vết của ôxy và hÆ¡i nÆ°á»›c.[6] Khí quyển khá là bụi bặm, chứa các hạt bụi Ä‘Æ°á»ng kính khoảng 1,5 µm khiến cho bầu trá»i Sao Há»a có màu vàng nâu khi đứng nhìn từ bá» mặt của nó.[102] +Bản đồ mêtan + +Mêtan đã được phát hiện trong khí quyển hành tinh Ä‘á» vá»›i tá»· lệ mol vào khoảng 30 ppb;[12][103] nó xuất hiện theo những luồng mở rá»™ng và ở những vị trí rá»i rạc khác nhau. Vào giữa mùa hè ở bán cầu bắc, luồng chính chứa tá»›i 19.000 tấn mêtan, và các nhà thiên văn Æ°á»›c lượng cÆ°á»ng Ä‘á»™ ở nguồn vào khoảng 0,6 kilôgam trên giây.[104][105] Nghiên cứu cÅ©ng cho thấy có hai nguồn chính phát ra mêtan, nguồn thứ nhất gần tá»a Ä‘á»™ 30° B, 260° T và nguồn hai gần tá»a Ä‘á»™ 0° B, 310° T.[104] Các nhà khoa há»c cÅ©ng Æ°á»›c lượng được Sao Há»a sản sinh ra khoảng 270 tấn mêtan trong má»™t năm.[104][106] + +Nghiên cứu cÅ©ng chỉ ra khoảng thá»i gian để lượng mêtan phân hủy có thể dài bằng 4 năm hoặc ngắn bằng 0,6 năm Trái Äất.[104][107] Sá»± phân hủy nhanh chóng và lượng mêtan được bổ sung ám chỉ có những nguồn còn hoạt Ä‘á»™ng Ä‘ang giải phóng lượng khí này. Những hoạt Ä‘á»™ng núi lá»­a, sao chổi rÆ¡i xuống, và khả năng có mặt của các dạng sống vi sinh vật sản sinh ra mêtan. Mêtan cÅ©ng có thể sinh ra từ quá trình vô cÆ¡ nhÆ° sá»± serpentin hóa (serpentinization)[b] vá»›i sá»± tham gia của nÆ°á»›c, cacbon Ä‘iôxít và khoáng vật olivin, nó tồn tại khá phổ biến trên Sao Há»a.[108] +Khí hậu[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Khí hậu Sao Há»a + +Trong số các hành tinh trong hệ Mặt Trá»i, các mùa trên Sao Há»a là gần giống vá»›i trên Trái Äất nhất, do sá»± gần bằng vá» Ä‘á»™ nghiêng của trục tá»± quay ở hai hành tinh. Äá»™ dài các mùa trên Há»a Tinh bằng khoảng hai lần trên Trái Äất, do khoảng cách từ Sao Há»a đến Mặt Trá»i lá»›n hÆ¡n dẫn đến má»™t năm trên hành tinh này bằng khoảng hai năm Trái Äất. Nhiệt Ä‘á»™ Sao Há»a thay đổi từ nhiệt Ä‘á»™ rất thấp -87 °C trong thá»i gian mùa đông ở các cá»±c cho đến -5 °C vào mùa hè.[46] Biên Ä‘á»™ nhiệt Ä‘á»™ lá»›n nhÆ° vậy là vì bầu khí quyển quá má»ng không thể giữ lại được nhiệt lượng từ Mặt Trá»i, do áp suất khí quyển thấp, và do tỉ số thể tích nhiệt rung riêng (thermal inertia) của đất Sao Há»a thấp.[109] Hành tinh cÅ©ng nằm xa Mặt Trá»i gấp 1,52 lần so vá»›i Trái Äất, do vậy nó chỉ nhận được khoảng 43% lượng ánh sáng so vá»›i Trái Äất.[110] + +Nếu Sao Há»a nằm vào quỹ đạo của Trái Äất, các mùa trên hành tinh này sẽ giống vá»›i trên địa cầu do Ä‘á»™ lá»›n góc nghiêng trục quay hai hành tinh giống nhau. Äá»™ lệch tâm quỹ đạo tÆ°Æ¡ng đối lá»›n của nó cÅ©ng có má»™t tác Ä‘á»™ng quan trá»ng. Khi Há»a Tinh gần cận Ä‘iểm quỹ đạo thì ở bán cầu bắc là mùa đông và bán cầu nam là mùa hè, khi nó gần viá»…n Ä‘iểm quỹ đạo thì ngược lại. Các mùa ở bán cầu nam diá»…n ra khắc nghiệt hÆ¡n so vá»›i bán cầu bắc. Nhiệt Ä‘á»™ trong mùa hè ở bán cầu nam có thể cao hÆ¡n tá»›i 30 °C (86 °F) so vá»›i mùa hè ở bán cầu bắc.[111] + +Sao Há»a cÅ©ng có những trận bão bụi lá»›n nhất trong hệ Mặt Trá»i. Chúng có thể biến đổi từ má»™t cÆ¡n bão trong má»™t vùng nhá» cho đến hình thành cÆ¡n bão khổng lồ bao phủ toàn bá»™ hành tinh. Những trận bão bụi thÆ°á»ng xuất hiện khi Sao Há»a nằm gần Mặt Trá»i và khi đó nhiệt Ä‘á»™ toàn cầu cÅ©ng tăng lên do tác Ä‘á»™ng của bão bụi.[112] +Ảnh chụp qua kính thiên văn Hubble so sánh Sao Há»a trÆ°á»›c và sau trận bão bụi bao phủ toàn cầu. +Quỹ đạo và chu kỳ quay[sá»­a | sá»­a mã nguồn] + +Khoảng cách trung bình từ Sao Há»a đến Mặt Trá»i vào khoảng 230 triệu km (1,5 AU) và chu kỳ quỹ đạo của nó bằng 687 ngày Trái Äất. Ngày mặt trá»i (viết tắt sol) trên Sao Há»a hÆ¡i dài hÆ¡n ngày Trái Äất và bằng: 24 giá», 39 phút, và 35,244 giây. Má»™t năm Sao Há»a bằng 1,8809 năm Trái Äất; hay 1 năm, 320 ngày, và 18,2 giá».[6] + +Äá»™ nghiêng trục quay bằng 25,19 Ä‘á»™ và gần bằng vá»›i Ä‘á»™ nghiêng trục quay của Trái Äất.[6] Kết quả là Sao Há»a có các mùa gần giống vá»›i Trái Äất mặc dù chúng có thá»i gian kéo dài gần gấp đôi trong má»™t năm dài hÆ¡n. Hiện tại hÆ°á»›ng của cá»±c bắc Há»a Tinh nằm gần vá»›i ngôi sao Deneb.[13] Sao Há»a đã vượt qua cận Ä‘iểm quỹ đạo vào tháng 3, 2011 và vượt qua viá»…n Ä‘iểm quỹ đạo vào tháng 2, 2012.[113] + +Sao Há»a có Ä‘á»™ lệch tâm quỹ đạo tÆ°Æ¡ng đối lá»›n vào khoảng 0,09; trong bảy hành tinh còn lại của hệ Mặt Trá»i, chỉ có Sao Thủy có Ä‘á»™ lệch tâm lá»›n hÆ¡n. Các nhà khoa há»c biết rằng trong quá khứ Sao Há»a có quỹ đạo tròn hÆ¡n so vá»›i bây giá». Cách đây khoảng 1,35 triệu năm Trái Äất, Sao Há»a có Ä‘á»™ lệch tâm gần bằng 0,002, nhá» hÆ¡n nhiá»u so vá»›i Trái Äất ngày nay.[114] Chu kỳ Ä‘á»™ lệch tâm của Sao Há»a bằng 96.000 năm Trái Äất so vá»›i chu kỳ lệch tâm của Trái Äất bằng 100.000 năm.[115] Sao Há»a cÅ©ng đã từng có chu kỳ lệch tâm bằng 2,2 triệu năm Trái Äất. Trong vòng 35.000 năm trÆ°á»›c đây, quỹ đạo Sao Há»a trở lên elip hÆ¡n do ảnh hưởng hấp dẫn từ những hành tinh khác. Khoảng cách gần nhất giữa Trái Äất và Sao Há»a sẽ giảm nhẹ dần trong vòng 25.000 năm tá»›i.[116] +ThePlanets Orbits Ceres Mars PolarView.svg Ảnh bên trái so sánh quỹ đạo của Sao Há»a và hành tinh lùn Ceres nằm trong vành Ä‘ai tiểu hành tinh, khi nhìn từ cá»±c bắc của hoàng đạo, trong khi bức ảnh bên phải nhìn từ Ä‘iểm nút lên của quỹ đạo. Các Ä‘oạn của quỹ đạo nằm ở phía nam hoàng đạo được vẽ bằng màu tối. Cận Ä‘iểm quỹ đạo (q) và viá»…n Ä‘iểm quỹ đạo (Q) được đánh dấu vá»›i ngày gần nhất thiên thể sẽ vượt qua. Quỹ đạo Sao Há»a có màu Ä‘á», Ceres có màu vàng. ThePlanets Orbits Ceres Mars.svg +Vệ tinh tá»± nhiên[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Vệ tinh tá»± nhiên của Sao Há»a, Phobos (vệ tinh), và Deimos (vệ tinh) + +Ảnh màu chụp bởi Mars Reconnaissance Orbiter – HiRISE, ngày 23 tháng 3, 2008 +Ảnh màu Deimos chụp ngày 21 tháng 2, 2009 cÅ©ng bởi tàu này (không theo tá»· lệ) + +Sao Há»a có hai vệ tinh tá»± nhiên tÆ°Æ¡ng đối nhá», Phobos và Deimos, chúng quay quanh trên những quỹ đạo khá gần hành tinh. Lý thuyết vá» tiểu hành tinh bị hành tinh Ä‘á» bắt giữ đã thu hút sá»± quan tâm từ lâu nhÆ°ng nguồn gốc của nó vẫn còn nhiá»u bí ẩn.[117] Nhà thiên văn há»c Asaph Hall đã phát hiện ra 2 vệ tinh này vào năm 1877, và ông đặt tên chúng theo tên các nhân vật trong thần thoại Hy Lạp là Phobos (Ä‘au Ä‘á»›n/sợ hãi) và Deimos (kinh hoàng/khiếp sợ), hai ngÆ°á»i con cùng tham gia những trận đánh của vị thần chiến tranh Ares. Ares trong thần thoại La Mã tên là Mars (mà ngÆ°á»i La Mã dùng tên của vị thần đó đặt tên cho Sao Há»a).[118][119] + +Nhìn từ bá» mặt Há»a Tinh, chuyển Ä‘á»™ng của Phobos và Deimos hiện lên rất khác lạ so vá»›i chuyển Ä‘á»™ng của Mặt Trăng. Phobos má»c lên ở phía tây, lặn ở phía đông, và lại má»c lên chỉ sau 11 giá». Deimos nằm ngay bên ngoài quỹ đạo đồng bộ—tại đó chu kỳ quỹ đạo bằng vá»›i chu kỳ tá»± quay của hành tinh—nó má»c lên ở phía đông nhÆ°ng rất chậm. Mặc dù chu kỳ quỹ đạo của nó bằng 30 giá», nó phải mất 2,7 ngày để lặn ở phía tây khi nó chậm dần Ä‘i vá» phía sau sá»± quay của Sao Há»a, và sau đó phải khá lâu nó má»›i má»c trở lại.[120] + +Bởi vì quỹ đạo của Phobos nằm bên trong quỹ đạo đồng bá»™, lá»±c thủy triá»u từ Há»a Tinh Ä‘ang dần dần hút vệ tinh này vá» phía nó. Trong khoảng 50 triệu năm nữa vệ tinh này sẽ đâm xuống bá» mặt Sao Há»a hoặc bị phá tan thành má»™t cái vành bụi quay quanh hành tinh.[120] + +Nguồn gốc của hai vệ tinh này vẫn chÆ°a được hiểu đầy đủ. Äặc tính suất phản chiếu hình há»c thấp và thành phần cấu tạo bằng "thiên thạch hạt chứa than" (carbonaceous chondrite) giống vá»›i tính chất của các tiểu hành tinh là má»™t trong những bằng chứng ủng há»™ lý thuyết tiểu hành tinh bị bắt. Quỹ đạo không ổn định của Phobos dÆ°á»ng nhÆ° là má»™t chứng cứ khác cho thấy nó bị bắt trong thá»i gian khá gần ngày nay. Tuy vậy, cả hai vệ tinh có quỹ đạo tròn, mặt phẳng quỹ đạo rất gần vá»›i mặt phẳng xích đạo hành tinh, lại là má»™t Ä‘iá»u không thông thÆ°á»ng cho các vật thể bị bắt và nhÆ° thế đòi há»i quá trình Ä‘á»™ng lá»±c bắt giữ 2 vệ tinh này rất phức tạp. Sá»± bồi tụ trong buổi đầu lịch sá»­ hình thành Sao Há»a cÅ©ng là má»™t khả năng khác nhÆ°ng lý thuyết này lại không giải thích được thành phần cấu tạo của 2 vệ tinh giống vá»›i các tiểu hành tinh hÆ¡n là giống vá»›i thành phần của Há»a Tinh. + +Má»™t khả năng khác đó là sá»± tham gia của má»™t vật thể thứ ba hoặc má»™t kiểu va chạm gây nhiá»…u loạn.[121] Những dữ liệu gần đây cho thấy khả năng vệ tinh Phobos có cấu trúc bên trong khá rá»—ng[122] và các nhà khoa há»c Ä‘á» xuất thành phần chính của nó là khoáng phyllosilicat và những loại khoáng vật khác đã có trên Sao Há»a,[123] và há» chỉ ra trá»±c tiếp rằng nguồn gốc của Phobos là từ những vật liệu bắn ra từ má»™t thiên thể va chạm vá»›i Sao Há»a và sau đó tích tụ lại trên quỹ đạo quanh hành tinh này,[124] tÆ°Æ¡ng tá»± nhÆ° lý thuyết giải thích cho nguồn gốc Mặt Trăng. Trong khi phổ VNIR của các vệ tinh Sao Há»a giống vá»›i phổ của các tiểu hành tinh trong vành Ä‘ai tiểu hành tinh, thì phổ hồng ngoại nhiệt (thermal infrared) của Phobos lại không hoàn toàn tÆ°Æ¡ng thích vá»›i phổ của bất kỳ lá»›p khoáng vật chondrit.[123] +Tên ÄÆ°á»ng kính +(km) Khối lượng +(kg) Bán trục +lá»›n (km) Chu kỳ +quỹ đạo (giá») Chu kỳ +trăng má»c +trung bình +(giá», ngày) +Phobos 22,2 km (27×21,6×18,8) 1,08×1016 9 377 km 7,66 11,12 giá» +(0,463 ngày) +Deimos 12,6 km (10×12×16) 2×1015 23 460 km 30,35 131 giá» +(5,44 ngày) +Sá»± sống[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: NÆ°á»›c trên Sao Há»a và Sá»± sống trên Sao Há»a + +Những hiểu biết hiện tại vá» hành tinh ở được—khả năng má»™t thế giá»›i cho sá»± sống phát triển và duy trì—ưu tiên những hành tinh có nÆ°á»›c lá»ng tồn tại trên bá» mặt của chúng. Äiá»u này trÆ°á»›c tiên đòi há»i quỹ đạo hành tinh nằm trong vùng ở được, mà đối vá»›i Mặt Trá»i hiện nay là vùng mở rá»™ng ngày bên ngoài quỹ đạo Sao Kim đến bán trục lá»›n của Sao Há»a.[125] Trong thá»i gian Sao Há»a nằm gần cận Ä‘iểm quỹ đạo thì nó cÅ©ng nằm sâu bên trong vùng ở được, nhÆ°ng bầu khí quyển má»ng của hành tinh (và do đó áp suất khí quyển thấp) không đủ để cho nÆ°á»›c lá»ng tồn tại trên diện rá»™ng và trong thá»i gian dài. Những dòng chảy trong quá khứ của nÆ°á»›c lá»ng có khả năng mang lại tính ở được cho hành tinh Ä‘á». Má»™t số chứng cứ hiện nay cÅ©ng cho thấy nếu nÆ°á»›c lá»ng có tồn tại trên bá» mặt Sao Há»a thì nó sẽ quá mặn và có tính a xít cao để có thể duy trì má»™t sá»± sống thông thÆ°á»ng.[126] + +Sao Há»a thiếu Ä‘i từ quyển và có má»™t bầu khí quyển cá»±c má»ng cÅ©ng là má»™t thách thức: sẽ có ít sá»± truyá»n nhiệt trên toàn bá» mặt hành tinh, đồng thá»i khí quyển cÅ©ng không thể ngăn được sá»± bắn phá của gió Mặt Trá»i và má»™t áp suất quá thấp để duy trì nÆ°á»›c dÆ°á»›i dạng lá»ng (thay vào đó nÆ°á»›c sẽ lập tức thăng hoa thành dạng hÆ¡i). Sao Há»a cÅ©ng gần nhÆ°, hay có lẽ hoàn toàn không còn các hoạt Ä‘á»™ng địa chất; sá»± ngÆ°ng hoạt Ä‘á»™ng của các núi lá»­a rõ ràng làm ngừng sá»± tuần hoàn của các khoáng chất và hợp chất hóa há»c giữa bá» mặt và phần bên trong hành tinh.[127] + +Nhiá»u bằng chứng ủng há»™ cho Há»a Tinh trÆ°á»›c đây đã từng có những Ä‘iá»u kiện cho sá»± sống phát triển hÆ¡n so vá»›i ngày nay, nhÆ°ng liệu các sinh vật sống có từng tồn tại hay không vẫn còn là bí ẩn. Các tàu thăm dò Viking trong giữa thập niên 1970 đã thá»±c hiện những thí nghiệm được thiết kế nhằm xác định các vi sinh vật trong đất Sao Há»a ở những vị trí chúng đổ bá»™ và đã cho kết quả khả quan, bao gồm sá»± tăng tạm thá»i của sản phẩm CO2 khi trá»™n những mẫu đất vá»›i nÆ°á»›c và khoáng chất. Dấu hiệu của sá»± sống này đã gây ra tranh cãi trong cá»™ng đồng các nhà khoa há»c, và vẫn còn là má»™t vấn Ä‘á» mở, trong đó nhà khoa há»c NASA Gilbert Levin cho rằng tàu Viking có thể đã tìm thấy sá»± sống. Má»™t cuá»™c phân tích lại những dữ liệu từ Viking, trong ánh sáng của hiểu biết hiện đại vá» dạng sống trong môi trÆ°á»ng cá»±c kỳ khắc nghiệt (extremophile forms), cho thấy các thí nghiệm trong chÆ°Æ¡ng trình Viking không đủ Ä‘á»™ phức tạp để xác định được những dạng sống này. Thậm chí những thí nghiệm này có thể đã giết chết những dạng vi sinh vật (giả thuyết là tồn tại).[128] Các thí nghiệm thá»±c hiện bởi tàu đổ bá»™ Phoenix đã chỉ ra đất ở vị trí đáp xuống có tính kiá»m pH khá cao và nó chứa magiê, natri, kali và clo.[129] Những chất dinh dưỡng trong đất có thể giúp phát triển sá»± sống những sá»± sống vẫn cần phải được bảo vệ từ những ánh sáng cá»±c tím rất mạnh.[130] + +Tại phòng thí nghiệm Trung tâm không gian Johnson, má»™t số hình dạng thú vị đã được tìm thấy trong khối vẫn thạch ALH84001. Má»™t số nhà khoa há»c Ä‘á» xuất là những hình dạng này có khả năng là hóa thạch của những vi sinh vật đã từng tồn tại trên Sao Há»a trÆ°á»›c khi vẫn thạch này bị bắn vào không gian bởi má»™t vụ chạm của thiên thạch vá»›i hành tinh Ä‘á» và gá»­i nó Ä‘i trong chuyến hành trình khoảng 15 triệu năm tá»›i Trái Äất. Äá» xuất vá» nguồn gốc phi hữu cÆ¡ cho những hình dạng này cÅ©ng đã được nêu ra.[131] + +Những lượng nhá» mêtan và fomanđêhít xác định được gần đây bởi các tàu quỹ đạo Ä‘á»u được coi là những dấu hiệu cho sá»± sống, và những hợp chất hóa há»c này cÅ©ng nhanh chóng bị phân hủy trong bầu khí quyển của Há»a Tinh.[132][133] CÅ©ng có khả năng những hợp chất này được bổ sung bởi hoạt Ä‘á»™ng địa chất hay núi lá»­a cÅ©ng nhÆ° sá»± serpentin hóa của khoáng chất (serpentinization).[108] + +Trong tÆ°Æ¡ng lai, có thể là nhiệt Ä‘á»™ bá» mặt Sao Há»a sẽ tăng từ từ, hÆ¡i nÆ°á»›c và CO2 hiện tại Ä‘ang đóng băng dÆ°á»›i regolith bá» mặt sẽ giải phóng vào khí quyển tạo nên hiệu ứng nhà kính nung nóng hành tinh cho đến khi nó đạt những Ä‘iá»u kiện tÆ°Æ¡ng Ä‘Æ°Æ¡ng vá»›i Trái Äất ngày nay, do đó cung cấp nÆ¡i trú chân tiá»m năng trong tÆ°Æ¡ng lai cho sinh vật trên Trái Äất.[134] +Quá trình thám hiểm[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Thám hiểm Sao Há»a + +Tàu đổ bá»™ Viking 1 vào tháng 2, 1978. + +Hàng tá tàu không gian, bao gồm tàu quỹ đạo, tàu đổ bá»™, và robot tá»± hành, đã được gá»­i đến Sao Há»a bởi Liên Xô, Hoa Kỳ, châu Âu, và Nhật Bản nhằm nghiên cứu bá» mặt, khí hậu và địa chất hành tinh Ä‘á». Äến năm 2008, chi phí cho vận chuyển vật liệu từ bá» mặt Trái Äất lên bá» mặt Sao Há»a có giá xấp xỉ 309.000US$ trên má»™t kilôgam.[135] + +Những tàu còn hoạt Ä‘á»™ng cho đến năm 2011 bao gồm Mars Reconnaissance Orbiter (từ 2006), Mars Express (từ 2003), 2001 Mars Odyssey (từ 2001), và trên bá» mặt là robot tá»± hành Opportunity (từ 2004). Những phi vụ kết thúc gần đây bao gồm Mars Global Surveyor (1997–2006) và Robot tá»± hành Spirit (2004–2010). + +Gần hai phần ba số tàu không gian được thiết kế đến Sao Há»a đã bị lá»—i trong giai Ä‘oạn phóng, hành trình hoặc trÆ°á»›c khi bắt đầu thá»±c hiện phi vụ hoặc không hoàn tất phi vụ của chúng, chủ yếu trong giai Ä‘oạn cuối thế ká»· 20. Sang thế ká»· 21, những thất bại trong các phi vụ đã được giảm bá»›t nhiá»u.[136] Những lá»—i trong các phi vụ chủ yếu là do vấn Ä‘á» kÄ© thuật, nhÆ° mất liên lạc hoặc sai lầm trong thiết kế, và thÆ°á»ng do hạn chế vá» tài chính và thiếu năng lá»±c trong các phi vụ.[136] Số thất bại nhiá»u nhÆ° vậy đã làm cho công chúng liên tưởng đến những Ä‘iá»u viá»…n tưởng nhÆ° "Tam giác Bermuda", "Lá»i nguyá»n" Sao Há»a, hoặc "ma cà rồng" trong thiên hà đã ăn những tàu không gian này.[136] Những thất bại gần đây bao gồm phi vụ Beagle 2 (2003), Mars Climate Orbiter (1999), và Mars 96 (1996). +Các phi vụ trong quá khứ[sá»­a | sá»­a mã nguồn] +Tàu Mars 3 trên con tem năm 1972. + +Chuyến bay ngang qua Sao Há»a thành công đầu tiên bởi tàu Mariner 4 của NASA vào ngày 14–15 tháng 7, 1965. Ngày 14 tháng 11, 1971 tàu Mariner 9 trở thành tàu không gian đầu tiên quay quanh má»™t hành tinh khác khi nó Ä‘i vào quỹ đạo quanh Sao Há»a.[137] Con tàu đầu tiên đổ bá»™ thành công xuống bá» mặt là hai tàu của Liên Xô: Mars 2 vào ngày 27 tháng 11 và Mars 3 vào ngày 2 tháng 12, 1971, nhÆ°ng cả hai đã bị mất tín hiệu liên lạc chỉ vài giây sau khi đổ bá»™ thành công. Năm 1975 NASA triển khai chÆ°Æ¡ng trình Viking bao gồm hai tàu quỹ đạo, má»—i tàu có má»™t thiết bị đổ bá»™; và cả hai đã đổ bá»™ thành công vào năm 1976. Tàu quỹ đạo Viking 1 còn hoạt Ä‘á»™ng tiếp được 6 năm, trong khi Viking 2 hoạt Ä‘á»™ng được 3 năm. Các thiết bị đổ bá»™ đã gá»­i bức ảnh màu toàn cảnh tại vị trí đổ bá»™ vá» Sao Há»a[138] và hai tàu quỹ đạo đã chụp ảnh bá» mặt hành tinh mà vẫn còn được sá»­ dụng cho tá»›i ngày nay. + +Tàu thám hiểm của Liên Xô Phobos 1 và 2 được gá»­i đến Sao Há»a năm 1988 nhằm nghiên cứu hành tinh và hai vệ tinh của nó. Phobos 1 bị mất liên lạc trong hành trình đến Sao Há»a còn Phobos 2 đã thành công khi chụp ảnh được Sao Há»a và vệ tinh Phobos nhÆ°ng đã không thành công khi gá»­i thiết bị đổ bá»™ xuống bá» mặt Phobos.[139] + +Sau thất bại của tàu quỹ đạo Mars Observer vào năm 1992, tàu Mars Global Surveyor của NASA đã Ä‘i vào quỹ đạo hành tinh này năm 1997. Phi vụ này đã thành công và kết thúc nhiệm vụ chính là vẽ bản đồ vào đầu năm 2001. Trong chÆ°Æ¡ng trình mở rá»™ng lần thứ 3, con tàu này đã bị mất liên lạc vào tháng 11 năm 2006, tổng cá»™ng nó đã hoạt Ä‘á»™ng tá»›i 10 năm trong không gian. Tàu quỹ đạo Mars Pathfinder của NASA, mang theo má»™t robot thám hiểm là Sojourner, đã đổ bá»™ xuống thung lÅ©ng Ares Vallis vào mùa hè năm 1997, và gá»­i vá» nhiá»u bức ảnh giá trị.[140] +Robot Spirit đổ bá»™ lên Sao Há»a năm 2004 +Nhìn từ tàu đổ bá»™ Phoenix năm 2008 + +Tàu đổ bá»™ Phoenix đã hạ cánh xuống vùng cá»±c bắc Sao Há»a vào ngày 25 tháng 5, 2008.[141] Cánh tay robot của nó được sá»­ dụng để đào đất và sá»± có mặt của băng nÆ°á»›c đã được xác nhận vào ngày 20 tháng 6.[142][143][143] Phi vụ này kết thúc vào ngày 10 tháng 11, 2008 sau khi liên lạc vá»›i tàu thất bại.[144] + +Tháng 11 năm 2011, phi vụ Fobos-Grunt và Huỳnh Há»a 1 được phóng lên trong chÆ°Æ¡ng trình hợp tác giữa Liên bang Nga và Trung Quốc. NhÆ°ng tàu Fobos-Grunt đã không khởi Ä‘á»™ng được Ä‘á»™ng cÆ¡ đẩy sau khi nó được phóng lên quỹ đạo quanh Trái Äất. Fobos-Grunt là phi vụ gá»­i má»™t tàu quỹ đạo đến Sao Há»a đồng thá»i phóng má»™t thiết bị đổ bá»™ xuống vệ tinh Phobos nhằm thu thập mẫu đất đá sau đó gá»­i vá» Trái Äất. Các nhà khoa há»c Nga đã không thể liên lạc được vá»›i tàu và khả năng con tàu sẽ rÆ¡i trở lại Trái Äất vào tháng 1 năm 2012. +Phi vụ hiện tại[sá»­a | sá»­a mã nguồn] + +Tàu Mars Odyssey của NASA Ä‘i vào quỹ đạo Há»a Tinh năm 2001.[145] Phổ kế tia gamma trên tàu Odyssey đã phát hiện má»™t lượng đáng kể hiÄ‘rô chỉ cách lá»›p phủ regolith ở bá» mặt có vài mét trên Sao Há»a. Lượng hiÄ‘rô này được chứa trong lá»›p băng tàng trữ ở phía dÆ°á»›i.[146] + +Tàu quỹ đạo Mars Express của cÆ¡ quan không gian châu Âu (ESA) đến Sao Há»a năm 2003. Nó mang theo thiết bị đổ bá»™ Beagle 2 nhÆ°ng đã đổ bá»™ không thành công trong quá trình Ä‘i vào bầu khí quyển và được coi là mất hoàn toàn vào tháng 2 năm 2004.[147] Äầu năm 2004, Ä‘á»™i phân tích phổ kế Fourier hành tinh (Planetary Fourier Spectrometer team) đã thông báo rằng tàu quỹ đạo đã xác định được sá»± có mặt của mêtan trong bầu khí quyển Há»a Tinh. CÆ¡ quan ESA thông báo tàu của hỠđã quan sát được hiện tượng cá»±c quang trên Sao Há»a vào tháng 6 năm 2006.[148] + +Tháng 1 năm 2004, hai tàu giống nhau của NASA thuá»™c chÆ°Æ¡ng trình robot tá»± hành thám hiểm Sao Há»a là Spirit (MER-A) và Opportunity (MER-B) đã đáp thành công xuống bá» mặt hành tinh Ä‘á». Cả hai Ä‘á»u đã hoàn thành mục tiêu của chúng. Má»™t trong những kết quả khoa há»c quan trá»ng nhất đó là chứng cứ thu được vá» sá»± tồn tại của nÆ°á»›c lá»ng trong quá khứ ở cả hai địa Ä‘iểm đổ bá»™. Bão bụi (dust devils) và gió bão đã thÆ°á»ng xuyên làm sạch các tấm pin mặt trá»i ở 2 robot tá»± hành, do vậy hai robot có Ä‘iá»u kiện để mở rá»™ng thá»i gian tìm kiếm trên Há»a Tinh.[149] Tháng 3 năm 2010 robot Spirit đã ngừng hoạt Ä‘á»™ng sau má»™t thá»i gian bị mắc kẹt trong cát. + +Ngày 10 tháng 3 năm 2006, tàu Mars Reconnaissance Orbiter (MRO) của NASA Ä‘i vào quỹ đạo hành tinh này để thá»±c hiện nhiệm vụ 2 năm khảo sát khoa há»c. Con tàu đã vẽ bản đổ địa hình và khí hậu Sao Há»a nhằm tìm những địa Ä‘iểm phù hợp cho các phi vụ đổ bá»™ trong tÆ°Æ¡ng lai. Ngày 3 tháng 3 năm 2008, các nhà khoa há»c thông báo tàu MRO đã lần đầu tiên chụp được bức ảnh vá» má»™t chuá»—i các hoạt Ä‘á»™ng sạt lở đất đá gần cá»±c bắc hành tinh.[150] + +Tàu Dawn đã bay ngang qua Sao Há»a vào tháng 2 năm 2009 để nhận thêm lá»±c đẩy hấp dẫn nhằm tăng tốc đến tiểu hành tinh Vesta và sau đó là hành tinh lùn Ceres.[151] + Wikimedia Commons có thÆ° viện hình ảnh và phÆ°Æ¡ng tiện truyá»n tải vá» Hình do Curiosity truyá»n vá» + +ChÆ°Æ¡ng trình Mars Science Laboratory, vá»›i robot tá»± hành mang tên Curiosity, được phóng lên ngày 26 tháng 12 năm 2011. Robot tá»± hành này là má»™t phiên bản lá»›n hÆ¡n và hiện đại hÆ¡n so vá»›i hai robot tá»± hành trong chÆ°Æ¡ng trình Mars Exploration Rovers, vá»›i khả năng di chuyển tá»›i 90 m/h. Nó cÅ©ng được thiết kế vá»›i khả năng thá»±c hiện thí nghiệm vá»›i các mẫu đất đá lấy từ mÅ©i khoan ở cánh tay robot hoặc thu được thành phần đất đá từ việc chiếu tia laser có tầm xa tá»›i. Robot này cÅ©ng sẽ thá»±c hiện khả năng đổ bá»™ chính xác trong vùng bán kính khoảng 20 km nằm trong hố Gale nhá» lần đầu tiên sá»­ dụng thiết bị phản lá»±c có tên "Sky crane".[152] + +Năm 2008, NASA tài trợ cho chÆ°Æ¡ng trình MAVEN, má»™t phi vụ gá»­i tàu quỹ đạo được phóng lên năm 2013 nhằm nghiên cứu bầu khí quyển của Sao Há»a. Con tàu sẽ Ä‘i vào quỹ đạo hành tinh Ä‘á» vào năm 2014.[153] +Các phi vụ trong tÆ°Æ¡ng lai[sá»­a | sá»­a mã nguồn] + +Năm 2018 cÆ¡ quan ESA có kế hoạch phóng robot tá»± hành đầu tiên của há» lên hành tinh này; robot ExoMars có khả năng khoan sâu 2 m vào đất nhằm tìm kiếm các phân tá»­ hữu cÆ¡.[154] + +NASA sẽ gá»­i robot đổ bá»™ InSight dá»±a trên thiết kế tàu đổ bá»™ Phoenix nhằm nghiên cứu cấu trúc sâu bên trong Sao Há»a vào năm 2016.[155] + +Năm 2020, má»™t robot tá»± hành có thiết kế tÆ°Æ¡ng tá»± nhÆ° Curiosity sẽ được phóng lên nhằm mục đích tiếp tục nghiên cứu hành tinh này của cÆ¡ quan NASA.[156] + +ChÆ°Æ¡ng trình MetNet hợp tác giữa Phần Lan-Nga sẽ gá»­i má»™t tàu quỹ đạo nhằm nghiên cứu cấu trúc khí quyển, khí tượng hành tinh đồng thá»i nó sẽ gá»­i má»™t thiết bị nhá» xuống bá» mặt hành tinh.[157][158] +Kế hoạch Ä‘Æ°a ngÆ°á»i lên Sao Há»a[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Phi vụ Ä‘Æ°a ngÆ°á»i lên Sao Há»a + +CÆ¡ quan ESA hi vá»ng Ä‘Æ°a ngÆ°á»i đặt chân lên Sao Há»a trong khoảng thá»i gian 2030 và 2035.[159] Quá trình này sẽ tiếp bÆ°á»›c sau khi phóng những con tàu lá»›n má»™t cách thành công đến hành tinh, mà bắt đầu từ tàu ExoMars[160] và phi vụ hợp tác NASA-ESA nhằm gá»­i vá» Trái Äất mẫu đất của Sao Há»a.[161] + +Quá trình thám hiểm có con ngÆ°á»i của Hoa Kỳ đã được định ra là má»™t mục tiêu lâu dài trong chÆ°Æ¡ng trình Viá»…n cảnh thám hiểm không gian công bố năm 2004 bởi Tổng thống George W. Bush.[162] Vá»›i kế hoạch chế tạo tàu Orion nhằm Ä‘Æ°a ngÆ°á»i trở lại Mặt Trăng trong thập niên 2020 được coi là má»™t bÆ°á»›c cÆ¡ bản trong quá trình Ä‘Æ°a ngÆ°á»i lên Sao Há»a. Ngày 28 tháng 9 năm 2007, ngÆ°á»i đứng đầu cÆ¡ quan NASA Michael D. Griffin phát biểu NASA hÆ°á»›ng mục tiêu Ä‘Æ°a ngÆ°á»i lên Sao Há»a vào năm 2037.[163] + +Mars Direct, má»™t chÆ°Æ¡ng trình thám hiểm Há»a Tinh có ngÆ°á»i lái vá»›i chi phí thấp được Ä‘á» xuất bởi Robert Zubrin, sáng lập viên của Mars Society, sẽ sá»­ dụng lá»›p tên lá»­a sức nâng lá»›n Saturn V, nhÆ° Space X Falcon X, hoặc Ares V, để bá» qua giai Ä‘oạn trên quỹ đạo quanh Trái Äất và nạp nhiên liệu trên Mặt Trăng.[164] + +MARS-500 là má»™t dá»± án hợp tác giữa Nga (Roskosmos, Viện Hàn lâm Khoa há»c Nga), Liên minh châu Âu (ESA) và Trung Quốc[165] mô phá»ng các Ä‘iá»u kiện y-sinh trên Sao Há»a nhằm nghiên cứu khả năng thích nghi của con ngÆ°á»i vá»›i hành trình dài trên 500 ngày-thá»i gian tối thiểu theo tính toán để hoàn thành chuyến bay lên hành tinh Ä‘á» và quay vá». 3 mô-Ä‘un lắp đặt năm 2006, 2 mô-Ä‘un xây dá»±ng năm 2007 và 2008[166] là nÆ¡i để 6 tình nguyện viên đã sống và làm việc cô lập trong 520 ngày.[167] +Thiên văn trên Sao Há»a[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Thiên văn trên Sao Há»a + +Phobos Ä‘i qua Mặt Trá»i, chụp từ robot Opportunity vào ngày 10 tháng 3, 2004. + +Vá»›i những tàu quỹ đạo, tàu đổ bá»™ và robot tá»± hành Ä‘ang hoạt Ä‘á»™ng trên Sao Há»a mà các nhà thiên văn há»c có thể nghiên cứu thiên văn há»c từ bầu trá»i Sao Há»a. Vệ tinh Phobos hiện lên có Ä‘Æ°á»ng kính góc chỉ bằng má»™t phần ba so vá»›i lúc Trăng tròn trên Trái Äất, trong khi đó Deimos hiện lên nhÆ° má»™t ngôi sao, chỉ hÆ¡i sáng hÆ¡n Sao Kim má»™t chút khi nhìn Sao Kim từ Trái Äất.[168] + +CÅ©ng có nhiá»u hiện tượng từng được biết trên Trái Äất mà đã được quan sát trên Sao Há»a, nhÆ° thiên thạch rÆ¡i và cá»±c quang.[148] Sá»± kiện Trái Äất Ä‘i qua Ä‘Ä©a Mặt Trá»i khi quan sát từ Sao Há»a được tiên Ä‘oán sẽ xảy ra vào ngày 10 tháng 11 năm 2084.[169] TÆ°Æ¡ng tá»±, sá»± kiện Sao Thủy và Sao Kim Ä‘i qua Ä‘Ä©a Mặt Trá»i khi nhìn từ Sao Há»a cÅ©ng được tiên Ä‘oán. Do Ä‘Æ°á»ng kính góc của hai vệ tinh Phobos và Deimos quá nhá» cho nên sẽ chỉ có hiện tượng nhật thá»±c má»™t phần (hay Ä‘i ngang qua) trên Sao Há»a.[170][171] +Quan sát Sao Há»a[sá»­a | sá»­a mã nguồn] +Chuyển Ä‘á»™ng nghịch hành biểu kiến của Sao Há»a vào năm 2003 khi nhìn từ Trái Äất + +Bởi vì quỹ đạo Sao Há»a có Ä‘á»™ lệch tâm đáng kể cho nên Ä‘á»™ sáng biểu kiến của nó ở vị trí xung đối vá»›i Mặt Trá»i có thể thay đổi trong khoảng −3,0 đến −1,4. Äá»™ sáng nhá» nhất của nó tÆ°Æ¡ng ứng vá»›i cấp sao +1,6 khi hành tinh ở vị trí giao há»™i vá»›i Mặt Trá»i.[7] Sao Há»a khi quan sát qua kính thiên văn nhá» thÆ°á»ng hiện lên có màu vàng, cam hay Ä‘á» nâu; trong khi màu sắc thá»±c sá»± của Sao Há»a gần vá»›i màu bÆ¡, và màu Ä‘á» là do khí quyển Sao Há»a chứa rất nhiá»u bụi; bên dÆ°á»›i là bức ảnh mà robot Spirit chụp được trên Sao Há»a vá»›i màu nâu-xanh nhạt, màu bùn vá»›i những tảng đá xám-xanh và cát màu Ä‘á» nhạt.[172] Khi hành tinh hÆ°á»›ng vá» phía gần Mặt Trá»i, nó sẽ rất khó quan sát trong má»™t vài tháng bởi ánh sáng mạnh của Mặt Trá»i. Ở những thá»i Ä‘iểm thích hợp—khoảng thá»i gian 15 hoặc 17 năm, và luôn luôn là giữa cuối tháng 7 cho đến tháng 9—có thể quan sát những chi tiết trên bá» mặt Sao Há»a qua kính thiên văn nghiệp dÆ°. Thậm chí đối vá»›i các kính thiên văn Ä‘á»™ phóng đại nhá», vẫn có thể quan sát thấy các chá»m băng ở cá»±c.[173] + +Khi Sao Há»a tiến gần vào vị trí xung đối nó bắt đầu vào giai Ä‘oạn của chuyển Ä‘á»™ng nghịch hành biểu kiến khi quan sát từ Trái Äất, có nghÄ©a là nó dÆ°á»ng nhÆ° di chuyển ngược lại thành vòng tròn trên ná»n bầu trá»i. Khoảng thá»i gian diá»…n ra chuyển Ä‘á»™ng nghịch hành trong khoảng 72 ngày và Sao Há»a đạt đến Ä‘á»™ sáng biểu kiến cá»±c đại vào giữa giai Ä‘oạn này.[174] +Ảnh chụp Mặt Trá»i lặn ở hố va chạm Gusev chụp bởi robot Spirit vào ngày 19 tháng 5, 2005. +Những lần tiếp cận gần nhất[sá»­a | sá»­a mã nguồn] +Gần tÆ°Æ¡ng đối[sá»­a | sá»­a mã nguồn] + +Khi Sao Há»a ở gần vị trí xung đối vá»›i Mặt Trá»i thì đây là thá»i Ä‘iểm hành tinh nằm gần vá»›i Trái Äất nhất. Giai Ä‘oạn xung đối có thể kéo dài trong khoảng 8½ ngày xung quanh thá»i Ä‘iểm hai hành tinh nằm gần nhau. Khoảng cách lúc hai hành tinh tiếp cận gần nhau nhất có thể thay đổi trong khoảng từ 54[175] đến 103 triệu km do quỹ đạo của hai hành tinh có hình elip, và do đó cÅ©ng làm thay đổi Ä‘Æ°á»ng kính góc của Sao Há»a khi nhìn từ Trái Äất.[176] Lần xung đối gần đây nhất (2011) diá»…n ra vào ngày 29 tháng 1 năm 2010. Lần tiếp theo sẽ xảy ra vào ngày 3 tháng 3 năm 2012 ở khoảng cách khoảng 100 triệu km.[177] Thá»i gian trung bình giữa hai lần xung đối, hay chu kỳ giao há»™i của hành tinh, là 780 ngày nhÆ°ng số ngày chính xác giữa hai lần xung đối kế tiếp có thể thay đổi từ 764 đến 812 ngày.[178] + +Khi Há»a Tinh vào thá»i kỳ xung đối nó cÅ©ng bắt đầu vào giai Ä‘oạn chuyển Ä‘á»™ng biểu kiến nghịch hành vá»›i thá»i gian khoảng 72 ngày. +Lần tiếp cận gần nhất[sá»­a | sá»­a mã nguồn] +Vị trí xung đối của hành tinh Ä‘á» trong thá»i gian 2003–2018, khi nhìn trên mặt phẳng hoàng đạo vá»›i Trái Äất ở chính giữa. + +Sao Há»a nằm gần Trái Äất nhất trong vòng khoảng 60.000 năm qua là vào thá»i Ä‘iểm 9:51:13 UT ở khoảng cách 55.758.006 km (0,372719 AU), Ä‘á»™ sáng biểu kiến đạt −2,88. Thá»i Ä‘iểm này xảy ra khi Sao Há»a đã vào ở vị trí xung đối được má»™t ngày và khoảng ba ngày từ cận Ä‘iểm quỹ đạo làm cho Sao Há»a dá»… dàng nhìn thấy từ Trái Äất. Lần cuối hành tinh Ä‘á» nằm gần nhất vá»›i Trái Äất được Æ°á»›c tính đã diá»…n ra vào ngày 12 tháng 9 năm 57.617 trÆ°á»›c Công nguyên, lần tiếp theo được Æ°á»›c tính diá»…n ra vào năm 2287.[179] Ká»· lục tiếp cận gần nhất năm 2003 chỉ hÆ¡i bé hÆ¡n so vá»›i má»™t số lần tiếp cận gần nhất trong thá»i gian gần đây. Ví dụ, khoảng cách nhá» nhất giữa hai hành tinh xảy ra vào ngày 22 tháng 8 năm 1924 là 0,37285 AU, và vào ngày 24 tháng 8 năm 2208 sẽ là 0,37279 AU.[115] + +Trong năm 2003, và những năm sau, đã có má»™t trò chÆ¡i khăm phát tán trên internet nói rằng năm 2003 Sao Há»a sẽ nằm gần Trái Äất nhất trong hàng nghìn năm qua và nó sẽ hiện lên to nhÆ° Mặt Trăng trên bầu trá»i.[180] +Lịch sá»­ quan sát Sao Há»a[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Lịch sá»­ quan sát Sao Há»a + +Lịch sá»­ quan sát Sao Há»a được đánh dấu bởi những lần hành tinh này ở vị trí xung đối, khi nó nằm gần Trái Äất và vì vậy dá»… dàng có thể quan sát bằng mắt thÆ°á»ng, và những lần xung đối xảy ra khoảng 2 năm má»™t lần. Những lần xảy ra xung đối nổi bật hÆ¡n cả trong lịch sá»­ đó là khoảng thá»i gian cách nhau 15 đến 17 năm khi lần xung đối xảy ra trùng hoặc gần vá»›i cận Ä‘iểm quỹ đạo của Há»a Tinh, Ä‘iá»u này càng làm cho nó dá»… dàng quan sát được từ Trái Äất. + +Sá»± tồn tại của Sao Há»a nhÆ° má»™t thiên thể Ä‘i lang thang trên bầu trá»i đêm đã được ghi lại bởi những nhà thiên văn há»c Ai Cập cổ đại và vào năm 1534 TCN hỠđã nhận thấy được chuyển Ä‘á»™ng nghịch hành biểu kiến của hành tinh Ä‘á».[181] Trong lịch sá»­ của đế chế Babylon lần hai, các nhà thiên văn Babylon đã quan sát má»™t cách có hệ thống và ghi chép thÆ°á»ng xuyên vị trí của các hành tinh. Äối vá»›i Sao Há»a, há» biết rằng hành tinh này thá»±c hiện được 37 chu kỳ giao há»™i, hay Ä‘i được 42 vòng trên vòng hoàng đạo, trong khoảng 79 năm Trái Äất. Há» cÅ©ng đã phát minh ra phÆ°Æ¡ng pháp số há»c nhằm hiệu chỉnh những Ä‘á»™ lệch nhá» trong việc tiên Ä‘oán vị trí của các hành tinh.[182][183] + +Trong thế ká»· thứ tÆ° trÆ°á»›c Công nguyên, Aristoteles đã phát hiện ra Sao Há»a biến mất đằng sau Mặt Trăng trong má»™t lần che khuất, và ông nhận xét rằng hành tinh này phải nằm xa hÆ¡n Mặt Trăng.[184] Ptolemaeus, nhà thiên văn Hy Lạp cổ đại ở Alexandria,[185] đã cố gắng giải quyết vấn Ä‘á» chuyển Ä‘á»™ng quỹ đạo của Há»a Tinh. Mô hình của Ptolemaeus và tập hợp những nghiên cứu của ông vá» thiên văn há»c đã được trình bày trong bản thảo nhiá»u tập mang tên Almagest, và nó đã trở thành ná»™i dung được phổ biến trong thiên văn há»c phÆ°Æ¡ng Tây trong gần mÆ°á»i bốn thế ká»· sau.[186] Các tÆ° liệu lịch sá»­ Trung Hoa cổ đại cho thấy Sao Há»a được các nhà thiên văn Trung Hoa cổ đại biết đến không muá»™n hÆ¡n thế ká»· thứ tÆ° trÆ°á»›c Công nguyên.[187] Ở thế ká»· thứ năm, trong tài liệu ghi chép thiên văn của Ấn Äá»™ mang tên Surya Siddhanta đã ghi lại Æ°á»›c tính Ä‘Æ°á»ng kính Sao Há»a của những nhà thiên văn Ấn Äá»™.[188] + +Trong thế ká»· thứ mÆ°á»i bảy, Tycho Brahe đã Ä‘o thị sai ngày của Sao Há»a và dữ liệu này được Johannes Kepler sá»­ dụng để tính toán sÆ¡ bá»™ vá» khoảng cách tÆ°Æ¡ng đối đến hành tinh Ä‘á».[189] Khi kính thiên văn được phát minh ra và trở lên phổ biến hÆ¡n, thị sai ngày của Sao Há»a đã được Ä‘o lại cẩn thận trong ná»— lá»±c nhằm xác định khoảng cách Trái Äất-Mặt Trá»i. Ná»— lá»±c này lần đầu tiên được thá»±c hiện bởi Giovanni Domenico Cassini năm 1672. Những Ä‘o đạc thị sai trong thá»i kỳ này đã bị cản trở bởi chất lượng của dụng cụ quan sát.[190] Ngày 13 tháng 10 năm 1590, sá»± kiện Sao Há»a bị Sao Kim che khuất đã được Michael Maestlin ở Heidelberg ghi nhận.[191] Năm 1610, Galileo Galilei là ngÆ°á»i đầu tiên đã quan sát Sao Há»a qua má»™t kính thiên văn.[192] NgÆ°á»i đầu tiên cố gắng vẽ ra tấm bản đồ Sao Há»a thể hiện những đặc Ä‘iểm trên bá» mặt của nó là nhà thiên văn há»c ngÆ°á»i Hà Lan Christiaan Huygens.[193] +"Kênh đào" Sao Há»a[sá»­a | sá»­a mã nguồn] +Bản đồ Sao Há»a của Giovanni Schiaparelli. +Phác há»a bản đồ Sao Há»a bởi Lowell trÆ°á»›c năm 1914. +Bản đồ Sao Há»a chụp bởi kính thiên văn không gian Hubble khi hành tinh ở gần vị trí xung đối năm 1999. + + Bài chi tiết: Kênh đào Sao Há»a + +Cho đến thế ká»· 19, Ä‘á»™ phóng đại của các kính thiên văn đã đạt đến mức cần thiết cho việc phân giải các đặc Ä‘iểm trên bá» mặt hành tinh Ä‘á». Trong tháng 9 năm 1877, sá»± kiện Sao Há»a tiến đến vị trí xung đối đã được dá»± Ä‘oán xảy ra vào ngày 5 tháng 9. Nhá» vào sá»± kiện này, nhà thiên văn ngÆ°á»i Italia Giovanni Schiaparelli sá»­ dụng kính thiên văn 22 cm ở Milano nhằm quan sát hành tinh này để vẽ ra tấm bản đồ chi tiết đầu tiên vá» Sao Há»a mà ông thấy qua ống kính. Trên bản đồ này có đánh dấu những đặc Ä‘iểm mà ông gá»i là canali, mặc dù sau đó được chỉ ra là những ảo ảnh quang há»c. Những canali được vẽ là những Ä‘Æ°á»ng thẳng trên bá» mặt Sao Há»a và ông đặt tên của chúng theo tên của những con sông nổi tiếng trên Trái Äất. Trong ngôn ngữ của ông, canali có nghÄ©a là "kênh đào" hoặc "rãnh", và được dịch má»™t cách hiểu nhầm sang tiếng Anh là "canals" (kênh đào).[194][195] + +Ảnh hưởng bởi những quan sát này, nhà Äông phÆ°Æ¡ng há»c Percival Lowell đã xây dá»±ng má»™t đài quan sát mà sau này mang tên đài quan sát Lowell vá»›i hai kính thiên văn Ä‘Æ°á»ng kính 300 và 450 mm. Äài quan sát này được sá»­ dụng để quan sát Sao Há»a trong lần xung đối hiếm có vào năm 1894 và những lần xung đối thông thÆ°á»ng vá» sau. Lowell đã xuất bản má»™t vài cuốn sách vá» Há»a Tinh và Ä‘á» cập đến sá»± sống trên hành tinh này, chúng đã có những ảnh hưởng nhất định đối vá»›i công chúng vá» hành tinh này.[196] Äặc Ä‘iểm canali cÅ©ng đã được má»™t số nhà thiên văn há»c tìm thấy, nhÆ° Henri Joseph Perrotin và Louis Thollon ở Nice, nhá» sá»­ dụng má»™t trong những kính thiên văn lá»›n nhất thá»i bấy giá».[197][198] + +Sá»± thay đổi theo mùa (bao gồm sá»± thu hẹp diện tích của các chá»m băng vùng cá»±c và những miá»n tối hình thành trong mùa hè trên Há»a Tinh) kết hợp vá»›i ý niệm vá» kênh đào đã dẫn đến những phá»ng Ä‘oán vá» sá»± sống trên Sao Há»a, và nhiá»u ngÆ°á»i có niá»m tin lâu dài rằng Sao Há»a có những vùng biển rá»™ng lá»›n và những cánh đồng bạt ngàn. Tuy nhiên những kính thiên văn thá»i này không đủ Ä‘á»™ phân giải đủ lá»›n để chứng minh hay bác bá» những phá»ng Ä‘oán này. Khi những kính thiên văn lá»›n hÆ¡n ra Ä‘á»i, những canali thẳng, ngắn hÆ¡n được quan sát rõ hÆ¡n. Khi Camille Flammarion thá»±c hiện quan sát năm 1909 vá»›i kính Ä‘Æ°á»ng kính 840 mm, những địa hình không đồng Ä‘á»u được nhận ra nhÆ°ng không má»™t đặc Ä‘iểm canali được trông thấy.[199] + +Thậm chí những bài báo trong thập niên 1960 vá» sinh há»c vÅ© trụ trên Sao Há»a, nhiá»u tác giả đã giải thích theo khía cạnh sá»± sống cho những đặc Ä‘iểm thay đổi theo mùa trên hành tinh này. Những kịch bản cụ thể vá» quá trình trao đổi chất và chu trình hóa há»c cho những hệ sinh thái cÅ©ng đã được xuất bản.[200] + +Cho đến khi những tàu vÅ© trụ viếng thăm hành tinh này trong chÆ°Æ¡ng trình Mariner của NASA trong thập niên 1960 thì những bí ẩn này má»›i được sáng tá». Những chấp nhận chung vá» má»™t hành tinh đã chết được khẳng định trong thí nghiệm nhằm xác định sá»± sống của tàu Viking và những ảnh chụp tại nÆ¡i nó đổ bá»™.[201] + +Má»™t vài bản đồ vá» Sao Há»a đã được lập ra nhá» sá»­ dụng các dữ liệu thu được từ các phi vụ này, nhÆ°ng cho đến tận phi vụ của tàu Mars Global Surveyor, phóng lên vào năm 1996 và ngừng hoạt Ä‘á»™ng năm 2006, đã mang lại những chi tiết đầy đủ nhất vá» bản đồ địa hình, từ trÆ°á»ng và sá»± phân bố khoáng chất trên bá» mặt.[202] Những bản đồ vá» Sao Há»a hiện nay đã được cung cấp trên má»™t số dịch vụ trá»±c tuyến, nhÆ° Google Mars. +Trong văn hóa[sá»­a | sá»­a mã nguồn] + + Bài chi tiết: Sao Há»a trong văn hóa + +Sao Há»a trong ngôn ngữ phÆ°Æ¡ng Tây được mang tên của vị thần chiến tranh trong thần thoại. Từ há»a cÅ©ng là tên của má»™t trong năm yếu tố của ngÅ© hành trong triết há»c cổ Trung Hoa. Biểu tượng Sao Há»a, gồm má»™t vòng tròn vá»›i má»™t mÅ©i tên chỉ ra ngoài, cÅ©ng là biểu tượng cho giống Ä‘á»±c. + +à tưởng cho rằng trên Sao Há»a có những sinh vật có trí thông minh đã xuất hiện từ cuối thế ká»· 19. Quan sát các "canali" (kênh đào) của Giovanni Schiaparelli kết hợp vá»›i cuốn sách của Percival Lowell vỠý tưởng này đã làm cÆ¡ sở cho những bàn luận vá» má»™t hành tinh Ä‘ang hạn hát, lạnh lẽo, má»™t thế giá»›i chết vá»›i ná»n văn minh trên đó Ä‘ang xây dá»±ng những hệ thống tÆ°á»›i tiêu.[203] + +Nhiá»u quan sát khác và những lá»i tuyên bố bởi những ngÆ°á»i có ảnh hưởng đã làm dấy lên cái gá»i là "CÆ¡n sốt Sao Há»a".[204] Năm 1899, khi Ä‘ang nghiên cứu Ä‘á»™ ồn vô tuyến trong khí quyển bằng cách sá»­ dụng máy thu ở phòng thí nghiệm Colorado Springs, nhà sáng chế Nikola Tesla đã nhận ra sá»± lặp lại trong tín hiệu mà sau đó ông Ä‘oán có thể là tín hiệu liên lạc vô tuyến đến từ má»™t hành tinh khác, và khả năng là Sao Há»a. Năm 1901, trong má»™t cuá»™c phá»ng vấn, Tesla nói: + + Ở thá»i Ä‘iểm sau khi có má»™t ý nghÄ© lóe lên trong đầu tôi rằng những nhiá»…u loạn mà tôi đã thu được có thể là do sá»± Ä‘iá»u khiển từ má»™t ná»n văn minh. Mặc dù tôi không thể giải mã ý nghÄ©a của chúng, nhÆ°ng tôi không thể nghÄ© rằng đó chỉ hoàn toàn là sá»± ngẫu nhiên. Cảm giác tăng dần trong tôi rằng lần đầu tiên tôi đã nghe được lá»i chào từ má»™t hành tinh khác.[205] + +à nghÄ© của Tesla nhận được sá»± ủng há»™ từ Lord Kelvin, ông này khi viếng thăm Hoa Kỳ năm 1902, đã nói là ông nghÄ© rằng những tín hiệu mà Tesla thu được là do từ hành tinh Ä‘á» gá»­i đến Hoa Kỳ.[206] Kelvin "nhấn mạnh" từ chối lá»i nói này ngay trÆ°á»›c khi ông rá»i Hoa Kỳ: "Cái mà tôi thá»±c sá»± nói rằng những cÆ° dân Sao Há»a, nếu có, sẽ không nghi ngá» khi há» có thể nhìn thấy New York, đặc biệt từ ánh sáng đèn Ä‘iện."[207] + +Trong má»™t bài viết trên tá» New York Times năm 1901, Edward Charles Pickering, giám đốc Äài quan sát Harvard College, Ä‘Æ°a tin hỠđã nhận được má»™t Ä‘iện tín từ Äài quan sát Lowell ở Arizona vá»›i ná»™i dung xác nhận là dÆ°á»ng nhÆ° ná»n văn minh trên Sao Há»a Ä‘ang cố liên lạc vá»›i Trái Äất.[208] + + Äầu tháng 12 năm 1900, chúng tôi nhận được bức Ä‘iện tín từ Äài quan sát Lowell ở Arizona rằng má»™t luồng ánh sáng chiếu từ Sao Há»a (đài quan sát Lowell luôn dành sá»± quan tâm đặc biệt đến Sao Há»a) kéo dài trong khoảng 70 phút. Tôi đã gá»­i những thông tin này sang châu Âu cÅ©ng nhÆ° bản sao của Ä‘iện tín đến khắp nÆ¡i trên đất nÆ°á»›c này. Những ngÆ°á»i quan sát đã rất cẩn thận, đáng tin và do vậy không có lý do gì để nghi ngá» vá» sá»± tồn tại của tia sáng. NgÆ°á»i ta cho rằng nó bắt nguồn từ má»™t vị trí địa lý nổi tiếng trên Sao Há»a. Tất cả là thế. Bây giá» câu chuyện đã lan ra trên toàn thế giá»›i. Ở châu Âu, ngÆ°á»i ta nói rằng tôi đã liên lạc vá»›i ngÆ°á»i Sao Há»a và đủ má»i thông tin cÆ°á»ng Ä‘iệu đã xuất hiện. Cho dù thứ ánh sáng đó là gì, chúng ta cÅ©ng không biết ý nghÄ©a của nó. Không ai có thể nói được đó là từ má»™t ná»n văn minh hay không phải. Nó tuyệt đối không thể giải thích được.[208] + +Pickering sau đó Ä‘á» xuất lắp đặt má»™t loạt tấm gÆ°Æ¡ng ở Texas nhằm thu các tín hiệu từ Sao Há»a.[209] + +Trong những thập ká»· gần đây, nhá» những tấm bản đồ Ä‘á»™ phân giải cao vá» bá» mặt Sao Há»a, đặc biệt từ tàu Mars Global Surveyor và Mars Reconnaissance Orbiter, cho thấy không há» có má»™t dấu hiệu của sá»± sống có trí tuệ trên hành tinh này, mặc dù những phá»ng Ä‘oán giả khoa há»c vá» sá»± sống có trí thông minh trên Sao Há»a vẫn xuất hiện từ những biên tập viên nhÆ° Richard C. Hoagland. Nhá»› lại những tranh luận trÆ°á»›c đây vỠđặc Ä‘iểm canali, xuất hiện má»™t số suy Ä‘oán vá» những hình tượng kích cỡ nhá» trên má»™t số bức ảnh từ tàu không gian, nhÆ° 'kim tá»± tháp' và 'khuôn mặt trên Sao Há»a'. Nhà thiên văn há»c hành tinh Carl Sagan đã viết: + + Sao Há»a đã trở thành má»™t sân khấu cho những vở kịch thần thoại mà ở đó chúng ta chiếu lên những hi vá»ng và sợ hãi của chúng ta trên Trái Äất.[195] + +Minh há»a sinh vật ba chân Há»a Tinh trong tác phẩm ấn bản tiếng Pháp xuất bản năm 1906, The War of the Worlds của nhà văn H.G. Wells. + +Các miêu tả Sao Há»a trong tiểu thuyết đã bị kích thích bởi màu đỠđặc trÆ°ng của nó và bởi những suy Ä‘oán mang tính khoa há»c ở thế ká»· 19 vá» các Ä‘iá»u kiện bá» mặt hành tinh không những duy trì cho sá»± sống mà còn tồn tại ná»n văn minh trên đó.[210] Äã có nhiá»u những tác phẩm khoa há»c viá»…n tưởng được ra Ä‘á»i, trong số đó có tác phẩm The War of the Worlds của H. G. Wells xuất bản năm 1898, vá»›i ná»™i dung vá» những sinh vật Sao Há»a Ä‘ang cố gắng thoát khá»i hành tinh Ä‘ang chết dần và chúng xuống xâm lược Äịa cầu. Sau đó, ngày 30 tháng 10 năm 1938, phát thanh viên Orson Welles đã dá»±a vào tác phẩm này và gây ra trò đùa trên đài phát thanh làm cho nhiá»u thính giả thiếu hiểu biết bị hiểu nhầm.[211] + +Những tác phẩm có tính ảnh hưởng bao gồm The Martian Chronicles của Ray Bradbury, trong đó cuá»™c thám hiểm của con ngÆ°á»i đã trở thành má»™t tai nạn phá hủy ná»n văn minh Há»a Tinh, Barsoom của Edgar Rice Burroughs, tiểu thuyết Out of the Silent Planet của C. S. Lewis (1938),[212] và má»™t số câu chuyện của Robert A. Heinlein trong những năm 60.[213] + +Tác giả Jonathan Swift đã từng miêu tả vá» các Mặt Trăng của Sao Há»a, khoảng 150 năm trÆ°á»›c khi chúng được nhà thiên văn há»c Asaph Hall phát hiện ra. J.Swift đã miêu tả khá chính xác và chi tiết vá» quỹ đạo của chúng trong chÆ°Æ¡ng 19 của tiểu thuyết Gulliver's Travels.[214] + +Má»™t nhân vật truyện tranh thể hiện trí thông minh Sao Há»a, Marvin, đã xuất hiện trên truyá»n hình năm 1948 trong bá»™ phim hoạt hình Looney Tunes của hãng Warner Brothers, và nó vẫn còn tiếp tục xuất hiện trong văn hóa đại chúng phÆ°Æ¡ng Tây hiện nay.[215] + +Sau khi các tàu Mariner và Viking gá»­i vá» các bức ảnh chụp Há»a Tinh, má»™t thế giá»›i không có sá»± sống và những kênh đào, thì những quan niệm vá» ná»n văn minh Sao Há»a ngay lập tức bị từ bá», và thay vào đó là những miêu tả vá» viá»…n cảnh con ngÆ°á»i sẽ đến khai phá hành tinh này, nổi tiếng nhất có lẽ là tác phẩm bá»™ ba Sao Há»a của Kim Stanley Robinson. Những suy Ä‘oán giả khoa há»c vá» Khuôn mặt trên Sao Há»a và những địa hình bí ẩn khác được chụp bởi các tàu quỹ đạo đã trở thành bối cảnh phổ biến cho những tác phẩm khoa há»c viá»…n tưởng, đặc biệt trong phim ảnh.[216] + +Bối cảnh con ngÆ°á»i trên Sao Há»a đấu tranh giành Ä‘á»™c lập khá»i Trái Äất cÅ©ng là má»™t ná»™i dung chính trong tiểu thuyết của Greg Bear cÅ©ng nhÆ° bá»™ phim Total Recall (dá»±a trên câu chuyện ngắn của Philip K. Dick) và sê ri truyá»n hình Babylon 5. Má»™t số trò chÆ¡i cÅ©ng sá»­ dụng bối cảnh này, bao gồm Red Faction và Zone of the Enders. Sao Há»a (và vệ tinh của nó) cÅ©ng xuất hiện trong video game nhượng quyá»n thÆ°Æ¡ng mại Doom và Martian Gothic. diff --git a/xpcom/tests/moz.build b/xpcom/tests/moz.build new file mode 100644 index 0000000000..461432acfb --- /dev/null +++ b/xpcom/tests/moz.build @@ -0,0 +1,57 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +TEST_DIRS += [ + "gtest", +] + +if CONFIG["OS_ARCH"] == "WINNT": + TEST_DIRS += ["windows"] + +if CONFIG["OS_TARGET"] == "Linux": + CppUnitTests( + [ + "TestMemoryPressureWatcherLinux", + ] + ) + +EXPORTS.testing += [ + "TestHarness.h", +] + +test_progs = [ + "TestArguments", + "TestBlockingProcess", + "TestPRIntN", + "TestQuickReturn", + "TestUnicodeArguments", +] +SimplePrograms(test_progs) + +USE_LIBS += ["mozglue"] + +XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"] + +if CONFIG["COMPILE_ENVIRONMENT"]: + TEST_HARNESS_FILES.xpcshell.xpcom.tests.unit += [ + "!%s%s" % (f, CONFIG["BIN_SUFFIX"]) for f in test_progs + ] + +XPIDL_MODULE = "xpcomtest" +XPIDL_SOURCES += [ + "NotXPCOMTest.idl", +] + +LOCAL_INCLUDES += [ + "../base", + "../ds", +] + +RESOURCE_FILES += [ + "test.properties", +] + +CRASHTEST_MANIFESTS += ["crashtests/crashtests.list"] diff --git a/xpcom/tests/resources.h b/xpcom/tests/resources.h new file mode 100644 index 0000000000..7ba590e475 --- /dev/null +++ b/xpcom/tests/resources.h @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ +#ifndef resources_h___ +#define resources_h___ + +#define TIMER_1SECOND 40000 +#define TIMER_5SECOND 40001 +#define TIMER_10SECOND 40002 + +#define TIMER_1REPEAT 40003 +#define TIMER_5REPEAT 40004 +#define TIMER_10REPEAT 40005 + +#define TIMER_CANCEL 40006 +#define TIMER_EXIT 40010 + +#endif /* resources_h___ */ diff --git a/xpcom/tests/test.properties b/xpcom/tests/test.properties new file mode 100644 index 0000000000..19cae97028 --- /dev/null +++ b/xpcom/tests/test.properties @@ -0,0 +1,14 @@ +# 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/. +1=1 + 2=2 +3 =3 + 4 =4 +5=5 +6= 6 +7=7 +8= 8 +# this is a comment +9=this is the first part of a continued line \ + and here is the 2nd part diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist b/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist new file mode 100644 index 0000000000..8388fa2a55 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + SmallApp + CFBundleIdentifier + com.yourcompany.SmallApp + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + SmallApp + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.0 + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp b/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp new file mode 100755 index 0000000000..c821003d34 Binary files /dev/null and b/xpcom/tests/unit/data/SmallApp.app/Contents/MacOS/SmallApp differ diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo b/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo new file mode 100644 index 0000000000..bd04210fb4 --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings new file mode 100644 index 0000000000..5e45963c38 Binary files /dev/null and b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/InfoPlist.strings differ diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib new file mode 100644 index 0000000000..59f8803c5d --- /dev/null +++ b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/designable.nib @@ -0,0 +1,343 @@ + + + + 0 + 9E17 + 644 + 949.33 + 352.00 + + YES + + + + YES + com.apple.InterfaceBuilderKit + com.apple.InterfaceBuilder.CocoaPlugin + + + YES + + NSApplication + + + FirstResponder + + + NSApplication + + + AMainMenu + + YES + + + NewApplication + + 1048576 + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + NewApplication + + YES + + + Quit NewApplication + q + 1048576 + 2147483647 + + + + + _NSAppleMenu + + + + + File + + 1048576 + 2147483647 + + + + + + Edit + + 1048576 + 2147483647 + + + + + + Format + + 1048576 + 2147483647 + + + + + + View + + 1048576 + 2147483647 + + + + + + Window + + 1048576 + 2147483647 + + + + + + Help + + 1048576 + 2147483647 + + + + + _NSMainMenu + + + + + YES + + + terminate: + + + + 369 + + + + + YES + + 0 + + YES + + + + + + -2 + + + RmlsZSdzIE93bmVyA + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + YES + + + + + + + + + + MainMenu + + + 19 + + + YES + + + + + 56 + + + YES + + + + + + 103 + + + YES + + + 1 + + + 217 + + + YES + + + + + 83 + + + YES + + + + + 57 + + + YES + + + + + + 136 + + + 1111 + + + 295 + + + YES + + + + + 299 + + + YES + + + + + + + YES + + YES + -1.IBPluginDependency + -2.IBPluginDependency + -3.IBPluginDependency + 103.IBPluginDependency + 103.ImportedFromIB2 + 136.IBPluginDependency + 136.ImportedFromIB2 + 19.IBPluginDependency + 19.ImportedFromIB2 + 217.IBPluginDependency + 217.ImportedFromIB2 + 29.IBEditorWindowLastContentRect + 29.IBPluginDependency + 29.ImportedFromIB2 + 29.WindowOrigin + 29.editorWindowContentRectSynchronizationRect + 295.IBPluginDependency + 299.IBPluginDependency + 56.IBPluginDependency + 56.ImportedFromIB2 + 57.IBEditorWindowLastContentRect + 57.IBPluginDependency + 57.ImportedFromIB2 + 57.editorWindowContentRectSynchronizationRect + 83.IBPluginDependency + 83.ImportedFromIB2 + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilderKit + com.apple.InterfaceBuilderKit + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{0, 975}, {478, 20}} + com.apple.InterfaceBuilder.CocoaPlugin + + {74, 862} + {{6, 978}, {478, 20}} + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + {{12, 952}, {218, 23}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{23, 794}, {245, 183}} + com.apple.InterfaceBuilder.CocoaPlugin + + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 374 + + + 0 + ../SmallApp.xcodeproj + 3 + + diff --git a/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib new file mode 100644 index 0000000000..bb27d4a5d6 Binary files /dev/null and b/xpcom/tests/unit/data/SmallApp.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/xpcom/tests/unit/data/bug121341-2.properties b/xpcom/tests/unit/data/bug121341-2.properties new file mode 100644 index 0000000000..f7885e4fca --- /dev/null +++ b/xpcom/tests/unit/data/bug121341-2.properties @@ -0,0 +1,9 @@ +# this file contains invalid UTF-8 sequence +# no property should be loaded + +1 = test + +# property with invalid UTF-8 sequence (0xa0) +2 = a b + +3 = test2 diff --git a/xpcom/tests/unit/data/bug121341.properties b/xpcom/tests/unit/data/bug121341.properties new file mode 100644 index 0000000000..b45fc9698c --- /dev/null +++ b/xpcom/tests/unit/data/bug121341.properties @@ -0,0 +1,68 @@ +# simple check +1=abc +# test whitespace trimming in key and value + 2 = xy +# test parsing of escaped values +3 = \u1234\t\r\n\uAB\ +\u1\n +# test multiline properties +4 = this is \ +multiline property +5 = this is \ + another multiline property +# property with DOS EOL +6 = test\u0036 +# test multiline property with with DOS EOL +7 = yet another multi\ + line propery +# trimming should not trim escaped whitespaces +8 = \ttest5\u0020 +# another variant of #8 +9 = \ test6\t +# test UTF-8 encoded property/value +10aሴb = cì·¯d +# next property should test unicode escaping at the boundary of parsing buffer +# buffer size is expected to be 4096 so add comments to get to this offset +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +################################################################################ +############################################################################### +11 = \uABCD diff --git a/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini new file mode 100644 index 0000000000..46b134b197 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser01-utf16leBOM.ini @@ -0,0 +1 @@ +ÿþ \ No newline at end of file diff --git a/xpcom/tests/unit/data/iniparser01-utf8BOM.ini b/xpcom/tests/unit/data/iniparser01-utf8BOM.ini new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/xpcom/tests/unit/data/iniparser01-utf8BOM.ini @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/xpcom/tests/unit/data/iniparser01.ini b/xpcom/tests/unit/data/iniparser01.ini new file mode 100644 index 0000000000..e69de29bb2 diff --git a/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini new file mode 100644 index 0000000000..49cc8ef0e1 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser02-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser02-utf8BOM.ini b/xpcom/tests/unit/data/iniparser02-utf8BOM.ini new file mode 100644 index 0000000000..e02abfc9b0 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser02-utf8BOM.ini @@ -0,0 +1 @@ + diff --git a/xpcom/tests/unit/data/iniparser02.ini b/xpcom/tests/unit/data/iniparser02.ini new file mode 100644 index 0000000000..d3f5a12faa --- /dev/null +++ b/xpcom/tests/unit/data/iniparser02.ini @@ -0,0 +1 @@ + diff --git a/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini new file mode 100644 index 0000000000..05255100a2 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser03-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser03-utf8BOM.ini b/xpcom/tests/unit/data/iniparser03-utf8BOM.ini new file mode 100644 index 0000000000..b76e44e194 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser03-utf8BOM.ini @@ -0,0 +1 @@ +[] diff --git a/xpcom/tests/unit/data/iniparser03.ini b/xpcom/tests/unit/data/iniparser03.ini new file mode 100644 index 0000000000..60b0742537 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser03.ini @@ -0,0 +1 @@ +[] diff --git a/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini new file mode 100644 index 0000000000..e95d971134 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser04-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser04-utf8BOM.ini b/xpcom/tests/unit/data/iniparser04-utf8BOM.ini new file mode 100644 index 0000000000..47ef32c0a9 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser04-utf8BOM.ini @@ -0,0 +1 @@ +[section1] diff --git a/xpcom/tests/unit/data/iniparser04.ini b/xpcom/tests/unit/data/iniparser04.ini new file mode 100644 index 0000000000..23a50d155f --- /dev/null +++ b/xpcom/tests/unit/data/iniparser04.ini @@ -0,0 +1 @@ +[section1] diff --git a/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini new file mode 100644 index 0000000000..a49491816c Binary files /dev/null and b/xpcom/tests/unit/data/iniparser05-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser05-utf8BOM.ini b/xpcom/tests/unit/data/iniparser05-utf8BOM.ini new file mode 100644 index 0000000000..eb33b5ccf1 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser05-utf8BOM.ini @@ -0,0 +1 @@ +[section1]junk diff --git a/xpcom/tests/unit/data/iniparser05.ini b/xpcom/tests/unit/data/iniparser05.ini new file mode 100644 index 0000000000..ade1373377 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser05.ini @@ -0,0 +1 @@ +[section1]junk diff --git a/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini new file mode 100644 index 0000000000..e9023ac7c9 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser06-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser06-utf8BOM.ini b/xpcom/tests/unit/data/iniparser06-utf8BOM.ini new file mode 100644 index 0000000000..073d841cf3 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser06-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] + diff --git a/xpcom/tests/unit/data/iniparser06.ini b/xpcom/tests/unit/data/iniparser06.ini new file mode 100644 index 0000000000..c24821e6ed --- /dev/null +++ b/xpcom/tests/unit/data/iniparser06.ini @@ -0,0 +1,2 @@ +[section1] + diff --git a/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini new file mode 100644 index 0000000000..d1c167e6e3 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser07-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser07-utf8BOM.ini b/xpcom/tests/unit/data/iniparser07-utf8BOM.ini new file mode 100644 index 0000000000..38176d9444 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser07-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] +name1 diff --git a/xpcom/tests/unit/data/iniparser07.ini b/xpcom/tests/unit/data/iniparser07.ini new file mode 100644 index 0000000000..49816873b2 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser07.ini @@ -0,0 +1,2 @@ +[section1] +name1 diff --git a/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini new file mode 100644 index 0000000000..e450566a0f Binary files /dev/null and b/xpcom/tests/unit/data/iniparser08-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser08-utf8BOM.ini b/xpcom/tests/unit/data/iniparser08-utf8BOM.ini new file mode 100644 index 0000000000..5fa7d2495c --- /dev/null +++ b/xpcom/tests/unit/data/iniparser08-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] +name1= diff --git a/xpcom/tests/unit/data/iniparser08.ini b/xpcom/tests/unit/data/iniparser08.ini new file mode 100644 index 0000000000..cfa15c9ffe --- /dev/null +++ b/xpcom/tests/unit/data/iniparser08.ini @@ -0,0 +1,2 @@ +[section1] +name1= diff --git a/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini new file mode 100644 index 0000000000..ef1da39e27 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser09-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser09-utf8BOM.ini b/xpcom/tests/unit/data/iniparser09-utf8BOM.ini new file mode 100644 index 0000000000..e3edce4d49 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser09-utf8BOM.ini @@ -0,0 +1,2 @@ +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser09.ini b/xpcom/tests/unit/data/iniparser09.ini new file mode 100644 index 0000000000..1c87762ba4 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser09.ini @@ -0,0 +1,2 @@ +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini new file mode 100644 index 0000000000..e5e70b6612 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser10-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser10-utf8BOM.ini b/xpcom/tests/unit/data/iniparser10-utf8BOM.ini new file mode 100644 index 0000000000..bda15fcc7b --- /dev/null +++ b/xpcom/tests/unit/data/iniparser10-utf8BOM.ini @@ -0,0 +1,3 @@ + +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser10.ini b/xpcom/tests/unit/data/iniparser10.ini new file mode 100644 index 0000000000..037fd79303 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser10.ini @@ -0,0 +1,3 @@ + +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini new file mode 100644 index 0000000000..932d4004bc Binary files /dev/null and b/xpcom/tests/unit/data/iniparser11-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser11-utf8BOM.ini b/xpcom/tests/unit/data/iniparser11-utf8BOM.ini new file mode 100644 index 0000000000..78caafaba5 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser11-utf8BOM.ini @@ -0,0 +1,3 @@ +# comment +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser11.ini b/xpcom/tests/unit/data/iniparser11.ini new file mode 100644 index 0000000000..f8d573a284 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser11.ini @@ -0,0 +1,3 @@ +# comment +[section1] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini new file mode 100644 index 0000000000..8a29127222 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser12-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser12-utf8BOM.ini b/xpcom/tests/unit/data/iniparser12-utf8BOM.ini new file mode 100644 index 0000000000..09ca62779d --- /dev/null +++ b/xpcom/tests/unit/data/iniparser12-utf8BOM.ini @@ -0,0 +1,3 @@ +[section1] +# [sectionBAD] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser12.ini b/xpcom/tests/unit/data/iniparser12.ini new file mode 100644 index 0000000000..2727940c09 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser12.ini @@ -0,0 +1,3 @@ +[section1] +# [sectionBAD] +name1=value1 diff --git a/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini new file mode 100644 index 0000000000..ebd9a51d3e Binary files /dev/null and b/xpcom/tests/unit/data/iniparser13-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser13-utf8BOM.ini b/xpcom/tests/unit/data/iniparser13-utf8BOM.ini new file mode 100644 index 0000000000..8c9499b669 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser13-utf8BOM.ini @@ -0,0 +1,3 @@ +[section1] +name1=value1 +# nameBAD=valueBAD diff --git a/xpcom/tests/unit/data/iniparser13.ini b/xpcom/tests/unit/data/iniparser13.ini new file mode 100644 index 0000000000..21d40b140c --- /dev/null +++ b/xpcom/tests/unit/data/iniparser13.ini @@ -0,0 +1,3 @@ +[section1] +name1=value1 +# nameBAD=valueBAD diff --git a/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini new file mode 100644 index 0000000000..bbc3413aa1 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser14-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser14-utf8BOM.ini b/xpcom/tests/unit/data/iniparser14-utf8BOM.ini new file mode 100644 index 0000000000..d109052c8d --- /dev/null +++ b/xpcom/tests/unit/data/iniparser14-utf8BOM.ini @@ -0,0 +1,6 @@ +[section1] +name1=value1 +name2=value2 +[section2] +name1=value1 +name2=foopy diff --git a/xpcom/tests/unit/data/iniparser14.ini b/xpcom/tests/unit/data/iniparser14.ini new file mode 100644 index 0000000000..744af4cb65 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser14.ini @@ -0,0 +1,6 @@ +[section1] +name1=value1 +name2=value2 +[section2] +name1=value1 +name2=foopy diff --git a/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini new file mode 100644 index 0000000000..e60525dec6 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser15-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser15-utf8BOM.ini b/xpcom/tests/unit/data/iniparser15-utf8BOM.ini new file mode 100644 index 0000000000..172803f90b --- /dev/null +++ b/xpcom/tests/unit/data/iniparser15-utf8BOM.ini @@ -0,0 +1,6 @@ +[section1] +name1=value1 +[section2] +name1=foopy +[section1] +name1=newValue1 diff --git a/xpcom/tests/unit/data/iniparser15.ini b/xpcom/tests/unit/data/iniparser15.ini new file mode 100644 index 0000000000..608a27d8fb --- /dev/null +++ b/xpcom/tests/unit/data/iniparser15.ini @@ -0,0 +1,6 @@ +[section1] +name1=value1 +[section2] +name1=foopy +[section1] +name1=newValue1 diff --git a/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini b/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini new file mode 100644 index 0000000000..142b175902 Binary files /dev/null and b/xpcom/tests/unit/data/iniparser16-utf16leBOM.ini differ diff --git a/xpcom/tests/unit/data/iniparser16-utf8BOM.ini b/xpcom/tests/unit/data/iniparser16-utf8BOM.ini new file mode 100644 index 0000000000..bba1018dab --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16-utf8BOM.ini @@ -0,0 +1,13 @@ +#á¿»á¹Ò³Ï–·Ì˄ȡǨŅ©& +[☺♫] +#ѼΏá¹Ò³Ï– +♫=☻ +#·Ì˄ȡǨŅ© +♪=♥ +#‽ἧᵿΏá¹Ò³ +#ϖ·Ì˄ȡǨŅ©& +[☼] +♣=â™  +♦=♥ +#‽ἧᵿΏá¹Ò³ +#·Ì˄ȡǨŅ© diff --git a/xpcom/tests/unit/data/iniparser16.ini b/xpcom/tests/unit/data/iniparser16.ini new file mode 100644 index 0000000000..b94607d15d --- /dev/null +++ b/xpcom/tests/unit/data/iniparser16.ini @@ -0,0 +1,13 @@ +#á¿»á¹Ò³Ï–·Ì˄ȡǨŅ©& +[☺♫] +#ѼΏá¹Ò³Ï– +♫=☻ +#·Ì˄ȡǨŅ© +♪=♥ +#‽ἧᵿΏá¹Ò³ +#ϖ·Ì˄ȡǨŅ©& +[☼] +♣=â™  +♦=♥ +#‽ἧᵿΏá¹Ò³ +#·Ì˄ȡǨŅ© diff --git a/xpcom/tests/unit/data/iniparser17.ini b/xpcom/tests/unit/data/iniparser17.ini new file mode 100644 index 0000000000..bc4815b8c7 --- /dev/null +++ b/xpcom/tests/unit/data/iniparser17.ini @@ -0,0 +1,7 @@ +[section] +key= + +[] + +[empty] +=foo diff --git a/xpcom/tests/unit/data/presentation.key/.typeAttributes.dict b/xpcom/tests/unit/data/presentation.key/.typeAttributes.dict new file mode 100644 index 0000000000..e69de29bb2 diff --git a/xpcom/tests/unit/data/presentation.key/Contents/PkgInfo b/xpcom/tests/unit/data/presentation.key/Contents/PkgInfo new file mode 100644 index 0000000000..b0bc8e0761 --- /dev/null +++ b/xpcom/tests/unit/data/presentation.key/Contents/PkgInfo @@ -0,0 +1 @@ +???????? \ No newline at end of file diff --git a/xpcom/tests/unit/data/presentation.key/index.apxl.gz b/xpcom/tests/unit/data/presentation.key/index.apxl.gz new file mode 100644 index 0000000000..26178d809e Binary files /dev/null and b/xpcom/tests/unit/data/presentation.key/index.apxl.gz differ diff --git a/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff new file mode 100644 index 0000000000..8b49316b4d Binary files /dev/null and b/xpcom/tests/unit/data/presentation.key/thumbs/st0.tiff differ diff --git a/xpcom/tests/unit/data/process_directive.manifest b/xpcom/tests/unit/data/process_directive.manifest new file mode 100644 index 0000000000..3deb2ec444 --- /dev/null +++ b/xpcom/tests/unit/data/process_directive.manifest @@ -0,0 +1,2 @@ +category directives-test main-process @mozilla.org/supports-cstring;1 process=main +category directives-test content-process @mozilla.org/supports-cstring;1 process=content diff --git a/xpcom/tests/unit/head_xpcom.js b/xpcom/tests/unit/head_xpcom.js new file mode 100644 index 0000000000..e2ae79cb12 --- /dev/null +++ b/xpcom/tests/unit/head_xpcom.js @@ -0,0 +1,21 @@ +let CC = Components.Constructor; + +function get_test_program(prog) { + var progPath = do_get_cwd(); + progPath.append(prog); + progPath.leafName = progPath.leafName + mozinfo.bin_suffix; + return progPath; +} + +function set_process_running_environment() { + // Importing Services here messes up appInfo for some of the tests. + // eslint-disable-next-line mozilla/use-services + var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService( + Ci.nsIProperties + ); + var greBinDir = dirSvc.get("GreBinD", Ci.nsIFile); + Services.env.set("DYLD_LIBRARY_PATH", greBinDir.path); + // For Linux + Services.env.set("LD_LIBRARY_PATH", greBinDir.path); + // XXX: handle windows +} diff --git a/xpcom/tests/unit/test_bug121341.js b/xpcom/tests/unit/test_bug121341.js new file mode 100644 index 0000000000..2796fc47f2 --- /dev/null +++ b/xpcom/tests/unit/test_bug121341.js @@ -0,0 +1,62 @@ +const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm"); + +function run_test() { + var dataFile = do_get_file("data/bug121341.properties"); + var channel = NetUtil.newChannel({ + uri: Services.io.newFileURI(dataFile, null, null), + loadUsingSystemPrincipal: true, + }); + var inp = channel.open(); + + var properties = Cu.createPersistentProperties(); + properties.load(inp); + + var value; + + value = properties.getStringProperty("1"); + Assert.equal(value, "abc"); + + value = properties.getStringProperty("2"); + Assert.equal(value, "xy"); + + value = properties.getStringProperty("3"); + Assert.equal(value, "\u1234\t\r\n\u00AB\u0001\n"); + + value = properties.getStringProperty("4"); + Assert.equal(value, "this is multiline property"); + + value = properties.getStringProperty("5"); + Assert.equal(value, "this is another multiline property"); + + value = properties.getStringProperty("6"); + Assert.equal(value, "test\u0036"); + + value = properties.getStringProperty("7"); + Assert.equal(value, "yet another multiline propery"); + + value = properties.getStringProperty("8"); + Assert.equal(value, "\ttest5\u0020"); + + value = properties.getStringProperty("9"); + Assert.equal(value, " test6\t"); + + value = properties.getStringProperty("10a\u1234b"); + Assert.equal(value, "c\uCDEFd"); + + value = properties.getStringProperty("11"); + Assert.equal(value, "\uABCD"); + + dataFile = do_get_file("data/bug121341-2.properties"); + + var channel2 = NetUtil.newChannel({ + uri: Services.io.newFileURI(dataFile, null, null), + loadUsingSystemPrincipal: true, + }); + inp = channel2.open(); + + var properties2 = Cu.createPersistentProperties(); + try { + properties2.load(inp); + do_throw("load() didn't fail"); + } catch (e) {} +} diff --git a/xpcom/tests/unit/test_bug1434856.js b/xpcom/tests/unit/test_bug1434856.js new file mode 100644 index 0000000000..a8dfa08079 --- /dev/null +++ b/xpcom/tests/unit/test_bug1434856.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +function run_test() { + let complete = false; + + let runnable = { + internalQI: ChromeUtils.generateQI(["nsIRunnable"]), + // eslint-disable-next-line mozilla/use-chromeutils-generateqi + QueryInterface(iid) { + // Attempt to schedule another runnable. This simulates a GC/CC + // being scheduled while executing the JS QI. + Services.tm.dispatchToMainThread(() => false); + return this.internalQI(iid); + }, + + run() { + complete = true; + }, + }; + + Services.tm.dispatchToMainThread(runnable); + Services.tm.spinEventLoopUntil( + "Test(test_bug1434856.js:run_test)", + () => complete + ); +} diff --git a/xpcom/tests/unit/test_bug325418.js b/xpcom/tests/unit/test_bug325418.js new file mode 100644 index 0000000000..5840aacf74 --- /dev/null +++ b/xpcom/tests/unit/test_bug325418.js @@ -0,0 +1,72 @@ +// 5 seconds. +const kExpectedDelay1 = 5; +// 1 second. +const kExpectedDelay2 = 1; + +var gStartTime1; +var gStartTime2; +var timer; + +var observer1 = { + observe: function observeTC1(subject, topic, data) { + if (topic == "timer-callback") { + // Stop timer, so it doesn't repeat (if test runs slowly). + timer.cancel(); + + // Actual delay may not be exact, so convert to seconds and round. + Assert.equal( + Math.round((Date.now() - gStartTime1) / 1000), + kExpectedDelay1 + ); + + timer = null; + + info( + "1st timer triggered (before being cancelled). Should not have happened!" + ); + Assert.ok(false); + } + }, +}; + +var observer2 = { + observe: function observeTC2(subject, topic, data) { + if (topic == "timer-callback") { + // Stop timer, so it doesn't repeat (if test runs slowly). + timer.cancel(); + + // Actual delay may not be exact, so convert to seconds and round. + Assert.equal( + Math.round((Date.now() - gStartTime2) / 1000), + kExpectedDelay2 + ); + + timer = null; + + do_test_finished(); + } + }, +}; + +function run_test() { + do_test_pending(); + + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + + // Initialize the timer (with some delay), then cancel it. + gStartTime1 = Date.now(); + timer.init( + observer1, + kExpectedDelay1 * 1000, + timer.TYPE_REPEATING_PRECISE_CAN_SKIP + ); + timer.cancel(); + + // Re-initialize the timer (with a different delay). + gStartTime2 = Date.now(); + timer.init( + observer2, + kExpectedDelay2 * 1000, + timer.TYPE_REPEATING_PRECISE_CAN_SKIP + ); +} diff --git a/xpcom/tests/unit/test_bug332389.js b/xpcom/tests/unit/test_bug332389.js new file mode 100644 index 0000000000..605772f12a --- /dev/null +++ b/xpcom/tests/unit/test_bug332389.js @@ -0,0 +1,14 @@ +function run_test() { + var f = Services.dirsvc.get("CurProcD", Ci.nsIFile); + + var terminated = false; + for (var i = 0; i < 100; i++) { + if (f == null) { + terminated = true; + break; + } + f = f.parent; + } + + Assert.ok(terminated); +} diff --git a/xpcom/tests/unit/test_bug333505.js b/xpcom/tests/unit/test_bug333505.js new file mode 100644 index 0000000000..97016519da --- /dev/null +++ b/xpcom/tests/unit/test_bug333505.js @@ -0,0 +1,10 @@ +function run_test() { + var dirEntries = do_get_cwd().directoryEntries; + + while (dirEntries.hasMoreElements()) { + dirEntries.getNext(); + } + + // We ensure there is no crash + dirEntries.hasMoreElements(); +} diff --git a/xpcom/tests/unit/test_bug364285-1.js b/xpcom/tests/unit/test_bug364285-1.js new file mode 100644 index 0000000000..167e94e852 --- /dev/null +++ b/xpcom/tests/unit/test_bug364285-1.js @@ -0,0 +1,43 @@ +var nameArray = [ + "ascii", // ASCII + "fran\u00E7ais", // Latin-1 + "\u0420\u0443\u0441\u0441\u043A\u0438\u0439", // Cyrillic + "\u65E5\u672C\u8A9E", // Japanese + "\u4E2D\u6587", // Chinese + "\uD55C\uAD6D\uC5B4", // Korean + "\uD801\uDC0F\uD801\uDC2D\uD801\uDC3B\uD801\uDC2B", // Deseret +]; + +function getTempDir() { + return Services.dirsvc.get("TmpD", Ci.nsIFile); +} + +function create_file(fileName) { + var outFile = getTempDir(); + outFile.append(fileName); + outFile.createUnique(outFile.NORMAL_FILE_TYPE, 0o600); + + var stream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance( + Ci.nsIFileOutputStream + ); + stream.init(outFile, 0x02 | 0x08 | 0x20, 0o600, 0); + stream.write("foo", 3); + stream.close(); + + Assert.equal(outFile.leafName.substr(0, fileName.length), fileName); + + return outFile; +} + +function test_create(fileName) { + var file1 = create_file(fileName); + var file2 = create_file(fileName); + file1.remove(false); + file2.remove(false); +} + +function run_test() { + for (var i = 0; i < nameArray.length; ++i) { + test_create(nameArray[i]); + } +} diff --git a/xpcom/tests/unit/test_bug374754.js b/xpcom/tests/unit/test_bug374754.js new file mode 100644 index 0000000000..0d20d90b2c --- /dev/null +++ b/xpcom/tests/unit/test_bug374754.js @@ -0,0 +1,65 @@ +var addedTopic = "xpcom-category-entry-added"; +var removedTopic = "xpcom-category-entry-removed"; +var testCategory = "bug-test-category"; +var testEntry = "@mozilla.org/bug-test-entry;1"; + +var testValue = "check validity"; +var result = ""; +var expected = "add remove add remove "; +var timer; + +var observer = { + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + + observe(subject, topic, data) { + if (topic == "timer-callback") { + Assert.equal(result, expected); + + Services.obs.removeObserver(this, addedTopic); + Services.obs.removeObserver(this, removedTopic); + + do_test_finished(); + + timer = null; + } + + if ( + subject.QueryInterface(Ci.nsISupportsCString).data != testEntry || + data != testCategory + ) { + return; + } + + if (topic == addedTopic) { + result += "add "; + } else if (topic == removedTopic) { + result += "remove "; + } + }, +}; + +function run_test() { + do_test_pending(); + + Services.obs.addObserver(observer, addedTopic); + Services.obs.addObserver(observer, removedTopic); + + Services.catMan.addCategoryEntry( + testCategory, + testEntry, + testValue, + false, + true + ); + Services.catMan.addCategoryEntry( + testCategory, + testEntry, + testValue, + false, + true + ); + Services.catMan.deleteCategoryEntry(testCategory, testEntry, false); + + timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.init(observer, 0, timer.TYPE_ONE_SHOT); +} diff --git a/xpcom/tests/unit/test_bug476919.js b/xpcom/tests/unit/test_bug476919.js new file mode 100644 index 0000000000..5ba64758c7 --- /dev/null +++ b/xpcom/tests/unit/test_bug476919.js @@ -0,0 +1,25 @@ +/* global __LOCATION__ */ + +function run_test() { + var testDir = __LOCATION__.parent; + // create a test file, then symlink it, then check that we think it's a symlink + var targetFile = testDir.clone(); + targetFile.append("target.txt"); + if (!targetFile.exists()) { + targetFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + } + + var link = testDir.clone(); + link.append("link"); + if (link.exists()) { + link.remove(false); + } + + var ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + ln.initWithPath("/bin/ln"); + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(ln); + var args = ["-s", targetFile.path, link.path]; + process.run(true, args, args.length); + Assert.ok(link.isSymlink()); +} diff --git a/xpcom/tests/unit/test_bug478086.js b/xpcom/tests/unit/test_bug478086.js new file mode 100644 index 0000000000..6debb58fbc --- /dev/null +++ b/xpcom/tests/unit/test_bug478086.js @@ -0,0 +1,23 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/licenses/publicdomain/ */ + +function run_test() { + var nsIFile = Ci.nsIFile; + var root = Cc["@mozilla.org/file/local;1"].createInstance(nsIFile); + + // copied from http://mxr.mozilla.org/mozilla-central/source/image/test/unit/test_imgtools.js#135 + // nsIXULRuntime.OS doesn't seem to be available in xpcshell, so we'll use + // this as a kludgy way to figure out if we're running on Windows. + if (mozinfo.os == "win") { + root.initWithPath("\\\\."); + } else { + return; // XXX disabled, since this causes intermittent failures on Mac (bug 481369). + // root.initWithPath("/"); + } + var drives = root.directoryEntries; + Assert.ok(drives.hasMoreElements()); + while (drives.hasMoreElements()) { + var newPath = drives.nextFile.path; + Assert.equal(newPath.indexOf("\0"), -1); + } +} diff --git a/xpcom/tests/unit/test_bug745466.js b/xpcom/tests/unit/test_bug745466.js new file mode 100644 index 0000000000..a655bf45b4 --- /dev/null +++ b/xpcom/tests/unit/test_bug745466.js @@ -0,0 +1,7 @@ +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +function run_test() { + Assert.ok(FileUtils.File("~").equals(FileUtils.getDir("Home", []))); +} diff --git a/xpcom/tests/unit/test_console_service_callFunctionAndLogException.js b/xpcom/tests/unit/test_console_service_callFunctionAndLogException.js new file mode 100644 index 0000000000..eeb9ceb425 --- /dev/null +++ b/xpcom/tests/unit/test_console_service_callFunctionAndLogException.js @@ -0,0 +1,265 @@ +let lastMessage; +const consoleListener = { + observe(message) { + dump(" >> new message: " + message.errorMessage + "\n"); + lastMessage = message; + }, +}; +Services.console.registerListener(consoleListener); + +// The Console Service notifies its listener after one event loop cycle. +// So wait for one tick after each action dispatching a message/error to the service. +function waitForATick() { + return new Promise(resolve => Services.tm.dispatchToMainThread(resolve)); +} + +add_task(async function customScriptError() { + const scriptError = Cc["@mozilla.org/scripterror;1"].createInstance( + Ci.nsIScriptError + ); + scriptError.init( + "foo", + "file.js", + null, + 1, + 2, + Ci.nsIScriptError.warningFlag, + "some javascript" + ); + Services.console.logMessage(scriptError); + + await waitForATick(); + + Assert.equal( + lastMessage, + scriptError, + "We receive the exact same nsIScriptError object" + ); + + Assert.equal(lastMessage.errorMessage, "foo"); + Assert.equal(lastMessage.sourceName, "file.js"); + Assert.equal(lastMessage.lineNumber, 1); + Assert.equal(lastMessage.columnNumber, 2); + Assert.equal(lastMessage.flags, Ci.nsIScriptError.warningFlag); + Assert.equal(lastMessage.category, "some javascript"); + + Assert.equal( + lastMessage.stack, + undefined, + "Custom nsIScriptError object created from JS can't convey any stack" + ); +}); + +add_task(async function callFunctionAndLogExceptionWithChromeGlobal() { + try { + Services.console.callFunctionAndLogException(globalThis, function () { + throw new Error("custom exception"); + }); + Assert.fail("callFunctionAndLogException should throw"); + } catch (e) { + Assert.equal( + e.name, + "NS_ERROR_XPC_JAVASCRIPT_ERROR", + "callFunctionAndLogException thrown" + ); + } + + await waitForATick(); + + Assert.ok(!!lastMessage, "Got the message"); + Assert.ok( + lastMessage instanceof Ci.nsIScriptError, + "This is a nsIScriptError" + ); + + Assert.equal(lastMessage.errorMessage, "Error: custom exception"); + Assert.equal(lastMessage.sourceName, _TEST_FILE); + Assert.equal(lastMessage.lineNumber, 56); + Assert.equal(lastMessage.columnNumber, 13); + Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag); + Assert.equal(lastMessage.category, "chrome javascript"); + Assert.ok(lastMessage.stack, "It has a stack"); + Assert.equal(lastMessage.stack.source, _TEST_FILE); + Assert.equal(lastMessage.stack.line, 56); + Assert.equal(lastMessage.stack.column, 13); + Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame"); + Assert.equal( + lastMessage.innerWindowID, + 0, + "The message isn't bound to any WindowGlobal" + ); +}); + +add_task(async function callFunctionAndLogExceptionWithContentGlobal() { + const window = createContentWindow(); + try { + Services.console.callFunctionAndLogException(window, function () { + throw new Error("another custom exception"); + }); + Assert.fail("callFunctionAndLogException should throw"); + } catch (e) { + Assert.equal( + e.name, + "NS_ERROR_XPC_JAVASCRIPT_ERROR", + "callFunctionAndLogException thrown" + ); + } + + await waitForATick(); + + Assert.ok(!!lastMessage, "Got the message"); + Assert.ok( + lastMessage instanceof Ci.nsIScriptError, + "This is a nsIScriptError" + ); + + Assert.equal(lastMessage.errorMessage, "Error: another custom exception"); + Assert.equal(lastMessage.sourceName, _TEST_FILE); + Assert.equal(lastMessage.lineNumber, 97); + Assert.equal(lastMessage.columnNumber, 13); + Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag); + Assert.equal(lastMessage.category, "content javascript"); + Assert.ok(lastMessage.stack, "It has a stack"); + Assert.equal(lastMessage.stack.source, _TEST_FILE); + Assert.equal(lastMessage.stack.line, 97); + Assert.equal(lastMessage.stack.column, 13); + Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame"); + Assert.ok( + !!window.windowGlobalChild.innerWindowId, + "The window has a innerWindowId" + ); + Assert.equal( + lastMessage.innerWindowID, + window.windowGlobalChild.innerWindowId, + "The message is bound to the content window" + ); +}); + +add_task(async function callFunctionAndLogExceptionForContentScriptSandboxes() { + const { sandbox, window } = createContentScriptSandbox(); + Cu.evalInSandbox( + `function foo() { throw new Error("sandbox exception"); }`, + sandbox, + null, + "sandbox-file.js", + 1, + 0 + ); + try { + Services.console.callFunctionAndLogException(window, sandbox.foo); + Assert.fail("callFunctionAndLogException should throw"); + } catch (e) { + Assert.equal( + e.name, + "NS_ERROR_XPC_JAVASCRIPT_ERROR", + "callFunctionAndLogException thrown" + ); + } + + await waitForATick(); + + Assert.ok(!!lastMessage, "Got the message"); + // Note that it is important to "instanceof" in order to expose the nsIScriptError attributes. + Assert.ok( + lastMessage instanceof Ci.nsIScriptError, + "This is a nsIScriptError" + ); + + Assert.equal(lastMessage.errorMessage, "Error: sandbox exception"); + Assert.equal(lastMessage.sourceName, "sandbox-file.js"); + Assert.equal(lastMessage.lineNumber, 1); + Assert.equal(lastMessage.columnNumber, 24); + Assert.equal(lastMessage.flags, Ci.nsIScriptError.errorFlag); + Assert.equal(lastMessage.category, "content javascript"); + Assert.ok(lastMessage.stack, "It has a stack"); + Assert.equal(lastMessage.stack.source, "sandbox-file.js"); + Assert.equal(lastMessage.stack.line, 1); + Assert.equal(lastMessage.stack.column, 24); + Assert.ok(!!lastMessage.stack.parent, "stack has a parent frame"); + Assert.ok( + !!window.windowGlobalChild.innerWindowId, + "The sandbox's prototype is a window and has a innerWindowId" + ); + Assert.equal( + lastMessage.innerWindowID, + window.windowGlobalChild.innerWindowId, + "The message is bound to the sandbox's prototype WindowGlobal" + ); +}); + +add_task( + async function callFunctionAndLogExceptionForContentScriptSandboxesWrappedInChrome() { + const { sandbox, window } = createContentScriptSandbox(); + Cu.evalInSandbox( + `function foo() { throw new Error("sandbox exception"); }`, + sandbox, + null, + "sandbox-file.js", + 1, + 0 + ); + try { + Services.console.callFunctionAndLogException(window, function () { + sandbox.foo(); + }); + Assert.fail("callFunctionAndLogException should throw"); + } catch (e) { + Assert.equal( + e.name, + "NS_ERROR_XPC_JAVASCRIPT_ERROR", + "callFunctionAndLogException thrown" + ); + } + + await waitForATick(); + + Assert.ok(!!lastMessage, "Got the message"); + // Note that it is important to "instanceof" in order to expose the nsIScriptError attributes. + Assert.ok( + lastMessage instanceof Ci.nsIScriptError, + "This is a nsIScriptError" + ); + + Assert.ok( + !!window.windowGlobalChild.innerWindowId, + "The sandbox's prototype is a window and has a innerWindowId" + ); + Assert.equal( + lastMessage.innerWindowID, + window.windowGlobalChild.innerWindowId, + "The message is bound to the sandbox's prototype WindowGlobal" + ); + } +); + +add_task(function teardown() { + Services.console.unregisterListener(consoleListener); +}); + +// We are in xpcshell, so we can't have a real DOM Window as in Firefox +// but let's try to have a fake one. +function createContentWindow() { + const principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + "http://example.com/" + ); + + const webnav = Services.appShell.createWindowlessBrowser(false); + + webnav.docShell.createAboutBlankContentViewer(principal, principal); + + return webnav.document.defaultView; +} + +// Create a Sandbox as in WebExtension content scripts +function createContentScriptSandbox() { + const window = createContentWindow(); + // The sandboxPrototype is the key here in order to + // make xpc::SandboxWindowOrNull ignore the sandbox + // and instead retrieve its prototype and link the error message + // to the window instead of the sandbox. + return { + sandbox: Cu.Sandbox(window, { sandboxPrototype: window }), + window, + }; +} diff --git a/xpcom/tests/unit/test_debugger_malloc_size_of.js b/xpcom/tests/unit/test_debugger_malloc_size_of.js new file mode 100644 index 0000000000..3141d8c2c3 --- /dev/null +++ b/xpcom/tests/unit/test_debugger_malloc_size_of.js @@ -0,0 +1,32 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +// This is just a sanity test that Gecko is giving SpiderMonkey a MallocSizeOf +// function for new JSRuntimes. There is more extensive testing around the +// expected byte sizes within SpiderMonkey's jit-tests, we just want to make +// sure that Gecko is providing SpiderMonkey with the callback it needs. + +const { byteSize } = Cu.getJSTestingFunctions(); + +function run_test() { + const objects = [ + {}, + { w: 1, x: 2, y: 3, z: 4, a: 5 }, + [], + Array(10).fill(null), + new RegExp("(2|two) problems", "g"), + new Date(), + new Uint8Array(64), + Promise.resolve(1), + function f() {}, + Object, + ]; + + for (let obj of objects) { + info(uneval(obj)); + ok(byteSize(obj), "We should get some (non-zero) byte size"); + } +} diff --git a/xpcom/tests/unit/test_file_createUnique.js b/xpcom/tests/unit/test_file_createUnique.js new file mode 100644 index 0000000000..510002bda2 --- /dev/null +++ b/xpcom/tests/unit/test_file_createUnique.js @@ -0,0 +1,29 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * 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/. */ + +function run_test() { + // Generate a leaf name that is 255 characters long. + var longLeafName = new Array(256).join("T"); + + // Generate the path for a file located in a directory with a long name. + var tempFile = Services.dirsvc.get("TmpD", Ci.nsIFile); + tempFile.append(longLeafName); + tempFile.append("test.txt"); + + try { + tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + do_throw("Creating an item in a folder with a very long name should throw"); + } catch (e) { + if ( + !( + e instanceof Ci.nsIException && + e.result == Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH + ) + ) { + throw e; + } + // We expect the function not to crash but to raise this exception. + } +} diff --git a/xpcom/tests/unit/test_file_equality.js b/xpcom/tests/unit/test_file_equality.js new file mode 100644 index 0000000000..74ea8046d8 --- /dev/null +++ b/xpcom/tests/unit/test_file_equality.js @@ -0,0 +1,37 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +var LocalFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath"); + +function run_test() { + test_normalized_vs_non_normalized(); +} + +function test_normalized_vs_non_normalized() { + // get a directory that exists on all platforms + var tmp1 = Services.dirsvc.get("TmpD", Ci.nsIFile); + var exists = tmp1.exists(); + Assert.ok(exists); + if (!exists) { + return; + } + + // the test logic below assumes we're starting with a normalized path, but the + // default location on macos is a symbolic link, so resolve it before starting + tmp1.normalize(); + + // this has the same exact path as tmp1, it should equal tmp1 + var tmp2 = new LocalFile(tmp1.path); + Assert.ok(tmp1.equals(tmp2)); + + // this is a non-normalized version of tmp1, it should not equal tmp1 + tmp2.appendRelativePath("."); + Assert.ok(!tmp1.equals(tmp2)); + + // normalize and make sure they are equivalent again + tmp2.normalize(); + Assert.ok(tmp1.equals(tmp2)); +} diff --git a/xpcom/tests/unit/test_file_renameTo.js b/xpcom/tests/unit/test_file_renameTo.js new file mode 100644 index 0000000000..a6e8633773 --- /dev/null +++ b/xpcom/tests/unit/test_file_renameTo.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * 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/. */ + +function run_test() { + // Create the base directory. + let base = Services.dirsvc.get("TmpD", Ci.nsIFile); + base.append("renameTesting"); + if (base.exists()) { + base.remove(true); + } + base.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0777", 8)); + + // Create a sub directory under the base. + let subdir = base.clone(); + subdir.append("subdir"); + subdir.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0777", 8)); + + // Create a file under the sub directory. + let tempFile = subdir.clone(); + tempFile.append("file0.txt"); + tempFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0777", 8)); + + // Test renameTo in the base directory + tempFile.renameTo(null, "file1.txt"); + Assert.ok(exists(subdir, "file1.txt")); + + // Test moving across directories + tempFile = subdir.clone(); + tempFile.append("file1.txt"); + tempFile.renameTo(base, ""); + Assert.ok(exists(base, "file1.txt")); + + // Test moving across directories and renaming at the same time + tempFile = base.clone(); + tempFile.append("file1.txt"); + tempFile.renameTo(subdir, "file2.txt"); + Assert.ok(exists(subdir, "file2.txt")); + + // Test moving a directory + subdir.renameTo(base, "renamed"); + Assert.ok(exists(base, "renamed")); + let renamed = base.clone(); + renamed.append("renamed"); + Assert.ok(exists(renamed, "file2.txt")); + + base.remove(true); +} + +function exists(parent, filename) { + let file = parent.clone(); + file.append(filename); + return file.exists(); +} diff --git a/xpcom/tests/unit/test_getTimers.js b/xpcom/tests/unit/test_getTimers.js new file mode 100644 index 0000000000..58a048a4bc --- /dev/null +++ b/xpcom/tests/unit/test_getTimers.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +const timerManager = Cc["@mozilla.org/timer-manager;1"].getService( + Ci.nsITimerManager +); + +function newTimer(name, delay, type) { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback( + { + QueryInterface: ChromeUtils.generateQI(["nsITimerCallback", "nsINamed"]), + name, + notify: () => {}, + }, + delay, + type + ); + return timer; +} + +function getTimers() { + return timerManager.getTimers().filter(t => { + if (t.name == "BackgroundHangThread_timer") { + // BHR is Nightly-only, so just ignore it. + return false; + } + + if (AppConstants.platform == "win" && t.name == "nsAnonTempFileRemover") { + // On Windows there's a 3min timer added at startup to then add an + // idle observer that finally triggers removing leftover temp files. + // Ignore that too. + return false; + } + + return true; + }); +} + +function run_test() { + { + let timers = getTimers(); + for (let timer of timers) { + // Print info about unexpected startup timers to help debugging. + info(`${timer.name}: ${timer.delay}ms, ${timer.type}`); + } + Assert.equal( + timers.length, + 0, + "there should be no timer at xpcshell startup" + ); + } + + let timerData = [ + ["t1", 500, Ci.nsITimer.TYPE_ONE_SHOT], + ["t2", 1500, Ci.nsITimer.TYPE_REPEATING_SLACK], + ["t3", 2500, Ci.nsITimer.TYPE_REPEATING_PRECISE], + ["t4", 3500, Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP], + ["t5", 5500, Ci.nsITimer.TYPE_REPEATING_SLACK_LOW_PRIORITY], + ["t6", 7500, Ci.nsITimer.TYPE_ONE_SHOT_LOW_PRIORITY], + ]; + + info("Add timers one at a time."); + for (let [name, delay, type] of timerData) { + let timer = newTimer(name, delay, type); + let timers = getTimers(); + Assert.equal(timers.length, 1, "there should be only one timer"); + Assert.equal(name, timers[0].name, "the name is correct"); + Assert.equal(delay, timers[0].delay, "the delay is correct"); + Assert.equal(type, timers[0].type, "the type is correct"); + + timer.cancel(); + Assert.equal(getTimers().length, 0, "no timer left after cancelling"); + } + + info("Add all timers at once."); + let timers = []; + for (let [name, delay, type] of timerData) { + timers.push(newTimer(name, delay, type)); + } + while (timers.length) { + Assert.equal(getTimers().length, timers.length, "correct timer count"); + timers.pop().cancel(); + } + Assert.equal(getTimers().length, 0, "no timer left after cancelling"); +} diff --git a/xpcom/tests/unit/test_hidden_files.js b/xpcom/tests/unit/test_hidden_files.js new file mode 100644 index 0000000000..27d87e6f54 --- /dev/null +++ b/xpcom/tests/unit/test_hidden_files.js @@ -0,0 +1,24 @@ +const NS_OS_TEMP_DIR = "TmpD"; + +var hiddenUnixFile; +function createUNIXHiddenFile() { + var tmpDir = Services.dirsvc.get(NS_OS_TEMP_DIR, Ci.nsIFile); + hiddenUnixFile = tmpDir.clone(); + hiddenUnixFile.append(".foo"); + // we don't care if this already exists because we don't care + // about the file's contents (just the name) + if (!hiddenUnixFile.exists()) { + hiddenUnixFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666); + } + return hiddenUnixFile.exists(); +} + +function run_test() { + // Skip this test on Windows + if (mozinfo.os == "win") { + return; + } + + Assert.ok(createUNIXHiddenFile()); + Assert.ok(hiddenUnixFile.isHidden()); +} diff --git a/xpcom/tests/unit/test_home.js b/xpcom/tests/unit/test_home.js new file mode 100644 index 0000000000..e3a4af9796 --- /dev/null +++ b/xpcom/tests/unit/test_home.js @@ -0,0 +1,18 @@ +const CWD = do_get_cwd(); +function checkOS(os) { + const nsILocalFile_ = "nsILocalFile" + os; + return nsILocalFile_ in Ci && CWD instanceof Ci[nsILocalFile_]; +} + +const isWin = checkOS("Win"); + +function run_test() { + var envVar = isWin ? "USERPROFILE" : "HOME"; + + var homeDir = Services.dirsvc.get("Home", Ci.nsIFile); + + var expected = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + expected.initWithPath(Services.env.get(envVar)); + + Assert.equal(homeDir.path, expected.path); +} diff --git a/xpcom/tests/unit/test_iniParser.js b/xpcom/tests/unit/test_iniParser.js new file mode 100644 index 0000000000..a586c4c060 --- /dev/null +++ b/xpcom/tests/unit/test_iniParser.js @@ -0,0 +1,476 @@ +var testnum = 0; +var factory; + +function parserForFile(filename) { + let parser = null; + try { + let file = do_get_file(filename); + Assert.ok(!!file); + parser = factory.createINIParser(file); + Assert.ok(!!parser); + } catch (e) { + dump("INFO | caught error: " + e); + // checkParserOutput will handle a null parser when it's expected. + } + return parser; +} + +function checkParserOutput(parser, expected) { + // If the expected output is null, we expect the parser to have + // failed (and vice-versa). + if (!parser || !expected) { + Assert.equal(parser, null); + Assert.equal(expected, null); + return; + } + + let output = getParserOutput(parser); + for (let section in expected) { + Assert.ok(section in output); + for (let key in expected[section]) { + Assert.ok(key in output[section]); + Assert.equal(output[section][key], expected[section][key]); + delete output[section][key]; + } + for (let key in output[section]) { + Assert.equal(key, "wasn't expecting this key!"); + } + delete output[section]; + } + for (let section in output) { + Assert.equal(section, "wasn't expecting this section!"); + } +} + +function getParserOutput(parser) { + let output = {}; + + for (let section of parser.getSections()) { + Assert.equal(false, section in output); // catch dupes + output[section] = {}; + + for (let key of parser.getKeys(section)) { + Assert.equal(false, key in output[section]); // catch dupes + let value = parser.getString(section, key); + output[section][key] = value; + } + } + return output; +} + +function run_test() { + try { + var testdata = [ + { filename: "data/iniparser01.ini", reference: {} }, + { filename: "data/iniparser02.ini", reference: {} }, + { filename: "data/iniparser03.ini", reference: {} }, + { filename: "data/iniparser04.ini", reference: {} }, + { filename: "data/iniparser05.ini", reference: {} }, + { filename: "data/iniparser06.ini", reference: {} }, + { filename: "data/iniparser07.ini", reference: {} }, + { + filename: "data/iniparser08.ini", + reference: { section1: { name1: "" } }, + }, + { + filename: "data/iniparser09.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser10.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser11.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser12.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser13.ini", + reference: { section1: { name1: "value1" } }, + }, + { + filename: "data/iniparser14.ini", + reference: { + section1: { name1: "value1", name2: "value2" }, + section2: { name1: "value1", name2: "foopy" }, + }, + }, + { + filename: "data/iniparser15.ini", + reference: { + section1: { name1: "newValue1" }, + section2: { name1: "foopy" }, + }, + }, + { + filename: "data/iniparser16.ini", + reference: { + "☺♫": { "♫": "☻", "♪": "♥" }, + "☼": { "♣": "â™ ", "♦": "♥" }, + }, + }, + { filename: "data/iniparser17.ini", reference: { section: { key: "" } } }, + ]; + + testdata.push({ + filename: "data/iniparser01-utf8BOM.ini", + reference: testdata[0].reference, + }); + testdata.push({ + filename: "data/iniparser02-utf8BOM.ini", + reference: testdata[1].reference, + }); + testdata.push({ + filename: "data/iniparser03-utf8BOM.ini", + reference: testdata[2].reference, + }); + testdata.push({ + filename: "data/iniparser04-utf8BOM.ini", + reference: testdata[3].reference, + }); + testdata.push({ + filename: "data/iniparser05-utf8BOM.ini", + reference: testdata[4].reference, + }); + testdata.push({ + filename: "data/iniparser06-utf8BOM.ini", + reference: testdata[5].reference, + }); + testdata.push({ + filename: "data/iniparser07-utf8BOM.ini", + reference: testdata[6].reference, + }); + testdata.push({ + filename: "data/iniparser08-utf8BOM.ini", + reference: testdata[7].reference, + }); + testdata.push({ + filename: "data/iniparser09-utf8BOM.ini", + reference: testdata[8].reference, + }); + testdata.push({ + filename: "data/iniparser10-utf8BOM.ini", + reference: testdata[9].reference, + }); + testdata.push({ + filename: "data/iniparser11-utf8BOM.ini", + reference: testdata[10].reference, + }); + testdata.push({ + filename: "data/iniparser12-utf8BOM.ini", + reference: testdata[11].reference, + }); + testdata.push({ + filename: "data/iniparser13-utf8BOM.ini", + reference: testdata[12].reference, + }); + testdata.push({ + filename: "data/iniparser14-utf8BOM.ini", + reference: testdata[13].reference, + }); + testdata.push({ + filename: "data/iniparser15-utf8BOM.ini", + reference: testdata[14].reference, + }); + testdata.push({ + filename: "data/iniparser16-utf8BOM.ini", + reference: testdata[15].reference, + }); + + // Intentional test for appInfo that can't be preloaded. + // eslint-disable-next-line mozilla/use-services + let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS; + if ("WINNT" === os) { + testdata.push({ + filename: "data/iniparser01-utf16leBOM.ini", + reference: testdata[0].reference, + }); + testdata.push({ + filename: "data/iniparser02-utf16leBOM.ini", + reference: testdata[1].reference, + }); + testdata.push({ + filename: "data/iniparser03-utf16leBOM.ini", + reference: testdata[2].reference, + }); + testdata.push({ + filename: "data/iniparser04-utf16leBOM.ini", + reference: testdata[3].reference, + }); + testdata.push({ + filename: "data/iniparser05-utf16leBOM.ini", + reference: testdata[4].reference, + }); + testdata.push({ + filename: "data/iniparser06-utf16leBOM.ini", + reference: testdata[5].reference, + }); + testdata.push({ + filename: "data/iniparser07-utf16leBOM.ini", + reference: testdata[6].reference, + }); + testdata.push({ + filename: "data/iniparser08-utf16leBOM.ini", + reference: testdata[7].reference, + }); + testdata.push({ + filename: "data/iniparser09-utf16leBOM.ini", + reference: testdata[8].reference, + }); + testdata.push({ + filename: "data/iniparser10-utf16leBOM.ini", + reference: testdata[9].reference, + }); + testdata.push({ + filename: "data/iniparser11-utf16leBOM.ini", + reference: testdata[10].reference, + }); + testdata.push({ + filename: "data/iniparser12-utf16leBOM.ini", + reference: testdata[11].reference, + }); + testdata.push({ + filename: "data/iniparser13-utf16leBOM.ini", + reference: testdata[12].reference, + }); + testdata.push({ + filename: "data/iniparser14-utf16leBOM.ini", + reference: testdata[13].reference, + }); + testdata.push({ + filename: "data/iniparser15-utf16leBOM.ini", + reference: testdata[14].reference, + }); + testdata.push({ + filename: "data/iniparser16-utf16leBOM.ini", + reference: testdata[15].reference, + }); + } + + /* ========== 0 ========== */ + factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService( + Ci.nsIINIParserFactory + ); + Assert.ok(!!factory); + + // Test reading from a variety of files and strings. While we're at it, + // write out each one and read it back to ensure that nothing changed. + while (testnum < testdata.length) { + dump("\nINFO | test #" + ++testnum); + let filename = testdata[testnum - 1].filename; + dump(", filename " + filename + "\n"); + let parser = parserForFile(filename); + checkParserOutput(parser, testdata[testnum - 1].reference); + if (!parser) { + continue; + } + Assert.ok(parser instanceof Ci.nsIINIParserWriter); + // write contents out to a new file + let newfilename = filename + ".new"; + let newfile = do_get_file(filename); + newfile.leafName += ".new"; + parser.writeFile(newfile); + // read new file and make sure the contents are the same. + parser = parserForFile(newfilename); + checkParserOutput(parser, testdata[testnum - 1].reference); + // cleanup after the test + newfile.remove(false); + + // ensure that `writeString` works correctly + Assert.ok(parser instanceof Ci.nsIINIParserWriter); + let formatted = parser.writeToString(); + parser = factory.createINIParser(null); + // re-parsing the formatted string is the easiest + // way to verify correctness... + parser.initFromString(formatted); + checkParserOutput(parser, testdata[testnum - 1].reference); + } + + dump("INFO | test #" + ++testnum + "\n"); + + // test writing to a new file. + var newfile = do_get_file("data/"); + newfile.append("nonexistent-file.ini"); + if (newfile.exists()) { + newfile.remove(false); + } + Assert.ok(!newfile.exists()); + + try { + var parser = factory.createINIParser(newfile); + Assert.ok(false, "Should have thrown an exception"); + } catch (e) { + Assert.equal( + e.result, + Cr.NS_ERROR_FILE_NOT_FOUND, + "Caught a file not found exception" + ); + } + parser = factory.createINIParser(); + Assert.ok(!!parser); + Assert.ok(parser instanceof Ci.nsIINIParserWriter); + checkParserOutput(parser, {}); + parser.writeFile(newfile); + Assert.ok(newfile.exists()); + + // test adding a new section and new key + parser.setString("section", "key", "value"); + parser.setString("section", "key2", ""); + parser.writeFile(newfile); + Assert.ok(newfile.exists()); + checkParserOutput(parser, { section: { key: "value", key2: "" } }); + // read it in again, check for same data. + parser = parserForFile("data/nonexistent-file.ini"); + checkParserOutput(parser, { section: { key: "value", key2: "" } }); + // cleanup after the test + newfile.remove(false); + + dump("INFO | test #" + ++testnum + "\n"); + + // test modifying a existing key's value (in an existing section) + parser = parserForFile("data/iniparser09.ini"); + checkParserOutput(parser, { section1: { name1: "value1" } }); + + Assert.ok(parser instanceof Ci.nsIINIParserWriter); + parser.setString("section1", "name1", "value2"); + checkParserOutput(parser, { section1: { name1: "value2" } }); + + dump("INFO | test #" + ++testnum + "\n"); + + // test trying to set illegal characters + var caughtError; + caughtError = null; + checkParserOutput(parser, { section1: { name1: "value2" } }); + + // Bad characters in section name + try { + parser.setString("bad\0", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("bad\r", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("bad\n", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("bad[", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("bad]", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("", "ok", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + + // Bad characters in key name + caughtError = null; + try { + parser.setString("ok", "bad\0", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "bad\r", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "bad\n", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "bad=", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "", "ok"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + + // Bad characters in value + caughtError = null; + try { + parser.setString("ok", "ok", "bad\0"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "ok", "bad\r"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "ok", "bad\n"); + } catch (e) { + caughtError = e; + } + Assert.ok(caughtError); + Assert.equal(caughtError.result, Cr.NS_ERROR_INVALID_ARG); + caughtError = null; + try { + parser.setString("ok", "ok", "good="); + } catch (e) { + caughtError = e; + } + Assert.ok(!caughtError); + caughtError = null; + } catch (e) { + throw new Error(`FAILED in test #${testnum} -- ${e}`); + } +} diff --git a/xpcom/tests/unit/test_ioutil.js b/xpcom/tests/unit/test_ioutil.js new file mode 100644 index 0000000000..e269b424a2 --- /dev/null +++ b/xpcom/tests/unit/test_ioutil.js @@ -0,0 +1,29 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ +/* 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/. */ + +const util = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil); + +function run_test() { + try { + util.inputStreamIsBuffered(null); + do_throw("inputStreamIsBuffered should have thrown"); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_INVALID_POINTER); + } + + try { + util.outputStreamIsBuffered(null); + do_throw("outputStreamIsBuffered should have thrown"); + } catch (e) { + Assert.equal(e.result, Cr.NS_ERROR_INVALID_POINTER); + } + + var s = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + var body = "This is a test"; + s.setData(body, body.length); + Assert.equal(util.inputStreamIsBuffered(s), true); +} diff --git a/xpcom/tests/unit/test_localfile.js b/xpcom/tests/unit/test_localfile.js new file mode 100644 index 0000000000..c90d91b278 --- /dev/null +++ b/xpcom/tests/unit/test_localfile.js @@ -0,0 +1,288 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); + +const MAX_TIME_DIFFERENCE = 2500; +const MILLIS_PER_DAY = 1000 * 60 * 60 * 24; + +var LocalFile = CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath"); + +function sleep(ms) { + // We are measuring timestamps, which are slightly fuzzed, and just need to + // measure that they are increasing. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + return new Promise(resolve => setTimeout(() => resolve(), ms)); +} + +add_task(function test_toplevel_parent_is_null() { + try { + var lf = new LocalFile("C:\\"); + + // not required by API, but a property on which the implementation of + // parent == null relies for correctness + Assert.ok(lf.path.length == 2); + + Assert.ok(lf.parent === null); + } catch (e) { + // not Windows + Assert.equal(e.result, Cr.NS_ERROR_FILE_UNRECOGNIZED_PATH); + } +}); + +add_task(function test_normalize_crash_if_media_missing() { + const a = "a".charCodeAt(0); + const z = "z".charCodeAt(0); + for (var i = a; i <= z; ++i) { + try { + LocalFile(String.fromCharCode(i) + ":.\\test").normalize(); + } catch (e) {} + } +}); + +// Tests that changing a file's modification time is possible +add_task(async function test_file_modification_time() { + let file = do_get_profile(); + file.append("testfile"); + + // Should never happen but get rid of it anyway + if (file.exists()) { + file.remove(true); + } + + const now = Date.now(); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + Assert.ok(file.exists()); + + const atime = file.lastAccessedTime; + + // Modification time may be out by up to 2 seconds on FAT filesystems. Test + // with a bit of leeway, close enough probably means it is correct. + let diff = Math.abs(file.lastModifiedTime - now); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + const yesterday = now - MILLIS_PER_DAY; + file.lastModifiedTime = yesterday; + + diff = Math.abs(file.lastModifiedTime - yesterday); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + Assert.equal( + file.lastAccessedTime, + atime, + "Setting lastModifiedTime should not set lastAccessedTime" + ); + + const tomorrow = now + MILLIS_PER_DAY; + file.lastModifiedTime = tomorrow; + + diff = Math.abs(file.lastModifiedTime - tomorrow); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + const bug377307 = 1172950238000; + file.lastModifiedTime = bug377307; + + diff = Math.abs(file.lastModifiedTime - bug377307); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + await sleep(1000); + + file.lastModifiedTime = 0; + Assert.greater( + file.lastModifiedTime, + now, + "Setting lastModifiedTime to 0 should set it to current date and time" + ); + + file.remove(true); +}); + +add_task(async function test_lastAccessedTime() { + const file = do_get_profile(); + + file.append("test-atime"); + if (file.exists()) { + file.remove(true); + } + + const now = Date.now(); + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + Assert.ok(file.exists()); + + const mtime = file.lastModifiedTime; + + // Modification time may be out by up to 2 seconds on FAT filesystems. Test + // with a bit of leeway, close enough probably means it is correct. + let diff = Math.abs(file.lastModifiedTime - now); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + const yesterday = now - MILLIS_PER_DAY; + file.lastAccessedTime = yesterday; + + diff = Math.abs(file.lastAccessedTime - yesterday); + Assert.ok(diff < MAX_TIME_DIFFERENCE, `${diff} < ${MAX_TIME_DIFFERENCE}`); + Assert.equal( + file.lastModifiedTime, + mtime, + "Setting lastAccessedTime should not set lastModifiedTime" + ); + + const tomorrow = now + MILLIS_PER_DAY; + file.lastAccessedTime = tomorrow; + + diff = Math.abs(file.lastAccessedTime - tomorrow); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + const bug377307 = 1172950238000; + file.lastAccessedTime = bug377307; + + diff = Math.abs(file.lastAccessedTime - bug377307); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + await sleep(1000); + + file.lastAccessedTime = 0; + Assert.greater( + file.lastAccessedTime, + now, + "Setting lastAccessedTime to 0 should set it to the current date and time" + ); +}); + +// Tests that changing a directory's modification time is possible +add_task(function test_directory_modification_time() { + var dir = do_get_profile(); + dir.append("testdir"); + + // Should never happen but get rid of it anyway + if (dir.exists()) { + dir.remove(true); + } + + var now = Date.now(); + dir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + Assert.ok(dir.exists()); + + // Modification time may be out by up to 2 seconds on FAT filesystems. Test + // with a bit of leeway, close enough probably means it is correct. + var diff = Math.abs(dir.lastModifiedTime - now); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + var yesterday = now - MILLIS_PER_DAY; + dir.lastModifiedTime = yesterday; + + diff = Math.abs(dir.lastModifiedTime - yesterday); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + var tomorrow = now - MILLIS_PER_DAY; + dir.lastModifiedTime = tomorrow; + + diff = Math.abs(dir.lastModifiedTime - tomorrow); + Assert.ok(diff < MAX_TIME_DIFFERENCE); + + dir.remove(true); +}); + +add_task(function test_diskSpaceAvailable() { + let file = do_get_profile(); + file.QueryInterface(Ci.nsIFile); + + let bytes = file.diskSpaceAvailable; + Assert.ok(bytes > 0); + + file.append("testfile"); + if (file.exists()) { + file.remove(true); + } + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + bytes = file.diskSpaceAvailable; + Assert.ok(bytes > 0); + + file.remove(true); +}); + +add_task(function test_diskCapacity() { + let file = do_get_profile(); + file.QueryInterface(Ci.nsIFile); + + const startBytes = file.diskCapacity; + Assert.ok(!!startBytes); // Not 0, undefined etc. + + file.append("testfile"); + if (file.exists()) { + file.remove(true); + } + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + const endBytes = file.diskCapacity; + Assert.ok(!!endBytes); // Not 0, undefined etc. + Assert.ok(startBytes === endBytes); + + file.remove(true); +}); + +add_task( + { + // nsIFile::CreationTime is only supported on macOS and Windows. + skip_if: () => !["macosx", "win"].includes(AppConstants.platform), + }, + function test_file_creation_time() { + const file = do_get_profile(); + // If we re-use the same file name from the other tests, even if the + // file.exists() check fails at 165, this test will likely fail due to the + // creation time being copied over from the previous instance of the file on + // Windows. + file.append("testfile-creation-time"); + + if (file.exists()) { + file.remove(true); + } + + const now = Date.now(); + + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + Assert.ok(file.exists()); + + const creationTime = file.creationTime; + Assert.ok(creationTime === file.lastModifiedTime); + + file.lastModifiedTime = now + MILLIS_PER_DAY; + + Assert.ok(creationTime !== file.lastModifiedTime); + Assert.ok(creationTime === file.creationTime); + + file.remove(true); + } +); + +add_task(function test_file_append_parent() { + const SEPARATOR = AppConstants.platform === "win" ? "\\" : "/"; + + const file = do_get_profile(); + + Assert.throws( + () => file.append(".."), + /NS_ERROR_FILE_UNRECOGNIZED_PATH/, + `nsLocalFile::Append("..") throws` + ); + + Assert.throws( + () => file.appendRelativePath(".."), + /NS_ERROR_FILE_UNRECOGNIZED_PATH/, + `nsLocalFile::AppendRelativePath("..") throws` + ); + + Assert.throws( + () => file.appendRelativePath(`foo${SEPARATOR}..${SEPARATOR}baz`), + /NS_ERROR_FILE_UNRECOGNIZED_PATH/, + `nsLocalFile::AppendRelativePath(path) fails when path contains ".."` + ); +}); diff --git a/xpcom/tests/unit/test_mac_bundle.js b/xpcom/tests/unit/test_mac_bundle.js new file mode 100644 index 0000000000..6703e8a2b8 --- /dev/null +++ b/xpcom/tests/unit/test_mac_bundle.js @@ -0,0 +1,18 @@ +function run_test() { + // this is a hack to skip the rest of the code on non-Mac platforms, + // since #ifdef is not available to xpcshell tests... + if (mozinfo.os != "mac") { + return; + } + + // OK, here's the real part of the test: + // make sure these two test bundles are recognized as bundles (or "packages") + var keynoteBundle = do_get_file("data/presentation.key"); + var appBundle = do_get_file("data/SmallApp.app"); + + Assert.ok(keynoteBundle instanceof Ci.nsILocalFileMac); + Assert.ok(appBundle instanceof Ci.nsILocalFileMac); + + Assert.ok(keynoteBundle.isPackage()); + Assert.ok(appBundle.isPackage()); +} diff --git a/xpcom/tests/unit/test_mac_xattrs.js b/xpcom/tests/unit/test_mac_xattrs.js new file mode 100644 index 0000000000..b387358d74 --- /dev/null +++ b/xpcom/tests/unit/test_mac_xattrs.js @@ -0,0 +1,98 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +const { FileUtils } = ChromeUtils.importESModule( + "resource://gre/modules/FileUtils.sys.mjs" +); + +const ATTR = "bogus.attr"; +const VALUE = new TextEncoder().encode("bogus"); + +async function run_test() { + const path = PathUtils.join( + Services.dirsvc.get("TmpD", Ci.nsIFile).path, + "macos-xattrs.tmp.d" + ); + await IOUtils.makeDirectory(path); + + try { + await test_macos_xattr(path); + } finally { + await IOUtils.remove(path, { recursive: true }); + } +} + +async function test_macos_xattr(tmpDir) { + const path = PathUtils.join(tmpDir, "file.tmp"); + + await IOUtils.writeUTF8(path, ""); + + const file = new FileUtils.File(path); + file.queryInterface(Cc.nsILocalFileMac); + + Assert.ok(!file.exists(), "File should not exist"); + + info("Testing reading an attribute on a file that does not exist"); + Assert.throws( + () => file.hasXAttr(ATTR), + /NS_ERROR_FILE_NOT_FOUND/, + "Non-existant files can't have attributes checked" + ); + Assert.throws( + () => file.getXAttr(ATTR), + /NS_ERROR_FILE_NOT_FOUND/, + "Non-existant files can't have attributes read" + ); + + file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + Assert.ok(file.exists(), "File exists after creation"); + + info("Testing reading an attribute that does not exist"); + Assert.ok( + !file.hasXAttr(ATTR), + "File should not have attribute before being set." + ); + Assert.throws( + () => file.getXAttr(ATTR), + /NS_ERROR_NOT_AVAILABLE/, + "Attempting to get an attribute that does not exist throws" + ); + + { + info("Testing setting and reading an attribute"); + file.setXAttr(ATTR, VALUE); + Assert.ok( + file.hasXAttr(ATTR), + "File should have attribute after being set" + ); + const result = file.getXAttr(ATTR); + + Assert.deepEqual( + Array.from(result), + Array.from(VALUE), + "File should have attribute value matching what was set" + ); + } + + info("Testing removing an attribute"); + file.delXAttr(ATTR); + Assert.ok( + !file.hasXAttr(ATTR), + "File should no longer have the attribute after removal" + ); + Assert.throws( + () => file.getXAttr(ATTR), + /NS_ERROR_NOT_AVAILABLE/, + "Attempting to get an attribute after removal results in an error" + ); + + info("Testing removing an attribute that does not exist"); + Assert.throws( + () => file.delXAttr(ATTR), + /NS_ERROR_NOT_AVAILABLE/, + "Attempting to remove an attribute that does not exist throws" + ); +} diff --git a/xpcom/tests/unit/test_notxpcom_scriptable.js b/xpcom/tests/unit/test_notxpcom_scriptable.js new file mode 100644 index 0000000000..5362894b70 --- /dev/null +++ b/xpcom/tests/unit/test_notxpcom_scriptable.js @@ -0,0 +1,67 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- + * 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/. */ + +const kCID = Components.ID("{1f9f7181-e6c5-4f4c-8f71-08005cec8468}"); +const kContract = "@testing/notxpcomtest"; + +function run_test() { + let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar); + + ok(Ci.nsIScriptableWithNotXPCOM); + + let method1Called = false; + + let testObject = { + QueryInterface: ChromeUtils.generateQI([ + "nsIScriptableOK", + "nsIScriptableWithNotXPCOM", + ]), + + method1() { + method1Called = true; + }, + + method2() { + ok(false, "method2 should not have been called!"); + }, + + method3() { + ok(false, "mehod3 should not have been called!"); + }, + + jsonly: true, + }; + + let factory = { + QueryInterface: ChromeUtils.generateQI(["nsIFactory"]), + + createInstance(iid) { + return testObject.QueryInterface(iid); + }, + }; + + registrar.registerFactory(kCID, null, kContract, factory); + + let xpcomObject = Cc[kContract].createInstance(); + ok(xpcomObject); + strictEqual(xpcomObject.jsonly, undefined); + + xpcomObject.QueryInterface(Ci.nsIScriptableOK); + + xpcomObject.method1(); + ok(method1Called); + + try { + xpcomObject.QueryInterface(Ci.nsIScriptableWithNotXPCOM); + ok(false, "Should not have implemented nsIScriptableWithNotXPCOM"); + } catch (e) { + ok( + true, + "Should not have implemented nsIScriptableWithNotXPCOM. Correctly threw error: " + + e + ); + } + strictEqual(xpcomObject.method2, undefined); +} diff --git a/xpcom/tests/unit/test_nsIMutableArray.js b/xpcom/tests/unit/test_nsIMutableArray.js new file mode 100644 index 0000000000..525196a48f --- /dev/null +++ b/xpcom/tests/unit/test_nsIMutableArray.js @@ -0,0 +1,131 @@ +/* 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/. */ + +var MutableArray = CC("@mozilla.org/array;1", "nsIMutableArray"); +var SupportsString = CC("@mozilla.org/supports-string;1", "nsISupportsString"); + +function create_n_element_array(n) { + var arr = new MutableArray(); + for (let i = 0; i < n; i++) { + let str = new SupportsString(); + str.data = "element " + i; + arr.appendElement(str); + } + return arr; +} + +function test_appending_null_actually_inserts() { + var arr = new MutableArray(); + Assert.equal(0, arr.length); + arr.appendElement(null); + Assert.equal(1, arr.length); +} + +function test_object_gets_appended() { + var arr = new MutableArray(); + var str = new SupportsString(); + str.data = "hello"; + arr.appendElement(str); + Assert.equal(1, arr.length); + var obj = arr.queryElementAt(0, Ci.nsISupportsString); + Assert.equal(str, obj); +} + +function test_insert_at_beginning() { + var arr = create_n_element_array(5); + // just a sanity check + Assert.equal(5, arr.length); + var str = new SupportsString(); + str.data = "hello"; + arr.insertElementAt(str, 0); + Assert.equal(6, arr.length); + var obj = arr.queryElementAt(0, Ci.nsISupportsString); + Assert.equal(str, obj); + // check the data of all the other objects + for (let i = 1; i < arr.length; i++) { + let obj2 = arr.queryElementAt(i, Ci.nsISupportsString); + Assert.equal("element " + (i - 1), obj2.data); + } +} + +function test_replace_element() { + var arr = create_n_element_array(5); + // just a sanity check + Assert.equal(5, arr.length); + var str = new SupportsString(); + str.data = "hello"; + // replace first element + arr.replaceElementAt(str, 0); + Assert.equal(5, arr.length); + var obj = arr.queryElementAt(0, Ci.nsISupportsString); + Assert.equal(str, obj); + // replace last element + arr.replaceElementAt(str, arr.length - 1); + Assert.equal(5, arr.length); + obj = arr.queryElementAt(arr.length - 1, Ci.nsISupportsString); + Assert.equal(str, obj); + // replace after last element, should insert empty elements + arr.replaceElementAt(str, 9); + Assert.equal(10, arr.length); + obj = arr.queryElementAt(9, Ci.nsISupportsString); + Assert.equal(str, obj); + // AFAIK there's no way to check the empty elements, since you can't QI them. +} + +function test_clear() { + var arr = create_n_element_array(5); + // just a sanity check + Assert.equal(5, arr.length); + arr.clear(); + Assert.equal(0, arr.length); +} + +function test_enumerate() { + var arr = create_n_element_array(5); + Assert.equal(5, arr.length); + var i = 0; + for (let str of arr.enumerate()) { + Assert.ok(str instanceof Ci.nsISupportsString); + Assert.equal(str.data, "element " + i); + i++; + } + Assert.equal(arr.length, i); +} + +function test_nsiarrayextensions() { + // Tests to check that the extensions that make an nsArray act like an + // nsISupportsArray for iteration purposes works. + // Note: we do not want to QI here, just want to make sure the magic glue + // works as a drop-in replacement. + + let fake_nsisupports_array = create_n_element_array(5); + + // Check that |Count| works. + Assert.equal(5, fake_nsisupports_array.Count()); + + for (let i = 0; i < fake_nsisupports_array.Count(); i++) { + // Check that the generic |GetElementAt| works. + let elm = fake_nsisupports_array.GetElementAt(i); + Assert.notEqual(elm, null); + let str = elm.QueryInterface(Ci.nsISupportsString); + Assert.notEqual(str, null); + Assert.equal(str.data, "element " + i); + } +} + +var tests = [ + test_appending_null_actually_inserts, + test_object_gets_appended, + test_insert_at_beginning, + test_replace_element, + test_clear, + test_enumerate, + test_nsiarrayextensions, +]; + +function run_test() { + for (var i = 0; i < tests.length; i++) { + tests[i](); + } +} diff --git a/xpcom/tests/unit/test_nsIProcess.js b/xpcom/tests/unit/test_nsIProcess.js new file mode 100644 index 0000000000..582d10440c --- /dev/null +++ b/xpcom/tests/unit/test_nsIProcess.js @@ -0,0 +1,184 @@ +/* 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/. */ +// nsIProcess unit test +const TEST_ARGS = [ + "mozilla", + "firefox", + "thunderbird", + "seamonkey", + "foo", + "bar", + "argument with spaces", + '"argument with quotes"', +]; + +const TEST_UNICODE_ARGS = [ + "M\u00F8z\u00EEll\u00E5", + "\u041C\u043E\u0437\u0438\u043B\u043B\u0430", + "\u09AE\u09CB\u099C\u09BF\u09B2\u09BE", + "\uD808\uDE2C\uD808\uDF63\uD808\uDDB7", +]; + +// test if a process can be started, polled for its running status +// and then killed +function test_kill() { + var file = get_test_program("TestBlockingProcess"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + Assert.ok(!process.isRunning); + + try { + process.kill(); + do_throw("Attempting to kill a not-running process should throw"); + } catch (e) {} + + process.run(false, [], 0); + + Assert.ok(process.isRunning); + + process.kill(); + + Assert.ok(!process.isRunning); + + try { + process.kill(); + do_throw("Attempting to kill a not-running process should throw"); + } catch (e) {} +} + +// test if we can get an exit value from an application that is +// guaranteed to return an exit value of 42 +function test_quick() { + var file = get_test_program("TestQuickReturn"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + // to get an exit value it must be a blocking process + process.run(true, [], 0); + + Assert.equal(process.exitValue, 42); +} + +function test_args(file, args, argsAreASCII) { + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + if (argsAreASCII) { + process.run(true, args, args.length); + } else { + process.runw(true, args, args.length); + } + + Assert.equal(process.exitValue, 0); +} + +// test if an argument can be successfully passed to an application +// that will return 0 if "mozilla" is the only argument +function test_arguments() { + test_args(get_test_program("TestArguments"), TEST_ARGS, true); +} + +// test if Unicode arguments can be successfully passed to an application +function test_unicode_arguments() { + test_args(get_test_program("TestUnicodeArguments"), TEST_UNICODE_ARGS, false); +} + +function rename_and_test(asciiName, unicodeName, args, argsAreASCII) { + var asciiFile = get_test_program(asciiName); + var asciiLeaf = asciiFile.leafName; + var unicodeLeaf = asciiLeaf.replace(asciiName, unicodeName); + + asciiFile.moveTo(null, unicodeLeaf); + + var unicodeFile = get_test_program(unicodeName); + + test_args(unicodeFile, args, argsAreASCII); + + unicodeFile.moveTo(null, asciiLeaf); +} + +// test passing ASCII and Unicode arguments to an application with a Unicode name +function test_unicode_app() { + rename_and_test( + "TestArguments", + // "Unicode" in Tamil + "\u0BAF\u0BC1\u0BA9\u0BBF\u0B95\u0BCB\u0B9F\u0BCD", + TEST_ARGS, + true + ); + + rename_and_test( + "TestUnicodeArguments", + // "Unicode" in Thai + "\u0E22\u0E39\u0E19\u0E34\u0E42\u0E04\u0E14", + TEST_UNICODE_ARGS, + false + ); +} + +// test if we get notified about a blocking process +function test_notify_blocking() { + var file = get_test_program("TestQuickReturn"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + process.runAsync([], 0, { + observe(subject, topic, data) { + process = subject.QueryInterface(Ci.nsIProcess); + Assert.equal(topic, "process-failed"); + Assert.equal(process.exitValue, 42); + test_notify_nonblocking(); + }, + }); +} + +// test if we get notified about a non-blocking process +function test_notify_nonblocking() { + var file = get_test_program("TestArguments"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + process.runAsync(TEST_ARGS, TEST_ARGS.length, { + observe(subject, topic, data) { + process = subject.QueryInterface(Ci.nsIProcess); + Assert.equal(topic, "process-finished"); + Assert.equal(process.exitValue, 0); + test_notify_killed(); + }, + }); +} + +// test if we get notified about a killed process +function test_notify_killed() { + var file = get_test_program("TestBlockingProcess"); + + var process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + process.runAsync([], 0, { + observe(subject, topic, data) { + process = subject.QueryInterface(Ci.nsIProcess); + Assert.equal(topic, "process-failed"); + do_test_finished(); + }, + }); + + process.kill(); +} + +function run_test() { + set_process_running_environment(); + test_kill(); + test_quick(); + test_arguments(); + test_unicode_arguments(); + test_unicode_app(); + do_test_pending(); + test_notify_blocking(); +} diff --git a/xpcom/tests/unit/test_nsIProcess_stress.js b/xpcom/tests/unit/test_nsIProcess_stress.js new file mode 100644 index 0000000000..802f9d70aa --- /dev/null +++ b/xpcom/tests/unit/test_nsIProcess_stress.js @@ -0,0 +1,24 @@ +function run_test() { + set_process_running_environment(); + + var file = get_test_program("TestQuickReturn"); + var tm = Cc["@mozilla.org/thread-manager;1"].getService(); + + for (var i = 0; i < 1000; i++) { + var process = Cc["@mozilla.org/process/util;1"].createInstance( + Ci.nsIProcess + ); + process.init(file); + + process.run(false, [], 0); + + try { + process.kill(); + } catch (e) {} + + // We need to ensure that we process any events on the main thread - + // this allow threads to clean up properly and avoid out of memory + // errors during the test. + tm.spinEventLoopUntilEmpty(); + } +} diff --git a/xpcom/tests/unit/test_pipe.js b/xpcom/tests/unit/test_pipe.js new file mode 100644 index 0000000000..e3ccc6ef3f --- /dev/null +++ b/xpcom/tests/unit/test_pipe.js @@ -0,0 +1,55 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +var Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init"); + +function run_test() { + test_not_initialized(); + test_ends_are_threadsafe(); +} + +function test_not_initialized() { + var p = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe); + try { + var dummy = p.outputStream; + dump("dummy: " + dummy + "\n"); + throw Components.Exception("", Cr.NS_ERROR_FAILURE); + } catch (e) { + if (e.result != Cr.NS_ERROR_NOT_INITIALIZED) { + do_throw( + "using a pipe before initializing it should throw NS_ERROR_NOT_INITIALIZED" + ); + } + } +} + +function test_ends_are_threadsafe() { + var p, is, os; + + p = new Pipe(true, true, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + Assert.ok(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); + + p = new Pipe(true, false, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + Assert.ok(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); + + p = new Pipe(false, true, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + Assert.ok(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); + + p = new Pipe(false, false, 1024, 1, null); + is = p.inputStream.QueryInterface(Ci.nsIClassInfo); + os = p.outputStream.QueryInterface(Ci.nsIClassInfo); + Assert.ok(Boolean(is.flags & Ci.nsIClassInfo.THREADSAFE)); + Assert.ok(Boolean(os.flags & Ci.nsIClassInfo.THREADSAFE)); +} diff --git a/xpcom/tests/unit/test_process_directives.js b/xpcom/tests/unit/test_process_directives.js new file mode 100644 index 0000000000..b1975a43da --- /dev/null +++ b/xpcom/tests/unit/test_process_directives.js @@ -0,0 +1,20 @@ +function categoryExists(category, entry) { + try { + Services.catMan.getCategoryEntry(category, entry); + return true; + } catch (e) { + return false; + } +} + +function run_test() { + Components.manager.autoRegister( + do_get_file("data/process_directive.manifest") + ); + + let isChild = + Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT; + + Assert.equal(categoryExists("directives-test", "main-process"), !isChild); + Assert.equal(categoryExists("directives-test", "content-process"), isChild); +} diff --git a/xpcom/tests/unit/test_process_directives_child.js b/xpcom/tests/unit/test_process_directives_child.js new file mode 100644 index 0000000000..dca63b3563 --- /dev/null +++ b/xpcom/tests/unit/test_process_directives_child.js @@ -0,0 +1,3 @@ +function run_test() { + run_test_in_child("test_process_directives.js"); +} diff --git a/xpcom/tests/unit/test_seek_multiplex.js b/xpcom/tests/unit/test_seek_multiplex.js new file mode 100644 index 0000000000..7a469d9f48 --- /dev/null +++ b/xpcom/tests/unit/test_seek_multiplex.js @@ -0,0 +1,164 @@ +/* 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/. */ + +// The string we use as data. +const data = "0123456789"; +// Number of streams in the multiplex stream. +const count = 10; + +function test_multiplex_streams() { + var MultiplexStream = CC( + "@mozilla.org/io/multiplex-input-stream;1", + "nsIMultiplexInputStream" + ); + Assert.equal(1, 1); + + var multiplex = new MultiplexStream(); + for (var i = 0; i < count; ++i) { + let s = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + s.setData(data, data.length); + + multiplex.appendStream(s); + } + var seekable = multiplex.QueryInterface(Ci.nsISeekableStream); + var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(seekable); + // Read some data. + var readData = sis.read(20); + Assert.equal(readData, data + data); + // -- Tests for NS_SEEK_SET + // Seek to a non-zero, non-stream-boundary offset. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 2); + Assert.equal(seekable.tell(), 2); + Assert.equal(seekable.available(), 98); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 9); + Assert.equal(seekable.tell(), 9); + Assert.equal(seekable.available(), 91); + // Seek across stream boundary. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 35); + Assert.equal(seekable.tell(), 35); + Assert.equal(seekable.available(), 65); + readData = sis.read(5); + Assert.equal(readData, data.slice(5)); + Assert.equal(seekable.available(), 60); + // Seek at stream boundaries. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 40); + Assert.equal(seekable.tell(), 40); + Assert.equal(seekable.available(), 60); + readData = sis.read(10); + Assert.equal(readData, data); + Assert.equal(seekable.tell(), 50); + Assert.equal(seekable.available(), 50); + // Rewind and read across streams. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 39); + Assert.equal(seekable.tell(), 39); + Assert.equal(seekable.available(), 61); + readData = sis.read(11); + Assert.equal(readData, data.slice(9) + data); + Assert.equal(seekable.tell(), 50); + Assert.equal(seekable.available(), 50); + // Rewind to the beginning. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + Assert.equal(seekable.tell(), 0); + Assert.equal(seekable.available(), 100); + // Seek to some random location + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 50); + // -- Tests for NS_SEEK_CUR + // Positive seek. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, 15); + Assert.equal(seekable.tell(), 65); + Assert.equal(seekable.available(), 35); + readData = sis.read(10); + Assert.equal(readData, data.slice(5) + data.slice(0, 5)); + Assert.equal(seekable.tell(), 75); + Assert.equal(seekable.available(), 25); + // Negative seek. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15); + Assert.equal(seekable.tell(), 60); + Assert.equal(seekable.available(), 40); + readData = sis.read(10); + Assert.equal(readData, data); + Assert.equal(seekable.tell(), 70); + Assert.equal(seekable.available(), 30); + + // -- Tests for NS_SEEK_END + // Normal read. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -5); + Assert.equal(seekable.tell(), data.length * count - 5); + readData = sis.read(5); + Assert.equal(readData, data.slice(5)); + Assert.equal(seekable.tell(), data.length * count); + // Read across streams. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, -15); + Assert.equal(seekable.tell(), data.length * count - 15); + readData = sis.read(15); + Assert.equal(readData, data.slice(5) + data); + Assert.equal(seekable.tell(), data.length * count); + + // -- Try to do various edge cases + // Forward seek from the end, should throw. + var caught = false; + try { + seekable.seek(Ci.nsISeekableStream.NS_SEEK_END, 15); + } catch (e) { + caught = true; + } + Assert.equal(caught, true); + Assert.equal(seekable.tell(), data.length * count); + // Backward seek from the beginning, should be clamped. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + Assert.equal(seekable.tell(), 0); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -15); + Assert.equal(seekable.tell(), 0); + // Seek too far: should be clamped. + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0); + Assert.equal(seekable.tell(), 0); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, 3 * data.length * count); + Assert.equal(seekable.tell(), 100); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, data.length * count); + Assert.equal(seekable.tell(), 100); + seekable.seek(Ci.nsISeekableStream.NS_SEEK_CUR, -2 * data.length * count); + Assert.equal(seekable.tell(), 0); +} + +function test_multiplex_bug797871() { + var data2 = "1234567890123456789012345678901234567890"; + + var MultiplexStream = CC( + "@mozilla.org/io/multiplex-input-stream;1", + "nsIMultiplexInputStream" + ); + Assert.equal(1, 1); + + var multiplex = new MultiplexStream(); + let s = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + s.setData(data2, data2.length); + + multiplex.appendStream(s); + + var seekable = multiplex.QueryInterface(Ci.nsISeekableStream); + var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(seekable); + + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 8); + Assert.equal(seekable.tell(), 8); + sis.read(2); + Assert.equal(seekable.tell(), 10); + + seekable.seek(Ci.nsISeekableStream.NS_SEEK_SET, 20); + Assert.equal(seekable.tell(), 20); +} + +function run_test() { + test_multiplex_streams(); + test_multiplex_bug797871(); +} diff --git a/xpcom/tests/unit/test_storagestream.js b/xpcom/tests/unit/test_storagestream.js new file mode 100644 index 0000000000..2a0fb1b569 --- /dev/null +++ b/xpcom/tests/unit/test_storagestream.js @@ -0,0 +1,152 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* 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/. */ + +/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "unusedVariable" }] */ + +function run_test() { + test1(); + test2(); + test3(); + test4(); +} + +/** + * Checks that getting an input stream from a storage stream which has never had + * anything written to it throws a not-initialized exception. + */ +function test1() { + var ss = Cc["@mozilla.org/storagestream;1"].createInstance( + Ci.nsIStorageStream + ); + ss.init(1024, 1024, null); + + var unusedVariable = ss.getOutputStream(0); + var inp2 = ss.newInputStream(0); + Assert.equal(inp2.available(), 0); + Assert.ok(inp2.isNonBlocking()); + + var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(inp2); + + var threw = false; + try { + sis.read(1); + } catch (ex) { + if (ex.result == Cr.NS_BASE_STREAM_WOULD_BLOCK) { + threw = true; + } else { + throw ex; + } + } + Assert.ok(threw); +} + +/** + * Checks that getting an input stream from a storage stream to which 0 bytes of + * data have been explicitly written doesn't throw an exception. + */ +function test2() { + var ss = Cc["@mozilla.org/storagestream;1"].createInstance( + Ci.nsIStorageStream + ); + ss.init(1024, 1024, null); + + var out = ss.getOutputStream(0); + out.write("", 0); + try { + ss.newInputStream(0); + } catch (e) { + do_throw("shouldn't throw exception when new input stream created"); + } +} + +/** + * Checks that reading any non-zero amount of data from a storage stream + * which has had 0 bytes written to it explicitly works correctly. + */ +function test3() { + var ss = Cc["@mozilla.org/storagestream;1"].createInstance( + Ci.nsIStorageStream + ); + ss.init(1024, 1024, null); + + var out = ss.getOutputStream(0); + out.write("", 0); + try { + var inp = ss.newInputStream(0); + } catch (e) { + do_throw("newInputStream(0) shouldn't throw if write() is called: " + e); + } + + Assert.ok(inp.isNonBlocking(), "next test expects a non-blocking stream"); + + try { + var threw = false; + var bis = BIS(inp); + bis.readByteArray(5); + } catch (e) { + if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) { + do_throw("wrong error thrown: " + e); + } + threw = true; + } + Assert.ok(threw, "should have thrown (nsStorageInputStream is nonblocking)"); +} + +/** + * Basic functionality test for storagestream: write data to it, get an input + * stream, and read the data back to see that it matches. + */ +function test4() { + var bytes = [65, 66, 67, 68, 69, 70, 71, 72, 73, 74]; + + var ss = Cc["@mozilla.org/storagestream;1"].createInstance( + Ci.nsIStorageStream + ); + ss.init(1024, 1024, null); + + var outStream = ss.getOutputStream(0); + + var bos = Cc["@mozilla.org/binaryoutputstream;1"].createInstance( + Ci.nsIBinaryOutputStream + ); + bos.setOutputStream(outStream); + + bos.writeByteArray(bytes); + bos.close(); + + var inp = ss.newInputStream(0); + var bis = BIS(inp); + + var count = 0; + while (count < bytes.length) { + var data = bis.read8(1); + Assert.equal(data, bytes[count++]); + } + + var threw = false; + try { + data = bis.read8(1); + } catch (e) { + if (e.result != Cr.NS_ERROR_FAILURE) { + do_throw("wrong error thrown: " + e); + } + threw = true; + } + if (!threw) { + do_throw("should have thrown but instead returned: '" + data + "'"); + } +} + +function BIS(input) { + var bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + bis.setInputStream(input); + return bis; +} diff --git a/xpcom/tests/unit/test_streams.js b/xpcom/tests/unit/test_streams.js new file mode 100644 index 0000000000..c0c644bf19 --- /dev/null +++ b/xpcom/tests/unit/test_streams.js @@ -0,0 +1,170 @@ +/* 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/. */ + +var Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init"); +var BinaryOutput = CC( + "@mozilla.org/binaryoutputstream;1", + "nsIBinaryOutputStream", + "setOutputStream" +); +var BinaryInput = CC( + "@mozilla.org/binaryinputstream;1", + "nsIBinaryInputStream", + "setInputStream" +); + +/** + * Binary stream tests. + */ +function test_binary_streams() { + var p, is, os; + + p = new Pipe(false, false, 1024, 1, null); + is = new BinaryInput(p.inputStream); + os = new BinaryOutput(p.outputStream); + + const LargeNum = Math.pow(2, 18) + Math.pow(2, 12) + 1; + const HugeNum = Math.pow(2, 62); + const HelloStr = "Hello World"; + const HelloArray = Array.from(HelloStr, function (c) { + return c.charCodeAt(0); + }); + var countObj = {}; + var msg = {}; + var buffer = new ArrayBuffer(HelloArray.length); + + // Test reading immediately after writing. + os.writeBoolean(true); + Assert.equal(is.readBoolean(), true); + os.write8(4); + Assert.equal(is.read8(), 4); + os.write16(4); + Assert.equal(is.read16(), 4); + os.write16(1024); + Assert.equal(is.read16(), 1024); + os.write32(7); + Assert.equal(is.read32(), 7); + os.write32(LargeNum); + Assert.equal(is.read32(), LargeNum); + os.write64(LargeNum); + Assert.equal(is.read64(), LargeNum); + os.write64(1024); + Assert.equal(is.read64(), 1024); + os.write64(HugeNum); + Assert.equal(is.read64(), HugeNum); + os.writeFloat(2.5); + Assert.equal(is.readFloat(), 2.5); + // os.writeDouble(Math.SQRT2); + // do_check_eq(is.readDouble(), Math.SQRT2); + os.writeStringZ("Mozilla"); + Assert.equal(is.readCString(), "Mozilla"); + os.writeWStringZ("Gecko"); + Assert.equal(is.readString(), "Gecko"); + os.writeBytes(HelloStr, HelloStr.length); + Assert.equal(is.available(), HelloStr.length); + msg = is.readBytes(HelloStr.length); + Assert.equal(msg, HelloStr); + msg = null; + countObj.value = -1; + os.writeByteArray(HelloArray); + Assert.equal(is.available(), HelloStr.length); + msg = is.readByteArray(HelloStr.length); + Assert.equal(typeof msg, typeof HelloArray); + Assert.equal(msg.toSource(), HelloArray.toSource()); + Assert.equal(is.available(), 0); + os.writeByteArray(HelloArray); + Assert.equal( + is.readArrayBuffer(buffer.byteLength, buffer), + HelloArray.length + ); + Assert.equal([...new Uint8Array(buffer)].toSource(), HelloArray.toSource()); + Assert.equal(is.available(), 0); + + // Test writing in one big chunk. + os.writeBoolean(true); + os.write8(4); + os.write16(4); + os.write16(1024); + os.write32(7); + os.write32(LargeNum); + os.write64(LargeNum); + os.write64(1024); + os.write64(HugeNum); + os.writeFloat(2.5); + // os.writeDouble(Math.SQRT2); + os.writeStringZ("Mozilla"); + os.writeWStringZ("Gecko"); + os.writeBytes(HelloStr, HelloStr.length); + os.writeByteArray(HelloArray); + // Available should not be zero after a long write like this. + Assert.notEqual(is.available(), 0); + + // Test reading in one big chunk. + Assert.equal(is.readBoolean(), true); + Assert.equal(is.read8(), 4); + Assert.equal(is.read16(), 4); + Assert.equal(is.read16(), 1024); + Assert.equal(is.read32(), 7); + Assert.equal(is.read32(), LargeNum); + Assert.equal(is.read64(), LargeNum); + Assert.equal(is.read64(), 1024); + Assert.equal(is.read64(), HugeNum); + Assert.equal(is.readFloat(), 2.5); + // do_check_eq(is.readDouble(), Math.SQRT2); + Assert.equal(is.readCString(), "Mozilla"); + Assert.equal(is.readString(), "Gecko"); + // Remember, we wrote HelloStr twice - once as a string, and then as an array. + Assert.equal(is.available(), HelloStr.length * 2); + msg = is.readBytes(HelloStr.length); + Assert.equal(msg, HelloStr); + msg = null; + countObj.value = -1; + Assert.equal(is.available(), HelloStr.length); + msg = is.readByteArray(HelloStr.length); + Assert.equal(typeof msg, typeof HelloArray); + Assert.equal(msg.toSource(), HelloArray.toSource()); + Assert.equal(is.available(), 0); + + // Test for invalid actions. + os.close(); + is.close(); + + try { + os.writeBoolean(false); + do_throw("Not reached!"); + } catch (e) { + if ( + !(e instanceof Ci.nsIException && e.result == Cr.NS_BASE_STREAM_CLOSED) + ) { + throw e; + } + // do nothing + } + + try { + is.available(); + do_throw("Not reached!"); + } catch (e) { + if ( + !(e instanceof Ci.nsIException && e.result == Cr.NS_BASE_STREAM_CLOSED) + ) { + throw e; + } + // do nothing + } + + try { + is.readBoolean(); + do_throw("Not reached!"); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + // do nothing + } +} + +function run_test() { + test_binary_streams(); +} diff --git a/xpcom/tests/unit/test_stringstream.js b/xpcom/tests/unit/test_stringstream.js new file mode 100644 index 0000000000..0b9dcf3c5e --- /dev/null +++ b/xpcom/tests/unit/test_stringstream.js @@ -0,0 +1,20 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ +/* 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/. */ + +function run_test() { + var s = Cc["@mozilla.org/io/string-input-stream;1"].createInstance( + Ci.nsIStringInputStream + ); + var body = "This is a test"; + s.setData(body, body.length); + Assert.equal(s.available(), body.length); + + var sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( + Ci.nsIScriptableInputStream + ); + sis.init(s); + + Assert.equal(sis.read(body.length), body); +} diff --git a/xpcom/tests/unit/test_symlinks.js b/xpcom/tests/unit/test_symlinks.js new file mode 100644 index 0000000000..8c0d25f792 --- /dev/null +++ b/xpcom/tests/unit/test_symlinks.js @@ -0,0 +1,139 @@ +const CWD = do_get_cwd(); + +const DIR_TARGET = "target"; +const DIR_LINK = "link"; +const DIR_LINK_LINK = "link_link"; +const FILE_TARGET = "target.txt"; +const FILE_LINK = "link.txt"; +const FILE_LINK_LINK = "link_link.txt"; + +const DOES_NOT_EXIST = "doesnotexist"; +const DANGLING_LINK = "dangling_link"; +const LOOP_LINK = "loop_link"; + +const nsIFile = Ci.nsIFile; + +var process; +function createSymLink(from, to) { + if (!process) { + var ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + ln.initWithPath("/bin/ln"); + + process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(ln); + } + + const args = ["-s", from, to]; + process.run(true, args, args.length); + Assert.equal(process.exitValue, 0); +} + +function makeSymLink(from, toName, relative) { + var to = from.parent; + to.append(toName); + + if (relative) { + createSymLink(from.leafName, to.path); + } else { + createSymLink(from.path, to.path); + } + + Assert.ok(to.isSymlink()); + + print("---"); + print(from.path); + print(to.path); + print(to.target); + + if (from.leafName != DOES_NOT_EXIST && from.isSymlink()) { + // XXXjag wish I could set followLinks to false so we'd just get + // the symlink's direct target instead of the final target. + Assert.equal(from.target, to.target); + } else { + Assert.equal(from.path, to.target); + } + + return to; +} + +function setupTestDir(testDir, relative) { + var targetDir = testDir.clone(); + targetDir.append(DIR_TARGET); + + if (testDir.exists()) { + testDir.remove(true); + } + Assert.ok(!testDir.exists()); + + testDir.create(nsIFile.DIRECTORY_TYPE, 0o777); + + targetDir.create(nsIFile.DIRECTORY_TYPE, 0o777); + + var targetFile = testDir.clone(); + targetFile.append(FILE_TARGET); + targetFile.create(nsIFile.NORMAL_FILE_TYPE, 0o666); + + var imaginary = testDir.clone(); + imaginary.append(DOES_NOT_EXIST); + + var loop = testDir.clone(); + loop.append(LOOP_LINK); + + var dirLink = makeSymLink(targetDir, DIR_LINK, relative); + var fileLink = makeSymLink(targetFile, FILE_LINK, relative); + + makeSymLink(dirLink, DIR_LINK_LINK, relative); + makeSymLink(fileLink, FILE_LINK_LINK, relative); + + makeSymLink(imaginary, DANGLING_LINK, relative); + + try { + makeSymLink(loop, LOOP_LINK, relative); + Assert.ok(false); + } catch (e) {} +} + +function createSpaces(dirs, files, links) { + function longest(a, b) { + return a.length > b.length ? a : b; + } + return dirs.concat(files, links).reduce(longest, "").replace(/./g, " "); +} + +function testSymLinks(testDir, relative) { + setupTestDir(testDir, relative); + + const dirLinks = [DIR_LINK, DIR_LINK_LINK]; + const fileLinks = [FILE_LINK, FILE_LINK_LINK]; + const otherLinks = [DANGLING_LINK, LOOP_LINK]; + const dirs = [DIR_TARGET].concat(dirLinks); + const files = [FILE_TARGET].concat(fileLinks); + const links = otherLinks.concat(dirLinks, fileLinks); + + const spaces = createSpaces(dirs, files, links); + const bools = { false: " false", true: " true " }; + print(spaces + " dir file symlink"); + var dirEntries = testDir.directoryEntries; + while (dirEntries.hasMoreElements()) { + const file = dirEntries.nextFile; + const name = file.leafName; + print( + name + + spaces.substring(name.length) + + bools[file.isDirectory()] + + bools[file.isFile()] + + bools[file.isSymlink()] + ); + Assert.equal(file.isDirectory(), dirs.includes(name)); + Assert.equal(file.isFile(), files.includes(name)); + Assert.equal(file.isSymlink(), links.includes(name)); + } +} + +function run_test() { + var testDir = CWD; + testDir.append("test_symlinks"); + + testSymLinks(testDir, false); + testSymLinks(testDir, true); +} diff --git a/xpcom/tests/unit/test_systemInfo.js b/xpcom/tests/unit/test_systemInfo.js new file mode 100644 index 0000000000..6ab67796c9 --- /dev/null +++ b/xpcom/tests/unit/test_systemInfo.js @@ -0,0 +1,26 @@ +/* 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/. */ + +function run_test() { + const PROPERTIES = [ + "name", + "arch", + "version", + "pagesize", + "pageshift", + "memmapalign", + "memsize", + ]; + let sysInfo = Services.sysinfo; + + PROPERTIES.forEach(function (aPropertyName) { + print("Testing property: " + aPropertyName); + let value = sysInfo.getProperty(aPropertyName); + Assert.ok(!!value); + }); + + // This property must exist, but its value might be zero. + print("Testing property: umask"); + Assert.equal(typeof sysInfo.getProperty("umask"), "number"); +} diff --git a/xpcom/tests/unit/test_versioncomparator.js b/xpcom/tests/unit/test_versioncomparator.js new file mode 100644 index 0000000000..5744d62721 --- /dev/null +++ b/xpcom/tests/unit/test_versioncomparator.js @@ -0,0 +1,55 @@ +// Versions to test listed in ascending order, none can be equal +var comparisons = [ + "pre", + // A number that is too large to be supported should be normalized to 0. + String(0x1f0000000), + "0.9", + "0.9.1", + "1.0pre1", + "1.0pre2", + "1.0", + "1.1pre", + "1.1pre1a", + "1.1pre1", + "1.1pre10a", + "1.1pre10", + "1.1", + "1.1.0.1", + "1.1.1", + "1.1.*", + "1.*", + "2.0", + "2.1", + "3.0.-1", + "3.0", +]; + +// Every version in this list means the same version number +var equality = ["1.1pre", "1.1pre0", "1.0+"]; + +function run_test() { + for (var i = 0; i < comparisons.length; i++) { + for (var j = 0; j < comparisons.length; j++) { + var result = Services.vc.compare(comparisons[i], comparisons[j]); + if (i == j) { + if (result != 0) { + do_throw(comparisons[i] + " should be the same as itself"); + } + } else if (i < j) { + if (!(result < 0)) { + do_throw(comparisons[i] + " should be less than " + comparisons[j]); + } + } else if (!(result > 0)) { + do_throw(comparisons[i] + " should be greater than " + comparisons[j]); + } + } + } + + for (i = 0; i < equality.length; i++) { + for (j = 0; j < equality.length; j++) { + if (Services.vc.compare(equality[i], equality[j]) != 0) { + do_throw(equality[i] + " should equal " + equality[j]); + } + } + } +} diff --git a/xpcom/tests/unit/test_windows_cmdline_file.js b/xpcom/tests/unit/test_windows_cmdline_file.js new file mode 100644 index 0000000000..318f93ebec --- /dev/null +++ b/xpcom/tests/unit/test_windows_cmdline_file.js @@ -0,0 +1,22 @@ +let executableFile = Services.dirsvc.get("CurProcD", Ci.nsIFile); +executableFile.append("xpcshell.exe"); +function run_test() { + let quote = '"'; // Windows' cmd processor doesn't actually use single quotes. + for (let suffix of ["", " -osint", ` --blah "%PROGRAMFILES%"`]) { + let cmdline = quote + executableFile.path + quote + suffix; + info(`Testing with ${cmdline}`); + let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFileWin); + f.initWithCommandLine(cmdline); + Assert.equal( + f.path, + executableFile.path, + "Should be able to recover executable path" + ); + } + + let f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFileWin); + f.initWithCommandLine("%ComSpec% -c echo 'hi'"); + let cmd = Services.dirsvc.get("SysD", Ci.nsIFile); + cmd.append("cmd.exe"); + Assert.equal(f.path, cmd.path, "Should be able to replace env vars."); +} diff --git a/xpcom/tests/unit/test_windows_registry.js b/xpcom/tests/unit/test_windows_registry.js new file mode 100644 index 0000000000..6b89f55502 --- /dev/null +++ b/xpcom/tests/unit/test_windows_registry.js @@ -0,0 +1,237 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ + +/* 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/. */ + +const nsIWindowsRegKey = Ci.nsIWindowsRegKey; +let regKeyComponent = Cc["@mozilla.org/windows-registry-key;1"]; + +function run_test() { + //* create a key structure in a spot that's normally writable (somewhere under HKCU). + let testKey = regKeyComponent.createInstance(nsIWindowsRegKey); + + // If it's already present because a previous test crashed or didn't clean up properly, clean it up first. + let keyName = BASE_PATH + "\\" + TESTDATA_KEYNAME; + setup_test_run(testKey, keyName); + + //* test that the write* functions write stuff + test_writing_functions(testKey); + + //* check that the valueCount/getValueName functions work for the values we just wrote + test_value_functions(testKey); + + //* check that the get* functions work for the values we just wrote. + test_reading_functions(testKey); + + //* check that the get* functions fail with the right exception codes if we ask for the wrong type or if the value name doesn't exist at all + test_invalidread_functions(testKey); + + //* check that creating/enumerating/deleting child keys works + test_childkey_functions(testKey); + + test_watching_functions(testKey); + + //* clean up + cleanup_test_run(testKey, keyName); +} + +function setup_test_run(testKey, keyName) { + info("Setup test run"); + try { + testKey.open( + nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + keyName, + nsIWindowsRegKey.ACCESS_READ + ); + info("Test key exists. Needs cleanup."); + cleanup_test_run(testKey, keyName); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } + + testKey.create( + nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + keyName, + nsIWindowsRegKey.ACCESS_ALL + ); +} + +function test_writing_functions(testKey) { + strictEqual(testKey.valueCount, 0); + + strictEqual(testKey.hasValue(TESTDATA_STRNAME), false); + testKey.writeStringValue(TESTDATA_STRNAME, TESTDATA_STRVALUE); + strictEqual(testKey.hasValue(TESTDATA_STRNAME), true); + + strictEqual(testKey.hasValue(TESTDATA_INTNAME), false); + testKey.writeIntValue(TESTDATA_INTNAME, TESTDATA_INTVALUE); + + strictEqual(testKey.hasValue(TESTDATA_INT64NAME), false); + testKey.writeInt64Value(TESTDATA_INT64NAME, TESTDATA_INT64VALUE); + + strictEqual(testKey.hasValue(TESTDATA_BINARYNAME), false); + testKey.writeBinaryValue(TESTDATA_BINARYNAME, TESTDATA_BINARYVALUE); +} + +function test_value_functions(testKey) { + strictEqual(testKey.valueCount, 4); + strictEqual(testKey.getValueName(0), TESTDATA_STRNAME); + strictEqual(testKey.getValueName(1), TESTDATA_INTNAME); + strictEqual(testKey.getValueName(2), TESTDATA_INT64NAME); + strictEqual(testKey.getValueName(3), TESTDATA_BINARYNAME); +} + +function test_reading_functions(testKey) { + strictEqual( + testKey.getValueType(TESTDATA_STRNAME), + nsIWindowsRegKey.TYPE_STRING + ); + strictEqual(testKey.readStringValue(TESTDATA_STRNAME), TESTDATA_STRVALUE); + + strictEqual( + testKey.getValueType(TESTDATA_INTNAME), + nsIWindowsRegKey.TYPE_INT + ); + strictEqual(testKey.readIntValue(TESTDATA_INTNAME), TESTDATA_INTVALUE); + + strictEqual( + testKey.getValueType(TESTDATA_INT64NAME), + nsIWindowsRegKey.TYPE_INT64 + ); + strictEqual(testKey.readInt64Value(TESTDATA_INT64NAME), TESTDATA_INT64VALUE); + + strictEqual( + testKey.getValueType(TESTDATA_BINARYNAME), + nsIWindowsRegKey.TYPE_BINARY + ); + strictEqual( + testKey.readBinaryValue(TESTDATA_BINARYNAME), + TESTDATA_BINARYVALUE + ); +} + +function test_invalidread_functions(testKey) { + try { + testKey.readIntValue(TESTDATA_STRNAME); + do_throw("Reading an integer from a string registry value should throw."); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } + + try { + let val = testKey.readStringValue(TESTDATA_INTNAME); + do_throw( + "Reading an string from an Int registry value should throw." + val + ); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } + + try { + testKey.readStringValue(TESTDATA_INT64NAME); + do_throw("Reading an string from an Int64 registry value should throw."); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } + + try { + testKey.readStringValue(TESTDATA_BINARYNAME); + do_throw("Reading a string from an Binary registry value should throw."); + } catch (e) { + if (!(e instanceof Ci.nsIException && e.result == Cr.NS_ERROR_FAILURE)) { + throw e; + } + } +} + +function test_childkey_functions(testKey) { + strictEqual(testKey.childCount, 0); + strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false); + + let childKey = testKey.createChild( + TESTDATA_CHILD_KEY, + nsIWindowsRegKey.ACCESS_ALL + ); + childKey.close(); + + strictEqual(testKey.childCount, 1); + strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), true); + strictEqual(testKey.getChildName(0), TESTDATA_CHILD_KEY); + + childKey = testKey.openChild(TESTDATA_CHILD_KEY, nsIWindowsRegKey.ACCESS_ALL); + testKey.removeChild(TESTDATA_CHILD_KEY); + strictEqual(testKey.childCount, 0); + strictEqual(testKey.hasChild(TESTDATA_CHILD_KEY), false); +} + +function test_watching_functions(testKey) { + strictEqual(testKey.isWatching(), false); + strictEqual(testKey.hasChanged(), false); + + testKey.startWatching(true); + strictEqual(testKey.isWatching(), true); + + testKey.stopWatching(); + strictEqual(testKey.isWatching(), false); + + // Create a child key, and update a value + let childKey = testKey.createChild( + TESTDATA_CHILD_KEY, + nsIWindowsRegKey.ACCESS_ALL + ); + childKey.writeIntValue(TESTDATA_INTNAME, TESTDATA_INTVALUE); + + // Start a recursive watch, and update the child's value + testKey.startWatching(true); + strictEqual(testKey.isWatching(), true); + + childKey.writeIntValue(TESTDATA_INTNAME, 0); + strictEqual(testKey.hasChanged(), true); + testKey.stopWatching(); + strictEqual(testKey.isWatching(), false); + + childKey.removeValue(TESTDATA_INTNAME); + childKey.close(); + testKey.removeChild(TESTDATA_CHILD_KEY); +} + +function cleanup_test_run(testKey, keyName) { + info("Cleaning up test."); + + for (var i = 0; i < testKey.childCount; i++) { + testKey.removeChild(testKey.getChildName(i)); + } + testKey.close(); + + let baseKey = regKeyComponent.createInstance(nsIWindowsRegKey); + baseKey.open( + nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + BASE_PATH, + nsIWindowsRegKey.ACCESS_ALL + ); + baseKey.removeChild(TESTDATA_KEYNAME); + baseKey.close(); +} + +// Test data used above. +const BASE_PATH = "SOFTWARE"; +const TESTDATA_KEYNAME = "TestRegXPC"; +const TESTDATA_STRNAME = "AString"; +const TESTDATA_STRVALUE = "The quick brown fox jumps over the lazy dog."; +const TESTDATA_INTNAME = "AnInteger"; +const TESTDATA_INTVALUE = 65536; +const TESTDATA_INT64NAME = "AnInt64"; +const TESTDATA_INT64VALUE = 9223372036854775000; +const TESTDATA_BINARYNAME = "ABinary"; +const TESTDATA_BINARYVALUE = "She sells seashells by the seashore"; +const TESTDATA_CHILD_KEY = "TestChildKey"; diff --git a/xpcom/tests/unit/xpcshell.ini b/xpcom/tests/unit/xpcshell.ini new file mode 100644 index 0000000000..137ab2df45 --- /dev/null +++ b/xpcom/tests/unit/xpcshell.ini @@ -0,0 +1,69 @@ +[DEFAULT] +head = head_xpcom.js +support-files = + data/** + xpcomtest.xpt +generated-files = + xpcomtest.xpt + +[test_bug121341.js] +[test_bug325418.js] +[test_bug332389.js] +[test_bug333505.js] +[test_bug364285-1.js] +[test_bug374754.js] +[test_bug476919.js] +# Creating a symlink requires admin or developer mode on Windows. +skip-if = os == "win" +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_bug478086.js] +[test_bug1434856.js] +[test_console_service_callFunctionAndLogException.js] +[test_debugger_malloc_size_of.js] +[test_file_createUnique.js] +[test_file_equality.js] +[test_getTimers.js] +skip-if = os == "android" +[test_hidden_files.js] +[test_home.js] +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_iniParser.js] +[test_ioutil.js] +[test_localfile.js] +[test_mac_bundle.js] +[test_mac_xattrs.js] +skip-if = os != "mac" +[test_nsIMutableArray.js] +[test_nsIProcess.js] +skip-if = os == "win" || os == "linux" || os == "android" # bug 582821, bug 1325609, Bug 676998, Bug 1631671 +[test_nsIProcess_stress.js] +skip-if = os == "win" || ccov # bug 676412, test isn't needed on windows and runs really slowly, bug 1394989 +[test_pipe.js] +[test_process_directives.js] +[test_process_directives_child.js] +skip-if = os == "android" +[test_storagestream.js] +[test_streams.js] +[test_seek_multiplex.js] +[test_stringstream.js] +[test_symlinks.js] +# Creating a symlink requires admin or developer mode on Windows. +skip-if = os == "win" +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_systemInfo.js] +[test_versioncomparator.js] +skip-if = + os == "win" && asan # Bug 1763002 +[test_windows_cmdline_file.js] +skip-if = os != "win" +[test_bug745466.js] +skip-if = os == "win" +# Bug 676998: test fails consistently on Android +fail-if = os == "android" +[test_file_renameTo.js] +[test_notxpcom_scriptable.js] +[test_windows_registry.js] +skip-if = os != "win" diff --git a/xpcom/tests/windows/TestCOM.cpp b/xpcom/tests/windows/TestCOM.cpp new file mode 100644 index 0000000000..49f67db835 --- /dev/null +++ b/xpcom/tests/windows/TestCOM.cpp @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 +#include +#include +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "mozilla/gtest/MozAssertions.h" +#include "mozilla/RefPtr.h" + +// unknwn.h is needed to build with WIN32_LEAN_AND_MEAN +#include + +#include "gtest/gtest.h" + +// {5846BA30-B856-11d1-A98A-00805F8A7AC4} +#define NS_ITEST_COM_IID \ + { \ + 0x5846ba30, 0xb856, 0x11d1, { \ + 0xa9, 0x8a, 0x0, 0x80, 0x5f, 0x8a, 0x7a, 0xc4 \ + } \ + } + +class nsITestCom : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ITEST_COM_IID) + NS_IMETHOD Test() = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITestCom, NS_ITEST_COM_IID) + +/* + * nsTestCom + */ + +class nsTestCom final : public nsITestCom { + NS_DECL_ISUPPORTS + + public: + nsTestCom() {} + + NS_IMETHOD Test() override { return NS_OK; } + + static int sDestructions; + + private: + ~nsTestCom() { sDestructions++; } +}; + +int nsTestCom::sDestructions; + +NS_IMPL_QUERY_INTERFACE(nsTestCom, nsITestCom) + +MozExternalRefCountType nsTestCom::AddRef() { + nsrefcnt res = ++mRefCnt; + NS_LOG_ADDREF(this, mRefCnt, "nsTestCom", sizeof(*this)); + return res; +} + +MozExternalRefCountType nsTestCom::Release() { + nsrefcnt res = --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "nsTestCom"); + if (res == 0) { + delete this; + } + return res; +} + +TEST(TestCOM, WindowsInterop) +{ + // Test that we can QI an nsITestCom to an IUnknown. + RefPtr t = new nsTestCom(); + IUnknown* iUnknown = nullptr; + nsresult rv = t->QueryInterface(NS_GET_IID(nsISupports), (void**)&iUnknown); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(iUnknown); + + // Test we can QI an IUnknown to nsITestCom. + nsCOMPtr iTestCom; + GUID testGUID = NS_ITEST_COM_IID; + HRESULT hr = iUnknown->QueryInterface(testGUID, getter_AddRefs(iTestCom)); + ASSERT_TRUE(SUCCEEDED(hr)); + ASSERT_TRUE(iTestCom); + + // Make sure we can call our test function (and the pointer is valid). + rv = iTestCom->Test(); + ASSERT_NS_SUCCEEDED(rv); + + iUnknown->Release(); + iTestCom = nullptr; + t = nullptr; + + ASSERT_EQ(nsTestCom::sDestructions, 1); +} diff --git a/xpcom/tests/windows/TestNtPathToDosPath.cpp b/xpcom/tests/windows/TestNtPathToDosPath.cpp new file mode 100644 index 0000000000..ac29ddac50 --- /dev/null +++ b/xpcom/tests/windows/TestNtPathToDosPath.cpp @@ -0,0 +1,176 @@ +/* -*- 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 +#include + +#include "mozilla/FileUtilsWin.h" +#include "mozilla/DebugOnly.h" +#include "nsCRTGlue.h" + +#include "gtest/gtest.h" + +class DriveMapping { + public: + explicit DriveMapping(const nsAString& aRemoteUNCPath); + ~DriveMapping(); + + bool Init(); + bool ChangeDriveLetter(); + wchar_t GetDriveLetter() { return mDriveLetter; } + + private: + bool DoMapping(); + void Disconnect(wchar_t aDriveLetter); + + wchar_t mDriveLetter; + nsString mRemoteUNCPath; +}; + +DriveMapping::DriveMapping(const nsAString& aRemoteUNCPath) + : mDriveLetter(0), mRemoteUNCPath(aRemoteUNCPath) {} + +bool DriveMapping::Init() { + if (mDriveLetter) { + return false; + } + return DoMapping(); +} + +bool DriveMapping::DoMapping() { + wchar_t drvTemplate[] = L" :"; + NETRESOURCEW netRes = {0}; + netRes.dwType = RESOURCETYPE_DISK; + netRes.lpLocalName = drvTemplate; + netRes.lpRemoteName = + reinterpret_cast(mRemoteUNCPath.BeginWriting()); + wchar_t driveLetter = L'D'; + DWORD result = NO_ERROR; + do { + drvTemplate[0] = driveLetter; + result = WNetAddConnection2W(&netRes, nullptr, nullptr, CONNECT_TEMPORARY); + } while (result == ERROR_ALREADY_ASSIGNED && ++driveLetter <= L'Z'); + if (result != NO_ERROR) { + return false; + } + mDriveLetter = driveLetter; + return true; +} + +bool DriveMapping::ChangeDriveLetter() { + wchar_t prevDriveLetter = mDriveLetter; + bool result = DoMapping(); + MOZ_RELEASE_ASSERT(mDriveLetter != prevDriveLetter); + if (result && prevDriveLetter) { + Disconnect(prevDriveLetter); + } + return result; +} + +void DriveMapping::Disconnect(wchar_t aDriveLetter) { + wchar_t drvTemplate[] = {aDriveLetter, L':', L'\0'}; + DWORD result = WNetCancelConnection2W(drvTemplate, 0, TRUE); + MOZ_RELEASE_ASSERT(result == NO_ERROR); +} + +DriveMapping::~DriveMapping() { + if (mDriveLetter) { + Disconnect(mDriveLetter); + } +} + +bool DriveToNtPath(const wchar_t aDriveLetter, nsAString& aNtPath) { + const wchar_t drvTpl[] = {aDriveLetter, L':', L'\0'}; + aNtPath.SetLength(MAX_PATH); + DWORD pathLen; + while (true) { + pathLen = QueryDosDeviceW( + drvTpl, reinterpret_cast(aNtPath.BeginWriting()), + aNtPath.Length()); + if (pathLen || GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + break; + } + aNtPath.SetLength(aNtPath.Length() * 2); + } + if (!pathLen) { + return false; + } + // aNtPath contains embedded NULLs, so we need to figure out the real length + // via wcslen. + aNtPath.SetLength(NS_strlen(aNtPath.BeginReading())); + return true; +} + +bool TestNtPathToDosPath(const wchar_t* aNtPath, + const wchar_t* aExpectedDosPath) { + nsAutoString output; + bool result = mozilla::NtPathToDosPath(nsDependentString(aNtPath), output); + return result && output == reinterpret_cast( + aExpectedDosPath); +} + +TEST(NtPathToDosPath, Tests) +{ + nsAutoString cDrive; + ASSERT_TRUE(DriveToNtPath(L'C', cDrive)); + + // empty string + EXPECT_TRUE(TestNtPathToDosPath(L"", L"")); + + // non-existent device, must fail + EXPECT_FALSE( + TestNtPathToDosPath(L"\\Device\\ThisDeviceDoesNotExist\\Foo", nullptr)); + + // base case + nsAutoString testPath(cDrive); + testPath.Append(L"\\Program Files"); + EXPECT_TRUE(TestNtPathToDosPath(testPath.get(), L"C:\\Program Files")); + + // short filename + nsAutoString ntShortName(cDrive); + ntShortName.Append(L"\\progra~1"); + EXPECT_TRUE(TestNtPathToDosPath(ntShortName.get(), L"C:\\Program Files")); + + // drive letters as symbolic links (NtCreateFile uses these) + EXPECT_TRUE(TestNtPathToDosPath(L"\\??\\C:\\Foo", L"C:\\Foo")); + + // other symbolic links (should fail) + EXPECT_FALSE(TestNtPathToDosPath(L"\\??\\MountPointManager", nullptr)); + + // socket (should fail) + EXPECT_FALSE(TestNtPathToDosPath(L"\\Device\\Afd\\Endpoint", nullptr)); + + // UNC path (using MUP) + EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\Mup\\127.0.0.1\\C$", + L"\\\\127.0.0.1\\C$")); + + // UNC path (using LanmanRedirector) + EXPECT_TRUE(TestNtPathToDosPath(L"\\Device\\LanmanRedirector\\127.0.0.1\\C$", + L"\\\\127.0.0.1\\C$")); + + DriveMapping drvMapping(u"\\\\127.0.0.1\\C$"_ns); + // Only run these tests if we were able to map; some machines don't have perms + if (drvMapping.Init()) { + wchar_t expected[] = L" :\\"; + expected[0] = drvMapping.GetDriveLetter(); + nsAutoString networkPath; + ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath)); + + networkPath += u"\\"; + EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected)); + + // NtPathToDosPath must correctly handle paths whose drive letter mapping + // has changed. We need to test this because the APIs called by + // NtPathToDosPath return different info if this has happened. + ASSERT_TRUE(drvMapping.ChangeDriveLetter()); + + expected[0] = drvMapping.GetDriveLetter(); + ASSERT_TRUE(DriveToNtPath(drvMapping.GetDriveLetter(), networkPath)); + + networkPath += u"\\"; + EXPECT_TRUE(TestNtPathToDosPath(networkPath.get(), expected)); + } +} diff --git a/xpcom/tests/windows/TestPoisonIOInterposer.cpp b/xpcom/tests/windows/TestPoisonIOInterposer.cpp new file mode 100644 index 0000000000..057f262c67 --- /dev/null +++ b/xpcom/tests/windows/TestPoisonIOInterposer.cpp @@ -0,0 +1,152 @@ +/* -*- 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 + +#include "gtest/gtest.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/NativeNt.h" +#include "nsWindowsHelpers.h" + +using namespace mozilla; + +class TempFile final { + wchar_t mFullPath[MAX_PATH + 1]; + + public: + TempFile() : mFullPath{0} { + wchar_t tempDir[MAX_PATH + 1]; + DWORD len = ::GetTempPathW(ArrayLength(tempDir), tempDir); + if (!len) { + return; + } + + len = ::GetTempFileNameW(tempDir, L"poi", 0, mFullPath); + if (!len) { + return; + } + } + + operator const wchar_t*() const { return mFullPath[0] ? mFullPath : nullptr; } +}; + +class Overlapped final { + nsAutoHandle mEvent; + OVERLAPPED mOverlapped; + + public: + Overlapped() + : mEvent(::CreateEventW(nullptr, TRUE, FALSE, nullptr)), mOverlapped{} { + mOverlapped.hEvent = mEvent.get(); + } + + operator OVERLAPPED*() { return &mOverlapped; } + + bool Wait(HANDLE aHandle) { + DWORD numBytes; + if (!::GetOverlappedResult(aHandle, &mOverlapped, &numBytes, TRUE)) { + return false; + } + + return true; + } +}; + +const uint32_t kMagic = 0x12345678; + +void FileOpSync(const wchar_t* aPath) { + nsAutoHandle file(::CreateFileW(aPath, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, nullptr, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, nullptr)); + ASSERT_NE(file.get(), INVALID_HANDLE_VALUE); + + DWORD buffer = kMagic, numBytes = 0; + OVERLAPPED seek = {}; + EXPECT_TRUE(WriteFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek)); + EXPECT_TRUE(::FlushFileBuffers(file.get())); + + seek.Offset = 0; + buffer = 0; + EXPECT_TRUE( + ::ReadFile(file.get(), &buffer, sizeof(buffer), &numBytes, &seek)); + EXPECT_EQ(buffer, kMagic); + + WIN32_FILE_ATTRIBUTE_DATA fullAttr = {}; + EXPECT_TRUE(::GetFileAttributesExW(aPath, GetFileExInfoStandard, &fullAttr)); +} + +void FileOpAsync(const wchar_t* aPath) { + constexpr int kNumPages = 10; + constexpr int kPageSize = 4096; + + Array, kNumPages> pages; + Array segments; + for (int i = 0; i < kNumPages; ++i) { + auto p = reinterpret_cast(::VirtualAlloc( + nullptr, kPageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE)); + ASSERT_TRUE(p); + + pages[i].reset(p); + segments[i].Buffer = p; + + p[0] = kMagic; + } + + nsAutoHandle file(::CreateFileW( + aPath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED, + nullptr)); + ASSERT_NE(file.get(), INVALID_HANDLE_VALUE); + + Overlapped writeOp; + if (!::WriteFileGather(file.get(), segments.begin(), kNumPages * kPageSize, + nullptr, writeOp)) { + EXPECT_EQ(::GetLastError(), static_cast(ERROR_IO_PENDING)); + EXPECT_TRUE(writeOp.Wait(file.get())); + } + + for (int i = 0; i < kNumPages; ++i) { + *reinterpret_cast(pages[i].get()) = 0; + } + + Overlapped readOp; + if (!::ReadFileScatter(file.get(), segments.begin(), kNumPages * kPageSize, + nullptr, readOp)) { + EXPECT_EQ(::GetLastError(), static_cast(ERROR_IO_PENDING)); + EXPECT_TRUE(readOp.Wait(file.get())); + } + + for (int i = 0; i < kNumPages; ++i) { + EXPECT_EQ(*reinterpret_cast(pages[i].get()), kMagic); + } +} + +TEST(PoisonIOInterposer, NormalThread) +{ + mozilla::IOInterposerInit ioInterposerGuard; + TempFile tempFile; + FileOpSync(tempFile); + FileOpAsync(tempFile); + EXPECT_TRUE(::DeleteFileW(tempFile)); +} + +TEST(PoisonIOInterposer, NullTlsPointer) +{ + void* originalTls = mozilla::nt::RtlGetThreadLocalStoragePointer(); + mozilla::IOInterposerInit ioInterposerGuard; + + // Simulate a loader worker thread (TEB::LoaderWorker = 1) + // where ThreadLocalStorage is never allocated. + mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(nullptr); + + TempFile tempFile; + FileOpSync(tempFile); + FileOpAsync(tempFile); + EXPECT_TRUE(::DeleteFileW(tempFile)); + + mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(originalTls); +} diff --git a/xpcom/tests/windows/moz.build b/xpcom/tests/windows/moz.build new file mode 100644 index 0000000000..d77ec264e1 --- /dev/null +++ b/xpcom/tests/windows/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +UNIFIED_SOURCES += [ + "TestCOM.cpp", + "TestNtPathToDosPath.cpp", + "TestPoisonIOInterposer.cpp", +] + +OS_LIBS += [ + "mpr", +] + +FINAL_LIBRARY = "xul-gtest" diff --git a/xpcom/threads/AbstractThread.cpp b/xpcom/threads/AbstractThread.cpp new file mode 100644 index 0000000000..61289a6789 --- /dev/null +++ b/xpcom/threads/AbstractThread.cpp @@ -0,0 +1,359 @@ +/* -*- 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/AbstractThread.h" + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DelayedRunnable.h" +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" // We initialize the MozPromise logging in this file. +#include "mozilla/ProfilerRunnable.h" +#include "mozilla/StateWatching.h" // We initialize the StateWatching logging in this file. +#include "mozilla/StaticPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/Unused.h" +#include "nsContentUtils.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsIThreadInternal.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include + +namespace mozilla { + +LazyLogModule gMozPromiseLog("MozPromise"); +LazyLogModule gStateWatchingLog("StateWatching"); + +StaticRefPtr sMainThread; +MOZ_THREAD_LOCAL(AbstractThread*) AbstractThread::sCurrentThreadTLS; + +class XPCOMThreadWrapper final : public AbstractThread, + public nsIThreadObserver, + public nsIDirectTaskDispatcher { + public: + XPCOMThreadWrapper(nsIThreadInternal* aThread, bool aRequireTailDispatch, + bool aOnThread) + : AbstractThread(aRequireTailDispatch), + mThread(aThread), + mDirectTaskDispatcher(do_QueryInterface(aThread)), + mOnThread(aOnThread) { + MOZ_DIAGNOSTIC_ASSERT(mThread && mDirectTaskDispatcher); + MOZ_DIAGNOSTIC_ASSERT(!aOnThread || IsCurrentThreadIn()); + if (aOnThread) { + MOZ_ASSERT(!sCurrentThreadTLS.get(), + "There can only be a single XPCOMThreadWrapper available on a " + "thread"); + // Set the default current thread so that GetCurrent() never returns + // nullptr. + sCurrentThreadTLS.set(this); + } + } + + NS_DECL_THREADSAFE_ISUPPORTS + + nsresult Dispatch(already_AddRefed aRunnable, + DispatchReason aReason = NormalDispatch) override { + nsCOMPtr r = aRunnable; + AbstractThread* currentThread; + if (aReason != TailDispatch && (currentThread = GetCurrent()) && + RequiresTailDispatch(currentThread) && + currentThread->IsTailDispatcherAvailable()) { + return currentThread->TailDispatcher().AddTask(this, r.forget()); + } + + // At a certain point during shutdown, we stop processing events from the + // main thread event queue (this happens long after all _other_ XPCOM + // threads have been shut down). However, various bits of subsequent + // teardown logic (the media shutdown blocker and the final shutdown cycle + // collection) can trigger state watching and state mirroring notifications + // that result in dispatch to the main thread. This causes shutdown leaks, + // because the |Runner| wrapper below creates a guaranteed cycle + // (Thread->EventQueue->Runnable->Thread) until the event is processed. So + // if we put the event into a queue that will never be processed, we'll wind + // up with a leak. + // + // We opt to just release the runnable in that case. Ordinarily, this + // approach could cause problems for runnables that are only safe to be + // released on the target thread (and not the dispatching thread). This is + // why XPCOM thread dispatch explicitly leaks the runnable when dispatch + // fails, rather than releasing it. But given that this condition only + // applies very late in shutdown when only one thread remains operational, + // that concern is unlikely to apply. + if (gXPCOMMainThreadEventsAreDoomed) { + return NS_ERROR_FAILURE; + } + + RefPtr runner = new Runner(this, r.forget()); + return mThread->Dispatch(runner.forget(), NS_DISPATCH_NORMAL); + } + + // Prevent a GCC warning about the other overload of Dispatch being hidden. + using AbstractThread::Dispatch; + + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override { + return mThread->RegisterShutdownTask(aTask); + } + + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override { + return mThread->UnregisterShutdownTask(aTask); + } + + bool IsCurrentThreadIn() const override { + return mThread->IsOnCurrentThread(); + } + + TaskDispatcher& TailDispatcher() override { + MOZ_ASSERT(IsCurrentThreadIn()); + MOZ_ASSERT(IsTailDispatcherAvailable()); + if (!mTailDispatcher) { + mTailDispatcher = + std::make_unique(mDirectTaskDispatcher, + /* aIsTailDispatcher = */ true); + mThread->AddObserver(this); + } + + return *mTailDispatcher; + } + + bool IsTailDispatcherAvailable() override { + // Our tail dispatching implementation relies on nsIThreadObserver + // callbacks. If we're not doing event processing, it won't work. + bool inEventLoop = + static_cast(mThread.get())->RecursionDepth() > 0; + return inEventLoop; + } + + bool MightHaveTailTasks() override { return !!mTailDispatcher; } + + nsIEventTarget* AsEventTarget() override { return mThread; } + + //----------------------------------------------------------------------------- + // nsIThreadObserver + //----------------------------------------------------------------------------- + NS_IMETHOD OnDispatchedEvent() override { return NS_OK; } + + NS_IMETHOD AfterProcessNextEvent(nsIThreadInternal* thread, + bool eventWasProcessed) override { + // This is the primary case. + MaybeFireTailDispatcher(); + return NS_OK; + } + + NS_IMETHOD OnProcessNextEvent(nsIThreadInternal* thread, + bool mayWait) override { + // In general, the tail dispatcher is handled at the end of the current in + // AfterProcessNextEvent() above. However, if start spinning a nested event + // loop, it's generally better to fire the tail dispatcher before the first + // nested event, rather than after it. This check handles that case. + MaybeFireTailDispatcher(); + return NS_OK; + } + + //----------------------------------------------------------------------------- + // nsIDirectTaskDispatcher + //----------------------------------------------------------------------------- + // Forward calls to nsIDirectTaskDispatcher to the underlying nsThread object. + // We can't use the generated NS_FORWARD_NSIDIRECTTASKDISPATCHER macro + // as already_AddRefed type must be moved. + NS_IMETHOD DispatchDirectTask(already_AddRefed aEvent) override { + return mDirectTaskDispatcher->DispatchDirectTask(std::move(aEvent)); + } + NS_IMETHOD DrainDirectTasks() override { + return mDirectTaskDispatcher->DrainDirectTasks(); + } + NS_IMETHOD HaveDirectTasks(bool* aResult) override { + return mDirectTaskDispatcher->HaveDirectTasks(aResult); + } + + private: + const RefPtr mThread; + const nsCOMPtr mDirectTaskDispatcher; + std::unique_ptr mTailDispatcher; + const bool mOnThread; + + ~XPCOMThreadWrapper() { + if (mOnThread) { + MOZ_DIAGNOSTIC_ASSERT(IsCurrentThreadIn(), + "Must be destroyed on the thread it was created"); + sCurrentThreadTLS.set(nullptr); + } + } + + void MaybeFireTailDispatcher() { + if (mTailDispatcher) { + mTailDispatcher->DrainDirectTasks(); + mThread->RemoveObserver(this); + mTailDispatcher.reset(); + } + } + + class Runner : public Runnable { + public: + explicit Runner(XPCOMThreadWrapper* aThread, + already_AddRefed aRunnable) + : Runnable("XPCOMThreadWrapper::Runner"), + mThread(aThread), + mRunnable(aRunnable) {} + + NS_IMETHOD Run() override { + MOZ_ASSERT(mThread == AbstractThread::GetCurrent()); + MOZ_ASSERT(mThread->IsCurrentThreadIn()); + SerialEventTargetGuard guard(mThread); + AUTO_PROFILE_FOLLOWING_RUNNABLE(mRunnable); + return mRunnable->Run(); + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("AbstractThread::Runner"); + if (nsCOMPtr named = do_QueryInterface(mRunnable)) { + nsAutoCString name; + named->GetName(name); + if (!name.IsEmpty()) { + aName.AppendLiteral(" for "); + aName.Append(name); + } + } + return NS_OK; + } +#endif + + private: + const RefPtr mThread; + const RefPtr mRunnable; + }; +}; + +NS_IMPL_ISUPPORTS(XPCOMThreadWrapper, nsIThreadObserver, + nsIDirectTaskDispatcher, nsISerialEventTarget, nsIEventTarget) + +NS_IMETHODIMP_(bool) +AbstractThread::IsOnCurrentThreadInfallible() { return IsCurrentThreadIn(); } + +NS_IMETHODIMP +AbstractThread::IsOnCurrentThread(bool* aResult) { + *aResult = IsCurrentThreadIn(); + return NS_OK; +} + +NS_IMETHODIMP +AbstractThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + nsCOMPtr event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +AbstractThread::Dispatch(already_AddRefed aEvent, + uint32_t aFlags) { + return Dispatch(std::move(aEvent), NormalDispatch); +} + +NS_IMETHODIMP +AbstractThread::DelayedDispatch(already_AddRefed aEvent, + uint32_t aDelayMs) { + nsCOMPtr event = aEvent; + NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED); + + RefPtr r = + new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs); + nsresult rv = r->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + return Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +nsresult AbstractThread::TailDispatchTasksFor(AbstractThread* aThread) { + if (MightHaveTailTasks()) { + return TailDispatcher().DispatchTasksFor(aThread); + } + + return NS_OK; +} + +bool AbstractThread::HasTailTasksFor(AbstractThread* aThread) { + if (!MightHaveTailTasks()) { + return false; + } + return TailDispatcher().HasTasksFor(aThread); +} + +bool AbstractThread::RequiresTailDispatch(AbstractThread* aThread) const { + MOZ_ASSERT(aThread); + // We require tail dispatch if both the source and destination + // threads support it. + return SupportsTailDispatch() && aThread->SupportsTailDispatch(); +} + +bool AbstractThread::RequiresTailDispatchFromCurrentThread() const { + AbstractThread* current = GetCurrent(); + return current && RequiresTailDispatch(current); +} + +AbstractThread* AbstractThread::MainThread() { + MOZ_ASSERT(sMainThread); + return sMainThread; +} + +void AbstractThread::InitTLS() { + if (!sCurrentThreadTLS.init()) { + MOZ_CRASH(); + } +} + +void AbstractThread::InitMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sMainThread); + nsCOMPtr mainThread = + do_QueryInterface(nsThreadManager::get().GetMainThreadWeak()); + MOZ_DIAGNOSTIC_ASSERT(mainThread); + + if (!sCurrentThreadTLS.init()) { + MOZ_CRASH(); + } + sMainThread = new XPCOMThreadWrapper(mainThread.get(), + /* aRequireTailDispatch = */ true, + true /* onThread */); +} + +void AbstractThread::ShutdownMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + sMainThread = nullptr; +} + +void AbstractThread::DispatchStateChange( + already_AddRefed aRunnable) { + AbstractThread* currentThread = GetCurrent(); + MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist"); + if (currentThread->IsTailDispatcherAvailable()) { + currentThread->TailDispatcher().AddStateChangeTask(this, + std::move(aRunnable)); + } else { + // If the tail dispatcher isn't available, we just avoid sending state + // updates. + // + // This happens, specifically (1) During async shutdown (via the media + // shutdown blocker), and (2) During the final shutdown cycle collection. + // Both of these trigger changes to various watched and mirrored state. + nsCOMPtr neverDispatched = aRunnable; + } +} + +/* static */ +void AbstractThread::DispatchDirectTask( + already_AddRefed aRunnable) { + AbstractThread* currentThread = GetCurrent(); + MOZ_DIAGNOSTIC_ASSERT(currentThread, "An AbstractThread must exist"); + if (currentThread->IsTailDispatcherAvailable()) { + currentThread->TailDispatcher().AddDirectTask(std::move(aRunnable)); + } else { + // If the tail dispatcher isn't available, we post as a regular task. + currentThread->Dispatch(std::move(aRunnable)); + } +} + +} // namespace mozilla diff --git a/xpcom/threads/AbstractThread.h b/xpcom/threads/AbstractThread.h new file mode 100644 index 0000000000..b53bcf8ca3 --- /dev/null +++ b/xpcom/threads/AbstractThread.h @@ -0,0 +1,129 @@ +/* -*- 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/. */ + +#if !defined(AbstractThread_h_) +# define AbstractThread_h_ + +# include "mozilla/AlreadyAddRefed.h" +# include "mozilla/ThreadLocal.h" +# include "nscore.h" +# include "nsISerialEventTarget.h" +# include "nsISupports.h" + +class nsIEventTarget; +class nsIRunnable; +class nsIThread; + +namespace mozilla { + +class TaskDispatcher; + +/* + * We often want to run tasks on a target that guarantees that events will never + * run in parallel. There are various target types that achieve this - namely + * nsIThread and TaskQueue. Note that nsIThreadPool (which implements + * nsIEventTarget) does not have this property, so we do not want to use + * nsIEventTarget for this purpose. This class encapsulates the specifics of + * the structures we might use here and provides a consistent interface. + * + * At present, the supported AbstractThread implementations are TaskQueue, + * AbstractThread::MainThread() and XPCOMThreadWrapper which can wrap any + * nsThread. + * + * The primary use of XPCOMThreadWrapper is to allow any threads to provide + * Direct Task dispatching which is similar (but not identical to) the microtask + * semantics of JS promises. Instantiating a XPCOMThreadWrapper on the current + * nsThread is sufficient to enable direct task dispatching. + * + * You shouldn't use pointers when comparing AbstractThread or nsIThread to + * determine if you are currently on the thread, but instead use the + * nsISerialEventTarget::IsOnCurrentThread() method. + */ +class AbstractThread : public nsISerialEventTarget { + public: + // Returns the AbstractThread that the caller is currently running in, or null + // if the caller is not running in an AbstractThread. + static AbstractThread* GetCurrent() { return sCurrentThreadTLS.get(); } + + AbstractThread(bool aSupportsTailDispatch) + : mSupportsTailDispatch(aSupportsTailDispatch) {} + + // We don't use NS_DECL_NSIEVENTTARGET so that we can remove the default + // |flags| parameter from Dispatch. Otherwise, a single-argument Dispatch call + // would be ambiguous. + using nsISerialEventTarget::IsOnCurrentThread; + NS_IMETHOD_(bool) IsOnCurrentThreadInfallible(void) override; + NS_IMETHOD IsOnCurrentThread(bool* _retval) override; + NS_IMETHOD Dispatch(already_AddRefed event, + uint32_t flags) override; + NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override; + NS_IMETHOD DelayedDispatch(already_AddRefed event, + uint32_t delay) override; + + enum DispatchReason { NormalDispatch, TailDispatch }; + virtual nsresult Dispatch(already_AddRefed aRunnable, + DispatchReason aReason = NormalDispatch) = 0; + + virtual bool IsCurrentThreadIn() const = 0; + + // Returns a TaskDispatcher that will dispatch its tasks when the currently- + // running tasks pops off the stack. + // + // May only be called when running within the it is invoked up, and only on + // threads which support it. + virtual TaskDispatcher& TailDispatcher() = 0; + + // Returns true if we have tail tasks scheduled, or if this isn't known. + // Returns false if we definitely don't have any tail tasks. + virtual bool MightHaveTailTasks() { return true; } + + // Returns true if the tail dispatcher is available. In certain edge cases + // like shutdown, it might not be. + virtual bool IsTailDispatcherAvailable() { return true; } + + // Helper functions for methods on the tail TasklDispatcher. These check + // HasTailTasks to avoid allocating a TailDispatcher if it isn't + // needed. + nsresult TailDispatchTasksFor(AbstractThread* aThread); + bool HasTailTasksFor(AbstractThread* aThread); + + // Returns true if this supports the tail dispatcher. + bool SupportsTailDispatch() const { return mSupportsTailDispatch; } + + // Returns true if this thread requires all dispatches originating from + // aThread go through the tail dispatcher. + bool RequiresTailDispatch(AbstractThread* aThread) const; + bool RequiresTailDispatchFromCurrentThread() const; + + virtual nsIEventTarget* AsEventTarget() { MOZ_CRASH("Not an event target!"); } + + // Returns the non-DocGroup version of AbstractThread on the main thread. + // A DocGroup-versioned one is available in + // DispatcherTrait::AbstractThreadFor(). Note: + // DispatcherTrait::AbstractThreadFor() SHALL be used when possible. + static AbstractThread* MainThread(); + + // Must be called exactly once during startup. + static void InitTLS(); + static void InitMainThread(); + static void ShutdownMainThread(); + + void DispatchStateChange(already_AddRefed aRunnable); + + static void DispatchDirectTask(already_AddRefed aRunnable); + + protected: + virtual ~AbstractThread() = default; + static MOZ_THREAD_LOCAL(AbstractThread*) sCurrentThreadTLS; + + // True if we want to require that every task dispatched from tasks running in + // this queue go through our queue's tail dispatcher. + const bool mSupportsTailDispatch; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/BlockingResourceBase.cpp b/xpcom/threads/BlockingResourceBase.cpp new file mode 100644 index 0000000000..c2ba82e07a --- /dev/null +++ b/xpcom/threads/BlockingResourceBase.cpp @@ -0,0 +1,547 @@ +/* -*- 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/BlockingResourceBase.h" + +#ifdef DEBUG +# include "prthread.h" + +# ifndef MOZ_CALLSTACK_DISABLED +# include "CodeAddressService.h" +# include "nsHashKeys.h" +# include "mozilla/StackWalk.h" +# include "nsTHashtable.h" +# endif + +# include "mozilla/Attributes.h" +# include "mozilla/CondVar.h" +# include "mozilla/DeadlockDetector.h" +# include "mozilla/RecursiveMutex.h" +# include "mozilla/ReentrantMonitor.h" +# include "mozilla/Mutex.h" +# include "mozilla/RWLock.h" +# include "mozilla/UniquePtr.h" + +# if defined(MOZILLA_INTERNAL_API) +# include "mozilla/ProfilerThreadSleep.h" +# endif // MOZILLA_INTERNAL_API + +#endif // ifdef DEBUG + +namespace mozilla { +// +// BlockingResourceBase implementation +// + +// static members +const char* const BlockingResourceBase::kResourceTypeName[] = { + // needs to be kept in sync with BlockingResourceType + "Mutex", "ReentrantMonitor", "CondVar", "RecursiveMutex"}; + +#ifdef DEBUG + +PRCallOnceType BlockingResourceBase::sCallOnce; +MOZ_THREAD_LOCAL(BlockingResourceBase*) +BlockingResourceBase::sResourceAcqnChainFront; +BlockingResourceBase::DDT* BlockingResourceBase::sDeadlockDetector; + +void BlockingResourceBase::StackWalkCallback(uint32_t aFrameNumber, void* aPc, + void* aSp, void* aClosure) { +# ifndef MOZ_CALLSTACK_DISABLED + AcquisitionState* state = (AcquisitionState*)aClosure; + state->ref().AppendElement(aPc); +# endif +} + +void BlockingResourceBase::GetStackTrace(AcquisitionState& aState, + const void* aFirstFramePC) { +# ifndef MOZ_CALLSTACK_DISABLED + // Clear the array... + aState.reset(); + // ...and create a new one; this also puts the state to 'acquired' status + // regardless of whether we obtain a stack trace or not. + aState.emplace(); + + MozStackWalk(StackWalkCallback, aFirstFramePC, kAcquisitionStateStackSize, + aState.ptr()); +# endif +} + +/** + * PrintCycle + * Append to |aOut| detailed information about the circular + * dependency in |aCycle|. Returns true if it *appears* that this + * cycle may represent an imminent deadlock, but this is merely a + * heuristic; the value returned may be a false positive or false + * negative. + * + * *NOT* thread safe. Calls |Print()|. + * + * FIXME bug 456272 hack alert: because we can't write call + * contexts into strings, all info is written to stderr, but only + * some info is written into |aOut| + */ +static bool PrintCycle( + const BlockingResourceBase::DDT::ResourceAcquisitionArray& aCycle, + nsACString& aOut) { + NS_ASSERTION(aCycle.Length() > 1, "need > 1 element for cycle!"); + + bool maybeImminent = true; + + fputs("=== Cyclical dependency starts at\n", stderr); + aOut += "Cyclical dependency starts at\n"; + + const BlockingResourceBase::DDT::ResourceAcquisitionArray::value_type res = + aCycle.ElementAt(0); + maybeImminent &= res->Print(aOut); + + BlockingResourceBase::DDT::ResourceAcquisitionArray::index_type i; + BlockingResourceBase::DDT::ResourceAcquisitionArray::size_type len = + aCycle.Length(); + const BlockingResourceBase::DDT::ResourceAcquisitionArray::value_type* it = + 1 + aCycle.Elements(); + for (i = 1; i < len - 1; ++i, ++it) { + fputs("\n--- Next dependency:\n", stderr); + aOut += "\nNext dependency:\n"; + + maybeImminent &= (*it)->Print(aOut); + } + + fputs("\n=== Cycle completed at\n", stderr); + aOut += "Cycle completed at\n"; + (*it)->Print(aOut); + + return maybeImminent; +} + +bool BlockingResourceBase::Print(nsACString& aOut) const { + fprintf(stderr, "--- %s : %s", kResourceTypeName[mType], mName); + aOut += BlockingResourceBase::kResourceTypeName[mType]; + aOut += " : "; + aOut += mName; + + bool acquired = IsAcquired(); + + if (acquired) { + fputs(" (currently acquired)\n", stderr); + aOut += " (currently acquired)\n"; + } + + fputs(" calling context\n", stderr); +# ifdef MOZ_CALLSTACK_DISABLED + fputs(" [stack trace unavailable]\n", stderr); +# else + const AcquisitionState& state = acquired ? mAcquired : mFirstSeen; + + CodeAddressService<> addressService; + + for (uint32_t i = 0; i < state.ref().Length(); i++) { + const size_t kMaxLength = 1024; + char buffer[kMaxLength]; + addressService.GetLocation(i + 1, state.ref()[i], buffer, kMaxLength); + const char* fmt = " %s\n"; + aOut.AppendLiteral(" "); + aOut.Append(buffer); + aOut.AppendLiteral("\n"); + fprintf(stderr, fmt, buffer); + } + +# endif + + return acquired; +} + +BlockingResourceBase::BlockingResourceBase( + const char* aName, BlockingResourceBase::BlockingResourceType aType) + : mName(aName), + mType(aType) +# ifdef MOZ_CALLSTACK_DISABLED + , + mAcquired(false) +# else + , + mAcquired() +# endif +{ + MOZ_ASSERT(mName, "Name must be nonnull"); + // PR_CallOnce guaranatees that InitStatics is called in a + // thread-safe way + if (PR_SUCCESS != PR_CallOnce(&sCallOnce, InitStatics)) { + MOZ_CRASH("can't initialize blocking resource static members"); + } + + mChainPrev = 0; + sDeadlockDetector->Add(this); +} + +BlockingResourceBase::~BlockingResourceBase() { + // we don't check for really obviously bad things like freeing + // Mutexes while they're still locked. it is assumed that the + // base class, or its underlying primitive, will check for such + // stupid mistakes. + mChainPrev = 0; // racy only for stupidly buggy client code + if (sDeadlockDetector) { + sDeadlockDetector->Remove(this); + } +} + +size_t BlockingResourceBase::SizeOfDeadlockDetector( + MallocSizeOf aMallocSizeOf) { + return sDeadlockDetector + ? sDeadlockDetector->SizeOfIncludingThis(aMallocSizeOf) + : 0; +} + +PRStatus BlockingResourceBase::InitStatics() { + MOZ_ASSERT(sResourceAcqnChainFront.init()); + sDeadlockDetector = new DDT(); + if (!sDeadlockDetector) { + MOZ_CRASH("can't allocate deadlock detector"); + } + return PR_SUCCESS; +} + +void BlockingResourceBase::Shutdown() { + delete sDeadlockDetector; + sDeadlockDetector = 0; +} + +MOZ_NEVER_INLINE void BlockingResourceBase::CheckAcquire() { + if (mType == eCondVar) { + MOZ_ASSERT_UNREACHABLE( + "FIXME bug 456272: annots. to allow CheckAcquire()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + mozilla::UniquePtr cycle( + sDeadlockDetector->CheckAcquisition(chainFront ? chainFront : 0, this)); + if (!cycle) { + return; + } + +# ifndef MOZ_CALLSTACK_DISABLED + // Update the current stack before printing. + GetStackTrace(mAcquired, CallerPC()); +# endif + + fputs("###!!! ERROR: Potential deadlock detected:\n", stderr); + nsAutoCString out("Potential deadlock detected:\n"); + bool maybeImminent = PrintCycle(*cycle, out); + + if (maybeImminent) { + fputs("\n###!!! Deadlock may happen NOW!\n\n", stderr); + out.AppendLiteral("\n###!!! Deadlock may happen NOW!\n\n"); + } else { + fputs("\nDeadlock may happen for some other execution\n\n", stderr); + out.AppendLiteral("\nDeadlock may happen for some other execution\n\n"); + } + + // Only error out if we think a deadlock is imminent. + if (maybeImminent) { + NS_ERROR(out.get()); + } else { + NS_WARNING(out.get()); + } +} + +MOZ_NEVER_INLINE void BlockingResourceBase::Acquire() { + if (mType == eCondVar) { + MOZ_ASSERT_UNREACHABLE( + "FIXME bug 456272: annots. to allow Acquire()ing condvars"); + return; + } + NS_ASSERTION(!IsAcquired(), "reacquiring already acquired resource"); + + ResourceChainAppend(ResourceChainFront()); + +# ifdef MOZ_CALLSTACK_DISABLED + mAcquired = true; +# else + // Take a stack snapshot. + GetStackTrace(mAcquired, CallerPC()); + MOZ_ASSERT(IsAcquired()); + + if (!mFirstSeen) { + mFirstSeen = mAcquired.map( + [](AcquisitionState::ValueType& state) { return state.Clone(); }); + } +# endif +} + +void BlockingResourceBase::Release() { + if (mType == eCondVar) { + MOZ_ASSERT_UNREACHABLE( + "FIXME bug 456272: annots. to allow Release()ing condvars"); + return; + } + + BlockingResourceBase* chainFront = ResourceChainFront(); + NS_ASSERTION(chainFront && IsAcquired(), + "Release()ing something that hasn't been Acquire()ed"); + + if (chainFront == this) { + ResourceChainRemove(); + } else { + // remove this resource from wherever it lives in the chain + // we walk backwards in order of acquisition: + // (1) ...node<-prev<-curr... + // / / + // (2) ...prev<-curr... + BlockingResourceBase* curr = chainFront; + BlockingResourceBase* prev = nullptr; + while (curr && (prev = curr->mChainPrev) && (prev != this)) { + curr = prev; + } + if (prev == this) { + curr->mChainPrev = prev->mChainPrev; + } + } + + ClearAcquisitionState(); +} + +// +// Debug implementation of (OffTheBooks)Mutex +void OffTheBooksMutex::Lock() { + CheckAcquire(); + this->lock(); + mOwningThread = PR_GetCurrentThread(); + Acquire(); +} + +bool OffTheBooksMutex::TryLock() { + bool locked = this->tryLock(); + if (locked) { + mOwningThread = PR_GetCurrentThread(); + Acquire(); + } + return locked; +} + +void OffTheBooksMutex::Unlock() { + Release(); + mOwningThread = nullptr; + this->unlock(); +} + +void OffTheBooksMutex::AssertCurrentThreadOwns() const { + MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread()); +} + +// +// Debug implementation of RWLock +// + +bool RWLock::TryReadLock() { + bool locked = this->detail::RWLockImpl::tryReadLock(); + MOZ_ASSERT_IF(locked, mOwningThread == nullptr); + return locked; +} + +void RWLock::ReadLock() { + // All we want to ensure here is that we're not attempting to acquire the + // read lock while this thread is holding the write lock. + CheckAcquire(); + this->detail::RWLockImpl::readLock(); + MOZ_ASSERT(mOwningThread == nullptr); +} + +void RWLock::ReadUnlock() { + MOZ_ASSERT(mOwningThread == nullptr); + this->detail::RWLockImpl::readUnlock(); +} + +bool RWLock::TryWriteLock() { + bool locked = this->detail::RWLockImpl::tryWriteLock(); + if (locked) { + mOwningThread = PR_GetCurrentThread(); + Acquire(); + } + return locked; +} + +void RWLock::WriteLock() { + CheckAcquire(); + this->detail::RWLockImpl::writeLock(); + mOwningThread = PR_GetCurrentThread(); + Acquire(); +} + +void RWLock::WriteUnlock() { + Release(); + mOwningThread = nullptr; + this->detail::RWLockImpl::writeUnlock(); +} + +// +// Debug implementation of ReentrantMonitor +void ReentrantMonitor::Enter() { + BlockingResourceBase* chainFront = ResourceChainFront(); + + // the code below implements monitor reentrancy semantics + + if (this == chainFront) { + // immediately re-entered the monitor: acceptable + PR_EnterMonitor(mReentrantMonitor); + ++mEntryCount; + return; + } + + // this is sort of a hack around not recording the thread that + // owns this monitor + if (chainFront) { + for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br; + br = ResourceChainPrev(br)) { + if (br == this) { + NS_WARNING( + "Re-entering ReentrantMonitor after acquiring other resources."); + + // show the caller why this is potentially bad + CheckAcquire(); + + PR_EnterMonitor(mReentrantMonitor); + ++mEntryCount; + return; + } + } + } + + CheckAcquire(); + PR_EnterMonitor(mReentrantMonitor); + NS_ASSERTION(mEntryCount == 0, "ReentrantMonitor isn't free!"); + Acquire(); // protected by mReentrantMonitor + mEntryCount = 1; +} + +void ReentrantMonitor::Exit() { + if (--mEntryCount == 0) { + Release(); // protected by mReentrantMonitor + } + PRStatus status = PR_ExitMonitor(mReentrantMonitor); + NS_ASSERTION(PR_SUCCESS == status, "bad ReentrantMonitor::Exit()"); +} + +nsresult ReentrantMonitor::Wait(PRIntervalTime aInterval) { + AssertCurrentThreadIn(); + + // save monitor state and reset it to empty + int32_t savedEntryCount = mEntryCount; + AcquisitionState savedAcquisitionState = TakeAcquisitionState(); + BlockingResourceBase* savedChainPrev = mChainPrev; + mEntryCount = 0; + mChainPrev = 0; + + nsresult rv; + { +# if defined(MOZILLA_INTERNAL_API) + AUTO_PROFILER_THREAD_SLEEP; +# endif + // give up the monitor until we're back from Wait() + rv = PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS ? NS_OK + : NS_ERROR_FAILURE; + } + + // restore saved state + mEntryCount = savedEntryCount; + SetAcquisitionState(std::move(savedAcquisitionState)); + mChainPrev = savedChainPrev; + + return rv; +} + +// +// Debug implementation of RecursiveMutex +void RecursiveMutex::Lock() { + BlockingResourceBase* chainFront = ResourceChainFront(); + + // the code below implements mutex reentrancy semantics + + if (this == chainFront) { + // immediately re-entered the mutex: acceptable + LockInternal(); + ++mEntryCount; + return; + } + + // this is sort of a hack around not recording the thread that + // owns this monitor + if (chainFront) { + for (BlockingResourceBase* br = ResourceChainPrev(chainFront); br; + br = ResourceChainPrev(br)) { + if (br == this) { + NS_WARNING( + "Re-entering RecursiveMutex after acquiring other resources."); + + // show the caller why this is potentially bad + CheckAcquire(); + + LockInternal(); + ++mEntryCount; + return; + } + } + } + + CheckAcquire(); + LockInternal(); + NS_ASSERTION(mEntryCount == 0, "RecursiveMutex isn't free!"); + Acquire(); // protected by us + mOwningThread = PR_GetCurrentThread(); + mEntryCount = 1; +} + +void RecursiveMutex::Unlock() { + if (--mEntryCount == 0) { + Release(); // protected by us + mOwningThread = nullptr; + } + UnlockInternal(); +} + +void RecursiveMutex::AssertCurrentThreadIn() const { + MOZ_ASSERT(IsAcquired() && mOwningThread == PR_GetCurrentThread()); +} + +// +// Debug implementation of CondVar +void OffTheBooksCondVar::Wait() { + // Forward to the timed version of OffTheBooksCondVar::Wait to avoid code + // duplication. + CVStatus status = Wait(TimeDuration::Forever()); + MOZ_ASSERT(status == CVStatus::NoTimeout); +} + +CVStatus OffTheBooksCondVar::Wait(TimeDuration aDuration) { + AssertCurrentThreadOwnsMutex(); + + // save mutex state and reset to empty + AcquisitionState savedAcquisitionState = mLock->TakeAcquisitionState(); + BlockingResourceBase* savedChainPrev = mLock->mChainPrev; + PRThread* savedOwningThread = mLock->mOwningThread; + mLock->mChainPrev = 0; + mLock->mOwningThread = nullptr; + + // give up mutex until we're back from Wait() + CVStatus status; + { +# if defined(MOZILLA_INTERNAL_API) + AUTO_PROFILER_THREAD_SLEEP; +# endif + status = mImpl.wait_for(*mLock, aDuration); + } + + // restore saved state + mLock->SetAcquisitionState(std::move(savedAcquisitionState)); + mLock->mChainPrev = savedChainPrev; + mLock->mOwningThread = savedOwningThread; + + return status; +} + +#endif // ifdef DEBUG + +} // namespace mozilla diff --git a/xpcom/threads/BlockingResourceBase.h b/xpcom/threads/BlockingResourceBase.h new file mode 100644 index 0000000000..8bb7a78f6f --- /dev/null +++ b/xpcom/threads/BlockingResourceBase.h @@ -0,0 +1,339 @@ +/* -*- 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/. */ + +#ifndef mozilla_BlockingResourceBase_h +#define mozilla_BlockingResourceBase_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/Attributes.h" + +#include "nscore.h" +#include "nsDebug.h" + +#include "prtypes.h" + +#ifdef DEBUG + +// NB: Comment this out to enable callstack tracking. +# define MOZ_CALLSTACK_DISABLED + +# include "prinit.h" + +# ifndef MOZ_CALLSTACK_DISABLED +# include "mozilla/Maybe.h" +# include "nsTArray.h" +# endif + +#endif + +// +// This header is not meant to be included by client code. +// + +namespace mozilla { + +#ifdef DEBUG +template +class DeadlockDetector; +#endif + +/** + * BlockingResourceBase + * Base class of resources that might block clients trying to acquire them. + * Does debugging and deadlock detection in DEBUG builds. + **/ +class BlockingResourceBase { + public: + // Needs to be kept in sync with kResourceTypeNames. + enum BlockingResourceType { + eMutex, + eReentrantMonitor, + eCondVar, + eRecursiveMutex + }; + + /** + * kResourceTypeName + * Human-readable version of BlockingResourceType enum. + */ + static const char* const kResourceTypeName[]; + +#ifdef DEBUG + + static size_t SizeOfDeadlockDetector(MallocSizeOf aMallocSizeOf); + + /** + * Print + * Write a description of this blocking resource to |aOut|. If + * the resource appears to be currently acquired, the current + * acquisition context is printed and true is returned. + * Otherwise, we print the context from |aFirstSeen|, the + * first acquisition from which the code calling |Print()| + * became interested in us, and return false. + * + * *NOT* thread safe. Reads |mAcquisitionContext| without + * synchronization, but this will not cause correctness + * problems. + * + * FIXME bug 456272: hack alert: because we can't write call + * contexts into strings, all info is written to stderr, but + * only some info is written into |aOut| + */ + bool Print(nsACString& aOut) const; + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + // NB: |mName| is not reported as it's expected to be a static string. + // If we switch to a nsString it should be added to the tally. + // |mChainPrev| is not reported because its memory is not owned. + size_t n = aMallocSizeOf(this); + return n; + } + + // ``DDT'' = ``Deadlock Detector Type'' + typedef DeadlockDetector DDT; + + protected: +# ifdef MOZ_CALLSTACK_DISABLED + typedef bool AcquisitionState; +# else + // Using maybe to use emplacement as the acquisition state flag; we may not + // always get a stack trace because of possible stack walk suppression or + // errors, hence can't use !IsEmpty() on the array itself as indication. + static size_t const kAcquisitionStateStackSize = 24; + typedef Maybe > + AcquisitionState; +# endif + + /** + * BlockingResourceBase + * Initialize this blocking resource. Also hooks the resource into + * instrumentation code. + * + * Thread safe. + * + * @param aName A meaningful, unique name that can be used in + * error messages, et al. + * @param aType The specific type of |this|, if any. + **/ + BlockingResourceBase(const char* aName, BlockingResourceType aType); + + ~BlockingResourceBase(); + + /** + * CheckAcquire + * + * Thread safe. + **/ + void CheckAcquire(); + + /** + * Acquire + * + * *NOT* thread safe. Requires ownership of underlying resource. + **/ + void Acquire(); // NS_NEEDS_RESOURCE(this) + + /** + * Release + * Remove this resource from the current thread's acquisition chain. + * The resource does not have to be at the front of the chain, although + * it is confusing to release resources in a different order than they + * are acquired. This generates a warning. + * + * *NOT* thread safe. Requires ownership of underlying resource. + **/ + void Release(); // NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainFront + * + * Thread safe. + * + * @return the front of the resource acquisition chain, i.e., the last + * resource acquired. + */ + static BlockingResourceBase* ResourceChainFront() { + return sResourceAcqnChainFront.get(); + } + + /** + * ResourceChainPrev + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + static BlockingResourceBase* ResourceChainPrev( + const BlockingResourceBase* aResource) { + return aResource->mChainPrev; + } // NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainAppend + * Set |this| to the front of the resource acquisition chain, and link + * |this| to |aPrev|. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ResourceChainAppend(BlockingResourceBase* aPrev) { + mChainPrev = aPrev; + sResourceAcqnChainFront.set(this); + } // NS_NEEDS_RESOURCE(this) + + /** + * ResourceChainRemove + * Remove |this| from the front of the resource acquisition chain. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ResourceChainRemove() { + NS_ASSERTION(this == ResourceChainFront(), "not at chain front"); + sResourceAcqnChainFront.set(mChainPrev); + } // NS_NEEDS_RESOURCE(this) + + /** + * TakeAcquisitionState + * Return whether or not this resource was acquired and mark the resource + * as not acquired for subsequent uses. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + AcquisitionState TakeAcquisitionState() { +# ifdef MOZ_CALLSTACK_DISABLED + bool acquired = mAcquired; + ClearAcquisitionState(); + return acquired; +# else + return mAcquired.take(); +# endif + } + + /** + * SetAcquisitionState + * Set whether or not this resource was acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void SetAcquisitionState(AcquisitionState&& aAcquisitionState) { + mAcquired = std::move(aAcquisitionState); + } + + /** + * ClearAcquisitionState + * Indicate this resource is not acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + void ClearAcquisitionState() { +# ifdef MOZ_CALLSTACK_DISABLED + mAcquired = false; +# else + mAcquired.reset(); +# endif + } + + /** + * IsAcquired + * Indicates if this resource is acquired. + * + * *NOT* thread safe. Requires ownership of underlying resource. + */ + bool IsAcquired() const { return (bool)mAcquired; } + + /** + * mChainPrev + * A series of resource acquisitions creates a chain of orders. This + * chain is implemented as a linked list; |mChainPrev| points to the + * resource most recently Acquire()'d before this one. + **/ + BlockingResourceBase* mChainPrev; + + private: + /** + * mName + * A descriptive name for this resource. Used in error + * messages etc. + */ + const char* mName; + + /** + * mType + * The more specific type of this resource. Used to implement + * special semantics (e.g., reentrancy of monitors). + **/ + BlockingResourceType mType; + + /** + * mAcquired + * Indicates if this resource is currently acquired. + */ + AcquisitionState mAcquired; + +# ifndef MOZ_CALLSTACK_DISABLED + /** + * mFirstSeen + * Inidicates where this resource was first acquired. + */ + AcquisitionState mFirstSeen; +# endif + + /** + * sCallOnce + * Ensures static members are initialized only once, and in a + * thread-safe way. + */ + static PRCallOnceType sCallOnce; + + /** + * Thread-private pointer to the front of each thread's resource + * acquisition chain. + */ + static MOZ_THREAD_LOCAL(BlockingResourceBase*) sResourceAcqnChainFront; + + /** + * sDeadlockDetector + * Does as named. + */ + static DDT* sDeadlockDetector; + + /** + * InitStatics + * Inititialize static members of BlockingResourceBase that can't + * be statically initialized. + * + * *NOT* thread safe. + */ + static PRStatus InitStatics(); + + /** + * Shutdown + * Free static members. + * + * *NOT* thread safe. + */ + static void Shutdown(); + + static void StackWalkCallback(uint32_t aFrameNumber, void* aPc, void* aSp, + void* aClosure); + static void GetStackTrace(AcquisitionState& aState, + const void* aFirstFramePC); + +# ifdef MOZILLA_INTERNAL_API + // so it can call BlockingResourceBase::Shutdown() + friend void LogTerm(); +# endif // ifdef MOZILLA_INTERNAL_API + +#else // non-DEBUG implementation + + BlockingResourceBase(const char* aName, BlockingResourceType aType) {} + + ~BlockingResourceBase() {} + +#endif +}; + +} // namespace mozilla + +#endif // mozilla_BlockingResourceBase_h diff --git a/xpcom/threads/CPUUsageWatcher.cpp b/xpcom/threads/CPUUsageWatcher.cpp new file mode 100644 index 0000000000..922ca81e8d --- /dev/null +++ b/xpcom/threads/CPUUsageWatcher.cpp @@ -0,0 +1,252 @@ +/* -*- 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/CPUUsageWatcher.h" + +#include "prsystem.h" + +#ifdef XP_MACOSX +# include +# include +# include +#endif + +#ifdef CPU_USAGE_WATCHER_ACTIVE +# include "mozilla/BackgroundHangMonitor.h" +#endif + +namespace mozilla { + +#ifdef CPU_USAGE_WATCHER_ACTIVE + +// Even if the machine only has one processor, tolerate up to 50% +// external CPU usage. +static const float kTolerableExternalCPUUsageFloor = 0.5f; + +struct CPUStats { + // The average CPU usage time, which can be summed across all cores in the + // system, or averaged between them. Whichever it is, it needs to be in the + // same units as updateTime. + uint64_t usageTime; + // A monotonically increasing value in the same units as usageTime, which can + // be used to determine the percentage of active vs idle time + uint64_t updateTime; +}; + +# ifdef XP_MACOSX + +static const uint64_t kMicrosecondsPerSecond = 1000000LL; +static const uint64_t kNanosecondsPerMicrosecond = 1000LL; + +static uint64_t GetMicroseconds(timeval time) { + return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond + + (uint64_t)time.tv_usec; +} + +static uint64_t GetMicroseconds(mach_timespec_t time) { + return ((uint64_t)time.tv_sec) * kMicrosecondsPerSecond + + ((uint64_t)time.tv_nsec) / kNanosecondsPerMicrosecond; +} + +static Result GetProcessCPUStats( + int32_t numCPUs) { + CPUStats result = {}; + rusage usage; + int32_t rusageResult = getrusage(RUSAGE_SELF, &usage); + if (rusageResult == -1) { + return Err(GetProcessTimesError); + } + result.usageTime = + GetMicroseconds(usage.ru_utime) + GetMicroseconds(usage.ru_stime); + + clock_serv_t realtimeClock; + kern_return_t errorResult = + host_get_clock_service(mach_host_self(), REALTIME_CLOCK, &realtimeClock); + if (errorResult != KERN_SUCCESS) { + return Err(GetProcessTimesError); + } + mach_timespec_t time; + errorResult = clock_get_time(realtimeClock, &time); + if (errorResult != KERN_SUCCESS) { + return Err(GetProcessTimesError); + } + result.updateTime = GetMicroseconds(time); + + // getrusage will give us the sum of the values across all + // of our cores. Divide by the number of CPUs to get an average. + result.usageTime /= numCPUs; + return result; +} + +static Result GetGlobalCPUStats() { + CPUStats result = {}; + host_cpu_load_info_data_t loadInfo; + mach_msg_type_number_t loadInfoCount = HOST_CPU_LOAD_INFO_COUNT; + kern_return_t statsResult = + host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, + (host_info_t)&loadInfo, &loadInfoCount); + if (statsResult != KERN_SUCCESS) { + return Err(HostStatisticsError); + } + + result.usageTime = loadInfo.cpu_ticks[CPU_STATE_USER] + + loadInfo.cpu_ticks[CPU_STATE_NICE] + + loadInfo.cpu_ticks[CPU_STATE_SYSTEM]; + result.updateTime = result.usageTime + loadInfo.cpu_ticks[CPU_STATE_IDLE]; + return result; +} + +# endif // XP_MACOSX + +# ifdef XP_WIN + +// A FILETIME represents the number of 100-nanosecond ticks since 1/1/1601 UTC +uint64_t FiletimeToInteger(FILETIME filetime) { + return ((uint64_t)filetime.dwLowDateTime) | (uint64_t)filetime.dwHighDateTime + << 32; +} + +Result GetProcessCPUStats(int32_t numCPUs) { + CPUStats result = {}; + FILETIME creationFiletime; + FILETIME exitFiletime; + FILETIME kernelFiletime; + FILETIME userFiletime; + bool success = GetProcessTimes(GetCurrentProcess(), &creationFiletime, + &exitFiletime, &kernelFiletime, &userFiletime); + if (!success) { + return Err(GetProcessTimesError); + } + + result.usageTime = + FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime); + + FILETIME nowFiletime; + GetSystemTimeAsFileTime(&nowFiletime); + result.updateTime = FiletimeToInteger(nowFiletime); + + result.usageTime /= numCPUs; + + return result; +} + +Result GetGlobalCPUStats() { + CPUStats result = {}; + FILETIME idleFiletime; + FILETIME kernelFiletime; + FILETIME userFiletime; + bool success = GetSystemTimes(&idleFiletime, &kernelFiletime, &userFiletime); + + if (!success) { + return Err(GetSystemTimesError); + } + + result.usageTime = + FiletimeToInteger(kernelFiletime) + FiletimeToInteger(userFiletime); + result.updateTime = result.usageTime + FiletimeToInteger(idleFiletime); + + return result; +} + +# endif // XP_WIN + +Result CPUUsageWatcher::Init() { + mNumCPUs = PR_GetNumberOfProcessors(); + if (mNumCPUs <= 0) { + mExternalUsageThreshold = 1.0f; + return Err(GetNumberOfProcessorsError); + } + mExternalUsageThreshold = + std::max(1.0f - 1.0f / (float)mNumCPUs, kTolerableExternalCPUUsageFloor); + + CPUStats processTimes; + MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs)); + mProcessUpdateTime = processTimes.updateTime; + mProcessUsageTime = processTimes.usageTime; + + CPUStats globalTimes; + MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats()); + mGlobalUpdateTime = globalTimes.updateTime; + mGlobalUsageTime = globalTimes.usageTime; + + mInitialized = true; + + CPUUsageWatcher* self = this; + NS_DispatchToMainThread(NS_NewRunnableFunction( + "CPUUsageWatcher::Init", + [=]() { BackgroundHangMonitor::RegisterAnnotator(*self); })); + + return Ok(); +} + +void CPUUsageWatcher::Uninit() { + if (mInitialized) { + BackgroundHangMonitor::UnregisterAnnotator(*this); + } + mInitialized = false; +} + +Result CPUUsageWatcher::CollectCPUUsage() { + if (!mInitialized) { + return Ok(); + } + + mExternalUsageRatio = 0.0f; + + CPUStats processTimes; + MOZ_TRY_VAR(processTimes, GetProcessCPUStats(mNumCPUs)); + CPUStats globalTimes; + MOZ_TRY_VAR(globalTimes, GetGlobalCPUStats()); + + uint64_t processUsageDelta = processTimes.usageTime - mProcessUsageTime; + uint64_t processUpdateDelta = processTimes.updateTime - mProcessUpdateTime; + float processUsageNormalized = + processUsageDelta > 0 + ? (float)processUsageDelta / (float)processUpdateDelta + : 0.0f; + + uint64_t globalUsageDelta = globalTimes.usageTime - mGlobalUsageTime; + uint64_t globalUpdateDelta = globalTimes.updateTime - mGlobalUpdateTime; + float globalUsageNormalized = + globalUsageDelta > 0 ? (float)globalUsageDelta / (float)globalUpdateDelta + : 0.0f; + + mProcessUsageTime = processTimes.usageTime; + mProcessUpdateTime = processTimes.updateTime; + mGlobalUsageTime = globalTimes.usageTime; + mGlobalUpdateTime = globalTimes.updateTime; + + mExternalUsageRatio = + std::max(0.0f, globalUsageNormalized - processUsageNormalized); + + return Ok(); +} + +void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) { + if (!mInitialized) { + return; + } + + if (mExternalUsageRatio > mExternalUsageThreshold) { + aAnnotations.AddAnnotation(u"ExternalCPUHigh"_ns, true); + } +} + +#else // !CPU_USAGE_WATCHER_ACTIVE + +Result CPUUsageWatcher::Init() { return Ok(); } + +void CPUUsageWatcher::Uninit() {} + +Result CPUUsageWatcher::CollectCPUUsage() { + return Ok(); +} + +void CPUUsageWatcher::AnnotateHang(BackgroundHangAnnotations& aAnnotations) {} + +#endif // CPU_USAGE_WATCHER_ACTIVE + +} // namespace mozilla diff --git a/xpcom/threads/CPUUsageWatcher.h b/xpcom/threads/CPUUsageWatcher.h new file mode 100644 index 0000000000..c3a643378a --- /dev/null +++ b/xpcom/threads/CPUUsageWatcher.h @@ -0,0 +1,100 @@ +/* -*- 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/. */ + +#ifndef mozilla_CPUUsageWatcher_h +#define mozilla_CPUUsageWatcher_h + +#include + +#include "mozilla/HangAnnotations.h" +#include "mozilla/Result.h" + +// We only support OSX and Windows, because on Linux we're forced to read +// from /proc/stat in order to get global CPU values. We would prefer to not +// eat that cost for this. +#if defined(NIGHTLY_BUILD) && (defined(XP_WIN) || defined(XP_MACOSX)) +# define CPU_USAGE_WATCHER_ACTIVE +#endif + +namespace mozilla { + +// Start error values at 1 to allow using the UnusedZero Result +// optimization. +enum CPUUsageWatcherError : uint8_t { + ClockGetTimeError = 1, + GetNumberOfProcessorsError, + GetProcessTimesError, + GetSystemTimesError, + HostStatisticsError, + ProcStatError, +}; + +namespace detail { + +template <> +struct UnusedZero : UnusedZeroEnum { +}; + +} // namespace detail + +class CPUUsageHangAnnotator : public BackgroundHangAnnotator { + public: +}; + +class CPUUsageWatcher : public BackgroundHangAnnotator { + public: +#ifdef CPU_USAGE_WATCHER_ACTIVE + CPUUsageWatcher() + : mInitialized(false), + mExternalUsageThreshold(0), + mExternalUsageRatio(0), + mProcessUsageTime(0), + mProcessUpdateTime(0), + mGlobalUsageTime(0), + mGlobalUpdateTime(0), + mNumCPUs(0) {} +#endif + + Result Init(); + + void Uninit(); + + // Updates necessary values to allow AnnotateHang to function. This must be + // called on some semi-regular basis, as it will calculate the mean CPU + // usage values between now and the last time it was called. + Result CollectCPUUsage(); + + void AnnotateHang(BackgroundHangAnnotations& aAnnotations) final; + + private: +#ifdef CPU_USAGE_WATCHER_ACTIVE + bool mInitialized; + // The threshold above which we will mark a hang as occurring under high + // external CPU usage conditions + float mExternalUsageThreshold; + // The CPU usage (0-1) external to our process, averaged between the two + // most recent monitor thread runs + float mExternalUsageRatio; + // The total cumulative CPU usage time by our process as of the last + // CollectCPUUsage or Startup + uint64_t mProcessUsageTime; + // A time value in the same units as mProcessUsageTime used to + // determine the ratio of CPU usage time to idle time + uint64_t mProcessUpdateTime; + // The total cumulative CPU usage time by all processes as of the last + // CollectCPUUsage or Startup + uint64_t mGlobalUsageTime; + // A time value in the same units as mGlobalUsageTime used to + // determine the ratio of CPU usage time to idle time + uint64_t mGlobalUpdateTime; + // The number of virtual cores on our machine + uint64_t mNumCPUs; +#endif +}; + +} // namespace mozilla + +#endif // mozilla_CPUUsageWatcher_h diff --git a/xpcom/threads/CondVar.h b/xpcom/threads/CondVar.h new file mode 100644 index 0000000000..e427fc2d9e --- /dev/null +++ b/xpcom/threads/CondVar.h @@ -0,0 +1,139 @@ +/* -*- 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/. */ + +#ifndef mozilla_CondVar_h +#define mozilla_CondVar_h + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/PlatformConditionVariable.h" +#include "mozilla/Mutex.h" +#include "mozilla/TimeStamp.h" + +#if defined(MOZILLA_INTERNAL_API) && !defined(DEBUG) +# include "mozilla/ProfilerThreadSleep.h" +#endif // defined( MOZILLA_INTERNAL_API) && !defined(DEBUG) + +namespace mozilla { + +/** + * Similarly to OffTheBooksMutex, OffTheBooksCondvar is identical to CondVar, + * except that OffTheBooksCondVar doesn't include leak checking. Sometimes + * you want to intentionally "leak" a CondVar until shutdown; in these cases, + * OffTheBooksCondVar is for you. + */ +class OffTheBooksCondVar : BlockingResourceBase { + public: + /** + * OffTheBooksCondVar + * + * The CALLER owns |aLock|. + * + * @param aLock A Mutex to associate with this condition variable. + * @param aName A name which can reference this monitor + * @returns If failure, nullptr. + * If success, a valid Monitor* which must be destroyed + * by Monitor::DestroyMonitor() + **/ + OffTheBooksCondVar(OffTheBooksMutex& aLock, const char* aName) + : BlockingResourceBase(aName, eCondVar), mLock(&aLock) {} + + /** + * ~OffTheBooksCondVar + * Clean up after this OffTheBooksCondVar, but NOT its associated Mutex. + **/ + ~OffTheBooksCondVar() = default; + + /** + * Wait + * @see prcvar.h + **/ +#ifndef DEBUG + void Wait() { +# ifdef MOZILLA_INTERNAL_API + AUTO_PROFILER_THREAD_SLEEP; +# endif // MOZILLA_INTERNAL_API + mImpl.wait(*mLock); + } + + CVStatus Wait(TimeDuration aDuration) { +# ifdef MOZILLA_INTERNAL_API + AUTO_PROFILER_THREAD_SLEEP; +# endif // MOZILLA_INTERNAL_API + return mImpl.wait_for(*mLock, aDuration); + } +#else + // NOTE: debug impl is in BlockingResourceBase.cpp + void Wait(); + CVStatus Wait(TimeDuration aDuration); +#endif + + /** + * Notify + * @see prcvar.h + **/ + void Notify() { mImpl.notify_one(); } + + /** + * NotifyAll + * @see prcvar.h + **/ + void NotifyAll() { mImpl.notify_all(); } + +#ifdef DEBUG + /** + * AssertCurrentThreadOwnsMutex + * @see Mutex::AssertCurrentThreadOwns + **/ + void AssertCurrentThreadOwnsMutex() const MOZ_ASSERT_CAPABILITY(mLock) { + mLock->AssertCurrentThreadOwns(); + } + + /** + * AssertNotCurrentThreadOwnsMutex + * @see Mutex::AssertNotCurrentThreadOwns + **/ + void AssertNotCurrentThreadOwnsMutex() const MOZ_ASSERT_CAPABILITY(!mLock) { + mLock->AssertNotCurrentThreadOwns(); + } + +#else + void AssertCurrentThreadOwnsMutex() const MOZ_ASSERT_CAPABILITY(mLock) {} + void AssertNotCurrentThreadOwnsMutex() const MOZ_ASSERT_CAPABILITY(!mLock) {} + +#endif // ifdef DEBUG + + private: + OffTheBooksCondVar(); + OffTheBooksCondVar(const OffTheBooksCondVar&) = delete; + OffTheBooksCondVar& operator=(const OffTheBooksCondVar&) = delete; + + OffTheBooksMutex* mLock; + detail::ConditionVariableImpl mImpl; +}; + +/** + * CondVar + * Vanilla condition variable. Please don't use this unless you have a + * compelling reason --- Monitor provides a simpler API. + */ +class CondVar : public OffTheBooksCondVar { + public: + CondVar(OffTheBooksMutex& aLock, const char* aName) + : OffTheBooksCondVar(aLock, aName) { + MOZ_COUNT_CTOR(CondVar); + } + + MOZ_COUNTED_DTOR(CondVar) + + private: + CondVar(); + CondVar(const CondVar&); + CondVar& operator=(const CondVar&); +}; + +} // namespace mozilla + +#endif // ifndef mozilla_CondVar_h diff --git a/xpcom/threads/DataMutex.h b/xpcom/threads/DataMutex.h new file mode 100644 index 0000000000..44f0a35762 --- /dev/null +++ b/xpcom/threads/DataMutex.h @@ -0,0 +1,130 @@ +/* -*- 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/. */ + +#ifndef DataMutex_h__ +#define DataMutex_h__ + +#include +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" + +namespace mozilla { + +// A template to wrap a type with a mutex so that accesses to the type's +// data are required to take the lock before accessing it. This ensures +// that a mutex is explicitly associated with the data that it protects, +// and makes it impossible to access the data without first taking the +// associated mutex. +// +// This is based on Rust's std::sync::Mutex, which operates under the +// strategy of locking data, rather than code. +// +// Examples: +// +// DataMutex u32DataMutex(1, "u32DataMutex"); +// auto x = u32DataMutex.Lock(); +// *x = 4; +// assert(*x, 4u); +// +// DataMutex> arrayDataMutex("arrayDataMutex"); +// auto a = arrayDataMutex.Lock(); +// auto& x = a.ref(); +// x.AppendElement(1u); +// assert(x[0], 1u); +// +template +class DataMutexBase { + public: + template + class MOZ_STACK_CLASS AutoLockBase { + public: + V* operator->() const& { return &ref(); } + V* operator->() const&& = delete; + + V& operator*() const& { return ref(); } + V& operator*() const&& = delete; + + // Like RefPtr, make this act like its underlying raw pointer type + // whenever it is used in a context where a raw pointer is expected. + operator V*() const& { return &ref(); } + + // Like RefPtr, don't allow implicit conversion of temporary to raw pointer. + operator V*() const&& = delete; + + V& ref() const& { + MOZ_ASSERT(mOwner); + return mOwner->mValue; + } + V& ref() const&& = delete; + + AutoLockBase(AutoLockBase&& aOther) : mOwner(aOther.mOwner) { + aOther.mOwner = nullptr; + } + + ~AutoLockBase() { + if (mOwner) { + mOwner->mMutex.Unlock(); + mOwner = nullptr; + } + } + + private: + friend class DataMutexBase; + + AutoLockBase(const AutoLockBase& aOther) = delete; + + explicit AutoLockBase(DataMutexBase* aDataMutex) + : mOwner(aDataMutex) { + MOZ_ASSERT(!!mOwner); + mOwner->mMutex.Lock(); + } + + DataMutexBase* mOwner; + }; + + using AutoLock = AutoLockBase; + using ConstAutoLock = AutoLockBase; + + explicit DataMutexBase(const char* aName) : mMutex(aName) {} + + DataMutexBase(T&& aValue, const char* aName) + : mMutex(aName), mValue(std::move(aValue)) {} + + AutoLock Lock() { return AutoLock(this); } + ConstAutoLock ConstLock() { return ConstAutoLock(this); } + + const MutexType& Mutex() const { return mMutex; } + + private: + MutexType mMutex; + T mValue; +}; + +// Craft a version of StaticMutex that takes a const char* in its ctor. +// We need this so it works interchangeably with Mutex which requires a const +// char* aName in its ctor. +class StaticMutexNameless : public StaticMutex { + public: + explicit StaticMutexNameless(const char* aName) : StaticMutex() {} + + private: + // Disallow copy construction, `=`, `new`, and `delete` like BaseStaticMutex. +#ifdef DEBUG + StaticMutexNameless(StaticMutexNameless& aOther); +#endif // DEBUG + StaticMutexNameless& operator=(StaticMutexNameless* aRhs); + static void* operator new(size_t) noexcept(true); + static void operator delete(void*); +}; + +template +using DataMutex = DataMutexBase; +template +using StaticDataMutex = DataMutexBase; + +} // namespace mozilla + +#endif // DataMutex_h__ diff --git a/xpcom/threads/DeadlockDetector.h b/xpcom/threads/DeadlockDetector.h new file mode 100644 index 0000000000..5c40941328 --- /dev/null +++ b/xpcom/threads/DeadlockDetector.h @@ -0,0 +1,359 @@ +/* -*- 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/. */ +#ifndef mozilla_DeadlockDetector_h +#define mozilla_DeadlockDetector_h + +#include "mozilla/Attributes.h" + +#include + +#include "prlock.h" + +#include "nsClassHashtable.h" +#include "nsTArray.h" + +namespace mozilla { + +/** + * DeadlockDetector + * + * The following is an approximate description of how the deadlock detector + * works. + * + * The deadlock detector ensures that all blocking resources are + * acquired according to a partial order P. One type of blocking + * resource is a lock. If a lock l1 is acquired (locked) before l2, + * then we say that |l1 <_P l2|. The detector flags an error if two + * locks l1 and l2 have an inconsistent ordering in P; that is, if + * both |l1 <_P l2| and |l2 <_P l1|. This is a potential error + * because a thread acquiring l1,l2 according to the first order might + * race with a thread acquiring them according to the second order. + * If this happens under the right conditions, then the acquisitions + * will deadlock. + * + * This deadlock detector doesn't know at compile-time what P is. So, + * it tries to discover the order at run time. More precisely, it + * finds some order P, then tries to find chains of resource + * acquisitions that violate P. An example acquisition sequence, and + * the orders they impose, is + * l1.lock() // current chain: [ l1 ] + * // order: { } + * + * l2.lock() // current chain: [ l1, l2 ] + * // order: { l1 <_P l2 } + * + * l3.lock() // current chain: [ l1, l2, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: <_P is transitive, so also |l1 <_P l3|) + * + * l2.unlock() // current chain: [ l1, l3 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3 } + * // (note: it's OK, but weird, that l2 was unlocked out + * // of order. we still have l1 <_P l3). + * + * l2.lock() // current chain: [ l1, l3, l2 ] + * // order: { l1 <_P l2, l2 <_P l3, l1 <_P l3, + * l3 <_P l2 (!!!) } + * BEEP BEEP! Here the detector will flag a potential error, since + * l2 and l3 were used inconsistently (and potentially in ways that + * would deadlock). + */ +template +class DeadlockDetector { + public: + typedef nsTArray ResourceAcquisitionArray; + + private: + struct OrderingEntry; + typedef nsTArray HashEntryArray; + typedef typename HashEntryArray::index_type index_type; + typedef typename HashEntryArray::size_type size_type; + static const index_type NoIndex = HashEntryArray::NoIndex; + + /** + * Value type for the ordering table. Contains the other + * resources on which an ordering constraint |key < other| + * exists. The catch is that we also store the calling context at + * which the other resource was acquired; this improves the + * quality of error messages when potential deadlock is detected. + */ + struct OrderingEntry { + explicit OrderingEntry(const T* aResource) + : mOrderedLT() // FIXME bug 456272: set to empirical dep size? + , + mExternalRefs(), + mResource(aResource) {} + ~OrderingEntry() {} + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + n += mOrderedLT.ShallowSizeOfExcludingThis(aMallocSizeOf); + n += mExternalRefs.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; + } + + HashEntryArray mOrderedLT; // this <_o Other + HashEntryArray mExternalRefs; // hash entries that reference this + const T* mResource; + }; + + // Throwaway RAII lock to make the following code safer. + struct PRAutoLock { + explicit PRAutoLock(PRLock* aLock) : mLock(aLock) { PR_Lock(mLock); } + ~PRAutoLock() { PR_Unlock(mLock); } + PRLock* mLock; + }; + + public: + static const uint32_t kDefaultNumBuckets; + + /** + * DeadlockDetector + * Create a new deadlock detector. + * + * @param aNumResourcesGuess Guess at approximate number of resources + * that will be checked. + */ + explicit DeadlockDetector(uint32_t aNumResourcesGuess = kDefaultNumBuckets) + : mOrdering(aNumResourcesGuess) { + mLock = PR_NewLock(); + if (!mLock) { + MOZ_CRASH("couldn't allocate deadlock detector lock"); + } + } + + /** + * ~DeadlockDetector + * + * *NOT* thread safe. + */ + ~DeadlockDetector() { PR_DestroyLock(mLock); } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = aMallocSizeOf(this); + + { + PRAutoLock _(mLock); + n += mOrdering.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& data : mOrdering.Values()) { + // NB: Key is accounted for in the entry. + n += data->SizeOfIncludingThis(aMallocSizeOf); + } + } + + return n; + } + + /** + * Add + * Make the deadlock detector aware of |aResource|. + * + * WARNING: The deadlock detector owns |aResource|. + * + * Thread safe. + * + * @param aResource Resource to make deadlock detector aware of. + */ + void Add(const T* aResource) { + PRAutoLock _(mLock); + mOrdering.InsertOrUpdate(aResource, MakeUnique(aResource)); + } + + void Remove(const T* aResource) { + PRAutoLock _(mLock); + + OrderingEntry* entry = mOrdering.Get(aResource); + + // Iterate the external refs and remove the entry from them. + HashEntryArray& refs = entry->mExternalRefs; + for (index_type i = 0; i < refs.Length(); i++) { + refs[i]->mOrderedLT.RemoveElementSorted(entry); + } + + // Iterate orders and remove this entry from their refs. + HashEntryArray& orders = entry->mOrderedLT; + for (index_type i = 0; i < orders.Length(); i++) { + orders[i]->mExternalRefs.RemoveElementSorted(entry); + } + + // Now the entry can be safely removed. + mOrdering.Remove(aResource); + } + + /** + * CheckAcquisition This method is called after acquiring |aLast|, + * but before trying to acquire |aProposed|. + * It determines whether actually trying to acquire |aProposed| + * will create problems. It is OK if |aLast| is nullptr; this is + * interpreted as |aProposed| being the thread's first acquisition + * of its current chain. + * + * Iff acquiring |aProposed| may lead to deadlock for some thread + * interleaving (including the current one!), the cyclical + * dependency from which this was deduced is returned. Otherwise, + * 0 is returned. + * + * If a potential deadlock is detected and a resource cycle is + * returned, it is the *caller's* responsibility to free it. + * + * Thread safe. + * + * @param aLast Last resource acquired by calling thread (or 0). + * @param aProposed Resource calling thread proposes to acquire. + */ + ResourceAcquisitionArray* CheckAcquisition(const T* aLast, + const T* aProposed) { + if (!aLast) { + // don't check if |0 < aProposed|; just vamoose + return 0; + } + + NS_ASSERTION(aProposed, "null resource"); + PRAutoLock _(mLock); + + OrderingEntry* proposed = mOrdering.Get(aProposed); + NS_ASSERTION(proposed, "missing ordering entry"); + + OrderingEntry* current = mOrdering.Get(aLast); + NS_ASSERTION(current, "missing ordering entry"); + + // this is the crux of the deadlock detector algorithm + + if (current == proposed) { + // reflexive deadlock. fastpath b/c InTransitiveClosure is + // not applicable here. + ResourceAcquisitionArray* cycle = new ResourceAcquisitionArray(); + if (!cycle) { + MOZ_CRASH("can't allocate dep. cycle array"); + } + cycle->AppendElement(current->mResource); + cycle->AppendElement(aProposed); + return cycle; + } + if (InTransitiveClosure(current, proposed)) { + // we've already established |aLast < aProposed|. all is well. + return 0; + } + if (InTransitiveClosure(proposed, current)) { + // the order |aProposed < aLast| has been deduced, perhaps + // transitively. we're attempting to violate that + // constraint by acquiring resources in the order + // |aLast < aProposed|, and thus we may deadlock under the + // right conditions. + ResourceAcquisitionArray* cycle = GetDeductionChain(proposed, current); + // show how acquiring |aProposed| would complete the cycle + cycle->AppendElement(aProposed); + return cycle; + } + // |aLast|, |aProposed| are unordered according to our + // poset. this is fine, but we now need to add this + // ordering constraint. + current->mOrderedLT.InsertElementSorted(proposed); + proposed->mExternalRefs.InsertElementSorted(current); + return 0; + } + + /** + * Return true iff |aTarget| is in the transitive closure of |aStart| + * over the ordering relation `<_this'. + * + * @precondition |aStart != aTarget| + */ + bool InTransitiveClosure(const OrderingEntry* aStart, + const OrderingEntry* aTarget) const { + // NB: Using a static comparator rather than default constructing one shows + // a 9% improvement in scalability tests on some systems. + static nsDefaultComparator comp; + if (aStart->mOrderedLT.BinaryIndexOf(aTarget, comp) != NoIndex) { + return true; + } + + index_type i = 0; + size_type len = aStart->mOrderedLT.Length(); + for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) { + if (InTransitiveClosure(*it, aTarget)) { + return true; + } + } + return false; + } + + /** + * Return an array of all resource acquisitions + * aStart <_this r1 <_this r2 <_ ... <_ aTarget + * from which |aStart <_this aTarget| was deduced, including + * |aStart| and |aTarget|. + * + * Nb: there may be multiple deductions of |aStart <_this + * aTarget|. This function returns the first ordering found by + * depth-first search. + * + * Nb: |InTransitiveClosure| could be replaced by this function. + * However, this one is more expensive because we record the DFS + * search stack on the heap whereas the other doesn't. + * + * @precondition |aStart != aTarget| + */ + ResourceAcquisitionArray* GetDeductionChain(const OrderingEntry* aStart, + const OrderingEntry* aTarget) { + ResourceAcquisitionArray* chain = new ResourceAcquisitionArray(); + if (!chain) { + MOZ_CRASH("can't allocate dep. cycle array"); + } + chain->AppendElement(aStart->mResource); + + NS_ASSERTION(GetDeductionChain_Helper(aStart, aTarget, chain), + "GetDeductionChain called when there's no deadlock"); + return chain; + } + + // precondition: |aStart != aTarget| + // invariant: |aStart| is the last element in |aChain| + bool GetDeductionChain_Helper(const OrderingEntry* aStart, + const OrderingEntry* aTarget, + ResourceAcquisitionArray* aChain) { + if (aStart->mOrderedLT.BinaryIndexOf(aTarget) != NoIndex) { + aChain->AppendElement(aTarget->mResource); + return true; + } + + index_type i = 0; + size_type len = aStart->mOrderedLT.Length(); + for (auto it = aStart->mOrderedLT.Elements(); i < len; ++i, ++it) { + aChain->AppendElement((*it)->mResource); + if (GetDeductionChain_Helper(*it, aTarget, aChain)) { + return true; + } + aChain->RemoveLastElement(); + } + return false; + } + + /** + * The partial order on resource acquisitions used by the deadlock + * detector. + */ + nsClassHashtable, OrderingEntry> mOrdering; + + /** + * Protects contentious methods. + * Nb: can't use mozilla::Mutex since we are used as its deadlock + * detector. + */ + PRLock* mLock; + + private: + DeadlockDetector(const DeadlockDetector& aDD) = delete; + DeadlockDetector& operator=(const DeadlockDetector& aDD) = delete; +}; + +template +// FIXME bug 456272: tune based on average workload +const uint32_t DeadlockDetector::kDefaultNumBuckets = 32; + +} // namespace mozilla + +#endif // ifndef mozilla_DeadlockDetector_h diff --git a/xpcom/threads/DelayedRunnable.cpp b/xpcom/threads/DelayedRunnable.cpp new file mode 100644 index 0000000000..a9231442a7 --- /dev/null +++ b/xpcom/threads/DelayedRunnable.cpp @@ -0,0 +1,113 @@ +/* -*- 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 "DelayedRunnable.h" + +#include "mozilla/ProfilerRunnable.h" + +namespace mozilla { + +DelayedRunnable::DelayedRunnable(already_AddRefed aTarget, + already_AddRefed aRunnable, + uint32_t aDelay) + : mozilla::Runnable("DelayedRunnable"), + mTarget(aTarget), + mDelayedFrom(TimeStamp::NowLoRes()), + mDelay(aDelay), + mWrappedRunnable(aRunnable) {} + +nsresult DelayedRunnable::Init() { + MutexAutoLock lock(mMutex); + if (!mWrappedRunnable) { + MOZ_ASSERT_UNREACHABLE(); + return NS_ERROR_INVALID_ARG; + } + + nsresult rv = mTarget->RegisterShutdownTask(this); + if (NS_FAILED(rv)) { + MOZ_DIAGNOSTIC_ASSERT( + rv == NS_ERROR_UNEXPECTED, + "DelayedRunnable target must support RegisterShutdownTask"); + NS_WARNING("DelayedRunnable init after target is shutdown"); + return rv; + } + + rv = NS_NewTimerWithCallback(getter_AddRefs(mTimer), this, mDelay, + nsITimer::TYPE_ONE_SHOT, mTarget); + if (NS_WARN_IF(NS_FAILED(rv))) { + mTarget->UnregisterShutdownTask(this); + } + return rv; +} + +NS_IMETHODIMP DelayedRunnable::Run() { + MOZ_ASSERT(mTarget->IsOnCurrentThread()); + + nsCOMPtr runnable; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mTimer, "Init() must have been called"); + + // Already ran? + if (!mWrappedRunnable) { + return NS_OK; + } + + // Are we too early? + if ((mozilla::TimeStamp::NowLoRes() - mDelayedFrom).ToMilliseconds() < + mDelay) { + return NS_OK; // Let the nsITimer run us. + } + + mTimer->Cancel(); + mTarget->UnregisterShutdownTask(this); + runnable = mWrappedRunnable.forget(); + } + + AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable); + return runnable->Run(); +} + +NS_IMETHODIMP DelayedRunnable::Notify(nsITimer* aTimer) { + MOZ_ASSERT(mTarget->IsOnCurrentThread()); + + nsCOMPtr runnable; + { + MutexAutoLock lock(mMutex); + MOZ_ASSERT(mTimer, "Init() must have been called"); + + // We may have already run due to races + if (!mWrappedRunnable) { + return NS_OK; + } + + mTarget->UnregisterShutdownTask(this); + runnable = mWrappedRunnable.forget(); + } + + AUTO_PROFILE_FOLLOWING_RUNNABLE(runnable); + return runnable->Run(); +} + +void DelayedRunnable::TargetShutdown() { + MOZ_ASSERT(mTarget->IsOnCurrentThread()); + + // Called at shutdown + MutexAutoLock lock(mMutex); + if (!mWrappedRunnable) { + return; + } + mWrappedRunnable = nullptr; + + if (mTimer) { + mTimer->Cancel(); + } +} + +NS_IMPL_ISUPPORTS_INHERITED(DelayedRunnable, Runnable, nsITimerCallback, + nsITargetShutdownTask) + +} // namespace mozilla diff --git a/xpcom/threads/DelayedRunnable.h b/xpcom/threads/DelayedRunnable.h new file mode 100644 index 0000000000..242278fa5b --- /dev/null +++ b/xpcom/threads/DelayedRunnable.h @@ -0,0 +1,53 @@ +/* -*- 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/. */ + +#ifndef XPCOM_THREADS_DELAYEDRUNNABLE_H_ +#define XPCOM_THREADS_DELAYEDRUNNABLE_H_ + +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsITargetShutdownTask.h" +#include "nsITimer.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +class DelayedRunnable : public Runnable, + public nsITimerCallback, + public nsITargetShutdownTask { + public: + DelayedRunnable(already_AddRefed aTarget, + already_AddRefed aRunnable, uint32_t aDelay); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSITIMERCALLBACK + + nsresult Init(); + + /** + * Called when the target is going away so the runnable can be released safely + * on the target thread. + */ + void TargetShutdown() override; + + private: + ~DelayedRunnable() = default; + nsresult DoRun(); + + const nsCOMPtr mTarget; + const TimeStamp mDelayedFrom; + const uint32_t mDelay; + + mozilla::Mutex mMutex{"DelayedRunnable"}; + nsCOMPtr mWrappedRunnable; + nsCOMPtr mTimer; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/EventQueue.cpp b/xpcom/threads/EventQueue.cpp new file mode 100644 index 0000000000..0decc3ce4c --- /dev/null +++ b/xpcom/threads/EventQueue.cpp @@ -0,0 +1,131 @@ +/* -*- 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/EventQueue.h" + +#include "GeckoProfiler.h" +#include "InputTaskManager.h" +#include "VsyncTaskManager.h" +#include "nsIRunnable.h" +#include "TaskController.h" + +using namespace mozilla; +using namespace mozilla::detail; + +template +void EventQueueInternal::PutEvent( + already_AddRefed&& aEvent, EventQueuePriority aPriority, + const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aDelay) { + nsCOMPtr event(aEvent); + + static_assert(static_cast(nsIRunnablePriority::PRIORITY_IDLE) == + static_cast(EventQueuePriority::Idle)); + static_assert(static_cast(nsIRunnablePriority::PRIORITY_NORMAL) == + static_cast(EventQueuePriority::Normal)); + static_assert( + static_cast(nsIRunnablePriority::PRIORITY_MEDIUMHIGH) == + static_cast(EventQueuePriority::MediumHigh)); + static_assert( + static_cast(nsIRunnablePriority::PRIORITY_INPUT_HIGH) == + static_cast(EventQueuePriority::InputHigh)); + static_assert(static_cast(nsIRunnablePriority::PRIORITY_VSYNC) == + static_cast(EventQueuePriority::Vsync)); + static_assert( + static_cast(nsIRunnablePriority::PRIORITY_RENDER_BLOCKING) == + static_cast(EventQueuePriority::RenderBlocking)); + static_assert(static_cast(nsIRunnablePriority::PRIORITY_CONTROL) == + static_cast(EventQueuePriority::Control)); + + if (mForwardToTC) { + TaskController* tc = TaskController::Get(); + + TaskManager* manager = nullptr; + if (aPriority == EventQueuePriority::InputHigh) { + manager = InputTaskManager::Get(); + } else if (aPriority == EventQueuePriority::DeferredTimers || + aPriority == EventQueuePriority::Idle) { + manager = TaskController::Get()->GetIdleTaskManager(); + } else if (aPriority == EventQueuePriority::Vsync) { + manager = VsyncTaskManager::Get(); + } + + tc->DispatchRunnable(event.forget(), static_cast(aPriority), + manager); + return; + } + + if (profiler_thread_is_being_profiled(ThreadProfilingFeatures::Sampling)) { + // check to see if the profiler has been enabled since the last PutEvent + while (mDispatchTimes.Count() < mQueue.Count()) { + mDispatchTimes.Push(TimeStamp()); + } + mDispatchTimes.Push(aDelay ? TimeStamp::Now() - *aDelay : TimeStamp::Now()); + } + + mQueue.Push(std::move(event)); +} + +template +already_AddRefed EventQueueInternal::GetEvent( + const MutexAutoLock& aProofOfLock, mozilla::TimeDuration* aLastEventDelay) { + if (mQueue.IsEmpty()) { + if (aLastEventDelay) { + *aLastEventDelay = TimeDuration(); + } + return nullptr; + } + + // We always want to clear the dispatch times, even if the profiler is turned + // off, because we want to empty the (previously-collected) dispatch times, if + // any, from when the profiler was turned on. We only want to do something + // interesting with the dispatch times if the profiler is turned on, though. + if (!mDispatchTimes.IsEmpty()) { + TimeStamp dispatch_time = mDispatchTimes.Pop(); + if (profiler_is_active()) { + if (!dispatch_time.IsNull()) { + if (aLastEventDelay) { + *aLastEventDelay = TimeStamp::Now() - dispatch_time; + } + } + } + } else if (profiler_is_active()) { + if (aLastEventDelay) { + // if we just turned on the profiler, we don't have dispatch + // times for events already in the queue. + *aLastEventDelay = TimeDuration(); + } + } + + nsCOMPtr result = mQueue.Pop(); + return result.forget(); +} + +template +bool EventQueueInternal::IsEmpty( + const MutexAutoLock& aProofOfLock) { + return mQueue.IsEmpty(); +} + +template +bool EventQueueInternal::HasReadyEvent( + const MutexAutoLock& aProofOfLock) { + return !IsEmpty(aProofOfLock); +} + +template +size_t EventQueueInternal::Count( + const MutexAutoLock& aProofOfLock) const { + return mQueue.Count(); +} + +namespace mozilla { +template class EventQueueSized<16>; // Used by ThreadEventQueue +template class EventQueueSized<64>; // Used by ThrottledEventQueue +namespace detail { +template class EventQueueInternal<16>; // Used by ThreadEventQueue +template class EventQueueInternal<64>; // Used by ThrottledEventQueue +} // namespace detail +} // namespace mozilla diff --git a/xpcom/threads/EventQueue.h b/xpcom/threads/EventQueue.h new file mode 100644 index 0000000000..a604d03367 --- /dev/null +++ b/xpcom/threads/EventQueue.h @@ -0,0 +1,136 @@ +/* -*- 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/. */ + +#ifndef mozilla_EventQueue_h +#define mozilla_EventQueue_h + +#include "mozilla/Mutex.h" +#include "mozilla/Queue.h" +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" + +class nsIRunnable; + +namespace mozilla { + +#define EVENT_QUEUE_PRIORITY_LIST(EVENT_PRIORITY) \ + EVENT_PRIORITY(Idle, 0) \ + EVENT_PRIORITY(DeferredTimers, 1) \ + EVENT_PRIORITY(Low, 2) \ + EVENT_PRIORITY(InputLow, 3) \ + EVENT_PRIORITY(Normal, 4) \ + EVENT_PRIORITY(MediumHigh, 5) \ + EVENT_PRIORITY(InputHigh, 6) \ + EVENT_PRIORITY(Vsync, 7) \ + EVENT_PRIORITY(InputHighest, 8) \ + EVENT_PRIORITY(RenderBlocking, 9) \ + EVENT_PRIORITY(Control, 10) + +enum class EventQueuePriority { +#define EVENT_PRIORITY(NAME, VALUE) NAME = VALUE, + EVENT_QUEUE_PRIORITY_LIST(EVENT_PRIORITY) +#undef EVENT_PRIORITY + Invalid +}; + +class IdlePeriodState; + +namespace detail { + +// EventQueue is our unsynchronized event queue implementation. It is a queue +// of runnables used for non-main thread, as well as optionally providing +// forwarding to TaskController. +// +// Since EventQueue is unsynchronized, it should be wrapped in an outer +// SynchronizedEventQueue implementation (like ThreadEventQueue). +template +class EventQueueInternal { + public: + explicit EventQueueInternal(bool aForwardToTC) : mForwardToTC(aForwardToTC) {} + + // Add an event to the end of the queue. Implementors are free to use + // aPriority however they wish. If the runnable supports + // nsIRunnablePriority and the implementing class supports + // prioritization, aPriority represents the result of calling + // nsIRunnablePriority::GetPriority(). *aDelay is time the event has + // already been delayed (used when moving an event from one queue to + // another) + void PutEvent(already_AddRefed&& aEvent, + EventQueuePriority aPriority, const MutexAutoLock& aProofOfLock, + mozilla::TimeDuration* aDelay = nullptr); + + // Get an event from the front of the queue. This should return null if the + // queue is non-empty but the event in front is not ready to run. + // *aLastEventDelay is the time the event spent in queues before being + // retrieved. + already_AddRefed GetEvent( + const MutexAutoLock& aProofOfLock, + mozilla::TimeDuration* aLastEventDelay = nullptr); + + // Returns true if the queue is empty. Implies !HasReadyEvent(). + bool IsEmpty(const MutexAutoLock& aProofOfLock); + + // Returns true if the queue is non-empty and if the event in front is ready + // to run. Implies !IsEmpty(). This should return true iff GetEvent returns a + // non-null value. + bool HasReadyEvent(const MutexAutoLock& aProofOfLock); + + // Returns the number of events in the queue. + size_t Count(const MutexAutoLock& aProofOfLock) const; + // For some reason, if we put this in the .cpp file the linker can't find it + already_AddRefed PeekEvent(const MutexAutoLock& aProofOfLock) { + if (mQueue.IsEmpty()) { + return nullptr; + } + + nsCOMPtr result = mQueue.FirstElement(); + return result.forget(); + } + + void EnableInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + void FlushInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + void SuspendInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + void ResumeInputEventPrioritization(const MutexAutoLock& aProofOfLock) {} + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t size = mQueue.ShallowSizeOfExcludingThis(aMallocSizeOf); + size += mDispatchTimes.ShallowSizeOfExcludingThis(aMallocSizeOf); + return size; + } + + private: + mozilla::Queue, ItemsPerPage> mQueue; + // This queue is only populated when the profiler is turned on. + mozilla::Queue mDispatchTimes; + TimeDuration mLastEventDelay; + // This indicates PutEvent forwards runnables to the TaskController. This + // should be true for the top level event queue on the main thread. + bool mForwardToTC; +}; + +} // namespace detail + +class EventQueue final : public mozilla::detail::EventQueueInternal<16> { + public: + explicit EventQueue(bool aForwardToTC = false) + : mozilla::detail::EventQueueInternal<16>(aForwardToTC) {} +}; + +template +class EventQueueSized final + : public mozilla::detail::EventQueueInternal { + public: + explicit EventQueueSized(bool aForwardToTC = false) + : mozilla::detail::EventQueueInternal(aForwardToTC) {} +}; + +} // namespace mozilla + +#endif // mozilla_EventQueue_h diff --git a/xpcom/threads/EventTargetCapability.h b/xpcom/threads/EventTargetCapability.h new file mode 100644 index 0000000000..0cd85a7523 --- /dev/null +++ b/xpcom/threads/EventTargetCapability.h @@ -0,0 +1,95 @@ +/* -*- 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/. */ + +#ifndef XPCOM_THREADS_EVENTTARGETCAPABILITY_H_ +#define XPCOM_THREADS_EVENTTARGETCAPABILITY_H_ + +#include "mozilla/ThreadSafety.h" +#include "nsIEventTarget.h" + +namespace mozilla { + +// A helper to ensure that data access and function usage only take place on a +// specific nsIEventTarget. +// +// This class works with Clang's thread safety analysis so that static analysis +// can ensure AssertOnCurrentThread is called before using guarded data and +// functions. +// +// This means using the class is similar to calling +// `MOZ_ASSERT(mTarget->IsOnCurrentThread())` +// prior to accessing things we expect to be on `mTarget`. However, using this +// helper has the added benefit that static analysis will warn you if you +// fail to assert prior to usage. +// +// The following is a basic example of a class using this to ensure +// a data member is only accessed on a specific target. +// +// class SomeMediaHandlerThing { +// public: +// SomeMediaHandlerThing(nsIEventTarget* aTarget) : mTargetCapability(aTarget) +// {} +// +// void UpdateMediaState() { +// mTargetCapability.Dispatch( +// NS_NewRunnableFunction("UpdateMediaState", [this] { +// mTargetCapability.AssertOnCurrentThread(); +// IncreaseMediaCount(); +// })); +// } +// +// private: +// void IncreaseMediaCount() MOZ_REQUIRES(mTargetCapability) { mMediaCount += +// 1; } +// +// uint32_t mMediaCount MOZ_GUARDED_BY(mTargetCapability) = 0; +// EventTargetCapability mTargetCapability; +// }; +// +// NOTE: If you need a thread-safety capability for specifically the main +// thread, the static `mozilla::sMainThreadCapability` capability exists, and +// can be asserted using `AssertIsOnMainThread()`. + +template +class MOZ_CAPABILITY("event target") EventTargetCapability final { + static_assert(std::is_base_of_v, + "T must derive from nsIEventTarget"); + + public: + explicit EventTargetCapability(T* aTarget) : mTarget(aTarget) { + MOZ_ASSERT(mTarget, "mTarget should be non-null"); + } + ~EventTargetCapability() = default; + + EventTargetCapability(const EventTargetCapability&) = default; + EventTargetCapability(EventTargetCapability&&) = default; + EventTargetCapability& operator=(const EventTargetCapability&) = default; + EventTargetCapability& operator=(EventTargetCapability&&) = default; + + void AssertOnCurrentThread() const MOZ_ASSERT_CAPABILITY(this) { + MOZ_ASSERT(IsOnCurrentThread()); + } + + // Allow users to check if we're on the same thread as the event target. + bool IsOnCurrentThread() const { return mTarget->IsOnCurrentThread(); } + + // Allow users to get the event target, so classes don't have to store the + // target as a separate member to use it. + T* GetEventTarget() const { return mTarget; } + + // Helper to simplify dispatching to mTarget. + nsresult Dispatch(already_AddRefed aRunnable, + uint32_t aFlags = NS_DISPATCH_NORMAL) const { + return mTarget->Dispatch(std::move(aRunnable), aFlags); + } + + private: + RefPtr mTarget; +}; + +} // namespace mozilla + +#endif // XPCOM_THREADS_EVENTTARGETCAPABILITY_H_ diff --git a/xpcom/threads/IdlePeriodState.cpp b/xpcom/threads/IdlePeriodState.cpp new file mode 100644 index 0000000000..ca7f15321f --- /dev/null +++ b/xpcom/threads/IdlePeriodState.cpp @@ -0,0 +1,255 @@ +/* -*- 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/AppShutdown.h" +#include "mozilla/IdlePeriodState.h" +#include "mozilla/StaticPrefs_idle_period.h" +#include "mozilla/ipc/IdleSchedulerChild.h" +#include "nsIIdlePeriod.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "nsXULAppAPI.h" + +static uint64_t sIdleRequestCounter = 0; + +namespace mozilla { + +IdlePeriodState::IdlePeriodState(already_AddRefed&& aIdlePeriod) + : mIdlePeriod(aIdlePeriod) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); +} + +IdlePeriodState::~IdlePeriodState() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + if (mIdleScheduler) { + mIdleScheduler->Disconnect(); + } +} + +size_t IdlePeriodState::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mIdlePeriod) { + n += aMallocSizeOf(mIdlePeriod); + } + + return n; +} + +void IdlePeriodState::FlagNotIdle() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + EnsureIsActive(); + if (mIdleToken && mIdleToken < TimeStamp::Now()) { + ClearIdleToken(); + } +} + +void IdlePeriodState::RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mHasPendingEventsPromisedIdleEvent); + EnsureIsPaused(aProofOfUnlock); + ClearIdleToken(); +} + +TimeStamp IdlePeriodState::GetIdleDeadlineInternal( + bool aIsPeek, const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + bool shuttingDown; + TimeStamp localIdleDeadline = + GetLocalIdleDeadline(shuttingDown, aProofOfUnlock); + if (!localIdleDeadline) { + if (!aIsPeek) { + EnsureIsPaused(aProofOfUnlock); + ClearIdleToken(); + } + return TimeStamp(); + } + + TimeStamp idleDeadline = + mHasPendingEventsPromisedIdleEvent || shuttingDown + ? localIdleDeadline + : GetIdleToken(localIdleDeadline, aProofOfUnlock); + if (!idleDeadline) { + if (!aIsPeek) { + EnsureIsPaused(aProofOfUnlock); + + // Don't call ClearIdleToken() here, since we may have a pending + // request already. + // + // RequestIdleToken can do all sorts of IPC stuff that might + // take mutexes. This is one reason why we need the + // MutexAutoUnlock reference! + RequestIdleToken(localIdleDeadline); + } + return TimeStamp(); + } + + if (!aIsPeek) { + EnsureIsActive(); + } + return idleDeadline; +} + +TimeStamp IdlePeriodState::GetLocalIdleDeadline( + bool& aShuttingDown, const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + // If we are shutting down, we won't honor the idle period, and we will + // always process idle runnables. This will ensure that the idle queue + // gets exhausted at shutdown time to prevent intermittently leaking + // some runnables inside that queue and even worse potentially leaving + // some important cleanup work unfinished. + if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMShutdownThreads) || + nsThreadManager::get().GetCurrentThread()->ShuttingDown()) { + aShuttingDown = true; + return TimeStamp::Now(); + } + + aShuttingDown = false; + TimeStamp idleDeadline; + // This GetIdlePeriodHint() call is the reason we need a MutexAutoUnlock here. + mIdlePeriod->GetIdlePeriodHint(&idleDeadline); + + // If HasPendingEvents() has been called and it has returned true because of + // pending idle events, there is a risk that we may decide here that we aren't + // idle and return null, in which case HasPendingEvents() has effectively + // lied. Since we can't go back and fix the past, we have to adjust what we + // do here and forcefully pick the idle queue task here. Note that this means + // that we are choosing to run a task from the idle queue when we would + // normally decide that we aren't in an idle period, but this can only happen + // if we fall out of the idle period in between the call to HasPendingEvents() + // and here, which should hopefully be quite rare. We are effectively + // choosing to prioritize the sanity of our API semantics over the optimal + // scheduling. + if (!mHasPendingEventsPromisedIdleEvent && + (!idleDeadline || idleDeadline < TimeStamp::Now())) { + return TimeStamp(); + } + if (mHasPendingEventsPromisedIdleEvent && !idleDeadline) { + // If HasPendingEvents() has been called and it has returned true, but we're + // no longer in the idle period, we must return a valid timestamp to pretend + // that we are still in the idle period. + return TimeStamp::Now(); + } + return idleDeadline; +} + +TimeStamp IdlePeriodState::GetIdleToken(TimeStamp aLocalIdlePeriodHint, + const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + if (!ShouldGetIdleToken()) { + return aLocalIdlePeriodHint; + } + + if (mIdleToken) { + TimeStamp now = TimeStamp::Now(); + if (mIdleToken < now) { + ClearIdleToken(); + return mIdleToken; + } + return mIdleToken < aLocalIdlePeriodHint ? mIdleToken + : aLocalIdlePeriodHint; + } + return TimeStamp(); +} + +void IdlePeriodState::RequestIdleToken(TimeStamp aLocalIdlePeriodHint) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mActive); + + if (!mIdleSchedulerInitialized) { + mIdleSchedulerInitialized = true; + if (ShouldGetIdleToken()) { + // For now cross-process idle scheduler is supported only on the main + // threads of the child processes. + mIdleScheduler = ipc::IdleSchedulerChild::GetMainThreadIdleScheduler(); + if (mIdleScheduler) { + mIdleScheduler->Init(this); + } + } + } + + if (mIdleScheduler && !mIdleRequestId) { + TimeStamp now = TimeStamp::Now(); + if (aLocalIdlePeriodHint <= now) { + return; + } + + mIdleRequestId = ++sIdleRequestCounter; + mIdleScheduler->SendRequestIdleTime(mIdleRequestId, + aLocalIdlePeriodHint - now); + } +} + +void IdlePeriodState::SetIdleToken(uint64_t aId, TimeDuration aDuration) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + // We check the request ID. It's possible that the server may be granting a + // an ealier request that the client has since cancelled and re-requested. + if (mIdleRequestId == aId) { + mIdleToken = TimeStamp::Now() + aDuration; + } +} + +void IdlePeriodState::SetActive() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(!mActive); + if (mIdleScheduler) { + mIdleScheduler->SetActive(); + } + mActive = true; +} + +void IdlePeriodState::SetPaused(const MutexAutoUnlock& aProofOfUnlock) { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + MOZ_ASSERT(mActive); + if (mIdleScheduler && mIdleScheduler->SetPaused()) { + // We may have gotten a free cpu core for running idle tasks. + // We don't try to catch the case when there are prioritized processes + // running. + + // This SendSchedule call is why we need the MutexAutoUnlock here, because + // IPC can do weird things with mutexes. + mIdleScheduler->SendSchedule(); + } + mActive = false; +} + +void IdlePeriodState::ClearIdleToken() { + MOZ_ASSERT(NS_IsMainThread(), + "Why are we touching idle state off the main thread?"); + + if (mIdleRequestId) { + if (mIdleScheduler) { + // This SendIdleTimeUsed call is why we need to not be holding + // any locks here, because IPC can do weird things with mutexes. + // Ideally we'd have a MutexAutoUnlock& reference here, but some + // callers end up here while just not holding any locks at all. + mIdleScheduler->SendIdleTimeUsed(mIdleRequestId); + } + mIdleRequestId = 0; + mIdleToken = TimeStamp(); + } +} + +bool IdlePeriodState::ShouldGetIdleToken() { + return StaticPrefs::idle_period_cross_process_scheduling() && + XRE_IsContentProcess(); +} +} // namespace mozilla diff --git a/xpcom/threads/IdlePeriodState.h b/xpcom/threads/IdlePeriodState.h new file mode 100644 index 0000000000..d50fce64be --- /dev/null +++ b/xpcom/threads/IdlePeriodState.h @@ -0,0 +1,201 @@ +/* -*- 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/. */ + +#ifndef mozilla_IdlePeriodState_h +#define mozilla_IdlePeriodState_h + +/** + * A class for tracking the state of our idle period. This includes keeping + * track of both the state of our process-local idle period estimate and, for + * content processes, managing communication with the parent process for + * cross-pprocess idle detection. + */ + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "nsCOMPtr.h" + +#include + +class nsIIdlePeriod; + +namespace mozilla { +class TaskManager; +namespace ipc { +class IdleSchedulerChild; +} // namespace ipc + +class IdlePeriodState { + public: + explicit IdlePeriodState(already_AddRefed&& aIdlePeriod); + + ~IdlePeriodState(); + + // Integration with memory reporting. + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const; + + // Notification that whoever we are tracking idle state for has found a + // non-idle task to process. + // + // Must not be called while holding any locks. + void FlagNotIdle(); + + // Notification that whoever we are tracking idle state for has no more + // tasks (idle or not) to process. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + void RanOutOfTasks(const MutexAutoUnlock& aProofOfUnlock); + + // Notification that whoever we are tracking idle state has idle tasks that + // they are considering ready to run and that we should keep claiming they are + // ready to run until they call ForgetPendingTaskGuarantee(). + void EnforcePendingTaskGuarantee() { + mHasPendingEventsPromisedIdleEvent = true; + } + + // Notification that whoever we are tracking idle state for is done with our + // "we have an idle event ready to run" guarantee. When this happens, we can + // reset mHasPendingEventsPromisedIdleEvent to false, because we have + // fulfilled our contract. + void ForgetPendingTaskGuarantee() { + mHasPendingEventsPromisedIdleEvent = false; + } + + // Update our cached idle deadline so consumers can use it while holding + // locks. Consumers must ClearCachedIdleDeadline() once they are done. + void UpdateCachedIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) { + mCachedIdleDeadline = GetIdleDeadlineInternal(false, aProofOfUnlock); + } + + // If we have local idle deadline, but don't have an idle token, this will + // request such from the parent process when this is called in a child + // process. + void RequestIdleDeadlineIfNeeded(const MutexAutoUnlock& aProofOfUnlock) { + GetIdleDeadlineInternal(false, aProofOfUnlock); + } + + // Reset our cached idle deadline, so we stop allowing idle runnables to run. + void ClearCachedIdleDeadline() { mCachedIdleDeadline = TimeStamp(); } + + // Get the current cached idle deadline. This may return a null timestamp. + TimeStamp GetCachedIdleDeadline() { return mCachedIdleDeadline; } + + // Peek our current idle deadline into mCachedIdleDeadline. This can cause + // mCachedIdleDeadline to be a null timestamp (which means we are not idle + // right now). This method does not have any side-effects on our state, apart + // from guaranteeing that if it returns non-null then GetDeadlineForIdleTask + // will return non-null until ForgetPendingTaskGuarantee() is called, and its + // effects on mCachedIdleDeadline. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + void CachePeekedIdleDeadline(const MutexAutoUnlock& aProofOfUnlock) { + mCachedIdleDeadline = GetIdleDeadlineInternal(true, aProofOfUnlock); + } + + void SetIdleToken(uint64_t aId, TimeDuration aDuration); + + bool IsActive() { return mActive; } + + protected: + void EnsureIsActive() { + if (!mActive) { + SetActive(); + } + } + + void EnsureIsPaused(const MutexAutoUnlock& aProofOfUnlock) { + if (mActive) { + SetPaused(aProofOfUnlock); + } + } + + // Returns a null TimeStamp if we're not in the idle period. + TimeStamp GetLocalIdleDeadline(bool& aShuttingDown, + const MutexAutoUnlock& aProofOfUnlock); + + // Gets the idle token, which is the end time of the idle period. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + TimeStamp GetIdleToken(TimeStamp aLocalIdlePeriodHint, + const MutexAutoUnlock& aProofOfUnlock); + + // In case of child processes, requests idle time from the cross-process + // idle scheduler. + void RequestIdleToken(TimeStamp aLocalIdlePeriodHint); + + // Mark that we don't have idle time to use, nor are expecting to get an idle + // token from the idle scheduler. This must be called while not holding any + // locks, but some of the callers aren't holding locks to start with, so + // consumers just need to make sure they are not holding locks when they call + // this. + void ClearIdleToken(); + + // SetActive should be called when the event queue is running any type of + // tasks. + void SetActive(); + // SetPaused should be called once the event queue doesn't have more + // tasks to process, or is waiting for the idle token. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + void SetPaused(const MutexAutoUnlock& aProofOfUnlock); + + // Get or peek our idle deadline. When peeking, we generally don't change any + // of our internal state. When getting, we may request an idle token as + // needed. + // + // aProofOfUnlock is the proof that our caller unlocked its mutex. + TimeStamp GetIdleDeadlineInternal(bool aIsPeek, + const MutexAutoUnlock& aProofOfUnlock); + + // Whether we should be getting an idle token (i.e. are a content process + // and are using cross process idle scheduling). + bool ShouldGetIdleToken(); + + // Set to true if we have claimed we have a ready-to-run idle task when asked. + // In that case, we will ensure that we allow at least one task to run when + // someone tries to run a task, even if we have run out of idle period at that + // point. This ensures that we never fail to produce a task to run if we + // claim we have a task ready to run. + bool mHasPendingEventsPromisedIdleEvent = false; + + // mIdlePeriod keeps track of the current idle period. Calling + // mIdlePeriod->GetIdlePeriodHint() will give an estimate of when + // the current idle period will end. + nsCOMPtr mIdlePeriod; + + // If non-null, this timestamp represents the end time of the idle period. An + // idle period starts when we get the idle token from the parent process and + // ends when either there are no more things we want to run at idle priority + // or mIdleToken < TimeStamp::Now(), so we have reached our idle deadline. + TimeStamp mIdleToken; + + // The id of the last idle request to the cross-process idle scheduler. + uint64_t mIdleRequestId = 0; + + // If we're in a content process, we use mIdleScheduler to communicate with + // the parent process for purposes of cross-process idle tracking. + RefPtr mIdleScheduler; + + // Our cached idle deadline. This is set by UpdateCachedIdleDeadline() and + // cleared by ClearCachedIdleDeadline(). Consumers should do the former while + // not holding any locks, but may do the latter while holding locks. + TimeStamp mCachedIdleDeadline; + + // mIdleSchedulerInitialized is true if our mIdleScheduler has been + // initialized. It may be null even after initialiazation, in various + // situations. + bool mIdleSchedulerInitialized = false; + + // mActive is true when the PrioritizedEventQueue or TaskController we are + // associated with is running tasks. + bool mActive = true; +}; + +} // namespace mozilla + +#endif // mozilla_IdlePeriodState_h diff --git a/xpcom/threads/IdleTaskRunner.cpp b/xpcom/threads/IdleTaskRunner.cpp new file mode 100644 index 0000000000..8b467c54d6 --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.cpp @@ -0,0 +1,280 @@ +/* -*- 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 "IdleTaskRunner.h" +#include "mozilla/TaskCategory.h" +#include "mozilla/TaskController.h" +#include "nsRefreshDriver.h" + +namespace mozilla { + +already_AddRefed IdleTaskRunner::Create( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt) { + if (aMayStopProcessing && aMayStopProcessing()) { + return nullptr; + } + + RefPtr runner = new IdleTaskRunner( + aCallback, aRunnableName, aStartDelay, aMaxDelay, aMinimumUsefulBudget, + aRepeating, aMayStopProcessing, aRequestInterrupt); + runner->Schedule(false); // Initial scheduling shouldn't use idle dispatch. + return runner.forget(); +} + +class IdleTaskRunnerTask : public Task { + public: + explicit IdleTaskRunnerTask(IdleTaskRunner* aRunner) + : Task(true, EventQueuePriority::Idle), + mRunner(aRunner), + mRequestInterrupt(aRunner->mRequestInterrupt) { + SetManager(TaskController::Get()->GetIdleTaskManager()); + } + + bool Run() override { + if (mRunner) { + // IdleTaskRunner::Run can actually trigger the destruction of the + // IdleTaskRunner. Make sure it doesn't get destroyed before the method + // finished. + RefPtr runner(mRunner); + runner->Run(); + } + return true; + } + + void SetIdleDeadline(TimeStamp aDeadline) override { + if (mRunner) { + mRunner->SetIdleDeadline(aDeadline); + } + } + + void Cancel() { mRunner = nullptr; } + + bool GetName(nsACString& aName) override { + if (mRunner) { + aName.Assign(mRunner->GetName()); + } else { + aName.Assign("ExpiredIdleTaskRunner"); + } + return true; + } + + void RequestInterrupt(uint32_t aInterruptPriority) override { + if (mRequestInterrupt) { + mRequestInterrupt(aInterruptPriority); + } + } + + private: + IdleTaskRunner* mRunner; + + // Copied here and invoked even if there is no mRunner currently, to avoid + // race conditions checking mRunner when an interrupt is requested. + IdleTaskRunner::RequestInterruptCallbackType mRequestInterrupt; +}; + +IdleTaskRunner::IdleTaskRunner( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt) + : mCallback(aCallback), + mStartTime(TimeStamp::Now() + aStartDelay), + mMaxDelay(aMaxDelay), + mMinimumUsefulBudget(aMinimumUsefulBudget), + mRepeating(aRepeating), + mTimerActive(false), + mMayStopProcessing(aMayStopProcessing), + mRequestInterrupt(aRequestInterrupt), + mName(aRunnableName) {} + +void IdleTaskRunner::Run() { + if (!mCallback) { + return; + } + + // Deadline is null when called from timer or RunNextCollectorTimer rather + // than during idle time. + TimeStamp now = TimeStamp::Now(); + + // Note that if called from RunNextCollectorTimer, we may not have reached + // mStartTime yet. Pretend we are overdue for idle time. + bool overdueForIdle = mDeadline.IsNull(); + bool didRun = false; + bool allowIdleDispatch = false; + + if (mTask) { + // If we find ourselves here we should usually be running from this task, + // but there are exceptions. In any case we're doing the work now and don't + // need our task going forward unless we're re-scheduled. + nsRefreshDriver::CancelIdleTask(mTask); + // Extra safety, make sure a task can never have a dangling ptr. + mTask->Cancel(); + mTask = nullptr; + } + + if (overdueForIdle || ((now + mMinimumUsefulBudget) < mDeadline)) { + CancelTimer(); + didRun = mCallback(mDeadline); + // If we didn't do meaningful work, don't schedule using immediate + // idle dispatch, since that could lead to a loop until the idle + // period ends. + allowIdleDispatch = didRun; + } else if (now >= mDeadline) { + allowIdleDispatch = true; + } + + if (mCallback && (mRepeating || !didRun)) { + Schedule(allowIdleDispatch); + } else { + mCallback = nullptr; + } +} + +static void TimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr runner = static_cast(aClosure); + runner->Run(); +} + +void IdleTaskRunner::SetIdleDeadline(mozilla::TimeStamp aDeadline) { + mDeadline = aDeadline; +} + +void IdleTaskRunner::SetMinimumUsefulBudget(int64_t aMinimumUsefulBudget) { + mMinimumUsefulBudget = TimeDuration::FromMilliseconds(aMinimumUsefulBudget); +} + +void IdleTaskRunner::SetTimer(TimeDuration aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aTarget->IsOnCurrentThread()); + // aTarget is always the main thread event target provided from + // NS_DispatchToCurrentThreadQueue(). We ignore aTarget here to ensure that + // CollectorRunner always run specifically the main thread. + SetTimerInternal(aDelay); +} + +void IdleTaskRunner::Cancel() { + CancelTimer(); + mTimer = nullptr; + mScheduleTimer = nullptr; + mCallback = nullptr; +} + +static void ScheduleTimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr runnable = static_cast(aClosure); + runnable->Schedule(true); +} + +void IdleTaskRunner::Schedule(bool aAllowIdleDispatch) { + if (!mCallback) { + return; + } + + if (mMayStopProcessing && mMayStopProcessing()) { + Cancel(); + return; + } + + mDeadline = TimeStamp(); + + TimeStamp now = TimeStamp::Now(); + bool useRefreshDriver = false; + if (now >= mStartTime) { + // Detect whether the refresh driver is ticking by checking if + // GetIdleDeadlineHint returns its input parameter. + useRefreshDriver = + (nsRefreshDriver::GetIdleDeadlineHint( + now, nsRefreshDriver::IdleCheck::OnlyThisProcessRefreshDriver) != + now); + } else { + NS_WARNING_ASSERTION(!aAllowIdleDispatch, + "early callback, or time went backwards"); + } + + if (useRefreshDriver) { + if (!mTask) { + // If a task was already scheduled, no point rescheduling. + mTask = new IdleTaskRunnerTask(this); + // RefreshDriver is ticking, let it schedule the idle dispatch. + nsRefreshDriver::DispatchIdleTaskAfterTickUnlessExists(mTask); + } + // Ensure we get called at some point, even if RefreshDriver is stopped. + SetTimerInternal(mMaxDelay); + } else { + // RefreshDriver doesn't seem to be running. + if (aAllowIdleDispatch) { + SetTimerInternal(mMaxDelay); + if (!mTask) { + // If we have mTask we've already scheduled one, and the refresh driver + // shouldn't be running if we hit this code path. + mTask = new IdleTaskRunnerTask(this); + RefPtr task(mTask); + TaskController::Get()->AddTask(task.forget()); + } + } else { + if (!mScheduleTimer) { + mScheduleTimer = NS_NewTimer(); + if (!mScheduleTimer) { + return; + } + } else { + mScheduleTimer->Cancel(); + } + // We weren't allowed to do idle dispatch immediately, do it after a + // short timeout. (Or wait for our start time if we haven't started yet.) + uint32_t waitToSchedule = 16; /* ms */ + if (now < mStartTime) { + // + 1 to round milliseconds up to be sure to wait until after + // mStartTime. + waitToSchedule = (mStartTime - now).ToMilliseconds() + 1; + } + mScheduleTimer->InitWithNamedFuncCallback( + ScheduleTimedOut, this, waitToSchedule, + nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY, mName); + } + } +} + +IdleTaskRunner::~IdleTaskRunner() { CancelTimer(); } + +void IdleTaskRunner::CancelTimer() { + if (mTask) { + nsRefreshDriver::CancelIdleTask(mTask); + mTask->Cancel(); + mTask = nullptr; + } + if (mTimer) { + mTimer->Cancel(); + } + if (mScheduleTimer) { + mScheduleTimer->Cancel(); + } + mTimerActive = false; +} + +void IdleTaskRunner::SetTimerInternal(TimeDuration aDelay) { + if (mTimerActive) { + return; + } + + if (!mTimer) { + mTimer = NS_NewTimer(); + } else { + mTimer->Cancel(); + } + + if (mTimer) { + mTimer->InitWithNamedFuncCallback(TimedOut, this, aDelay.ToMilliseconds(), + nsITimer::TYPE_ONE_SHOT, mName); + mTimerActive = true; + } +} + +} // end of namespace mozilla diff --git a/xpcom/threads/IdleTaskRunner.h b/xpcom/threads/IdleTaskRunner.h new file mode 100644 index 0000000000..4052021f08 --- /dev/null +++ b/xpcom/threads/IdleTaskRunner.h @@ -0,0 +1,122 @@ +/* -*- 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/. */ + +#ifndef IdleTaskRunner_h +#define IdleTaskRunner_h + +#include "mozilla/TimeStamp.h" +#include "nsIEventTarget.h" +#include "nsISupports.h" +#include "nsITimer.h" +#include + +namespace mozilla { + +class IdleTaskRunnerTask; + +// A general purpose repeating callback runner (it can be configured to a +// one-time runner, too.) If it is running repeatedly, one has to either +// explicitly Cancel() the runner or have MayStopProcessing() callback return +// true to completely remove the runner. +class IdleTaskRunner { + public: + friend class IdleTaskRunnerTask; + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(IdleTaskRunner) + + // Return true if some meaningful work was done. + using CallbackType = std::function; + + // A callback for "stop processing" decision. Return true to + // stop processing. This can be an alternative to Cancel() or + // work together in different way. + using MayStopProcessingCallbackType = std::function; + + // A callback to be invoked when an interrupt is requested + // (eg during an idle activity when the user presses a key.) + // The callback takes an "interrupt priority" value as its + // sole parameter. + using RequestInterruptCallbackType = std::function; + + public: + // An IdleTaskRunner has (up to) three phases: + // + // - (duration aStartDelay) waiting to run (aStartDelay can be zero) + // + // - (duration aMaxDelay) attempting to find a long enough amount of idle + // time, at least aMinimumUsefulBudget + // + // - overdue for idle time, run as soon as possible + // + // If aRepeating is true, then aStartDelay applies only to the first run; the + // second run will attempt to run in the first idle slice that is long + // enough. + // + // All durations are in milliseconds. + // + static already_AddRefed Create( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt = nullptr); + + void Run(); + + // (Used by the task triggering code.) Record the end of the current idle + // period, or null if not running during idle time. + void SetIdleDeadline(mozilla::TimeStamp aDeadline); + + void SetTimer(TimeDuration aDelay, nsIEventTarget* aTarget); + + // Update the minimum idle time that this callback would be invoked for. + void SetMinimumUsefulBudget(int64_t aMinimumUsefulBudget); + + void Cancel(); + + void Schedule(bool aAllowIdleDispatch); + + const char* GetName() { return mName; } + + private: + explicit IdleTaskRunner( + const CallbackType& aCallback, const char* aRunnableName, + TimeDuration aStartDelay, TimeDuration aMaxDelay, + TimeDuration aMinimumUsefulBudget, bool aRepeating, + const MayStopProcessingCallbackType& aMayStopProcessing, + const RequestInterruptCallbackType& aRequestInterrupt); + ~IdleTaskRunner(); + void CancelTimer(); + void SetTimerInternal(TimeDuration aDelay); + + nsCOMPtr mTimer; + nsCOMPtr mScheduleTimer; + CallbackType mCallback; + + // Do not run until this time. + const mozilla::TimeStamp mStartTime; + + // Wait this long for idle time before giving up and running a non-idle + // callback. + TimeDuration mMaxDelay; + + // If running during idle time, the expected end of the current idle period. + // The null timestamp when the run is triggered by aMaxDelay instead of idle. + TimeStamp mDeadline; + + // The least duration worth calling the callback for during idle time. + TimeDuration mMinimumUsefulBudget; + + bool mRepeating; + bool mTimerActive; + MayStopProcessingCallbackType mMayStopProcessing; + RequestInterruptCallbackType mRequestInterrupt; + const char* mName; + RefPtr mTask; +}; + +} // end of namespace mozilla. + +#endif diff --git a/xpcom/threads/InputTaskManager.cpp b/xpcom/threads/InputTaskManager.cpp new file mode 100644 index 0000000000..42b2814290 --- /dev/null +++ b/xpcom/threads/InputTaskManager.cpp @@ -0,0 +1,156 @@ +/* -*- 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 "InputTaskManager.h" +#include "VsyncTaskManager.h" +#include "nsRefreshDriver.h" + +namespace mozilla { + +StaticRefPtr InputTaskManager::gInputTaskManager; + +void InputTaskManager::EnableInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_DISABLED); + mInputQueueState = STATE_ENABLED; +} + +void InputTaskManager::FlushInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_ENABLED || + mInputQueueState == STATE_SUSPEND); + mInputQueueState = + mInputQueueState == STATE_ENABLED ? STATE_FLUSHING : STATE_SUSPEND; +} + +void InputTaskManager::SuspendInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_ENABLED || + mInputQueueState == STATE_FLUSHING); + mInputQueueState = STATE_SUSPEND; +} + +void InputTaskManager::ResumeInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInputQueueState == STATE_SUSPEND); + mInputQueueState = STATE_ENABLED; +} + +int32_t InputTaskManager::GetPriorityModifierForEventLoopTurn( + const MutexAutoLock& aProofOfLock) { + // When the state is disabled, the input task that we have is + // very likely SuspendInputEventQueue, so here we also use + // normal priority as ResumeInputEventQueue, FlushInputEventQueue and + // SetInputEventQueueEnabled all uses normal priority, to + // ensure the ordering is correct. + if (State() == InputTaskManager::STATE_DISABLED) { + return static_cast(EventQueuePriority::Normal) - + static_cast(EventQueuePriority::InputHigh); + } + + return GetPriorityModifierForEventLoopTurnForStrictVsyncAlignment(); +} + +void InputTaskManager::WillRunTask() { + TaskManager::WillRunTask(); + mInputPriorityController.WillRunTask(); +} + +int32_t +InputTaskManager::GetPriorityModifierForEventLoopTurnForStrictVsyncAlignment() { + MOZ_ASSERT(!IsSuspended()); + + size_t inputCount = PendingTaskCount(); + if (inputCount > 0 && + mInputPriorityController.ShouldUseHighestPriority(this)) { + return static_cast(EventQueuePriority::InputHighest) - + static_cast(EventQueuePriority::InputHigh); + } + + if (State() == STATE_FLUSHING || + nsRefreshDriver::GetNextTickHint().isNothing()) { + return 0; + } + + return static_cast(EventQueuePriority::InputLow) - + static_cast(EventQueuePriority::InputHigh); +} + +InputTaskManager::InputPriorityController::InputPriorityController() + : mInputVsyncState(InputVsyncState::NoPendingVsync) {} + +bool InputTaskManager::InputPriorityController::ShouldUseHighestPriority( + InputTaskManager* aInputTaskManager) { + if (mInputVsyncState == InputVsyncState::HasPendingVsync) { + return true; + } + + if (mInputVsyncState == InputVsyncState::RunVsync) { + return false; + } + + if (mInputVsyncState == InputVsyncState::NoPendingVsync && + VsyncTaskManager::Get()->PendingTaskCount()) { + EnterPendingVsyncState(aInputTaskManager->PendingTaskCount()); + return true; + } + + return false; +} + +void InputTaskManager::InputPriorityController::EnterPendingVsyncState( + uint32_t aNumPendingTasks) { + MOZ_ASSERT(mInputVsyncState == InputVsyncState::NoPendingVsync); + + mInputVsyncState = InputVsyncState::HasPendingVsync; + mMaxInputTasksToRun = aNumPendingTasks; + mRunInputStartTime = TimeStamp::Now(); +} + +void InputTaskManager::InputPriorityController::WillRunVsync() { + if (mInputVsyncState == InputVsyncState::RunVsync || + mInputVsyncState == InputVsyncState::HasPendingVsync) { + LeavePendingVsyncState(false); + } +} + +void InputTaskManager::InputPriorityController::LeavePendingVsyncState( + bool aRunVsync) { + if (aRunVsync) { + MOZ_ASSERT(mInputVsyncState == InputVsyncState::HasPendingVsync); + mInputVsyncState = InputVsyncState::RunVsync; + } else { + mInputVsyncState = InputVsyncState::NoPendingVsync; + } + + mMaxInputTasksToRun = 0; +} + +void InputTaskManager::InputPriorityController::WillRunTask() { + switch (mInputVsyncState) { + case InputVsyncState::NoPendingVsync: + return; + case InputVsyncState::HasPendingVsync: + MOZ_ASSERT(mMaxInputTasksToRun > 0); + --mMaxInputTasksToRun; + if (!mMaxInputTasksToRun || + TimeStamp::Now() - mRunInputStartTime >= + TimeDuration::FromMilliseconds( + StaticPrefs::dom_input_event_queue_duration_max())) { + LeavePendingVsyncState(true); + } + return; + default: + MOZ_DIAGNOSTIC_ASSERT( + false, "Shouldn't run this input task when we suppose to run vsync"); + return; + } +} + +// static +void InputTaskManager::Init() { gInputTaskManager = new InputTaskManager(); } + +} // namespace mozilla diff --git a/xpcom/threads/InputTaskManager.h b/xpcom/threads/InputTaskManager.h new file mode 100644 index 0000000000..2f920a31ae --- /dev/null +++ b/xpcom/threads/InputTaskManager.h @@ -0,0 +1,141 @@ +/* -*- 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/. */ + +#ifndef mozilla_InputTaskManager_h +#define mozilla_InputTaskManager_h + +#include "nsTArray.h" +#include "nsXULAppAPI.h" +#include "TaskController.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/StaticPrefs_dom.h" + +namespace mozilla { + +class InputTaskManager : public TaskManager { + public: + int32_t GetPriorityModifierForEventLoopTurn( + const MutexAutoLock& aProofOfLock) final; + void WillRunTask() final; + + enum InputEventQueueState { + STATE_DISABLED, + STATE_FLUSHING, + STATE_SUSPEND, + STATE_ENABLED + }; + + void EnableInputEventPrioritization(); + void FlushInputEventPrioritization(); + void SuspendInputEventPrioritization(); + void ResumeInputEventPrioritization(); + + InputEventQueueState State() { return mInputQueueState; } + + void SetState(InputEventQueueState aState) { mInputQueueState = aState; } + + static InputTaskManager* Get() { return gInputTaskManager.get(); } + static void Cleanup() { gInputTaskManager = nullptr; } + static void Init(); + + bool IsSuspended(const MutexAutoLock& aProofOfLock) override { + MOZ_ASSERT(NS_IsMainThread()); + return mInputQueueState == STATE_SUSPEND || mSuspensionLevel > 0; + } + + bool IsSuspended() { + MOZ_ASSERT(NS_IsMainThread()); + return mSuspensionLevel > 0; + } + + void IncSuspensionLevel() { + MOZ_ASSERT(NS_IsMainThread()); + ++mSuspensionLevel; + } + + void DecSuspensionLevel() { + MOZ_ASSERT(NS_IsMainThread()); + --mSuspensionLevel; + } + + static bool CanSuspendInputEvent() { + // Ensure it's content process because InputTaskManager only + // works in e10s. + // + // Input tasks will have nullptr as their task manager when the + // event queue state is STATE_DISABLED, so we can't suspend + // input events. + return XRE_IsContentProcess() && + StaticPrefs::dom_input_events_canSuspendInBCG_enabled() && + InputTaskManager::Get()->State() != + InputEventQueueState::STATE_DISABLED; + } + + void NotifyVsync() { mInputPriorityController.WillRunVsync(); } + + private: + InputTaskManager() : mInputQueueState(STATE_DISABLED) {} + + class InputPriorityController { + public: + InputPriorityController(); + // Determines whether we should use the highest input priority for input + // tasks + bool ShouldUseHighestPriority(InputTaskManager*); + + void WillRunVsync(); + + // Gets called when a input task is going to run; If the current + // input vsync state is `HasPendingVsync`, determines whether we + // should continue running input tasks or leave the `HasPendingVsync` state + // based on + // 1. Whether we still have time to process input tasks + // 2. Whether we have processed the max number of tasks that + // we should process. + void WillRunTask(); + + private: + // Used to represents the relationship between Input and Vsync. + // + // HasPendingVsync: There are pending vsync tasks and we are using + // InputHighest priority for inputs. + // NoPendingVsync: No pending vsync tasks and no need to use InputHighest + // priority. + // RunVsync: Finished running input tasks and the vsync task + // should be run. + enum class InputVsyncState { + HasPendingVsync, + NoPendingVsync, + RunVsync, + }; + + void EnterPendingVsyncState(uint32_t aNumPendingTasks); + void LeavePendingVsyncState(bool aRunVsync); + + // Stores the number of pending input tasks when we enter the + // InputVsyncState::HasPendingVsync state. + uint32_t mMaxInputTasksToRun = 0; + + InputVsyncState mInputVsyncState; + + TimeStamp mRunInputStartTime; + }; + + int32_t GetPriorityModifierForEventLoopTurnForStrictVsyncAlignment(); + + Atomic mInputQueueState; + + static StaticRefPtr gInputTaskManager; + + // Number of BCGs have asked InputTaskManager to suspend input events + uint32_t mSuspensionLevel = 0; + + InputPriorityController mInputPriorityController; +}; + +} // namespace mozilla + +#endif // mozilla_InputTaskManager_h diff --git a/xpcom/threads/LazyIdleThread.cpp b/xpcom/threads/LazyIdleThread.cpp new file mode 100644 index 0000000000..4187fcc4ff --- /dev/null +++ b/xpcom/threads/LazyIdleThread.cpp @@ -0,0 +1,126 @@ +/* -*- 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 "LazyIdleThread.h" + +#include "nsIObserverService.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" + +#ifdef DEBUG +# define ASSERT_OWNING_THREAD() \ + do { \ + MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread()); \ + } while (0) +#else +# define ASSERT_OWNING_THREAD() /* nothing */ +#endif + +namespace mozilla { + +LazyIdleThread::LazyIdleThread(uint32_t aIdleTimeoutMS, const char* aName, + ShutdownMethod aShutdownMethod) + : mOwningEventTarget(GetCurrentSerialEventTarget()), + mThreadPool(new nsThreadPool()), + mTaskQueue(TaskQueue::Create(do_AddRef(mThreadPool), aName)) { + // Configure the threadpool to host a single thread. It will be responsible + // for managing the thread's lifetime. + MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetThreadLimit(1)); + MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetIdleThreadLimit(1)); + MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetIdleThreadTimeout(aIdleTimeoutMS)); + MOZ_ALWAYS_SUCCEEDS(mThreadPool->SetName(nsDependentCString(aName))); + + if (aShutdownMethod == ShutdownMethod::AutomaticShutdown && + NS_IsMainThread()) { + if (nsCOMPtr obs = + do_GetService(NS_OBSERVERSERVICE_CONTRACTID)) { + MOZ_ALWAYS_SUCCEEDS( + obs->AddObserver(this, "xpcom-shutdown-threads", false)); + } + } +} + +static void LazyIdleThreadShutdown(nsThreadPool* aThreadPool, + TaskQueue* aTaskQueue) { + aTaskQueue->BeginShutdown(); + aTaskQueue->AwaitShutdownAndIdle(); + aThreadPool->Shutdown(); +} + +LazyIdleThread::~LazyIdleThread() { + if (!mShutdown) { + mOwningEventTarget->Dispatch(NS_NewRunnableFunction( + "LazyIdleThread::~LazyIdleThread", + [threadPool = mThreadPool, taskQueue = mTaskQueue] { + LazyIdleThreadShutdown(threadPool, taskQueue); + })); + } +} + +void LazyIdleThread::Shutdown() { + ASSERT_OWNING_THREAD(); + + if (!mShutdown) { + mShutdown = true; + LazyIdleThreadShutdown(mThreadPool, mTaskQueue); + } +} + +nsresult LazyIdleThread::SetListener(nsIThreadPoolListener* aListener) { + return mThreadPool->SetListener(aListener); +} + +NS_IMPL_ISUPPORTS(LazyIdleThread, nsIEventTarget, nsISerialEventTarget, + nsIObserver) + +NS_IMETHODIMP +LazyIdleThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + nsCOMPtr event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +LazyIdleThread::Dispatch(already_AddRefed aEvent, + uint32_t aFlags) { + return mTaskQueue->Dispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +LazyIdleThread::DelayedDispatch(already_AddRefed, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +LazyIdleThread::IsOnCurrentThread(bool* aIsOnCurrentThread) { + return mTaskQueue->IsOnCurrentThread(aIsOnCurrentThread); +} + +NS_IMETHODIMP_(bool) +LazyIdleThread::IsOnCurrentThreadInfallible() { + return mTaskQueue->IsOnCurrentThreadInfallible(); +} + +NS_IMETHODIMP +LazyIdleThread::Observe(nsISupports* /* aSubject */, const char* aTopic, + const char16_t* /* aData */) { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + MOZ_ASSERT(!strcmp("xpcom-shutdown-threads", aTopic), "Bad topic!"); + + Shutdown(); + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/threads/LazyIdleThread.h b/xpcom/threads/LazyIdleThread.h new file mode 100644 index 0000000000..8a720bc69b --- /dev/null +++ b/xpcom/threads/LazyIdleThread.h @@ -0,0 +1,93 @@ +/* -*- 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/. */ + +#ifndef mozilla_lazyidlethread_h__ +#define mozilla_lazyidlethread_h__ + +#ifndef MOZILLA_INTERNAL_API +# error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)." +#endif + +#include "mozilla/TaskQueue.h" +#include "nsIObserver.h" +#include "nsThreadPool.h" + +namespace mozilla { + +/** + * This class provides a basic event target that creates its thread lazily and + * destroys its thread after a period of inactivity. It may be created and used + * on any thread but it may only be shut down from the thread on which it is + * created. + */ +class LazyIdleThread final : public nsISerialEventTarget, public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSIOBSERVER + + /** + * If AutomaticShutdown is specified, and the LazyIdleThread is created on the + * main thread, Shutdown() will automatically be called during + * xpcom-shutdown-threads. + */ + enum ShutdownMethod { AutomaticShutdown = 0, ManualShutdown }; + + /** + * Create a new LazyIdleThread that will destroy its thread after the given + * number of milliseconds. + */ + LazyIdleThread(uint32_t aIdleTimeoutMS, const char* aName, + ShutdownMethod aShutdownMethod = AutomaticShutdown); + + /** + * Shuts down the LazyIdleThread, waiting for any pending work to complete. + * Must be called from mOwningEventTarget. + */ + void Shutdown(); + + /** + * Register a nsIThreadPoolListener on the underlying threadpool to track the + * thread as it is created/destroyed. + */ + nsresult SetListener(nsIThreadPoolListener* aListener); + + private: + /** + * Asynchronously shuts down the LazyIdleThread on mOwningEventTarget. + */ + ~LazyIdleThread(); + + /** + * The thread which created this LazyIdleThread and is responsible for + * shutting it down. + */ + const nsCOMPtr mOwningEventTarget; + + /** + * The single-thread backing threadpool which provides the actual threads used + * by LazyIdleThread, and implements the timeout. + */ + const RefPtr mThreadPool; + + /** + * The serial event target providing a `nsISerialEventTarget` implementation + * when on the LazyIdleThread. + */ + const RefPtr mTaskQueue; + + /** + * Only safe to access on the owning thread or in the destructor (as no other + * threads have access then). If `true`, means the LazyIdleThread has already + * been shut down, so it does not need to be shut down asynchronously from the + * destructor. + */ + bool mShutdown = false; +}; + +} // namespace mozilla + +#endif // mozilla_lazyidlethread_h__ diff --git a/xpcom/threads/LeakRefPtr.h b/xpcom/threads/LeakRefPtr.h new file mode 100644 index 0000000000..ee260dad7e --- /dev/null +++ b/xpcom/threads/LeakRefPtr.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +/* Smart pointer which leaks its owning refcounted object by default. */ + +#ifndef LeakRefPtr_h +#define LeakRefPtr_h + +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { + +/** + * Instance of this class behaves like a raw pointer which leaks the + * resource it's owning if not explicitly released. + */ +template +class LeakRefPtr { + public: + explicit LeakRefPtr(already_AddRefed&& aPtr) : mRawPtr(aPtr.take()) {} + + explicit operator bool() const { return !!mRawPtr; } + + LeakRefPtr& operator=(already_AddRefed&& aPtr) { + mRawPtr = aPtr.take(); + return *this; + } + + T* get() const { return mRawPtr; } + + already_AddRefed take() { + T* rawPtr = mRawPtr; + mRawPtr = nullptr; + return already_AddRefed(rawPtr); + } + + void release() { NS_RELEASE(mRawPtr); } + + private: + T* MOZ_OWNING_REF mRawPtr; +}; + +} // namespace mozilla + +#endif // LeakRefPtr_h diff --git a/xpcom/threads/MainThreadIdlePeriod.cpp b/xpcom/threads/MainThreadIdlePeriod.cpp new file mode 100644 index 0000000000..0a25647285 --- /dev/null +++ b/xpcom/threads/MainThreadIdlePeriod.cpp @@ -0,0 +1,78 @@ +/* -*- 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 "MainThreadIdlePeriod.h" + +#include "mozilla/Maybe.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_idle_period.h" +#include "mozilla/dom/Document.h" +#include "VRManagerChild.h" +#include "nsRefreshDriver.h" +#include "nsThreadUtils.h" + +// The amount of idle time (milliseconds) reserved for a long idle period. +static const double kLongIdlePeriodMS = 50.0; + +// The minimum amount of time (milliseconds) required for an idle period to be +// scheduled on the main thread. N.B. layout.idle_period.time_limit adds +// padding at the end of the idle period, which makes the point in time that we +// expect to become busy again be: +// now + idle_period.min + layout.idle_period.time_limit +// or during page load +// now + idle_period.during_page_load.min + layout.idle_period.time_limit + +static const uint32_t kMaxTimerThreadBound = 5; // milliseconds + +namespace mozilla { + +NS_IMETHODIMP +MainThreadIdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aIdleDeadline); + + TimeStamp now = TimeStamp::Now(); + TimeStamp currentGuess = + now + TimeDuration::FromMilliseconds(kLongIdlePeriodMS); + + currentGuess = nsRefreshDriver::GetIdleDeadlineHint( + currentGuess, nsRefreshDriver::IdleCheck::AllVsyncListeners); + if (XRE_IsContentProcess()) { + currentGuess = gfx::VRManagerChild::GetIdleDeadlineHint(currentGuess); + } + currentGuess = NS_GetTimerDeadlineHintOnCurrentThread(currentGuess, + kMaxTimerThreadBound); + + // If the idle period is too small, then just return a null time + // to indicate we are busy. Otherwise return the actual deadline. + // + // If we're in high frequency rate mode, idle.period.min isn't used but limit + // is 1. + TimeDuration minIdlePeriod = TimeDuration::FromMilliseconds( + nsRefreshDriver::IsInHighRateMode() ? 1 : StaticPrefs::idle_period_min()); + bool busySoon = currentGuess.IsNull() || + (now >= (currentGuess - minIdlePeriod)) || + currentGuess < mLastIdleDeadline; + + // During page load use higher minimum idle period. + if (!busySoon && XRE_IsContentProcess() && + mozilla::dom::Document::HasRecentlyStartedForegroundLoads()) { + TimeDuration minIdlePeriod = TimeDuration::FromMilliseconds( + StaticPrefs::idle_period_during_page_load_min()); + busySoon = (now >= (currentGuess - minIdlePeriod)); + } + + if (!busySoon) { + *aIdleDeadline = mLastIdleDeadline = currentGuess; + } + + return NS_OK; +} + +/* static */ +float MainThreadIdlePeriod::GetLongIdlePeriod() { return kLongIdlePeriodMS; } + +} // namespace mozilla diff --git a/xpcom/threads/MainThreadIdlePeriod.h b/xpcom/threads/MainThreadIdlePeriod.h new file mode 100644 index 0000000000..7e382f6771 --- /dev/null +++ b/xpcom/threads/MainThreadIdlePeriod.h @@ -0,0 +1,31 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_mainthreadidleperiod_h +#define mozilla_dom_mainthreadidleperiod_h + +#include "mozilla/TimeStamp.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +class MainThreadIdlePeriod final : public IdlePeriod { + public: + NS_DECL_NSIIDLEPERIOD + + MainThreadIdlePeriod() : mLastIdleDeadline(TimeStamp::Now()) {} + + static float GetLongIdlePeriod(); + + private: + virtual ~MainThreadIdlePeriod() = default; + + TimeStamp mLastIdleDeadline; +}; + +} // namespace mozilla + +#endif // mozilla_dom_mainthreadidleperiod_h diff --git a/xpcom/threads/MainThreadUtils.h b/xpcom/threads/MainThreadUtils.h new file mode 100644 index 0000000000..152805eb66 --- /dev/null +++ b/xpcom/threads/MainThreadUtils.h @@ -0,0 +1,60 @@ +/* -*- 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/. */ + +#ifndef MainThreadUtils_h_ +#define MainThreadUtils_h_ + +#include "mozilla/Assertions.h" +#include "mozilla/ThreadSafety.h" +#include "nscore.h" + +class nsIThread; + +/** + * Get a reference to the main thread. + * + * @param aResult + * The resulting nsIThread object. + */ +extern nsresult NS_GetMainThread(nsIThread** aResult); + +#ifdef MOZILLA_INTERNAL_API +bool NS_IsMainThreadTLSInitialized(); +extern "C" { +bool NS_IsMainThread(); +} + +namespace mozilla { + +/** + * A dummy static capability for the thread safety analysis which can be + * required by functions and members using `MOZ_REQUIRE(sMainThreadCapability)` + * and `MOZ_GUARDED_BY(sMainThreadCapability)` and asserted using + * `AssertIsOnMainThread()`. + * + * If you want a thread-safety-analysis capability for a non-main thread, + * consider using the `EventTargetCapability` type. + */ +class MOZ_CAPABILITY("main thread") MainThreadCapability final {}; +constexpr MainThreadCapability sMainThreadCapability; + +# ifdef DEBUG +void AssertIsOnMainThread() MOZ_ASSERT_CAPABILITY(sMainThreadCapability); +# else +inline void AssertIsOnMainThread() + MOZ_ASSERT_CAPABILITY(sMainThreadCapability) {} +# endif + +inline void ReleaseAssertIsOnMainThread() + MOZ_ASSERT_CAPABILITY(sMainThreadCapability) { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); +} + +} // namespace mozilla + +#endif + +#endif // MainThreadUtils_h_ diff --git a/xpcom/threads/Monitor.h b/xpcom/threads/Monitor.h new file mode 100644 index 0000000000..7bb91f9ad7 --- /dev/null +++ b/xpcom/threads/Monitor.h @@ -0,0 +1,316 @@ +/* -*- 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/. */ + +#ifndef mozilla_Monitor_h +#define mozilla_Monitor_h + +#include "mozilla/CondVar.h" +#include "mozilla/Mutex.h" + +namespace mozilla { + +/** + * Monitor provides a *non*-reentrant monitor: *not* a Java-style + * monitor. If your code needs support for reentrancy, use + * ReentrantMonitor instead. (Rarely should reentrancy be needed.) + * + * Instead of directly calling Monitor methods, it's safer and simpler + * to instead use the RAII wrappers MonitorAutoLock and + * MonitorAutoUnlock. + */ +class MOZ_CAPABILITY("monitor") Monitor { + public: + explicit Monitor(const char* aName) + : mMutex(aName), mCondVar(mMutex, "[Monitor.mCondVar]") {} + + ~Monitor() = default; + + void Lock() MOZ_CAPABILITY_ACQUIRE() { mMutex.Lock(); } + [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true) { + return mMutex.TryLock(); + } + void Unlock() MOZ_CAPABILITY_RELEASE() { mMutex.Unlock(); } + + void Wait() MOZ_REQUIRES(this) { mCondVar.Wait(); } + CVStatus Wait(TimeDuration aDuration) MOZ_REQUIRES(this) { + return mCondVar.Wait(aDuration); + } + + void Notify() { mCondVar.Notify(); } + void NotifyAll() { mCondVar.NotifyAll(); } + + void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this) { + mMutex.AssertCurrentThreadOwns(); + } + void AssertNotCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(!this) { + mMutex.AssertNotCurrentThreadOwns(); + } + + private: + Monitor() = delete; + Monitor(const Monitor&) = delete; + Monitor& operator=(const Monitor&) = delete; + + Mutex mMutex; + CondVar mCondVar; +}; + +/** + * MonitorSingleWriter + * + * Monitor where a single writer exists, so that reads from the same thread + * will not generate data races or consistency issues. + * + * When possible, use MonitorAutoLock/MonitorAutoUnlock to lock/unlock this + * monitor within a scope, instead of calling Lock/Unlock directly. + * + * This requires an object implementing Mutex's SingleWriterLockOwner, so + * we can do correct-thread checks. + */ + +class MonitorSingleWriter : public Monitor { + public: + // aOwner should be the object that contains the mutex, typically. We + // will use that object (which must have a lifetime the same or greater + // than this object) to verify that we're running on the correct thread, + // typically only in DEBUG builds + explicit MonitorSingleWriter(const char* aName, SingleWriterLockOwner* aOwner) + : Monitor(aName) +#ifdef DEBUG + , + mOwner(aOwner) +#endif + { + MOZ_COUNT_CTOR(MonitorSingleWriter); + MOZ_ASSERT(mOwner); + } + + MOZ_COUNTED_DTOR(MonitorSingleWriter) + + void AssertOnWritingThread() const MOZ_ASSERT_CAPABILITY(this) { + MOZ_ASSERT(mOwner->OnWritingThread()); + } + void AssertOnWritingThreadOrHeld() const MOZ_ASSERT_CAPABILITY(this) { +#ifdef DEBUG + if (!mOwner->OnWritingThread()) { + AssertCurrentThreadOwns(); + } +#endif + } + + private: +#ifdef DEBUG + SingleWriterLockOwner* mOwner MOZ_UNSAFE_REF( + "This is normally the object that contains the MonitorSingleWriter, so " + "we don't want to hold a reference to ourselves"); +#endif + + MonitorSingleWriter() = delete; + MonitorSingleWriter(const MonitorSingleWriter&) = delete; + MonitorSingleWriter& operator=(const MonitorSingleWriter&) = delete; +}; + +/** + * Lock the monitor for the lexical scope instances of this class are + * bound to (except for MonitorAutoUnlock in nested scopes). + * + * The monitor must be unlocked when instances of this class are + * created. + */ +namespace detail { +template +class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS BaseMonitorAutoLock { + public: + explicit BaseMonitorAutoLock(T& aMonitor) MOZ_CAPABILITY_ACQUIRE(aMonitor) + : mMonitor(&aMonitor) { + mMonitor->Lock(); + } + + ~BaseMonitorAutoLock() MOZ_CAPABILITY_RELEASE() { mMonitor->Unlock(); } + // It's very hard to mess up MonitorAutoLock lock(mMonitor); ... lock.Wait(). + // The only way you can fail to hold the lock when you call lock.Wait() is to + // use MonitorAutoUnlock. For now we'll ignore that case. + void Wait() { + mMonitor->AssertCurrentThreadOwns(); + mMonitor->Wait(); + } + CVStatus Wait(TimeDuration aDuration) { + mMonitor->AssertCurrentThreadOwns(); + return mMonitor->Wait(aDuration); + } + + void Notify() { mMonitor->Notify(); } + void NotifyAll() { mMonitor->NotifyAll(); } + + // Assert that aLock is the monitor passed to the constructor and that the + // current thread owns the monitor. In coding patterns such as: + // + // void LockedMethod(const BaseAutoLock& aProofOfLock) + // { + // aProofOfLock.AssertOwns(mMonitor); + // ... + // } + // + // Without this assertion, it could be that mMonitor is not actually + // locked. It's possible to have code like: + // + // BaseAutoLock lock(someMonitor); + // ... + // BaseAutoUnlock unlock(someMonitor); + // ... + // LockedMethod(lock); + // + // and in such a case, simply asserting that the monitor pointers match is not + // sufficient; monitor ownership must be asserted as well. + // + // Note that if you are going to use the coding pattern presented above, you + // should use this method in preference to using AssertCurrentThreadOwns on + // the mutex you expected to be held, since this method provides stronger + // guarantees. + void AssertOwns(const T& aMonitor) const MOZ_ASSERT_CAPABILITY(aMonitor) { + MOZ_ASSERT(&aMonitor == mMonitor); + mMonitor->AssertCurrentThreadOwns(); + } + + private: + BaseMonitorAutoLock() = delete; + BaseMonitorAutoLock(const BaseMonitorAutoLock&) = delete; + BaseMonitorAutoLock& operator=(const BaseMonitorAutoLock&) = delete; + static void* operator new(size_t) noexcept(true); + + friend class MonitorAutoUnlock; + + protected: + T* mMonitor; +}; +} // namespace detail +typedef detail::BaseMonitorAutoLock MonitorAutoLock; +typedef detail::BaseMonitorAutoLock + MonitorSingleWriterAutoLock; + +// clang-format off +// Use if we've done AssertOnWritingThread(), and then later need to take the +// lock to write to a protected member. Instead of +// MutexSingleWriterAutoLock lock(mutex) +// use +// MutexSingleWriterAutoLockOnThread(lock, mutex) +// clang-format on +#define MonitorSingleWriterAutoLockOnThread(lock, monitor) \ + MOZ_PUSH_IGNORE_THREAD_SAFETY \ + MonitorSingleWriterAutoLock lock(monitor); \ + MOZ_POP_THREAD_SAFETY + +/** + * Unlock the monitor for the lexical scope instances of this class + * are bound to (except for MonitorAutoLock in nested scopes). + * + * The monitor must be locked by the current thread when instances of + * this class are created. + */ +namespace detail { +template +class MOZ_STACK_CLASS MOZ_SCOPED_CAPABILITY BaseMonitorAutoUnlock { + public: + explicit BaseMonitorAutoUnlock(T& aMonitor) + MOZ_SCOPED_UNLOCK_RELEASE(aMonitor) + : mMonitor(&aMonitor) { + mMonitor->Unlock(); + } + + ~BaseMonitorAutoUnlock() MOZ_SCOPED_UNLOCK_REACQUIRE() { mMonitor->Lock(); } + + private: + BaseMonitorAutoUnlock() = delete; + BaseMonitorAutoUnlock(const BaseMonitorAutoUnlock&) = delete; + BaseMonitorAutoUnlock& operator=(const BaseMonitorAutoUnlock&) = delete; + static void* operator new(size_t) noexcept(true); + + T* mMonitor; +}; +} // namespace detail +typedef detail::BaseMonitorAutoUnlock MonitorAutoUnlock; +typedef detail::BaseMonitorAutoUnlock + MonitorSingleWriterAutoUnlock; + +/** + * Lock the monitor for the lexical scope instances of this class are + * bound to (except for MonitorAutoUnlock in nested scopes). + * + * The monitor must be unlocked when instances of this class are + * created. + */ +class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS ReleasableMonitorAutoLock { + public: + explicit ReleasableMonitorAutoLock(Monitor& aMonitor) + MOZ_CAPABILITY_ACQUIRE(aMonitor) + : mMonitor(&aMonitor) { + mMonitor->Lock(); + mLocked = true; + } + + ~ReleasableMonitorAutoLock() MOZ_CAPABILITY_RELEASE() { + if (mLocked) { + mMonitor->Unlock(); + } + } + + // See BaseMonitorAutoLock::Wait + void Wait() { + mMonitor->AssertCurrentThreadOwns(); // someone could have called Unlock() + mMonitor->Wait(); + } + CVStatus Wait(TimeDuration aDuration) { + mMonitor->AssertCurrentThreadOwns(); + return mMonitor->Wait(aDuration); + } + + void Notify() { + MOZ_ASSERT(mLocked); + mMonitor->Notify(); + } + void NotifyAll() { + MOZ_ASSERT(mLocked); + mMonitor->NotifyAll(); + } + + // Allow dropping the lock prematurely; for example to support something like: + // clang-format off + // MonitorAutoLock lock(mMonitor); + // ... + // if (foo) { + // lock.Unlock(); + // MethodThatCantBeCalledWithLock() + // return; + // } + // clang-format on + void Unlock() MOZ_CAPABILITY_RELEASE() { + MOZ_ASSERT(mLocked); + mMonitor->Unlock(); + mLocked = false; + } + void Lock() MOZ_CAPABILITY_ACQUIRE() { + MOZ_ASSERT(!mLocked); + mMonitor->Lock(); + mLocked = true; + } + void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY() { + mMonitor->AssertCurrentThreadOwns(); + } + + private: + bool mLocked; + Monitor* mMonitor; + + ReleasableMonitorAutoLock() = delete; + ReleasableMonitorAutoLock(const ReleasableMonitorAutoLock&) = delete; + ReleasableMonitorAutoLock& operator=(const ReleasableMonitorAutoLock&) = + delete; + static void* operator new(size_t) noexcept(true); +}; + +} // namespace mozilla + +#endif // mozilla_Monitor_h diff --git a/xpcom/threads/MozPromise.h b/xpcom/threads/MozPromise.h new file mode 100644 index 0000000000..c0b0addf0a --- /dev/null +++ b/xpcom/threads/MozPromise.h @@ -0,0 +1,1763 @@ +/* -*- 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/. */ + +#if !defined(MozPromise_h_) +# define MozPromise_h_ + +# include +# include + +# include "mozilla/ErrorNames.h" +# include "mozilla/Logging.h" +# include "mozilla/Maybe.h" +# include "mozilla/Monitor.h" +# include "mozilla/Mutex.h" +# include "mozilla/RefPtr.h" +# include "mozilla/UniquePtr.h" +# include "mozilla/Variant.h" +# include "nsIDirectTaskDispatcher.h" +# include "nsISerialEventTarget.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +# ifdef MOZ_WIDGET_ANDROID +# include "mozilla/jni/GeckoResultUtils.h" +# endif + +# if MOZ_DIAGNOSTIC_ASSERT_ENABLED +# define PROMISE_DEBUG +# endif + +# ifdef PROMISE_DEBUG +# define PROMISE_ASSERT MOZ_RELEASE_ASSERT +# else +# define PROMISE_ASSERT(...) \ + do { \ + } while (0) +# endif + +# if DEBUG +# include "nsPrintfCString.h" +# endif + +namespace mozilla { + +namespace dom { +class Promise; +} + +extern LazyLogModule gMozPromiseLog; + +# define PROMISE_LOG(x, ...) \ + MOZ_LOG(gMozPromiseLog, mozilla::LogLevel::Debug, (x, ##__VA_ARGS__)) + +namespace detail { +template +struct MethodTraitsHelper : MethodTraitsHelper {}; +template +struct MethodTraitsHelper { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template +struct MethodTraitsHelper { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template +struct MethodTraitsHelper { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template +struct MethodTraitsHelper { + using ReturnType = Ret; + static const size_t ArgSize = sizeof...(ArgTypes); +}; +template +struct MethodTrait : MethodTraitsHelper> {}; + +} // namespace detail + +template +using TakesArgument = + std::integral_constant::ArgSize != 0>; + +template +using ReturnTypeIs = + std::is_convertible::ReturnType, + TargetType>; + +template +class MozPromise; + +template +struct IsMozPromise : std::false_type {}; + +template +struct IsMozPromise> + : std::true_type {}; + +/* + * A promise manages an asynchronous request that may or may not be able to be + * fulfilled immediately. When an API returns a promise, the consumer may attach + * callbacks to be invoked (asynchronously, on a specified thread) when the + * request is either completed (resolved) or cannot be completed (rejected). + * Whereas JS promise callbacks are dispatched from Microtask checkpoints, + * MozPromises resolution/rejection make a normal round-trip through the event + * loop, which simplifies their ordering semantics relative to other native + * code. + * + * MozPromises attempt to mirror the spirit of JS Promises to the extent that + * is possible (and desirable) in C++. While the intent is that MozPromises + * feel familiar to programmers who are accustomed to their JS-implemented + * cousin, we don't shy away from imposing restrictions and adding features that + * make sense for the use cases we encounter. + * + * A MozPromise is ThreadSafe, and may be ->Then()ed on any thread. The Then() + * call accepts resolve and reject callbacks, and returns a magic object which + * will be implicitly converted to a MozPromise::Request or a MozPromise object + * depending on how the return value is used. The magic object serves several + * purposes for the consumer. + * + * (1) When converting to a MozPromise::Request, it allows the caller to + * cancel the delivery of the resolve/reject value if it has not already + * occurred, via Disconnect() (this must be done on the target thread to + * avoid racing). + * + * (2) When converting to a MozPromise (which is called a completion promise), + * it allows promise chaining so ->Then() can be called again to attach + * more resolve and reject callbacks. If the resolve/reject callback + * returns a new MozPromise, that promise is chained to the completion + * promise, such that its resolve/reject value will be forwarded along + * when it arrives. If the resolve/reject callback returns void, the + * completion promise is resolved/rejected with the same value that was + * passed to the callback. + * + * The MozPromise APIs skirt traditional XPCOM convention by returning nsRefPtrs + * (rather than already_AddRefed) from various methods. This is done to allow + * elegant chaining of calls without cluttering up the code with intermediate + * variables, and without introducing separate API variants for callers that + * want a return value (from, say, ->Then()) from those that don't. + * + * When IsExclusive is true, the MozPromise does a release-mode assertion that + * there is at most one call to either Then(...) or ChainTo(...). + */ + +class MozPromiseRefcountable { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MozPromiseRefcountable) + protected: + virtual ~MozPromiseRefcountable() = default; +}; + +class MozPromiseBase : public MozPromiseRefcountable { + public: + virtual void AssertIsDead() = 0; +}; + +template +class MozPromiseHolder; +template +class MozPromiseRequestHolder; +template +class MozPromise : public MozPromiseBase { + static const uint32_t sMagic = 0xcecace11; + + // Return a |T&&| to enable move when IsExclusive is true or + // a |const T&| to enforce copy otherwise. + template > + static R MaybeMove(T& aX) { + return static_cast(aX); + } + + public: + typedef ResolveValueT ResolveValueType; + typedef RejectValueT RejectValueType; + class ResolveOrRejectValue { + public: + template + void SetResolve(ResolveValueType_&& aResolveValue) { + MOZ_ASSERT(IsNothing()); + mValue = Storage(VariantIndex{}, + std::forward(aResolveValue)); + } + + template + void SetReject(RejectValueType_&& aRejectValue) { + MOZ_ASSERT(IsNothing()); + mValue = Storage(VariantIndex{}, + std::forward(aRejectValue)); + } + + template + static ResolveOrRejectValue MakeResolve(ResolveValueType_&& aResolveValue) { + ResolveOrRejectValue val; + val.SetResolve(std::forward(aResolveValue)); + return val; + } + + template + static ResolveOrRejectValue MakeReject(RejectValueType_&& aRejectValue) { + ResolveOrRejectValue val; + val.SetReject(std::forward(aRejectValue)); + return val; + } + + bool IsResolve() const { return mValue.template is(); } + bool IsReject() const { return mValue.template is(); } + bool IsNothing() const { return mValue.template is(); } + + const ResolveValueType& ResolveValue() const { + return mValue.template as(); + } + ResolveValueType& ResolveValue() { + return mValue.template as(); + } + const RejectValueType& RejectValue() const { + return mValue.template as(); + } + RejectValueType& RejectValue() { return mValue.template as(); } + + private: + enum { NothingIndex, ResolveIndex, RejectIndex }; + using Storage = Variant; + Storage mValue = Storage(VariantIndex{}); + }; + + protected: + // MozPromise is the public type, and never constructed directly. Construct + // a MozPromise::Private, defined below. + MozPromise(const char* aCreationSite, bool aIsCompletionPromise) + : mCreationSite(aCreationSite), + mMutex("MozPromise Mutex"), + mHaveRequest(false), + mIsCompletionPromise(aIsCompletionPromise) +# ifdef PROMISE_DEBUG + , + mMagic4(&mMutex) +# endif + { + PROMISE_LOG("%s creating MozPromise (%p)", mCreationSite, this); + } + + public: + // MozPromise::Private allows us to separate the public interface (upon which + // consumers of the promise may invoke methods like Then()) from the private + // interface (upon which the creator of the promise may invoke Resolve() or + // Reject()). APIs should create and store a MozPromise::Private (usually + // via a MozPromiseHolder), and return a MozPromise to consumers. + // + // NB: We can include the definition of this class inline once B2G ICS is + // gone. + class Private; + + template + [[nodiscard]] static RefPtr CreateAndResolve( + ResolveValueType_&& aResolveValue, const char* aResolveSite) { + static_assert(std::is_convertible_v, + "Resolve() argument must be implicitly convertible to " + "MozPromise's ResolveValueT"); + RefPtr p = + new MozPromise::Private(aResolveSite); + p->Resolve(std::forward(aResolveValue), aResolveSite); + return p; + } + + template + [[nodiscard]] static RefPtr CreateAndReject( + RejectValueType_&& aRejectValue, const char* aRejectSite) { + static_assert(std::is_convertible_v, + "Reject() argument must be implicitly convertible to " + "MozPromise's RejectValueT"); + RefPtr p = + new MozPromise::Private(aRejectSite); + p->Reject(std::forward(aRejectValue), aRejectSite); + return p; + } + + template + [[nodiscard]] static RefPtr CreateAndResolveOrReject( + ResolveOrRejectValueType_&& aValue, const char* aSite) { + RefPtr p = new MozPromise::Private(aSite); + p->ResolveOrReject(std::forward(aValue), aSite); + return p; + } + + typedef MozPromise, RejectValueType, + IsExclusive> + AllPromiseType; + + typedef MozPromise, bool, IsExclusive> + AllSettledPromiseType; + + private: + class AllPromiseHolder : public MozPromiseRefcountable { + public: + explicit AllPromiseHolder(size_t aDependentPromises) + : mPromise(new typename AllPromiseType::Private(__func__)), + mOutstandingPromises(aDependentPromises) { + MOZ_ASSERT(aDependentPromises > 0); + mResolveValues.SetLength(aDependentPromises); + } + + template + void Resolve(size_t aIndex, ResolveValueType_&& aResolveValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mResolveValues[aIndex].emplace( + std::forward(aResolveValue)); + if (--mOutstandingPromises == 0) { + nsTArray resolveValues; + resolveValues.SetCapacity(mResolveValues.Length()); + for (auto&& resolveValue : mResolveValues) { + resolveValues.AppendElement(std::move(resolveValue.ref())); + } + + mPromise->Resolve(std::move(resolveValues), __func__); + mPromise = nullptr; + mResolveValues.Clear(); + } + } + + template + void Reject(RejectValueType_&& aRejectValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mPromise->Reject(std::forward(aRejectValue), __func__); + mPromise = nullptr; + mResolveValues.Clear(); + } + + AllPromiseType* Promise() { return mPromise; } + + private: + nsTArray> mResolveValues; + RefPtr mPromise; + size_t mOutstandingPromises; + }; + + // Trying to pass ResolveOrRejectValue by value fails static analysis checks, + // so we need to use either a const& or an rvalue reference, depending on + // whether IsExclusive is true or not. + typedef std::conditional_t + ResolveOrRejectValueParam; + + typedef std::conditional_t + ResolveValueTypeParam; + + typedef std::conditional_t + RejectValueTypeParam; + + class AllSettledPromiseHolder : public MozPromiseRefcountable { + public: + explicit AllSettledPromiseHolder(size_t aDependentPromises) + : mPromise(new typename AllSettledPromiseType::Private(__func__)), + mOutstandingPromises(aDependentPromises) { + MOZ_ASSERT(aDependentPromises > 0); + mValues.SetLength(aDependentPromises); + } + + void Settle(size_t aIndex, ResolveOrRejectValueParam aValue) { + if (!mPromise) { + // Already rejected. + return; + } + + mValues[aIndex].emplace(MaybeMove(aValue)); + if (--mOutstandingPromises == 0) { + nsTArray values; + values.SetCapacity(mValues.Length()); + for (auto&& value : mValues) { + values.AppendElement(std::move(value.ref())); + } + + mPromise->Resolve(std::move(values), __func__); + mPromise = nullptr; + mValues.Clear(); + } + } + + AllSettledPromiseType* Promise() { return mPromise; } + + private: + nsTArray> mValues; + RefPtr mPromise; + size_t mOutstandingPromises; + }; + + public: + [[nodiscard]] static RefPtr All( + nsISerialEventTarget* aProcessingTarget, + nsTArray>& aPromises) { + if (aPromises.Length() == 0) { + return AllPromiseType::CreateAndResolve( + CopyableTArray(), __func__); + } + + RefPtr holder = new AllPromiseHolder(aPromises.Length()); + RefPtr promise = holder->Promise(); + for (size_t i = 0; i < aPromises.Length(); ++i) { + aPromises[i]->Then( + aProcessingTarget, __func__, + [holder, i](ResolveValueTypeParam aResolveValue) -> void { + holder->Resolve(i, MaybeMove(aResolveValue)); + }, + [holder](RejectValueTypeParam aRejectValue) -> void { + holder->Reject(MaybeMove(aRejectValue)); + }); + } + return promise; + } + + [[nodiscard]] static RefPtr AllSettled( + nsISerialEventTarget* aProcessingTarget, + nsTArray>& aPromises) { + if (aPromises.Length() == 0) { + return AllSettledPromiseType::CreateAndResolve( + CopyableTArray(), __func__); + } + + RefPtr holder = + new AllSettledPromiseHolder(aPromises.Length()); + RefPtr promise = holder->Promise(); + for (size_t i = 0; i < aPromises.Length(); ++i) { + aPromises[i]->Then(aProcessingTarget, __func__, + [holder, i](ResolveOrRejectValueParam aValue) -> void { + holder->Settle(i, MaybeMove(aValue)); + }); + } + return promise; + } + + class Request : public MozPromiseRefcountable { + public: + virtual void Disconnect() = 0; + + protected: + Request() : mComplete(false), mDisconnected(false) {} + virtual ~Request() = default; + + bool mComplete; + bool mDisconnected; + }; + + protected: + /* + * A ThenValue tracks a single consumer waiting on the promise. When a + * consumer invokes promise->Then(...), a ThenValue is created. Once the + * Promise is resolved or rejected, a {Resolve,Reject}Runnable is dispatched, + * which invokes the resolve/reject method and then deletes the ThenValue. + */ + class ThenValueBase : public Request { + friend class MozPromise; + static const uint32_t sMagic = 0xfadece11; + + public: + class ResolveOrRejectRunnable final + : public PrioritizableCancelableRunnable { + public: + ResolveOrRejectRunnable(ThenValueBase* aThenValue, MozPromise* aPromise) + : PrioritizableCancelableRunnable( + aPromise->mPriority, + "MozPromise::ThenValueBase::ResolveOrRejectRunnable"), + mThenValue(aThenValue), + mPromise(aPromise) { + MOZ_DIAGNOSTIC_ASSERT(!mPromise->IsPending()); + } + + ~ResolveOrRejectRunnable() { + if (mThenValue) { + mThenValue->AssertIsDead(); + } + } + + NS_IMETHOD Run() override { + PROMISE_LOG("ResolveOrRejectRunnable::Run() [this=%p]", this); + mThenValue->DoResolveOrReject(mPromise->Value()); + mThenValue = nullptr; + mPromise = nullptr; + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr mThenValue; + RefPtr mPromise; + }; + + ThenValueBase(nsISerialEventTarget* aResponseTarget, const char* aCallSite) + : mResponseTarget(aResponseTarget), mCallSite(aCallSite) { + MOZ_ASSERT(aResponseTarget); + } + +# ifdef PROMISE_DEBUG + ~ThenValueBase() { + mMagic1 = 0; + mMagic2 = 0; + } +# endif + + void AssertIsDead() { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + // We want to assert that this ThenValues is dead - that is to say, that + // there are no consumers waiting for the result. In the case of a normal + // ThenValue, we check that it has been disconnected, which is the way + // that the consumer signals that it no longer wishes to hear about the + // result. If this ThenValue has a completion promise (which is mutually + // exclusive with being disconnectable), we recursively assert that every + // ThenValue associated with the completion promise is dead. + if (MozPromiseBase* p = CompletionPromise()) { + p->AssertIsDead(); + } else { +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + if (MOZ_UNLIKELY(!Request::mDisconnected)) { + MOZ_CRASH_UNSAFE_PRINTF( + "MozPromise::ThenValue created from '%s' destroyed without being " + "either disconnected, resolved, or rejected (dispatchRv: %s)", + mCallSite, + mDispatchRv ? GetStaticErrorName(*mDispatchRv) + : "not dispatched"); + } +# endif + } + } + + void Dispatch(MozPromise* aPromise) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + aPromise->mMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!aPromise->IsPending()); + + nsCOMPtr r = new ResolveOrRejectRunnable(this, aPromise); + PROMISE_LOG( + "%s Then() call made from %s [Runnable=%p, Promise=%p, ThenValue=%p] " + "%s dispatch", + aPromise->mValue.IsResolve() ? "Resolving" : "Rejecting", mCallSite, + r.get(), aPromise, this, + aPromise->mUseSynchronousTaskDispatch ? "synchronous" + : aPromise->mUseDirectTaskDispatch ? "directtask" + : "normal"); + + if (aPromise->mUseSynchronousTaskDispatch && + mResponseTarget->IsOnCurrentThread()) { + PROMISE_LOG("ThenValue::Dispatch running task synchronously [this=%p]", + this); + r->Run(); + return; + } + + if (aPromise->mUseDirectTaskDispatch && + mResponseTarget->IsOnCurrentThread()) { + PROMISE_LOG( + "ThenValue::Dispatch dispatch task via direct task queue [this=%p]", + this); + nsCOMPtr dispatcher = + do_QueryInterface(mResponseTarget); + if (dispatcher) { + SetDispatchRv(dispatcher->DispatchDirectTask(r.forget())); + return; + } + NS_WARNING( + nsPrintfCString( + "Direct Task dispatching not available for thread \"%s\"", + PR_GetThreadName(PR_GetCurrentThread())) + .get()); + MOZ_DIAGNOSTIC_ASSERT( + false, + "mResponseTarget must implement nsIDirectTaskDispatcher for direct " + "task dispatching"); + } + + // Promise consumers are allowed to disconnect the Request object and + // then shut down the thread or task queue that the promise result would + // be dispatched on. So we unfortunately can't assert that promise + // dispatch succeeds. :-( + // We do record whether or not it succeeds so that if the ThenValueBase is + // then destroyed and it was not disconnected, we can include that + // information in the assertion message. + SetDispatchRv(mResponseTarget->Dispatch(r.forget())); + } + + void Disconnect() override { + MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); + MOZ_DIAGNOSTIC_ASSERT(!Request::mComplete); + Request::mDisconnected = true; + + // We could support rejecting the completion promise on disconnection, but + // then we'd need to have some sort of default reject value. The use cases + // of disconnection and completion promise chaining seem pretty + // orthogonal, so let's use assert against it. + MOZ_DIAGNOSTIC_ASSERT(!CompletionPromise()); + } + + protected: + virtual MozPromiseBase* CompletionPromise() const = 0; + virtual void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) = 0; + + void DoResolveOrReject(ResolveOrRejectValue& aValue) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic); + MOZ_DIAGNOSTIC_ASSERT(mResponseTarget->IsOnCurrentThread()); + Request::mComplete = true; + if (Request::mDisconnected) { + PROMISE_LOG( + "ThenValue::DoResolveOrReject disconnected - bailing out [this=%p]", + this); + return; + } + + // Invoke the resolve or reject method. + DoResolveOrRejectInternal(aValue); + } + + void SetDispatchRv(nsresult aRv) { +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + mDispatchRv = Some(aRv); +# endif + } + + nsCOMPtr + mResponseTarget; // May be released on any thread. +# ifdef PROMISE_DEBUG + uint32_t mMagic1 = sMagic; +# endif + const char* mCallSite; +# ifdef PROMISE_DEBUG + uint32_t mMagic2 = sMagic; +# endif +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + Maybe mDispatchRv; +# endif + }; + + /* + * We create two overloads for invoking Resolve/Reject Methods so as to + * make the resolve/reject value argument "optional". + */ + template + static std::enable_if_t::value, + typename detail::MethodTrait::ReturnType> + InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { + return (aThisVal->*aMethod)(std::forward(aValue)); + } + + template + static std::enable_if_t::value, + typename detail::MethodTrait::ReturnType> + InvokeMethod(ThisType* aThisVal, MethodType aMethod, ValueType&& aValue) { + return (aThisVal->*aMethod)(); + } + + // Called when promise chaining is supported. + template + static std::enable_if_t InvokeCallbackMethod( + ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, + CompletionPromiseType&& aCompletionPromise) { + auto p = InvokeMethod(aThisVal, aMethod, std::forward(aValue)); + if (aCompletionPromise) { + p->ChainTo(aCompletionPromise.forget(), ""); + } + } + + // Called when promise chaining is not supported. + template + static std::enable_if_t InvokeCallbackMethod( + ThisType* aThisVal, MethodType aMethod, ValueType&& aValue, + CompletionPromiseType&& aCompletionPromise) { + MOZ_DIAGNOSTIC_ASSERT( + !aCompletionPromise, + "Can't do promise chaining for a non-promise-returning method."); + InvokeMethod(aThisVal, aMethod, std::forward(aValue)); + } + + template + class ThenCommand; + + template + class ThenValue; + + template + class ThenValue + : public ThenValueBase { + friend class ThenCommand; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait::ReturnType>::Type; + using R2 = typename RemoveSmartPointer< + typename detail::MethodTrait::ReturnType>::Type; + using SupportChaining = + std::integral_constant::value && + std::is_same_v>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, + ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite), + mThisVal(aThisVal), + mResolveMethod(aResolveMethod), + mRejectMethod(aRejectMethod) {} + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Null out our refcounted + // this-value now so that it's released predictably on the dispatch + // thread. + mThisVal = nullptr; + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + if (aValue.IsResolve()) { + InvokeCallbackMethod( + mThisVal.get(), mResolveMethod, MaybeMove(aValue.ResolveValue()), + std::move(mCompletionPromise)); + } else { + InvokeCallbackMethod( + mThisVal.get(), mRejectMethod, MaybeMove(aValue.RejectValue()), + std::move(mCompletionPromise)); + } + + // Null out mThisVal after invoking the callback so that any references + // are released predictably on the dispatch thread. Otherwise, it would be + // released on whatever thread last drops its reference to the ThenValue, + // which may or may not be ok. + mThisVal = nullptr; + } + + private: + RefPtr + mThisVal; // Only accessed and refcounted on dispatch thread. + ResolveMethodType mResolveMethod; + RejectMethodType mRejectMethod; + RefPtr mCompletionPromise; + }; + + template + class ThenValue : public ThenValueBase { + friend class ThenCommand; + + using R1 = typename RemoveSmartPointer::ReturnType>::Type; + using SupportChaining = + std::integral_constant::value>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, ThisType* aThisVal, + ResolveRejectMethodType aResolveRejectMethod, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite), + mThisVal(aThisVal), + mResolveRejectMethod(aResolveRejectMethod) {} + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Null out our refcounted + // this-value now so that it's released predictably on the dispatch + // thread. + mThisVal = nullptr; + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + InvokeCallbackMethod( + mThisVal.get(), mResolveRejectMethod, MaybeMove(aValue), + std::move(mCompletionPromise)); + + // Null out mThisVal after invoking the callback so that any references + // are released predictably on the dispatch thread. Otherwise, it would be + // released on whatever thread last drops its reference to the ThenValue, + // which may or may not be ok. + mThisVal = nullptr; + } + + private: + RefPtr + mThisVal; // Only accessed and refcounted on dispatch thread. + ResolveRejectMethodType mResolveRejectMethod; + RefPtr mCompletionPromise; + }; + + // NB: We could use std::function here instead of a template if it were + // supported. :-( + template + class ThenValue : public ThenValueBase { + friend class ThenCommand; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait::ReturnType>::Type; + using R2 = typename RemoveSmartPointer< + typename detail::MethodTrait::ReturnType>::Type; + using SupportChaining = + std::integral_constant::value && + std::is_same_v>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, + ResolveFunction&& aResolveFunction, + RejectFunction&& aRejectFunction, const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite) { + mResolveFunction.emplace(std::move(aResolveFunction)); + mRejectFunction.emplace(std::move(aRejectFunction)); + } + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Destroy our callbacks + // now so that any references in closures are released predictable on + // the dispatch thread. + mResolveFunction.reset(); + mRejectFunction.reset(); + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + // Note: The usage of InvokeCallbackMethod here requires that + // ResolveFunction/RejectFunction are capture-lambdas (i.e. anonymous + // classes with ::operator()), since it allows us to share code more + // easily. We could fix this if need be, though it's quite easy to work + // around by just capturing something. + if (aValue.IsResolve()) { + InvokeCallbackMethod( + mResolveFunction.ptr(), &ResolveFunction::operator(), + MaybeMove(aValue.ResolveValue()), std::move(mCompletionPromise)); + } else { + InvokeCallbackMethod( + mRejectFunction.ptr(), &RejectFunction::operator(), + MaybeMove(aValue.RejectValue()), std::move(mCompletionPromise)); + } + + // Destroy callbacks after invocation so that any references in closures + // are released predictably on the dispatch thread. Otherwise, they would + // be released on whatever thread last drops its reference to the + // ThenValue, which may or may not be ok. + mResolveFunction.reset(); + mRejectFunction.reset(); + } + + private: + Maybe + mResolveFunction; // Only accessed and deleted on dispatch thread. + Maybe + mRejectFunction; // Only accessed and deleted on dispatch thread. + RefPtr mCompletionPromise; + }; + + template + class ThenValue : public ThenValueBase { + friend class ThenCommand; + + using R1 = typename RemoveSmartPointer< + typename detail::MethodTrait::ReturnType>::Type; + using SupportChaining = + std::integral_constant::value>; + + // Fall back to MozPromise when promise chaining is not supported to make + // code compile. + using PromiseType = + std::conditional_t; + + public: + ThenValue(nsISerialEventTarget* aResponseTarget, + ResolveRejectFunction&& aResolveRejectFunction, + const char* aCallSite) + : ThenValueBase(aResponseTarget, aCallSite) { + mResolveRejectFunction.emplace(std::move(aResolveRejectFunction)); + } + + void Disconnect() override { + ThenValueBase::Disconnect(); + + // If a Request has been disconnected, we don't guarantee that the + // resolve/reject runnable will be dispatched. Destroy our callbacks + // now so that any references in closures are released predictable on + // the dispatch thread. + mResolveRejectFunction.reset(); + } + + protected: + MozPromiseBase* CompletionPromise() const override { + return mCompletionPromise; + } + + void DoResolveOrRejectInternal(ResolveOrRejectValue& aValue) override { + // Note: The usage of InvokeCallbackMethod here requires that + // ResolveRejectFunction is capture-lambdas (i.e. anonymous + // classes with ::operator()), since it allows us to share code more + // easily. We could fix this if need be, though it's quite easy to work + // around by just capturing something. + InvokeCallbackMethod( + mResolveRejectFunction.ptr(), &ResolveRejectFunction::operator(), + MaybeMove(aValue), std::move(mCompletionPromise)); + + // Destroy callbacks after invocation so that any references in closures + // are released predictably on the dispatch thread. Otherwise, they would + // be released on whatever thread last drops its reference to the + // ThenValue, which may or may not be ok. + mResolveRejectFunction.reset(); + } + + private: + Maybe + mResolveRejectFunction; // Only accessed and deleted on dispatch + // thread. + RefPtr mCompletionPromise; + }; + + public: + void ThenInternal(already_AddRefed aThenValue, + const char* aCallSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + RefPtr thenValue = aThenValue; + MutexAutoLock lock(mMutex); + MOZ_DIAGNOSTIC_ASSERT( + !IsExclusive || !mHaveRequest, + "Using an exclusive promise in a non-exclusive fashion"); + mHaveRequest = true; + PROMISE_LOG("%s invoking Then() [this=%p, aThenValue=%p, isPending=%d]", + aCallSite, this, thenValue.get(), (int)IsPending()); + if (!IsPending()) { + thenValue->Dispatch(this); + } else { + mThenValues.AppendElement(thenValue.forget()); + } + } + + protected: + /* + * A command object to store all information needed to make a request to + * the promise. This allows us to delay the request until further use is + * known (whether it is ->Then() again for more promise chaining or ->Track() + * to terminate chaining and issue the request). + * + * This allows a unified syntax for promise chaining and disconnection + * and feels more like its JS counterpart. + */ + template + class ThenCommand { + // Allow Promise1::ThenCommand to access the private constructor, + // Promise2::ThenCommand(ThenCommand&&). + template + friend class MozPromise; + + using PromiseType = typename ThenValueType::PromiseType; + using Private = typename PromiseType::Private; + + ThenCommand(const char* aCallSite, + already_AddRefed aThenValue, + MozPromise* aReceiver) + : mCallSite(aCallSite), mThenValue(aThenValue), mReceiver(aReceiver) {} + + ThenCommand(ThenCommand&& aOther) = default; + + public: + ~ThenCommand() { + // Issue the request now if the return value of Then() is not used. + if (mThenValue) { + mReceiver->ThenInternal(mThenValue.forget(), mCallSite); + } + } + + // Allow RefPtr p = somePromise->Then(); + // p->Then(thread1, ...); + // p->Then(thread2, ...); + operator RefPtr() { + static_assert( + ThenValueType::SupportChaining::value, + "The resolve/reject callback needs to return a RefPtr " + "in order to do promise chaining."); + + // mCompletionPromise must be created before ThenInternal() to avoid race. + RefPtr p = + new Private("", true /* aIsCompletionPromise */); + mThenValue->mCompletionPromise = p; + // Note ThenInternal() might nullify mCompletionPromise before return. + // So we need to return p instead of mCompletionPromise. + mReceiver->ThenInternal(mThenValue.forget(), mCallSite); + return p; + } + + template + auto Then(Ts&&... aArgs) -> decltype(std::declval().Then( + std::forward(aArgs)...)) { + return static_cast>(*this)->Then( + std::forward(aArgs)...); + } + + void Track(MozPromiseRequestHolder& aRequestHolder) { + aRequestHolder.Track(do_AddRef(mThenValue)); + mReceiver->ThenInternal(mThenValue.forget(), mCallSite); + } + + // Allow calling ->Then() again for more promise chaining or ->Track() to + // end chaining and track the request for future disconnection. + ThenCommand* operator->() { return this; } + + private: + const char* mCallSite; + RefPtr mThenValue; + RefPtr mReceiver; + }; + + public: + template , + typename ReturnType = ThenCommand> + ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + ThisType* aThisVal, Methods... aMethods) { + RefPtr thenValue = + new ThenValueType(aResponseTarget, aThisVal, aMethods..., aCallSite); + return ReturnType(aCallSite, thenValue.forget(), this); + } + + template , + typename ReturnType = ThenCommand> + ReturnType Then(nsISerialEventTarget* aResponseTarget, const char* aCallSite, + Functions&&... aFunctions) { + RefPtr thenValue = + new ThenValueType(aResponseTarget, std::move(aFunctions)..., aCallSite); + return ReturnType(aCallSite, thenValue.forget(), this); + } + + void ChainTo(already_AddRefed aChainedPromise, + const char* aCallSite) { + MutexAutoLock lock(mMutex); + MOZ_DIAGNOSTIC_ASSERT( + !IsExclusive || !mHaveRequest, + "Using an exclusive promise in a non-exclusive fashion"); + mHaveRequest = true; + RefPtr chainedPromise = aChainedPromise; + PROMISE_LOG( + "%s invoking Chain() [this=%p, chainedPromise=%p, isPending=%d]", + aCallSite, this, chainedPromise.get(), (int)IsPending()); + + // We want to use the same type of dispatching method with the chained + // promises. + + // We need to ensure that the UseSynchronousTaskDispatch branch isn't taken + // at compilation time to ensure we're not triggering the static_assert in + // UseSynchronousTaskDispatch method. if constexpr (IsExclusive) ensures + // that. + if (mUseDirectTaskDispatch) { + chainedPromise->UseDirectTaskDispatch(aCallSite); + } else if constexpr (IsExclusive) { + if (mUseSynchronousTaskDispatch) { + chainedPromise->UseSynchronousTaskDispatch(aCallSite); + } + } else { + chainedPromise->SetTaskPriority(mPriority, aCallSite); + } + + if (!IsPending()) { + ForwardTo(chainedPromise); + } else { + mChainedPromises.AppendElement(chainedPromise); + } + } + +# ifdef MOZ_WIDGET_ANDROID + // Creates a C++ MozPromise from its Java counterpart, GeckoResult. + [[nodiscard]] static RefPtr FromGeckoResult( + java::GeckoResult::Param aGeckoResult) { + using jni::GeckoResultCallback; + RefPtr p = new Private("GeckoResult Glue", false); + auto resolve = GeckoResultCallback::CreateAndAttach( + [p](ResolveValueType&& aArg) { + p->Resolve(MaybeMove(aArg), __func__); + }); + auto reject = GeckoResultCallback::CreateAndAttach( + [p](RejectValueType&& aArg) { p->Reject(MaybeMove(aArg), __func__); }); + aGeckoResult->NativeThen(resolve, reject); + return p; + } +# endif + + // Creates a C++ MozPromise from its JS counterpart, dom::Promise. + // FromDomPromise currently only supports primitive types (int8/16/32, float, + // double) And the reject value type must be a nsresult. + // To use, please include MozPromiseInlines.h + static RefPtr FromDomPromise(dom::Promise* aDOMPromise); + + // Note we expose the function AssertIsDead() instead of IsDead() since + // checking IsDead() is a data race in the situation where the request is not + // dead. Therefore we enforce the form |Assert(IsDead())| by exposing + // AssertIsDead() only. + void AssertIsDead() override { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + for (auto&& then : mThenValues) { + then->AssertIsDead(); + } + for (auto&& chained : mChainedPromises) { + chained->AssertIsDead(); + } + } + + bool IsResolved() const { return mValue.IsResolve(); } + + protected: + bool IsPending() const { return mValue.IsNothing(); } + + ResolveOrRejectValue& Value() { + // This method should only be called once the value has stabilized. As + // such, we don't need to acquire the lock here. + MOZ_DIAGNOSTIC_ASSERT(!IsPending()); + return mValue; + } + + void DispatchAll() { + mMutex.AssertCurrentThreadOwns(); + for (auto&& thenValue : mThenValues) { + thenValue->Dispatch(this); + } + mThenValues.Clear(); + + for (auto&& chainedPromise : mChainedPromises) { + ForwardTo(chainedPromise); + } + mChainedPromises.Clear(); + } + + void ForwardTo(Private* aOther) { + MOZ_ASSERT(!IsPending()); + if (mValue.IsResolve()) { + aOther->Resolve(MaybeMove(mValue.ResolveValue()), ""); + } else { + aOther->Reject(MaybeMove(mValue.RejectValue()), ""); + } + } + + virtual ~MozPromise() { + PROMISE_LOG("MozPromise::~MozPromise [this=%p]", this); + AssertIsDead(); + // We can't guarantee a completion promise will always be revolved or + // rejected since ResolveOrRejectRunnable might not run when dispatch fails. + if (!mIsCompletionPromise) { + MOZ_ASSERT(!IsPending()); + MOZ_ASSERT(mThenValues.IsEmpty()); + MOZ_ASSERT(mChainedPromises.IsEmpty()); + } +# ifdef PROMISE_DEBUG + mMagic1 = 0; + mMagic2 = 0; + mMagic3 = 0; + mMagic4 = nullptr; +# endif + }; + + const char* mCreationSite; // For logging + Mutex mMutex MOZ_UNANNOTATED; + ResolveOrRejectValue mValue; + bool mUseSynchronousTaskDispatch = false; + bool mUseDirectTaskDispatch = false; + uint32_t mPriority = nsIRunnablePriority::PRIORITY_NORMAL; +# ifdef PROMISE_DEBUG + uint32_t mMagic1 = sMagic; +# endif + // Try shows we never have more than 3 elements when IsExclusive is false. + // So '3' is a good value to avoid heap allocation in most cases. + AutoTArray, IsExclusive ? 1 : 3> mThenValues; +# ifdef PROMISE_DEBUG + uint32_t mMagic2 = sMagic; +# endif + nsTArray> mChainedPromises; +# ifdef PROMISE_DEBUG + uint32_t mMagic3 = sMagic; +# endif + bool mHaveRequest; + const bool mIsCompletionPromise; +# ifdef PROMISE_DEBUG + void* mMagic4; +# endif +}; + +template +class MozPromise::Private + : public MozPromise { + public: + explicit Private(const char* aCreationSite, bool aIsCompletionPromise = false) + : MozPromise(aCreationSite, aIsCompletionPromise) {} + + template + void Resolve(ResolveValueT_&& aResolveValue, const char* aResolveSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s resolving MozPromise (%p created at %s)", aResolveSite, + this, mCreationSite); + if (!IsPending()) { + PROMISE_LOG( + "%s ignored already resolved or rejected MozPromise (%p created at " + "%s)", + aResolveSite, this, mCreationSite); + return; + } + mValue.SetResolve(std::forward(aResolveValue)); + DispatchAll(); + } + + template + void Reject(RejectValueT_&& aRejectValue, const char* aRejectSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s rejecting MozPromise (%p created at %s)", aRejectSite, this, + mCreationSite); + if (!IsPending()) { + PROMISE_LOG( + "%s ignored already resolved or rejected MozPromise (%p created at " + "%s)", + aRejectSite, this, mCreationSite); + return; + } + mValue.SetReject(std::forward(aRejectValue)); + DispatchAll(); + } + + template + void ResolveOrReject(ResolveOrRejectValue_&& aValue, const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s resolveOrRejecting MozPromise (%p created at %s)", aSite, + this, mCreationSite); + if (!IsPending()) { + PROMISE_LOG( + "%s ignored already resolved or rejected MozPromise (%p created at " + "%s)", + aSite, this, mCreationSite); + return; + } + mValue = std::forward(aValue); + DispatchAll(); + } + + // If the caller and target are both on the same thread, run the the resolve + // or reject callback synchronously. Otherwise, the task will be dispatched + // via the target Dispatch method. + void UseSynchronousTaskDispatch(const char* aSite) { + static_assert( + IsExclusive, + "Synchronous dispatch can only be used with exclusive promises"); + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s UseSynchronousTaskDispatch MozPromise (%p created at %s)", + aSite, this, mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + mUseSynchronousTaskDispatch = true; + } + + // If the caller and target are both on the same thread, run the + // resolve/reject callback off the direct task queue instead. This avoids a + // full trip to the back of the event queue for each additional asynchronous + // step when using MozPromise, and is similar (but not identical to) the + // microtask semantics of JS promises. + void UseDirectTaskDispatch(const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s UseDirectTaskDispatch MozPromise (%p created at %s)", aSite, + this, mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + MOZ_ASSERT(!mUseSynchronousTaskDispatch, + "Promise already set for synchronous dispatch"); + mUseDirectTaskDispatch = true; + } + + // If the resolve/reject will be handled on a thread supporting priorities, + // one may want to tweak the priority of the task by passing a + // nsIRunnablePriority::PRIORITY_* to SetTaskPriority. + void SetTaskPriority(uint32_t aPriority, const char* aSite) { + PROMISE_ASSERT(mMagic1 == sMagic && mMagic2 == sMagic && + mMagic3 == sMagic && mMagic4 == &mMutex); + MutexAutoLock lock(mMutex); + PROMISE_LOG("%s TaskPriority MozPromise (%p created at %s)", aSite, this, + mCreationSite); + MOZ_ASSERT(IsPending(), + "A Promise must not have been already resolved or rejected to " + "set dispatch state"); + MOZ_ASSERT(!mUseSynchronousTaskDispatch, + "Promise already set for synchronous dispatch"); + MOZ_ASSERT(!mUseDirectTaskDispatch, + "Promise already set for direct dispatch"); + mPriority = aPriority; + } +}; + +// A generic promise type that does the trick for simple use cases. +typedef MozPromise GenericPromise; + +// A generic, non-exclusive promise type that does the trick for simple use +// cases. +typedef MozPromise + GenericNonExclusivePromise; + +/* + * Class to encapsulate a promise for a particular role. Use this as the member + * variable for a class whose method returns a promise. + */ +template +class MozPromiseHolderBase { + public: + MozPromiseHolderBase() = default; + + MozPromiseHolderBase(MozPromiseHolderBase&& aOther) = default; + MozPromiseHolderBase& operator=(MozPromiseHolderBase&& aOther) = default; + + ~MozPromiseHolderBase() { MOZ_ASSERT(!mPromise); } + + already_AddRefed Ensure(const char* aMethodName) { + static_cast(this)->Check(); + if (!mPromise) { + mPromise = new (typename PromiseType::Private)(aMethodName); + } + RefPtr p = mPromise.get(); + return p.forget(); + } + + bool IsEmpty() const { + static_cast(this)->Check(); + return !mPromise; + } + + already_AddRefed Steal() { + static_cast(this)->Check(); + return mPromise.forget(); + } + + template + void Resolve(ResolveValueType_&& aResolveValue, const char* aMethodName) { + static_assert(std::is_convertible_v, + "Resolve() argument must be implicitly convertible to " + "MozPromise's ResolveValueT"); + + static_cast(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->Resolve(std::forward(aResolveValue), + aMethodName); + mPromise = nullptr; + } + + template + void ResolveIfExists(ResolveValueType_&& aResolveValue, + const char* aMethodName) { + if (!IsEmpty()) { + Resolve(std::forward(aResolveValue), aMethodName); + } + } + + template + void Reject(RejectValueType_&& aRejectValue, const char* aMethodName) { + static_assert(std::is_convertible_v, + "Reject() argument must be implicitly convertible to " + "MozPromise's RejectValueT"); + + static_cast(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->Reject(std::forward(aRejectValue), aMethodName); + mPromise = nullptr; + } + + template + void RejectIfExists(RejectValueType_&& aRejectValue, + const char* aMethodName) { + if (!IsEmpty()) { + Reject(std::forward(aRejectValue), aMethodName); + } + } + + template + void ResolveOrReject(ResolveOrRejectValueType_&& aValue, + const char* aMethodName) { + static_cast(this)->Check(); + MOZ_ASSERT(mPromise); + mPromise->ResolveOrReject(std::forward(aValue), + aMethodName); + mPromise = nullptr; + } + + template + void ResolveOrRejectIfExists(ResolveOrRejectValueType_&& aValue, + const char* aMethodName) { + if (!IsEmpty()) { + ResolveOrReject(std::forward(aValue), + aMethodName); + } + } + + void UseSynchronousTaskDispatch(const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->UseSynchronousTaskDispatch(aSite); + } + + void UseDirectTaskDispatch(const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->UseDirectTaskDispatch(aSite); + } + + void SetTaskPriority(uint32_t aPriority, const char* aSite) { + MOZ_ASSERT(mPromise); + mPromise->SetTaskPriority(aPriority, aSite); + } + + private: + RefPtr mPromise; +}; + +template +class MozPromiseHolder + : public MozPromiseHolderBase> { + public: + using MozPromiseHolderBase< + PromiseType, MozPromiseHolder>::MozPromiseHolderBase; + static constexpr void Check(){}; +}; + +template +class MozMonitoredPromiseHolder + : public MozPromiseHolderBase> { + public: + // Provide a Monitor that should always be held when accessing this instance. + explicit MozMonitoredPromiseHolder(Monitor* const aMonitor) + : mMonitor(aMonitor) { + MOZ_ASSERT(aMonitor); + } + + MozMonitoredPromiseHolder(MozMonitoredPromiseHolder&& aOther) = delete; + MozMonitoredPromiseHolder& operator=(MozMonitoredPromiseHolder&& aOther) = + delete; + + void Check() const { mMonitor->AssertCurrentThreadOwns(); } + + private: + Monitor* const mMonitor; +}; + +/* + * Class to encapsulate a MozPromise::Request reference. Use this as the member + * variable for a class waiting on a MozPromise. + */ +template +class MozPromiseRequestHolder { + public: + MozPromiseRequestHolder() = default; + ~MozPromiseRequestHolder() { MOZ_ASSERT(!mRequest); } + + void Track(already_AddRefed aRequest) { + MOZ_DIAGNOSTIC_ASSERT(!Exists()); + mRequest = aRequest; + } + + void Complete() { + MOZ_DIAGNOSTIC_ASSERT(Exists()); + mRequest = nullptr; + } + + // Disconnects and forgets an outstanding promise. The resolve/reject methods + // will never be called. + void Disconnect() { + MOZ_ASSERT(Exists()); + mRequest->Disconnect(); + mRequest = nullptr; + } + + void DisconnectIfExists() { + if (Exists()) { + Disconnect(); + } + } + + bool Exists() const { return !!mRequest; } + + private: + RefPtr mRequest; +}; + +// Asynchronous Potentially-Cross-Thread Method Calls. +// +// This machinery allows callers to schedule a promise-returning function +// (a method and object, or a function object like a lambda) to be invoked +// asynchronously on a given thread, while at the same time receiving a +// promise upon which to invoke Then() immediately. InvokeAsync dispatches a +// task to invoke the function on the proper thread and also chain the +// resulting promise to the one that the caller received, so that resolve/ +// reject values are forwarded through. + +namespace detail { + +// Non-templated base class to allow us to use MOZ_COUNT_{C,D}TOR, which cause +// assertions when used on templated types. +class MethodCallBase { + public: + MOZ_COUNTED_DEFAULT_CTOR(MethodCallBase) + MOZ_COUNTED_DTOR_VIRTUAL(MethodCallBase) +}; + +template +class MethodCall : public MethodCallBase { + public: + template + MethodCall(MethodType aMethod, ThisType* aThisVal, Args&&... aArgs) + : mMethod(aMethod), + mThisVal(aThisVal), + mArgs(std::forward(aArgs)...) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "Storages and Args should have equal sizes"); + } + + RefPtr Invoke() { return mArgs.apply(mThisVal.get(), mMethod); } + + private: + MethodType mMethod; + RefPtr mThisVal; + RunnableMethodArguments mArgs; +}; + +template +class ProxyRunnable : public CancelableRunnable { + public: + ProxyRunnable( + typename PromiseType::Private* aProxyPromise, + MethodCall* aMethodCall) + : CancelableRunnable("detail::ProxyRunnable"), + mProxyPromise(aProxyPromise), + mMethodCall(aMethodCall) {} + + NS_IMETHOD Run() override { + RefPtr p = mMethodCall->Invoke(); + mMethodCall = nullptr; + p->ChainTo(mProxyPromise.forget(), ""); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr mProxyPromise; + UniquePtr> + mMethodCall; +}; + +template +static RefPtr InvokeAsyncImpl( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + MOZ_ASSERT(aTarget); + + typedef RefPtr (ThisType::*MethodType)(ArgTypes...); + typedef detail::MethodCall + MethodCallType; + typedef detail::ProxyRunnable + ProxyRunnableType; + + MethodCallType* methodCall = new MethodCallType( + aMethod, aThisVal, std::forward(aArgs)...); + RefPtr p = + new (typename PromiseType::Private)(aCallerName); + RefPtr r = new ProxyRunnableType(p, methodCall); + aTarget->Dispatch(r.forget()); + return p; +} + +constexpr bool Any() { return false; } + +template +constexpr bool Any(T1 a) { + return static_cast(a); +} + +template +constexpr bool Any(T1 a, Ts... aOthers) { + return a || Any(aOthers...); +} + +} // namespace detail + +// InvokeAsync with explicitly-specified storages. +// See ParameterStorage in nsThreadUtils.h for help. +template = 0> +static RefPtr InvokeAsync( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + static_assert( + sizeof...(Storages) == sizeof...(ArgTypes), + "Provided Storages and method's ArgTypes should have equal sizes"); + static_assert(sizeof...(Storages) == sizeof...(ActualArgTypes), + "Provided Storages and ActualArgTypes should have equal sizes"); + return detail::InvokeAsyncImpl( + aTarget, aThisVal, aCallerName, aMethod, + std::forward(aArgs)...); +} + +// InvokeAsync with no explicitly-specified storages, will copy arguments and +// then move them out of the runnable into the target method parameters. +template = 0> +static RefPtr InvokeAsync( + nsISerialEventTarget* aTarget, ThisType* aThisVal, const char* aCallerName, + RefPtr (ThisType::*aMethod)(ArgTypes...), + ActualArgTypes&&... aArgs) { + static_assert( + !detail::Any( + std::is_pointer_v>...), + "Cannot pass pointer types through InvokeAsync, Storages must be " + "provided"); + static_assert(sizeof...(ArgTypes) == sizeof...(ActualArgTypes), + "Method's ArgTypes and ActualArgTypes should have equal sizes"); + return detail::InvokeAsyncImpl< + StoreCopyPassByRRef>...>( + aTarget, aThisVal, aCallerName, aMethod, + std::forward(aArgs)...); +} + +namespace detail { + +template +class ProxyFunctionRunnable : public CancelableRunnable { + using FunctionStorage = std::decay_t; + + public: + template + ProxyFunctionRunnable(typename PromiseType::Private* aProxyPromise, + F&& aFunction) + : CancelableRunnable("detail::ProxyFunctionRunnable"), + mProxyPromise(aProxyPromise), + mFunction(new FunctionStorage(std::forward(aFunction))) {} + + NS_IMETHOD Run() override { + RefPtr p = (*mFunction)(); + mFunction = nullptr; + p->ChainTo(mProxyPromise.forget(), ""); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + + private: + RefPtr mProxyPromise; + UniquePtr mFunction; +}; + +// Note: The following struct and function are not for public consumption (yet?) +// as we would prefer all calls to pass on-the-spot lambdas (or at least moved +// function objects). They could be moved outside of detail if really needed. + +// We prefer getting function objects by non-lvalue-ref (to avoid copying them +// and their captures). This struct is a tag that allows the use of objects +// through lvalue-refs where necessary. +struct AllowInvokeAsyncFunctionLVRef {}; + +// Invoke a function object (e.g., lambda or std/mozilla::function) +// asynchronously; note that the object will be copied if provided by +// lvalue-ref. Return a promise that the function should eventually resolve or +// reject. +template +static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, + AllowInvokeAsyncFunctionLVRef, Function&& aFunction) + -> decltype(aFunction()) { + static_assert( + IsRefcountedSmartPointer::value && + IsMozPromise< + typename RemoveSmartPointer::Type>::value, + "Function object must return RefPtr"); + MOZ_ASSERT(aTarget); + typedef typename RemoveSmartPointer::Type PromiseType; + typedef detail::ProxyFunctionRunnable + ProxyRunnableType; + + auto p = MakeRefPtr(aCallerName); + auto r = MakeRefPtr(p, std::forward(aFunction)); + aTarget->Dispatch(r.forget()); + return p; +} + +} // namespace detail + +// Invoke a function object (e.g., lambda) asynchronously. +// Return a promise that the function should eventually resolve or reject. +template +static auto InvokeAsync(nsISerialEventTarget* aTarget, const char* aCallerName, + Function&& aFunction) -> decltype(aFunction()) { + static_assert(!std::is_lvalue_reference_v, + "Function object must not be passed by lvalue-ref (to avoid " + "unplanned copies); Consider move()ing the object."); + return detail::InvokeAsync(aTarget, aCallerName, + detail::AllowInvokeAsyncFunctionLVRef(), + std::forward(aFunction)); +} + +# undef PROMISE_LOG +# undef PROMISE_ASSERT +# undef PROMISE_DEBUG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/MozPromiseInlines.h b/xpcom/threads/MozPromiseInlines.h new file mode 100644 index 0000000000..def7e90461 --- /dev/null +++ b/xpcom/threads/MozPromiseInlines.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +#ifndef MozPromiseInlines_h_ +#define MozPromiseInlines_h_ + +#include + +#include "mozilla/MozPromise.h" +#include "mozilla/dom/PrimitiveConversions.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseNativeHandler.h" + +namespace mozilla { + +// Creates a C++ MozPromise from its JS counterpart, dom::Promise. +// FromDomPromise currently only supports primitive types (int8/16/32, float, +// double) And the reject value type must be a nsresult. +template +RefPtr> +MozPromise::FromDomPromise( + dom::Promise* aDOMPromise) { + static_assert(std::is_same_v, + "Reject type must be nsresult"); + RefPtr p = new Private(__func__); + RefPtr listener = new dom::DomPromiseListener( + [p](JSContext* aCx, JS::Handle aValue) { + ResolveValueT value; + bool ok = dom::ValueToPrimitive( + aCx, aValue, "Resolution value", &value); + if (!ok) { + p->Reject(NS_ERROR_FAILURE, __func__); + return; + } + p->Resolve(value, __func__); + }, + [p](nsresult aError) { p->Reject(aError, __func__); }); + aDOMPromise->AppendNativeHandler(listener); + return p; +} + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/Mutex.h b/xpcom/threads/Mutex.h new file mode 100644 index 0000000000..346e946166 --- /dev/null +++ b/xpcom/threads/Mutex.h @@ -0,0 +1,452 @@ +/* -*- 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/. */ + +#ifndef mozilla_Mutex_h +#define mozilla_Mutex_h + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/ThreadSafety.h" +#include "mozilla/PlatformMutex.h" +#include "nsISupports.h" + +// +// Provides: +// +// - Mutex, a non-recursive mutex +// - MutexAutoLock, an RAII class for ensuring that Mutexes are properly +// locked and unlocked +// - MutexAutoUnlock, complementary sibling to MutexAutoLock +// +// - OffTheBooksMutex, a non-recursive mutex that doesn't do leak checking +// - OffTheBooksMutexAuto{Lock,Unlock} - Like MutexAuto{Lock,Unlock}, but for +// an OffTheBooksMutex. +// +// Using MutexAutoLock/MutexAutoUnlock etc. is MUCH preferred to making bare +// calls to Lock and Unlock. +// +namespace mozilla { + +/** + * OffTheBooksMutex is identical to Mutex, except that OffTheBooksMutex doesn't + * include leak checking. Sometimes you want to intentionally "leak" a mutex + * until shutdown; in these cases, OffTheBooksMutex is for you. + */ +class MOZ_CAPABILITY("mutex") OffTheBooksMutex : public detail::MutexImpl, + BlockingResourceBase { + public: + /** + * @param aName A name which can reference this lock + * @returns If failure, nullptr + * If success, a valid Mutex* which must be destroyed + * by Mutex::DestroyMutex() + **/ + explicit OffTheBooksMutex(const char* aName) + : BlockingResourceBase(aName, eMutex) +#ifdef DEBUG + , + mOwningThread(nullptr) +#endif + { + } + + ~OffTheBooksMutex() { +#ifdef DEBUG + MOZ_ASSERT(!mOwningThread, "destroying a still-owned lock!"); +#endif + } + +#ifndef DEBUG + /** + * Lock this mutex. + **/ + void Lock() MOZ_CAPABILITY_ACQUIRE() { this->lock(); } + + /** + * Try to lock this mutex, returning true if we were successful. + **/ + [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true) { return this->tryLock(); } + + /** + * Unlock this mutex. + **/ + void Unlock() MOZ_CAPABILITY_RELEASE() { this->unlock(); } + + /** + * Assert that the current thread owns this mutex in debug builds. + * + * Does nothing in non-debug builds. + **/ + void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this) {} + + /** + * Assert that the current thread does not own this mutex. + * + * Note that this function is not implemented for debug builds *and* + * non-debug builds due to difficulties in dealing with memory ordering. + * + * It is therefore mostly useful as documentation. + **/ + void AssertNotCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(!this) {} + +#else + void Lock() MOZ_CAPABILITY_ACQUIRE(); + + [[nodiscard]] bool TryLock() MOZ_TRY_ACQUIRE(true); + void Unlock() MOZ_CAPABILITY_RELEASE(); + + void AssertCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(this); + void AssertNotCurrentThreadOwns() const MOZ_ASSERT_CAPABILITY(!this) { + // FIXME bug 476536 + } +#endif // ifndef DEBUG + + private: + OffTheBooksMutex() = delete; + OffTheBooksMutex(const OffTheBooksMutex&) = delete; + OffTheBooksMutex& operator=(const OffTheBooksMutex&) = delete; + + friend class OffTheBooksCondVar; + +#ifdef DEBUG + PRThread* mOwningThread; +#endif +}; + +/** + * Mutex + * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this + * mutex within a scope, instead of calling Lock/Unlock directly. + */ +class Mutex : public OffTheBooksMutex { + public: + explicit Mutex(const char* aName) : OffTheBooksMutex(aName) { + MOZ_COUNT_CTOR(Mutex); + } + + MOZ_COUNTED_DTOR(Mutex) + + private: + Mutex() = delete; + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; +}; + +/** + * MutexSingleWriter + * + * Mutex where a single writer exists, so that reads from the same thread + * will not generate data races or consistency issues. + * + * When possible, use MutexAutoLock/MutexAutoUnlock to lock/unlock this + * mutex within a scope, instead of calling Lock/Unlock directly. + * + * This requires an object implementing Mutex's SingleWriterLockOwner, so + * we can do correct-thread checks. + */ +// Subclass this in the object owning the mutex +class SingleWriterLockOwner { + public: + SingleWriterLockOwner() = default; + ~SingleWriterLockOwner() = default; + + virtual bool OnWritingThread() const = 0; +}; + +class MutexSingleWriter : public OffTheBooksMutex { + public: + // aOwner should be the object that contains the mutex, typically. We + // will use that object (which must have a lifetime the same or greater + // than this object) to verify that we're running on the correct thread, + // typically only in DEBUG builds + explicit MutexSingleWriter(const char* aName, SingleWriterLockOwner* aOwner) + : OffTheBooksMutex(aName) +#ifdef DEBUG + , + mOwner(aOwner) +#endif + { + MOZ_COUNT_CTOR(MutexSingleWriter); + MOZ_ASSERT(mOwner); + } + + MOZ_COUNTED_DTOR(MutexSingleWriter) + + /** + * Statically assert that we're on the only thread that modifies data + * guarded by this Mutex. This allows static checking for the pattern of + * having a single thread modify a set of data, and read it (under lock) + * on other threads, and reads on the thread that modifies it doesn't + * require a lock. This doesn't solve the issue of some data under the + * Mutex following this pattern, and other data under the mutex being + * written from multiple threads. + * + * We could set the writing thread and dynamically check it in debug + * builds, but this doesn't. We could also use thread-safety/capability + * system to provide direct thread assertions. + **/ + void AssertOnWritingThread() const MOZ_ASSERT_CAPABILITY(this) { + MOZ_ASSERT(mOwner->OnWritingThread()); + } + void AssertOnWritingThreadOrHeld() const MOZ_ASSERT_CAPABILITY(this) { +#ifdef DEBUG + if (!mOwner->OnWritingThread()) { + AssertCurrentThreadOwns(); + } +#endif + } + + private: +#ifdef DEBUG + SingleWriterLockOwner* mOwner MOZ_UNSAFE_REF( + "This is normally the object that contains the MonitorSingleWriter, so " + "we don't want to hold a reference to ourselves"); +#endif + + MutexSingleWriter() = delete; + MutexSingleWriter(const MutexSingleWriter&) = delete; + MutexSingleWriter& operator=(const MutexSingleWriter&) = delete; +}; + +namespace detail { +template +class MOZ_RAII BaseAutoUnlock; + +/** + * MutexAutoLock + * Acquires the Mutex when it enters scope, and releases it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to Mutex.Lock and Unlock. + */ +template +class MOZ_RAII MOZ_SCOPED_CAPABILITY BaseAutoLock { + public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aLock A valid mozilla::Mutex* returned by + * mozilla::Mutex::NewMutex. + **/ + explicit BaseAutoLock(T aLock) MOZ_CAPABILITY_ACQUIRE(aLock) : mLock(aLock) { + mLock.Lock(); + } + + ~BaseAutoLock(void) MOZ_CAPABILITY_RELEASE() { mLock.Unlock(); } + + // Assert that aLock is the mutex passed to the constructor and that the + // current thread owns the mutex. In coding patterns such as: + // + // void LockedMethod(const BaseAutoLock& aProofOfLock) + // { + // aProofOfLock.AssertOwns(mMutex); + // ... + // } + // + // Without this assertion, it could be that mMutex is not actually + // locked. It's possible to have code like: + // + // BaseAutoLock lock(someMutex); + // ... + // BaseAutoUnlock unlock(someMutex); + // ... + // LockedMethod(lock); + // + // and in such a case, simply asserting that the mutex pointers match is not + // sufficient; mutex ownership must be asserted as well. + // + // Note that if you are going to use the coding pattern presented above, you + // should use this method in preference to using AssertCurrentThreadOwns on + // the mutex you expected to be held, since this method provides stronger + // guarantees. + void AssertOwns(const T& aMutex) const MOZ_ASSERT_CAPABILITY(aMutex) { + MOZ_ASSERT(&aMutex == &mLock); + mLock.AssertCurrentThreadOwns(); + } + + private: + BaseAutoLock() = delete; + BaseAutoLock(BaseAutoLock&) = delete; + BaseAutoLock& operator=(BaseAutoLock&) = delete; + static void* operator new(size_t) noexcept(true); + + friend class BaseAutoUnlock; + + T mLock; +}; + +template +BaseAutoLock(MutexType&) -> BaseAutoLock; +} // namespace detail + +typedef detail::BaseAutoLock MutexAutoLock; +typedef detail::BaseAutoLock MutexSingleWriterAutoLock; +typedef detail::BaseAutoLock OffTheBooksMutexAutoLock; + +// Use if we've done AssertOnWritingThread(), and then later need to take the +// lock to write to a protected member. Instead of +// MutexSingleWriterAutoLock lock(mutex) +// use +// MutexSingleWriterAutoLockOnThread(lock, mutex) +#define MutexSingleWriterAutoLockOnThread(lock, mutex) \ + MOZ_PUSH_IGNORE_THREAD_SAFETY \ + MutexSingleWriterAutoLock lock(mutex); \ + MOZ_POP_THREAD_SAFETY + +namespace detail { +/** + * ReleasableMutexAutoLock + * Acquires the Mutex when it enters scope, and releases it when it leaves + * scope. Allows calling Unlock (and Lock) as an alternative to + * MutexAutoUnlock; this can avoid an extra lock/unlock pair. + * + */ +template +class MOZ_RAII MOZ_SCOPED_CAPABILITY ReleasableBaseAutoLock { + public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aLock A valid mozilla::Mutex& returned by + * mozilla::Mutex::NewMutex. + **/ + explicit ReleasableBaseAutoLock(T aLock) MOZ_CAPABILITY_ACQUIRE(aLock) + : mLock(aLock) { + mLock.Lock(); + mLocked = true; + } + + ~ReleasableBaseAutoLock(void) MOZ_CAPABILITY_RELEASE() { + if (mLocked) { + Unlock(); + } + } + + void AssertOwns(const T& aMutex) const MOZ_ASSERT_CAPABILITY(aMutex) { + MOZ_ASSERT(&aMutex == &mLock); + mLock.AssertCurrentThreadOwns(); + } + + // Allow dropping the lock prematurely; for example to support something like: + // clang-format off + // MutexAutoLock lock(mMutex); + // ... + // if (foo) { + // lock.Unlock(); + // MethodThatCantBeCalledWithLock() + // return; + // } + // clang-format on + void Unlock() MOZ_CAPABILITY_RELEASE() { + MOZ_ASSERT(mLocked); + mLock.Unlock(); + mLocked = false; + } + void Lock() MOZ_CAPABILITY_ACQUIRE() { + MOZ_ASSERT(!mLocked); + mLock.Lock(); + mLocked = true; + } + + private: + ReleasableBaseAutoLock() = delete; + ReleasableBaseAutoLock(ReleasableBaseAutoLock&) = delete; + ReleasableBaseAutoLock& operator=(ReleasableBaseAutoLock&) = delete; + static void* operator new(size_t) noexcept(true); + + bool mLocked; + T mLock; +}; + +template +ReleasableBaseAutoLock(MutexType&) -> ReleasableBaseAutoLock; +} // namespace detail + +typedef detail::ReleasableBaseAutoLock ReleasableMutexAutoLock; + +namespace detail { +/** + * BaseAutoUnlock + * Releases the Mutex when it enters scope, and re-acquires it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to Mutex.Unlock and Lock. + */ +template +class MOZ_RAII MOZ_SCOPED_CAPABILITY BaseAutoUnlock { + public: + explicit BaseAutoUnlock(T aLock) MOZ_SCOPED_UNLOCK_RELEASE(aLock) + : mLock(aLock) { + mLock.Unlock(); + } + + explicit BaseAutoUnlock(BaseAutoLock& aAutoLock) + /* MOZ_CAPABILITY_RELEASE(aAutoLock.mLock) */ + : mLock(aAutoLock.mLock) { + NS_ASSERTION(mLock, "null lock"); + mLock->Unlock(); + } + + ~BaseAutoUnlock() MOZ_SCOPED_UNLOCK_REACQUIRE() { mLock.Lock(); } + + private: + BaseAutoUnlock() = delete; + BaseAutoUnlock(BaseAutoUnlock&) = delete; + BaseAutoUnlock& operator=(BaseAutoUnlock&) = delete; + static void* operator new(size_t) noexcept(true); + + T mLock; +}; + +template +BaseAutoUnlock(MutexType&) -> BaseAutoUnlock; +} // namespace detail + +typedef detail::BaseAutoUnlock MutexAutoUnlock; +typedef detail::BaseAutoUnlock MutexSingleWriterAutoUnlock; +typedef detail::BaseAutoUnlock OffTheBooksMutexAutoUnlock; + +namespace detail { +/** + * BaseAutoTryLock + * Tries to acquire the Mutex when it enters scope, and releases it when it + * leaves scope. + * + * MUCH PREFERRED to bare calls to Mutex.TryLock and Unlock. + */ +template +class MOZ_RAII MOZ_SCOPED_CAPABILITY BaseAutoTryLock { + public: + explicit BaseAutoTryLock(T& aLock) MOZ_CAPABILITY_ACQUIRE(aLock) + : mLock(aLock.TryLock() ? &aLock : nullptr) {} + + ~BaseAutoTryLock() MOZ_CAPABILITY_RELEASE() { + if (mLock) { + mLock->Unlock(); + mLock = nullptr; + } + } + + explicit operator bool() const { return mLock; } + + private: + BaseAutoTryLock(BaseAutoTryLock&) = delete; + BaseAutoTryLock& operator=(BaseAutoTryLock&) = delete; + static void* operator new(size_t) noexcept(true); + + T* mLock; +}; +} // namespace detail + +typedef detail::BaseAutoTryLock MutexAutoTryLock; +typedef detail::BaseAutoTryLock OffTheBooksMutexAutoTryLock; + +} // namespace mozilla + +#endif // ifndef mozilla_Mutex_h diff --git a/xpcom/threads/PerformanceCounter.cpp b/xpcom/threads/PerformanceCounter.cpp new file mode 100644 index 0000000000..65ee441809 --- /dev/null +++ b/xpcom/threads/PerformanceCounter.cpp @@ -0,0 +1,73 @@ +/* -*- 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/Logging.h" +#include "mozilla/PerformanceCounter.h" + +using mozilla::DispatchCategory; +using mozilla::DispatchCounter; +using mozilla::PerformanceCounter; + +static mozilla::LazyLogModule sPerformanceCounter("PerformanceCounter"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sPerformanceCounter, mozilla::LogLevel::Debug, args) + +// Global counter used by PerformanceCounter CTOR via NextCounterID(). +static mozilla::Atomic gNextCounterID(0); + +static uint64_t NextCounterID() { + // This can return the same value on different processes but + // we're fine with this behavior because consumers can use a (pid, counter_id) + // tuple to make instances globally unique in a browser session. + return ++gNextCounterID; +} + +// this instance is the extension for the worker +const DispatchCategory DispatchCategory::Worker = + DispatchCategory((uint32_t)TaskCategory::Count); + +PerformanceCounter::PerformanceCounter(const nsACString& aName) + : mExecutionDuration(0), + mTotalDispatchCount(0), + mDispatchCounter(), + mName(aName), + mID(NextCounterID()) { + LOG(("PerformanceCounter created with ID %" PRIu64, mID)); +} + +void PerformanceCounter::IncrementDispatchCounter(DispatchCategory aCategory) { + mDispatchCounter[aCategory.GetValue()] += 1; + mTotalDispatchCount += 1; + LOG(("[%s][%" PRIu64 "] Total dispatch %" PRIu64, mName.get(), GetID(), + uint64_t(mTotalDispatchCount))); +} + +void PerformanceCounter::IncrementExecutionDuration(uint32_t aMicroseconds) { + mExecutionDuration += aMicroseconds; + LOG(("[%s][%" PRIu64 "] Total duration %" PRIu64, mName.get(), GetID(), + uint64_t(mExecutionDuration))); +} + +const DispatchCounter& PerformanceCounter::GetDispatchCounter() const { + return mDispatchCounter; +} + +uint64_t PerformanceCounter::GetExecutionDuration() const { + return mExecutionDuration; +} + +uint64_t PerformanceCounter::GetTotalDispatchCount() const { + return mTotalDispatchCount; +} + +uint32_t PerformanceCounter::GetDispatchCount( + DispatchCategory aCategory) const { + return mDispatchCounter[aCategory.GetValue()]; +} + +uint64_t PerformanceCounter::GetID() const { return mID; } diff --git a/xpcom/threads/PerformanceCounter.h b/xpcom/threads/PerformanceCounter.h new file mode 100644 index 0000000000..4181320e10 --- /dev/null +++ b/xpcom/threads/PerformanceCounter.h @@ -0,0 +1,139 @@ +/* -*- 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/. */ + +#ifndef mozilla_PerformanceCounter_h +#define mozilla_PerformanceCounter_h + +#include "mozilla/Array.h" +#include "mozilla/Atomics.h" +#include "mozilla/TaskCategory.h" +#include "nsISupportsImpl.h" +#include "nsString.h" + +namespace mozilla { + +/* + * The DispatchCategory class is used to fake the inheritance + * of the TaskCategory enum so we can extend it to hold + * one more value corresponding to the category + * we use when a worker dispatches a call. + * + */ +class DispatchCategory final { + public: + explicit DispatchCategory(uint32_t aValue) : mValue(aValue) { + // Since DispatchCategory is adding one single value to the + // TaskCategory enum, we can check here that the value is + // the next index e.g. TaskCategory::Count + MOZ_ASSERT(aValue == (uint32_t)TaskCategory::Count); + } + + constexpr explicit DispatchCategory(TaskCategory aValue) + : mValue((uint32_t)aValue) {} + + uint32_t GetValue() const { return mValue; } + + static const DispatchCategory Worker; + + private: + uint32_t mValue; +}; + +typedef Array, (uint32_t)TaskCategory::Count + 1> + DispatchCounter; + +// PerformanceCounter is a class that can be used to keep track of +// runnable execution times and dispatch counts. +// +// - runnable execution time: time spent in a runnable when called +// in nsThread::ProcessNextEvent (not counting recursive calls) +// - dispatch counts: number of times a tracked runnable is dispatched +// in nsThread. Useful to measure the activity of a tab or worker. +// +// The PerformanceCounter class is currently instantiated in DocGroup +// and WorkerPrivate in order to count how many scheduler dispatches +// are done through them, and how long the execution lasts. +// +// The execution time is calculated by the nsThread class (and its +// inherited WorkerThread class) in its ProcessNextEvent method. +// +// For each processed runnable, nsThread will reach out the +// PerformanceCounter attached to the runnable via its DocGroup +// or WorkerPrivate and call IncrementExecutionDuration() +// +// Notice that the execution duration counting takes into account +// recursivity. If an event triggers a recursive call to +// nsThread::ProcessNextEVent, the counter will discard the time +// spent in sub events. +class PerformanceCounter final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PerformanceCounter) + + explicit PerformanceCounter(const nsACString& aName); + + /** + * This is called everytime a runnable is dispatched. + * + * aCategory can be used to distinguish counts per TaskCategory + * + * Note that an overflow will simply reset the counter. + */ + void IncrementDispatchCounter(DispatchCategory aCategory); + + /** + * This is called via nsThread::ProcessNextEvent to measure runnable + * execution duration. + * + * Note that an overflow will simply reset the counter. + */ + void IncrementExecutionDuration(uint32_t aMicroseconds); + + /** + * Returns a category/counter array of all dispatches. + */ + const DispatchCounter& GetDispatchCounter() const; + + /** + * Returns the total execution duration. + */ + uint64_t GetExecutionDuration() const; + + /** + * Returns the number of dispatches per TaskCategory. + */ + uint32_t GetDispatchCount(DispatchCategory aCategory) const; + + /** + * Returns the total number of dispatches. + */ + uint64_t GetTotalDispatchCount() const; + + /** + * Returns the unique id for the instance. + * + * Used to distinguish instances since the lifespan of + * a PerformanceCounter can be shorter than the + * host it's tracking. That leads to edge cases + * where a counter appears to have values that go + * backwards. Having this id let the consumers + * detect that they are dealing with a new counter + * when it happens. + */ + uint64_t GetID() const; + + private: + ~PerformanceCounter() = default; + + Atomic mExecutionDuration; + Atomic mTotalDispatchCount; + DispatchCounter mDispatchCounter; + nsCString mName; + const uint64_t mID; +}; + +} // namespace mozilla + +#endif // mozilla_PerformanceCounter_h diff --git a/xpcom/threads/Queue.h b/xpcom/threads/Queue.h new file mode 100644 index 0000000000..fa36433fdf --- /dev/null +++ b/xpcom/threads/Queue.h @@ -0,0 +1,265 @@ +/* -*- 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/. */ + +#ifndef mozilla_Queue_h +#define mozilla_Queue_h + +#include +#include +#include "mozilla/MemoryReporting.h" +#include "mozilla/Assertions.h" +#include "mozalloc.h" + +namespace mozilla { + +// define to turn on additional (DEBUG) asserts +// #define EXTRA_ASSERTS 1 + +// A queue implements a singly linked list of pages, each of which contains some +// number of elements. Since the queue needs to store a "next" pointer, the +// actual number of elements per page won't be quite as many as were requested. +// +// Each page consists of N entries. We use the head buffer as a circular buffer +// if it's the only buffer; if we have more than one buffer when the head is +// empty we release it. This avoids occasional freeing and reallocating buffers +// every N entries. We'll still allocate and free every N if the normal queue +// depth is greated than N. A fancier solution would be to move an empty Head +// buffer to be an empty tail buffer, freeing if we have multiple empty tails, +// but that probably isn't worth it. +// +// Cases: +// a) single buffer, circular +// Push: if not full: +// Add to tail, bump tail and reset to 0 if at end +// full: +// Add new page, insert there and set tail to 1 +// Pop: +// take entry and bump head, reset to 0 if at end +// b) multiple buffers: +// Push: if not full: +// Add to tail, bump tail +// full: +// Add new page, insert there and set tail to 1 +// Pop: +// take entry and bump head, reset to 0 if at end +// if buffer is empty, free head buffer and promote next to head +// +template +class Queue { + public: + Queue() = default; + + Queue(Queue&& aOther) noexcept + : mHead(std::exchange(aOther.mHead, nullptr)), + mTail(std::exchange(aOther.mTail, nullptr)), + mOffsetHead(std::exchange(aOther.mOffsetHead, 0)), + mHeadLength(std::exchange(aOther.mHeadLength, 0)), + mTailLength(std::exchange(aOther.mTailLength, 0)) {} + + Queue& operator=(Queue&& aOther) noexcept { + Clear(); + + mHead = std::exchange(aOther.mHead, nullptr); + mTail = std::exchange(aOther.mTail, nullptr); + mOffsetHead = std::exchange(aOther.mOffsetHead, 0); + mHeadLength = std::exchange(aOther.mHeadLength, 0); + mTailLength = std::exchange(aOther.mTailLength, 0); + return *this; + } + + ~Queue() { Clear(); } + + // Discard all elements form the queue, clearing it to be empty. + void Clear() { + while (!IsEmpty()) { + Pop(); + } + if (mHead) { + free(mHead); + mHead = nullptr; + } + } + + T& Push(T&& aElement) { +#if defined(EXTRA_ASSERTS) && DEBUG + size_t original_length = Count(); +#endif + if (!mHead) { + mHead = NewPage(); + MOZ_ASSERT(mHead); + + mTail = mHead; + T* eltPtr = &mTail->mEvents[0]; + new (eltPtr) T(std::move(aElement)); + mOffsetHead = 0; + mHeadLength = 1; +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return *eltPtr; + } + if ((mHead == mTail && mHeadLength == ItemsPerPage) || + (mHead != mTail && mTailLength == ItemsPerPage)) { + // either we have one (circular) buffer and it's full, or + // we have multiple buffers and the last buffer is full + Page* page = NewPage(); + MOZ_ASSERT(page); + + mTail->mNext = page; + mTail = page; + T* eltPtr = &page->mEvents[0]; + new (eltPtr) T(std::move(aElement)); + mTailLength = 1; +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return *eltPtr; + } + if (mHead == mTail) { + // we have space in the (single) head buffer + uint16_t offset = (mOffsetHead + mHeadLength++) % ItemsPerPage; + T* eltPtr = &mTail->mEvents[offset]; + new (eltPtr) T(std::move(aElement)); +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return *eltPtr; + } + // else we have space to insert into last buffer + T* eltPtr = &mTail->mEvents[mTailLength++]; + new (eltPtr) T(std::move(aElement)); +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length + 1); +#endif + return *eltPtr; + } + + bool IsEmpty() const { + return !mHead || (mHead == mTail && mHeadLength == 0); + } + + T Pop() { +#if defined(EXTRA_ASSERTS) && DEBUG + size_t original_length = Count(); +#endif + MOZ_ASSERT(!IsEmpty()); + + T result = std::move(mHead->mEvents[mOffsetHead]); + mHead->mEvents[mOffsetHead].~T(); + mOffsetHead = (mOffsetHead + 1) % ItemsPerPage; + mHeadLength -= 1; + + // Check if mHead points to empty (circular) Page and we have more + // pages + if (mHead != mTail && mHeadLength == 0) { + Page* dead = mHead; + mHead = mHead->mNext; + free(dead); + mOffsetHead = 0; + // if there are still >1 pages, the new head is full. + if (mHead != mTail) { + mHeadLength = ItemsPerPage; + } else { + mHeadLength = mTailLength; + mTailLength = 0; + } + } + +#ifdef EXTRA_ASSERTS + MOZ_ASSERT(Count() == original_length - 1); +#endif + return result; + } + + T& FirstElement() { + MOZ_ASSERT(!IsEmpty()); + return mHead->mEvents[mOffsetHead]; + } + + const T& FirstElement() const { + MOZ_ASSERT(!IsEmpty()); + return mHead->mEvents[mOffsetHead]; + } + + T& LastElement() { + MOZ_ASSERT(!IsEmpty()); + uint16_t offset = + mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1; + return mTail->mEvents[offset]; + } + + const T& LastElement() const { + MOZ_ASSERT(!IsEmpty()); + uint16_t offset = + mHead == mTail ? mOffsetHead + mHeadLength - 1 : mTailLength - 1; + return mTail->mEvents[offset]; + } + + size_t Count() const { + // It is obvious count is 0 when the queue is empty. + if (!mHead) { + return 0; + } + + // Compute full (intermediate) pages; Doesn't count first or last page + int count = 0; + // 1 buffer will have mHead == mTail; 2 will have mHead->mNext == mTail + for (Page* page = mHead; page != mTail && page->mNext != mTail; + page = page->mNext) { + count += ItemsPerPage; + } + // add first and last page + count += mHeadLength + mTailLength; + MOZ_ASSERT(count >= 0); + + return count; + } + + size_t ShallowSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mHead) { + for (Page* page = mHead; page != mTail; page = page->mNext) { + n += aMallocSizeOf(page); + } + } + return n; + } + + size_t ShallowSizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(this) + ShallowSizeOfExcludingThis(aMallocSizeOf); + } + + private: + static_assert( + (RequestedItemsPerPage & (RequestedItemsPerPage - 1)) == 0, + "RequestedItemsPerPage should be a power of two to avoid heap slop."); + + // Since a Page must also contain a "next" pointer, we use one of the items to + // store this pointer. If sizeof(T) > sizeof(Page*), then some space will be + // wasted. So be it. + static const size_t ItemsPerPage = RequestedItemsPerPage - 1; + + // Page objects are linked together to form a simple deque. + struct Page { + struct Page* mNext; + T mEvents[ItemsPerPage]; + }; + + static Page* NewPage() { + return static_cast(moz_xcalloc(1, sizeof(Page))); + } + + Page* mHead = nullptr; + Page* mTail = nullptr; + + uint16_t mOffsetHead = 0; // Read position in head page + uint16_t mHeadLength = 0; // Number of items in the head page + uint16_t mTailLength = 0; // Number of items in the tail page +}; + +} // namespace mozilla + +#endif // mozilla_Queue_h diff --git a/xpcom/threads/RWLock.cpp b/xpcom/threads/RWLock.cpp new file mode 100644 index 0000000000..949934c8cc --- /dev/null +++ b/xpcom/threads/RWLock.cpp @@ -0,0 +1,28 @@ +/* -*- 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/RWLock.h" + +namespace mozilla { + +RWLock::RWLock(const char* aName) + : BlockingResourceBase(aName, eMutex) +#ifdef DEBUG + , + mOwningThread(nullptr) +#endif +{ +} + +#ifdef DEBUG +bool RWLock::LockedForWritingByCurrentThread() { + return mOwningThread == PR_GetCurrentThread(); +} +#endif + +} // namespace mozilla + +#undef NativeHandle diff --git a/xpcom/threads/RWLock.h b/xpcom/threads/RWLock.h new file mode 100644 index 0000000000..e03d008631 --- /dev/null +++ b/xpcom/threads/RWLock.h @@ -0,0 +1,243 @@ +/* -*- 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/. */ + +// An interface for read-write locks. + +#ifndef mozilla_RWLock_h +#define mozilla_RWLock_h + +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/PlatformRWLock.h" +#include "mozilla/ThreadSafety.h" + +namespace mozilla { + +// A RWLock is similar to a Mutex, but whereas a Mutex permits only a single +// reader thread or a single writer thread to access a piece of data, a +// RWLock distinguishes between readers and writers: you may have multiple +// reader threads concurrently accessing a piece of data or a single writer +// thread. This difference should guide your usage of RWLock: if you are not +// reading the data from multiple threads simultaneously or you are writing +// to the data roughly as often as read from it, then Mutex will suit your +// purposes just fine. +// +// You should be using the AutoReadLock and AutoWriteLock classes, below, +// for RAII read locking and write locking, respectively. If you really must +// take a read lock manually, call the ReadLock method; to relinquish that +// read lock, call the ReadUnlock method. Similarly, WriteLock and WriteUnlock +// perform the same operations, but for write locks. +// +// It is unspecified what happens when a given thread attempts to acquire the +// same lock in multiple ways; some underlying implementations of RWLock do +// support acquiring a read lock multiple times on a given thread, but you +// should not rely on this behavior. +// +// It is unspecified whether RWLock gives priority to waiting readers or +// a waiting writer when unlocking. +class MOZ_CAPABILITY("rwlock") RWLock : public detail::RWLockImpl, + public BlockingResourceBase { + public: + explicit RWLock(const char* aName); + +#ifdef DEBUG + bool LockedForWritingByCurrentThread(); + [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true); + void ReadLock() MOZ_ACQUIRE_SHARED(); + void ReadUnlock() MOZ_RELEASE_SHARED(); + [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true); + void WriteLock() MOZ_CAPABILITY_ACQUIRE(); + void WriteUnlock() MOZ_EXCLUSIVE_RELEASE(); +#else + [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true) { + return detail::RWLockImpl::tryReadLock(); + } + void ReadLock() MOZ_ACQUIRE_SHARED() { detail::RWLockImpl::readLock(); } + void ReadUnlock() MOZ_RELEASE_SHARED() { detail::RWLockImpl::readUnlock(); } + [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true) { + return detail::RWLockImpl::tryWriteLock(); + } + void WriteLock() MOZ_CAPABILITY_ACQUIRE() { detail::RWLockImpl::writeLock(); } + void WriteUnlock() MOZ_EXCLUSIVE_RELEASE() { + detail::RWLockImpl::writeUnlock(); + } +#endif + + private: + RWLock() = delete; + RWLock(const RWLock&) = delete; + RWLock& operator=(const RWLock&) = delete; + +#ifdef DEBUG + // We record the owning thread for write locks only. + PRThread* mOwningThread; +#endif +}; + +// We only use this once; not sure we can add thread safety attributions here +template +class MOZ_RAII BaseAutoTryReadLock { + public: + explicit BaseAutoTryReadLock(T& aLock) + : mLock(aLock.TryReadLock() ? &aLock : nullptr) {} + + ~BaseAutoTryReadLock() { + if (mLock) { + mLock->ReadUnlock(); + } + } + + explicit operator bool() const { return mLock; } + + private: + BaseAutoTryReadLock() = delete; + BaseAutoTryReadLock(const BaseAutoTryReadLock&) = delete; + BaseAutoTryReadLock& operator=(const BaseAutoTryReadLock&) = delete; + + T* mLock; +}; + +template +class MOZ_SCOPED_CAPABILITY MOZ_RAII BaseAutoReadLock { + public: + explicit BaseAutoReadLock(T& aLock) MOZ_ACQUIRE_SHARED(aLock) + : mLock(&aLock) { + MOZ_ASSERT(mLock, "null lock"); + mLock->ReadLock(); + } + + // Not MOZ_RELEASE_SHARED(), which would make sense - apparently this trips + // over a bug in clang's static analyzer and it says it expected an + // exclusive unlock. + ~BaseAutoReadLock() MOZ_RELEASE_GENERIC() { mLock->ReadUnlock(); } + + private: + BaseAutoReadLock() = delete; + BaseAutoReadLock(const BaseAutoReadLock&) = delete; + BaseAutoReadLock& operator=(const BaseAutoReadLock&) = delete; + + T* mLock; +}; + +// XXX Mutex attributions? +template +class MOZ_RAII BaseAutoTryWriteLock { + public: + explicit BaseAutoTryWriteLock(T& aLock) + : mLock(aLock.TryWriteLock() ? &aLock : nullptr) {} + + ~BaseAutoTryWriteLock() { + if (mLock) { + mLock->WriteUnlock(); + } + } + + explicit operator bool() const { return mLock; } + + private: + BaseAutoTryWriteLock() = delete; + BaseAutoTryWriteLock(const BaseAutoTryWriteLock&) = delete; + BaseAutoTryWriteLock& operator=(const BaseAutoTryWriteLock&) = delete; + + T* mLock; +}; + +template +class MOZ_SCOPED_CAPABILITY MOZ_RAII BaseAutoWriteLock final { + public: + explicit BaseAutoWriteLock(T& aLock) MOZ_CAPABILITY_ACQUIRE(aLock) + : mLock(&aLock) { + MOZ_ASSERT(mLock, "null lock"); + mLock->WriteLock(); + } + + ~BaseAutoWriteLock() MOZ_CAPABILITY_RELEASE() { mLock->WriteUnlock(); } + + private: + BaseAutoWriteLock() = delete; + BaseAutoWriteLock(const BaseAutoWriteLock&) = delete; + BaseAutoWriteLock& operator=(const BaseAutoWriteLock&) = delete; + + T* mLock; +}; + +// Read try-lock and unlock a RWLock with RAII semantics. Much preferred to +// bare calls to TryReadLock() and ReadUnlock(). +typedef BaseAutoTryReadLock AutoTryReadLock; + +// Read lock and unlock a RWLock with RAII semantics. Much preferred to bare +// calls to ReadLock() and ReadUnlock(). +typedef BaseAutoReadLock AutoReadLock; + +// Write try-lock and unlock a RWLock with RAII semantics. Much preferred to +// bare calls to TryWriteLock() and WriteUnlock(). +typedef BaseAutoTryWriteLock AutoTryWriteLock; + +// Write lock and unlock a RWLock with RAII semantics. Much preferred to bare +// calls to WriteLock() and WriteUnlock(). +typedef BaseAutoWriteLock AutoWriteLock; + +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS MOZ_CAPABILITY("rwlock") + StaticRWLock { + public: + // In debug builds, check that mLock is initialized for us as we expect by + // the compiler. In non-debug builds, don't declare a constructor so that + // the compiler can see that the constructor is trivial. +#ifdef DEBUG + StaticRWLock() { MOZ_ASSERT(!mLock); } +#endif + + [[nodiscard]] bool TryReadLock() MOZ_SHARED_TRYLOCK_FUNCTION(true) { + return Lock()->TryReadLock(); + } + void ReadLock() MOZ_ACQUIRE_SHARED() { Lock()->ReadLock(); } + void ReadUnlock() MOZ_RELEASE_SHARED() { Lock()->ReadUnlock(); } + [[nodiscard]] bool TryWriteLock() MOZ_TRY_ACQUIRE(true) { + return Lock()->TryWriteLock(); + } + void WriteLock() MOZ_CAPABILITY_ACQUIRE() { Lock()->WriteLock(); } + void WriteUnlock() MOZ_EXCLUSIVE_RELEASE() { Lock()->WriteUnlock(); } + + private: + [[nodiscard]] RWLock* Lock() MOZ_RETURN_CAPABILITY(*mLock) { + if (mLock) { + return mLock; + } + + RWLock* lock = new RWLock("StaticRWLock"); + if (!mLock.compareExchange(nullptr, lock)) { + delete lock; + } + + return mLock; + } + + Atomic mLock; + + // Disallow copy constructor, but only in debug mode. We only define + // a default constructor in debug mode (see above); if we declared + // this constructor always, the compiler wouldn't generate a trivial + // default constructor for us in non-debug mode. +#ifdef DEBUG + StaticRWLock(const StaticRWLock& aOther); +#endif + + // Disallow these operators. + StaticRWLock& operator=(StaticRWLock* aRhs) = delete; + static void* operator new(size_t) noexcept(true) = delete; + static void operator delete(void*) = delete; +}; + +typedef BaseAutoTryReadLock StaticAutoTryReadLock; +typedef BaseAutoReadLock StaticAutoReadLock; +typedef BaseAutoTryWriteLock StaticAutoTryWriteLock; +typedef BaseAutoWriteLock StaticAutoWriteLock; + +} // namespace mozilla + +#endif // mozilla_RWLock_h diff --git a/xpcom/threads/RecursiveMutex.cpp b/xpcom/threads/RecursiveMutex.cpp new file mode 100644 index 0000000000..7c45052c07 --- /dev/null +++ b/xpcom/threads/RecursiveMutex.cpp @@ -0,0 +1,85 @@ +/* -*- 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/RecursiveMutex.h" + +#ifdef XP_WIN +# include + +# define NativeHandle(m) (reinterpret_cast(&m)) +#endif + +namespace mozilla { + +RecursiveMutex::RecursiveMutex(const char* aName) + : BlockingResourceBase(aName, eRecursiveMutex) +#ifdef DEBUG + , + mOwningThread(nullptr), + mEntryCount(0) +#endif +{ +#ifdef XP_WIN + // This number was adapted from NSPR. + static const DWORD sLockSpinCount = 100; + +# if defined(RELEASE_OR_BETA) + // Vista and later automatically allocate and subsequently leak a debug info + // object for each critical section that we allocate unless we tell the + // system not to do that. + DWORD flags = CRITICAL_SECTION_NO_DEBUG_INFO; +# else + DWORD flags = 0; +# endif + BOOL r = + InitializeCriticalSectionEx(NativeHandle(mMutex), sLockSpinCount, flags); + MOZ_RELEASE_ASSERT(r); +#else + pthread_mutexattr_t attr; + + MOZ_RELEASE_ASSERT(pthread_mutexattr_init(&attr) == 0, + "pthread_mutexattr_init failed"); + + MOZ_RELEASE_ASSERT( + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) == 0, + "pthread_mutexattr_settype failed"); + + MOZ_RELEASE_ASSERT(pthread_mutex_init(&mMutex, &attr) == 0, + "pthread_mutex_init failed"); + + MOZ_RELEASE_ASSERT(pthread_mutexattr_destroy(&attr) == 0, + "pthread_mutexattr_destroy failed"); +#endif +} + +RecursiveMutex::~RecursiveMutex() { +#ifdef XP_WIN + DeleteCriticalSection(NativeHandle(mMutex)); +#else + MOZ_RELEASE_ASSERT(pthread_mutex_destroy(&mMutex) == 0, + "pthread_mutex_destroy failed"); +#endif +} + +void RecursiveMutex::LockInternal() { +#ifdef XP_WIN + EnterCriticalSection(NativeHandle(mMutex)); +#else + MOZ_RELEASE_ASSERT(pthread_mutex_lock(&mMutex) == 0, + "pthread_mutex_lock failed"); +#endif +} + +void RecursiveMutex::UnlockInternal() { +#ifdef XP_WIN + LeaveCriticalSection(NativeHandle(mMutex)); +#else + MOZ_RELEASE_ASSERT(pthread_mutex_unlock(&mMutex) == 0, + "pthread_mutex_unlock failed"); +#endif +} + +} // namespace mozilla diff --git a/xpcom/threads/RecursiveMutex.h b/xpcom/threads/RecursiveMutex.h new file mode 100644 index 0000000000..dde21c9a35 --- /dev/null +++ b/xpcom/threads/RecursiveMutex.h @@ -0,0 +1,120 @@ +/* -*- 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/. */ + +// A lock that can be acquired multiple times on the same thread. + +#ifndef mozilla_RecursiveMutex_h +#define mozilla_RecursiveMutex_h + +#include "mozilla/ThreadSafety.h" +#include "mozilla/BlockingResourceBase.h" + +#ifndef XP_WIN +# include +#endif + +namespace mozilla { + +class MOZ_CAPABILITY("recursive mutex") RecursiveMutex + : public BlockingResourceBase { + public: + explicit RecursiveMutex(const char* aName); + ~RecursiveMutex(); + +#ifdef DEBUG + void Lock() MOZ_CAPABILITY_ACQUIRE(); + void Unlock() MOZ_CAPABILITY_RELEASE(); +#else + void Lock() MOZ_CAPABILITY_ACQUIRE() { LockInternal(); } + void Unlock() MOZ_CAPABILITY_RELEASE() { UnlockInternal(); } +#endif + +#ifdef DEBUG + /** + * AssertCurrentThreadIn + **/ + void AssertCurrentThreadIn() const MOZ_ASSERT_CAPABILITY(this); + /** + * AssertNotCurrentThreadIn + **/ + void AssertNotCurrentThreadIn() const MOZ_EXCLUDES(this) { + // Not currently implemented. See bug 476536 for discussion. + } +#else + void AssertCurrentThreadIn() const MOZ_ASSERT_CAPABILITY(this) {} + void AssertNotCurrentThreadIn() const MOZ_EXCLUDES(this) {} +#endif + + private: + RecursiveMutex() = delete; + RecursiveMutex(const RecursiveMutex&) = delete; + RecursiveMutex& operator=(const RecursiveMutex&) = delete; + + void LockInternal(); + void UnlockInternal(); + +#ifdef DEBUG + PRThread* mOwningThread; + size_t mEntryCount; +#endif + +#if !defined(XP_WIN) + pthread_mutex_t mMutex; +#else + // We eschew including windows.h and using CRITICAL_SECTION here so that files + // including us don't also pull in windows.h. Just use a type that's big + // enough for CRITICAL_SECTION, and we'll fix it up later. + void* mMutex[6]; +#endif +}; + +class MOZ_RAII MOZ_SCOPED_CAPABILITY RecursiveMutexAutoLock { + public: + explicit RecursiveMutexAutoLock(RecursiveMutex& aRecursiveMutex) + MOZ_CAPABILITY_ACQUIRE(aRecursiveMutex) + : mRecursiveMutex(&aRecursiveMutex) { + NS_ASSERTION(mRecursiveMutex, "null mutex"); + mRecursiveMutex->Lock(); + } + + ~RecursiveMutexAutoLock(void) MOZ_CAPABILITY_RELEASE() { + mRecursiveMutex->Unlock(); + } + + private: + RecursiveMutexAutoLock() = delete; + RecursiveMutexAutoLock(const RecursiveMutexAutoLock&) = delete; + RecursiveMutexAutoLock& operator=(const RecursiveMutexAutoLock&) = delete; + static void* operator new(size_t) noexcept(true); + + mozilla::RecursiveMutex* mRecursiveMutex; +}; + +class MOZ_RAII MOZ_SCOPED_CAPABILITY RecursiveMutexAutoUnlock { + public: + explicit RecursiveMutexAutoUnlock(RecursiveMutex& aRecursiveMutex) + MOZ_SCOPED_UNLOCK_RELEASE(aRecursiveMutex) + : mRecursiveMutex(&aRecursiveMutex) { + NS_ASSERTION(mRecursiveMutex, "null mutex"); + mRecursiveMutex->Unlock(); + } + + ~RecursiveMutexAutoUnlock(void) MOZ_SCOPED_UNLOCK_REACQUIRE() { + mRecursiveMutex->Lock(); + } + + private: + RecursiveMutexAutoUnlock() = delete; + RecursiveMutexAutoUnlock(const RecursiveMutexAutoUnlock&) = delete; + RecursiveMutexAutoUnlock& operator=(const RecursiveMutexAutoUnlock&) = delete; + static void* operator new(size_t) noexcept(true); + + mozilla::RecursiveMutex* mRecursiveMutex; +}; + +} // namespace mozilla + +#endif // mozilla_RecursiveMutex_h diff --git a/xpcom/threads/ReentrantMonitor.h b/xpcom/threads/ReentrantMonitor.h new file mode 100644 index 0000000000..09debad577 --- /dev/null +++ b/xpcom/threads/ReentrantMonitor.h @@ -0,0 +1,251 @@ +/* -*- 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/. */ + +#ifndef mozilla_ReentrantMonitor_h +#define mozilla_ReentrantMonitor_h + +#include "prmon.h" + +#if defined(MOZILLA_INTERNAL_API) && !defined(DEBUG) +# include "mozilla/ProfilerThreadSleep.h" +#endif // defined( MOZILLA_INTERNAL_API) && !defined(DEBUG) + +#include "mozilla/BlockingResourceBase.h" +#include "mozilla/ThreadSafety.h" +#include "nsISupports.h" +// +// Provides: +// +// - ReentrantMonitor, a Java-like monitor +// - ReentrantMonitorAutoEnter, an RAII class for ensuring that +// ReentrantMonitors are properly entered and exited +// +// Using ReentrantMonitorAutoEnter is MUCH preferred to making bare calls to +// ReentrantMonitor.Enter and Exit. +// +namespace mozilla { + +/** + * ReentrantMonitor + * Java-like monitor. + * When possible, use ReentrantMonitorAutoEnter to hold this monitor within a + * scope, instead of calling Enter/Exit directly. + **/ +class MOZ_CAPABILITY("reentrant monitor") ReentrantMonitor + : BlockingResourceBase { + public: + /** + * ReentrantMonitor + * @param aName A name which can reference this monitor + */ + explicit ReentrantMonitor(const char* aName) + : BlockingResourceBase(aName, eReentrantMonitor) +#ifdef DEBUG + , + mEntryCount(0) +#endif + { + MOZ_COUNT_CTOR(ReentrantMonitor); + mReentrantMonitor = PR_NewMonitor(); + if (!mReentrantMonitor) { + MOZ_CRASH("Can't allocate mozilla::ReentrantMonitor"); + } + } + + /** + * ~ReentrantMonitor + **/ + ~ReentrantMonitor() { + NS_ASSERTION(mReentrantMonitor, + "improperly constructed ReentrantMonitor or double free"); + PR_DestroyMonitor(mReentrantMonitor); + mReentrantMonitor = 0; + MOZ_COUNT_DTOR(ReentrantMonitor); + } + +#ifndef DEBUG + /** + * Enter + * @see prmon.h + **/ + void Enter() MOZ_CAPABILITY_ACQUIRE() { PR_EnterMonitor(mReentrantMonitor); } + + /** + * Exit + * @see prmon.h + **/ + void Exit() MOZ_CAPABILITY_RELEASE() { PR_ExitMonitor(mReentrantMonitor); } + + /** + * Wait + * @see prmon.h + **/ + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) { + PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor); +# ifdef MOZILLA_INTERNAL_API + AUTO_PROFILER_THREAD_SLEEP; +# endif // MOZILLA_INTERNAL_API + return PR_Wait(mReentrantMonitor, aInterval) == PR_SUCCESS + ? NS_OK + : NS_ERROR_FAILURE; + } + +#else // ifndef DEBUG + void Enter() MOZ_CAPABILITY_ACQUIRE(); + void Exit() MOZ_CAPABILITY_RELEASE(); + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT); + +#endif // ifndef DEBUG + + /** + * Notify + * @see prmon.h + **/ + nsresult Notify() { + return PR_Notify(mReentrantMonitor) == PR_SUCCESS ? NS_OK + : NS_ERROR_FAILURE; + } + + /** + * NotifyAll + * @see prmon.h + **/ + nsresult NotifyAll() { + return PR_NotifyAll(mReentrantMonitor) == PR_SUCCESS ? NS_OK + : NS_ERROR_FAILURE; + } + +#ifdef DEBUG + /** + * AssertCurrentThreadIn + * @see prmon.h + **/ + void AssertCurrentThreadIn() MOZ_ASSERT_CAPABILITY(this) { + PR_ASSERT_CURRENT_THREAD_IN_MONITOR(mReentrantMonitor); + } + + /** + * AssertNotCurrentThreadIn + * @see prmon.h + **/ + void AssertNotCurrentThreadIn() MOZ_ASSERT_CAPABILITY(!this) { + // FIXME bug 476536 + } + +#else + void AssertCurrentThreadIn() MOZ_ASSERT_CAPABILITY(this) {} + void AssertNotCurrentThreadIn() MOZ_ASSERT_CAPABILITY(!this) {} + +#endif // ifdef DEBUG + + private: + ReentrantMonitor(); + ReentrantMonitor(const ReentrantMonitor&); + ReentrantMonitor& operator=(const ReentrantMonitor&); + + PRMonitor* mReentrantMonitor; +#ifdef DEBUG + int32_t mEntryCount; +#endif +}; + +/** + * ReentrantMonitorAutoEnter + * Enters the ReentrantMonitor when it enters scope, and exits it when + * it leaves scope. + * + * MUCH PREFERRED to bare calls to ReentrantMonitor.Enter and Exit. + */ +class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS ReentrantMonitorAutoEnter { + public: + /** + * Constructor + * The constructor aquires the given lock. The destructor + * releases the lock. + * + * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*. + **/ + explicit ReentrantMonitorAutoEnter( + mozilla::ReentrantMonitor& aReentrantMonitor) + MOZ_CAPABILITY_ACQUIRE(aReentrantMonitor) + : mReentrantMonitor(&aReentrantMonitor) { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->Enter(); + } + + ~ReentrantMonitorAutoEnter(void) MOZ_CAPABILITY_RELEASE() { + mReentrantMonitor->Exit(); + } + + nsresult Wait(PRIntervalTime aInterval = PR_INTERVAL_NO_TIMEOUT) { + return mReentrantMonitor->Wait(aInterval); + } + + nsresult Notify() { return mReentrantMonitor->Notify(); } + nsresult NotifyAll() { return mReentrantMonitor->NotifyAll(); } + + private: + ReentrantMonitorAutoEnter(); + ReentrantMonitorAutoEnter(const ReentrantMonitorAutoEnter&); + ReentrantMonitorAutoEnter& operator=(const ReentrantMonitorAutoEnter&); + static void* operator new(size_t) noexcept(true); + + friend class ReentrantMonitorAutoExit; + + mozilla::ReentrantMonitor* mReentrantMonitor; +}; + +/** + * ReentrantMonitorAutoExit + * Exit the ReentrantMonitor when it enters scope, and enters it when it leaves + * scope. + * + * MUCH PREFERRED to bare calls to ReentrantMonitor.Exit and Enter. + */ +class MOZ_SCOPED_CAPABILITY MOZ_STACK_CLASS ReentrantMonitorAutoExit { + public: + /** + * Constructor + * The constructor releases the given lock. The destructor + * acquires the lock. The lock must be held before constructing + * this object! + * + * @param aReentrantMonitor A valid mozilla::ReentrantMonitor*. It + * must be already locked. + **/ + explicit ReentrantMonitorAutoExit(ReentrantMonitor& aReentrantMonitor) + MOZ_EXCLUSIVE_RELEASE(aReentrantMonitor) + : mReentrantMonitor(&aReentrantMonitor) { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->AssertCurrentThreadIn(); + mReentrantMonitor->Exit(); + } + + explicit ReentrantMonitorAutoExit( + ReentrantMonitorAutoEnter& aReentrantMonitorAutoEnter) + MOZ_EXCLUSIVE_RELEASE(aReentrantMonitorAutoEnter.mReentrantMonitor) + : mReentrantMonitor(aReentrantMonitorAutoEnter.mReentrantMonitor) { + NS_ASSERTION(mReentrantMonitor, "null monitor"); + mReentrantMonitor->AssertCurrentThreadIn(); + mReentrantMonitor->Exit(); + } + + ~ReentrantMonitorAutoExit(void) MOZ_EXCLUSIVE_RELEASE() { + mReentrantMonitor->Enter(); + } + + private: + ReentrantMonitorAutoExit(); + ReentrantMonitorAutoExit(const ReentrantMonitorAutoExit&); + ReentrantMonitorAutoExit& operator=(const ReentrantMonitorAutoExit&); + static void* operator new(size_t) noexcept(true); + + ReentrantMonitor* mReentrantMonitor; +}; + +} // namespace mozilla + +#endif // ifndef mozilla_ReentrantMonitor_h diff --git a/xpcom/threads/SchedulerGroup.cpp b/xpcom/threads/SchedulerGroup.cpp new file mode 100644 index 0000000000..1bb8c615fc --- /dev/null +++ b/xpcom/threads/SchedulerGroup.cpp @@ -0,0 +1,125 @@ +/* -*- 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/SchedulerGroup.h" + +#include + +#include "jsfriendapi.h" +#include "mozilla/Atomics.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsINamed.h" +#include "nsQueryObject.h" +#include "nsThreadUtils.h" + +using namespace mozilla; + +/* static */ +nsresult SchedulerGroup::UnlabeledDispatch( + TaskCategory aCategory, already_AddRefed&& aRunnable) { + if (NS_IsMainThread()) { + return NS_DispatchToCurrentThread(std::move(aRunnable)); + } else { + return NS_DispatchToMainThread(std::move(aRunnable)); + } +} + +/* static */ +nsresult SchedulerGroup::Dispatch(TaskCategory aCategory, + already_AddRefed&& aRunnable) { + return LabeledDispatch(aCategory, std::move(aRunnable), nullptr); +} + +/* static */ +nsresult SchedulerGroup::LabeledDispatch( + TaskCategory aCategory, already_AddRefed&& aRunnable, + mozilla::PerformanceCounter* aPerformanceCounter) { + nsCOMPtr runnable(aRunnable); + if (XRE_IsContentProcess()) { + RefPtr internalRunnable = + new Runnable(runnable.forget(), aPerformanceCounter); + return InternalUnlabeledDispatch(aCategory, internalRunnable.forget()); + } + return UnlabeledDispatch(aCategory, runnable.forget()); +} + +/*static*/ +nsresult SchedulerGroup::InternalUnlabeledDispatch( + TaskCategory aCategory, already_AddRefed&& aRunnable) { + if (NS_IsMainThread()) { + // NS_DispatchToCurrentThread will not leak the passed in runnable + // when it fails, so we don't need to do anything special. + return NS_DispatchToCurrentThread(std::move(aRunnable)); + } + + RefPtr runnable(aRunnable); + nsresult rv = NS_DispatchToMainThread(do_AddRef(runnable)); + if (NS_FAILED(rv)) { + // Dispatch failed. This is a situation where we would have used + // NS_DispatchToMainThread rather than calling into the SchedulerGroup + // machinery, and the caller would be expecting to leak the nsIRunnable + // originally passed in. But because we've had to wrap things up + // internally, we were going to leak the nsIRunnable *and* our Runnable + // wrapper. But there's no reason that we have to leak our Runnable + // wrapper; we can just leak the wrapped nsIRunnable, and let the caller + // take care of unleaking it if they need to. + Unused << runnable->mRunnable.forget().take(); + nsrefcnt refcnt = runnable.get()->Release(); + MOZ_RELEASE_ASSERT(refcnt == 1, "still holding an unexpected reference!"); + } + + return rv; +} + +SchedulerGroup::Runnable::Runnable( + already_AddRefed&& aRunnable, + mozilla::PerformanceCounter* aPerformanceCounter) + : mozilla::Runnable("SchedulerGroup::Runnable"), + mRunnable(std::move(aRunnable)), + mPerformanceCounter(aPerformanceCounter) {} + +mozilla::PerformanceCounter* SchedulerGroup::Runnable::GetPerformanceCounter() + const { + return mPerformanceCounter; +} + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +SchedulerGroup::Runnable::GetName(nsACString& aName) { + // Try to get a name from the underlying runnable. + nsCOMPtr named = do_QueryInterface(mRunnable); + if (named) { + named->GetName(aName); + } + if (aName.IsEmpty()) { + aName.AssignLiteral("anonymous"); + } + + return NS_OK; +} +#endif + +NS_IMETHODIMP +SchedulerGroup::Runnable::Run() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + // The runnable's destructor can have side effects, so try to execute it in + // the scope of the SchedulerGroup. + nsCOMPtr runnable(std::move(mRunnable)); + return runnable->Run(); +} + +NS_IMETHODIMP +SchedulerGroup::Runnable::GetPriority(uint32_t* aPriority) { + *aPriority = nsIRunnablePriority::PRIORITY_NORMAL; + nsCOMPtr runnablePrio = do_QueryInterface(mRunnable); + return runnablePrio ? runnablePrio->GetPriority(aPriority) : NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED(SchedulerGroup::Runnable, mozilla::Runnable, + nsIRunnablePriority, SchedulerGroup::Runnable) diff --git a/xpcom/threads/SchedulerGroup.h b/xpcom/threads/SchedulerGroup.h new file mode 100644 index 0000000000..76dbfb12f8 --- /dev/null +++ b/xpcom/threads/SchedulerGroup.h @@ -0,0 +1,87 @@ +/* -*- 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/. */ + +#ifndef mozilla_SchedulerGroup_h +#define mozilla_SchedulerGroup_h + +#include "mozilla/RefPtr.h" +#include "mozilla/TaskCategory.h" +#include "mozilla/PerformanceCounter.h" +#include "nsCOMPtr.h" +#include "nsID.h" +#include "nsIRunnable.h" +#include "nsISupports.h" +#include "nsStringFwd.h" +#include "nsThreadUtils.h" +#include "nscore.h" + +class nsIEventTarget; +class nsIRunnable; +class nsISerialEventTarget; + +namespace mozilla { +class AbstractThread; +namespace dom { +class DocGroup; +} // namespace dom + +#define NS_SCHEDULERGROUPRUNNABLE_IID \ + { \ + 0xd31b7420, 0x872b, 0x4cfb, { \ + 0xa9, 0xc6, 0xae, 0x4c, 0x0f, 0x06, 0x36, 0x74 \ + } \ + } + +class SchedulerGroup { + public: + class Runnable final : public mozilla::Runnable, public nsIRunnablePriority { + public: + Runnable(already_AddRefed&& aRunnable, + mozilla::PerformanceCounter* aPerformanceCounter); + + mozilla::PerformanceCounter* GetPerformanceCounter() const; + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override; +#endif + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIRUNNABLEPRIORITY + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_SCHEDULERGROUPRUNNABLE_IID); + + private: + friend class SchedulerGroup; + + ~Runnable() = default; + + nsCOMPtr mRunnable; + RefPtr mPerformanceCounter; + }; + friend class Runnable; + + static nsresult Dispatch(TaskCategory aCategory, + already_AddRefed&& aRunnable); + + static nsresult UnlabeledDispatch(TaskCategory aCategory, + already_AddRefed&& aRunnable); + + static nsresult LabeledDispatch( + TaskCategory aCategory, already_AddRefed&& aRunnable, + mozilla::PerformanceCounter* aPerformanceCounter); + + protected: + static nsresult InternalUnlabeledDispatch( + TaskCategory aCategory, already_AddRefed&& aRunnable); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(SchedulerGroup::Runnable, + NS_SCHEDULERGROUPRUNNABLE_IID); + +} // namespace mozilla + +#endif // mozilla_SchedulerGroup_h diff --git a/xpcom/threads/SharedThreadPool.cpp b/xpcom/threads/SharedThreadPool.cpp new file mode 100644 index 0000000000..a2de1c4495 --- /dev/null +++ b/xpcom/threads/SharedThreadPool.cpp @@ -0,0 +1,221 @@ +/* -*- 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/SharedThreadPool.h" +#include "mozilla/Monitor.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "nsTHashMap.h" +#include "nsXPCOMCIDInternal.h" +#include "nsComponentManagerUtils.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIThreadManager.h" +#include "nsThreadPool.h" + +namespace mozilla { + +// Created and destroyed on the main thread. +static StaticAutoPtr sMonitor; + +// Hashtable, maps thread pool name to SharedThreadPool instance. +// Modified only on the main thread. +static StaticAutoPtr> sPools; + +static already_AddRefed CreateThreadPool(const nsCString& aName); + +class SharedThreadPoolShutdownObserver : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + protected: + virtual ~SharedThreadPoolShutdownObserver() = default; +}; + +NS_IMPL_ISUPPORTS(SharedThreadPoolShutdownObserver, nsIObserver, nsISupports) + +NS_IMETHODIMP +SharedThreadPoolShutdownObserver::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + MOZ_RELEASE_ASSERT(!strcmp(aTopic, "xpcom-shutdown-threads")); +#ifdef EARLY_BETA_OR_EARLIER + { + ReentrantMonitorAutoEnter mon(*sMonitor); + if (!sPools->IsEmpty()) { + nsAutoCString str; + for (const auto& key : sPools->Keys()) { + str.AppendPrintf("\"%s\" ", nsAutoCString(key).get()); + } + printf_stderr( + "SharedThreadPool in xpcom-shutdown-threads. Waiting for " + "pools %s\n", + str.get()); + } + } +#endif + SharedThreadPool::SpinUntilEmpty(); + sMonitor = nullptr; + sPools = nullptr; + return NS_OK; +} + +void SharedThreadPool::InitStatics() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sMonitor && !sPools); + sMonitor = new ReentrantMonitor("SharedThreadPool"); + sPools = new nsTHashMap(); + nsCOMPtr obsService = + mozilla::services::GetObserverService(); + nsCOMPtr obs = new SharedThreadPoolShutdownObserver(); + obsService->AddObserver(obs, "xpcom-shutdown-threads", false); +} + +/* static */ +bool SharedThreadPool::IsEmpty() { + ReentrantMonitorAutoEnter mon(*sMonitor); + return !sPools->Count(); +} + +/* static */ +void SharedThreadPool::SpinUntilEmpty() { + MOZ_ASSERT(NS_IsMainThread()); + SpinEventLoopUntil("SharedThreadPool::SpinUntilEmpty"_ns, []() -> bool { + sMonitor->AssertNotCurrentThreadIn(); + return IsEmpty(); + }); +} + +already_AddRefed SharedThreadPool::Get( + const nsCString& aName, uint32_t aThreadLimit) { + MOZ_ASSERT(sMonitor && sPools); + ReentrantMonitorAutoEnter mon(*sMonitor); + RefPtr pool; + + return sPools->WithEntryHandle( + aName, [&](auto&& entry) -> already_AddRefed { + if (entry) { + pool = entry.Data(); + if (NS_FAILED(pool->EnsureThreadLimitIsAtLeast(aThreadLimit))) { + NS_WARNING("Failed to set limits on thread pool"); + } + } else { + nsCOMPtr threadPool(CreateThreadPool(aName)); + if (NS_WARN_IF(!threadPool)) { + sPools->Remove(aName); // XXX entry.Remove() + return nullptr; + } + pool = new SharedThreadPool(aName, threadPool); + + // Set the thread and idle limits. Note that we don't rely on the + // EnsureThreadLimitIsAtLeast() call below, as the default thread + // limit is 4, and if aThreadLimit is less than 4 we'll end up with a + // pool with 4 threads rather than what we expected; so we'll have + // unexpected behaviour. + nsresult rv = pool->SetThreadLimit(aThreadLimit); + if (NS_WARN_IF(NS_FAILED(rv))) { + sPools->Remove(aName); // XXX entry.Remove() + return nullptr; + } + + rv = pool->SetIdleThreadLimit(aThreadLimit); + if (NS_WARN_IF(NS_FAILED(rv))) { + sPools->Remove(aName); // XXX entry.Remove() + return nullptr; + } + + entry.Insert(pool.get()); + } + + return pool.forget(); + }); +} + +NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::AddRef(void) { + MOZ_ASSERT(sMonitor); + ReentrantMonitorAutoEnter mon(*sMonitor); + MOZ_ASSERT(int32_t(mRefCnt) >= 0, "illegal refcnt"); + nsrefcnt count = ++mRefCnt; + NS_LOG_ADDREF(this, count, "SharedThreadPool", sizeof(*this)); + return count; +} + +NS_IMETHODIMP_(MozExternalRefCountType) SharedThreadPool::Release(void) { + MOZ_ASSERT(sMonitor); + ReentrantMonitorAutoEnter mon(*sMonitor); + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "SharedThreadPool"); + if (count) { + return count; + } + + // Remove SharedThreadPool from table of pools. + sPools->Remove(mName); + MOZ_ASSERT(!sPools->Get(mName)); + + // Dispatch an event to the main thread to call Shutdown() on + // the nsIThreadPool. The Runnable here will add a refcount to the pool, + // and when the Runnable releases the nsIThreadPool it will be deleted. + NS_DispatchToMainThread(NewRunnableMethod("nsIThreadPool::Shutdown", mPool, + &nsIThreadPool::Shutdown)); + + // Stabilize refcount, so that if something in the dtor QIs, it won't explode. + mRefCnt = 1; + delete this; + return 0; +} + +NS_IMPL_QUERY_INTERFACE(SharedThreadPool, nsIThreadPool, nsIEventTarget) + +SharedThreadPool::SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool) + : mName(aName), mPool(aPool), mRefCnt(0) {} + +SharedThreadPool::~SharedThreadPool() = default; + +nsresult SharedThreadPool::EnsureThreadLimitIsAtLeast(uint32_t aLimit) { + // We limit the number of threads that we use. Note that we + // set the thread limit to the same as the idle limit so that we're not + // constantly creating and destroying threads (see Bug 881954). When the + // thread pool threads shutdown they dispatch an event to the main thread + // to call nsIThread::Shutdown(), and if we're very busy that can take a + // while to run, and we end up with dozens of extra threads. Note that + // threads that are idle for 60 seconds are shutdown naturally. + uint32_t existingLimit = 0; + nsresult rv; + + rv = mPool->GetThreadLimit(&existingLimit); + NS_ENSURE_SUCCESS(rv, rv); + if (aLimit > existingLimit) { + rv = mPool->SetThreadLimit(aLimit); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = mPool->GetIdleThreadLimit(&existingLimit); + NS_ENSURE_SUCCESS(rv, rv); + if (aLimit > existingLimit) { + rv = mPool->SetIdleThreadLimit(aLimit); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +static already_AddRefed CreateThreadPool( + const nsCString& aName) { + nsCOMPtr pool = new nsThreadPool(); + + nsresult rv = pool->SetName(aName); + NS_ENSURE_SUCCESS(rv, nullptr); + + rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, nullptr); + + return pool.forget(); +} + +} // namespace mozilla diff --git a/xpcom/threads/SharedThreadPool.h b/xpcom/threads/SharedThreadPool.h new file mode 100644 index 0000000000..5c29ba11f2 --- /dev/null +++ b/xpcom/threads/SharedThreadPool.h @@ -0,0 +1,130 @@ +/* -*- 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/. */ + +#ifndef SharedThreadPool_h_ +#define SharedThreadPool_h_ + +#include +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RefCountType.h" +#include "nsCOMPtr.h" +#include "nsID.h" +#include "nsIThreadPool.h" +#include "nsString.h" +#include "nscore.h" + +class nsIRunnable; + +namespace mozilla { + +// Wrapper that makes an nsIThreadPool a singleton, and provides a +// consistent threadsafe interface to get instances. Callers simply get a +// SharedThreadPool by the name of its nsIThreadPool. All get requests of +// the same name get the same SharedThreadPool. Users must store a reference +// to the pool, and when the last reference to a SharedThreadPool is dropped +// the pool is shutdown and deleted. Users aren't required to manually +// shutdown the pool, and can release references on any thread. This can make +// it significantly easier to use thread pools, because the caller doesn't need +// to worry about joining and tearing it down. +// +// On Windows all threads in the pool have MSCOM initialized with +// COINIT_MULTITHREADED. Note that not all users of MSCOM use this mode see [1], +// and mixing MSCOM objects between the two is terrible for performance, and can +// cause some functions to fail. So be careful when using Win32 APIs on a +// SharedThreadPool, and avoid sharing objects if at all possible. +// +// [1] +// https://searchfox.org/mozilla-central/search?q=coinitialize&redirect=false +class SharedThreadPool : public nsIThreadPool { + public: + // Gets (possibly creating) the shared thread pool singleton instance with + // thread pool named aName. + static already_AddRefed Get(const nsCString& aName, + uint32_t aThreadLimit = 4); + + // We implement custom threadsafe AddRef/Release pair, that destroys the + // the shared pool singleton when the refcount drops to 0. The addref/release + // are implemented using locking, so it's not recommended that you use them + // in a tight loop. + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; + + // Forward behaviour to wrapped thread pool implementation. + NS_FORWARD_SAFE_NSITHREADPOOL(mPool); + + // Call this when dispatching from an event on the same + // threadpool that is about to complete. We should not create a new thread + // in that case since a thread is about to become idle. + nsresult DispatchFromEndOfTaskInThisPool(nsIRunnable* event) { + return Dispatch(event, NS_DISPATCH_AT_END); + } + + NS_IMETHOD DispatchFromScript(nsIRunnable* event, uint32_t flags) override { + return Dispatch(event, flags); + } + + NS_IMETHOD Dispatch(already_AddRefed event, + uint32_t flags = NS_DISPATCH_NORMAL) override { + return !mPool ? NS_ERROR_NULL_POINTER + : mPool->Dispatch(std::move(event), flags); + } + + NS_IMETHOD DelayedDispatch(already_AddRefed, uint32_t) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + using nsIEventTarget::Dispatch; + + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* task) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* task) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD IsOnCurrentThread(bool* _retval) override { + return !mPool ? NS_ERROR_NULL_POINTER : mPool->IsOnCurrentThread(_retval); + } + + NS_IMETHOD_(bool) IsOnCurrentThreadInfallible() override { + return mPool && mPool->IsOnCurrentThread(); + } + + // Creates necessary statics. Called once at startup. + static void InitStatics(); + + // Spins the event loop until all thread pools are shutdown. + // *Must* be called on the main thread. + static void SpinUntilEmpty(); + + private: + // Returns whether there are no pools in existence at the moment. + static bool IsEmpty(); + + // Creates a singleton SharedThreadPool wrapper around aPool. + // aName is the name of the aPool, and is used to lookup the + // SharedThreadPool in the hash table of all created pools. + SharedThreadPool(const nsCString& aName, nsIThreadPool* aPool); + virtual ~SharedThreadPool(); + + nsresult EnsureThreadLimitIsAtLeast(uint32_t aThreadLimit); + + // Name of mPool. + const nsCString mName; + + // Thread pool being wrapped. + nsCOMPtr mPool; + + // Refcount. We implement custom ref counting so that the thread pool is + // shutdown in a threadsafe manner and singletonness is preserved. + nsrefcnt mRefCnt; +}; + +} // namespace mozilla + +#endif // SharedThreadPool_h_ diff --git a/xpcom/threads/SpinEventLoopUntil.h b/xpcom/threads/SpinEventLoopUntil.h new file mode 100644 index 0000000000..a281177268 --- /dev/null +++ b/xpcom/threads/SpinEventLoopUntil.h @@ -0,0 +1,191 @@ +/* -*- 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/. */ + +#ifndef xpcom_threads_SpinEventLoopUntil_h__ +#define xpcom_threads_SpinEventLoopUntil_h__ + +#include "MainThreadUtils.h" +#include "mozilla/Maybe.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/StaticMutex.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "xpcpublic.h" + +class nsIThread; + +// A wrapper for nested event loops. +// +// This function is intended to make code more obvious (do you remember +// what NS_ProcessNextEvent(nullptr, true) means?) and slightly more +// efficient, as people often pass nullptr or NS_GetCurrentThread to +// NS_ProcessNextEvent, which results in needless querying of the current +// thread every time through the loop. +// +// You should use this function in preference to NS_ProcessNextEvent inside +// a loop unless one of the following is true: +// +// * You need to pass `false` to NS_ProcessNextEvent; or +// * You need to do unusual things around the call to NS_ProcessNextEvent, +// such as unlocking mutexes that you are holding. +// +// If you *do* need to call NS_ProcessNextEvent manually, please do call +// NS_GetCurrentThread() outside of your loop and pass the returned pointer +// into NS_ProcessNextEvent for a tiny efficiency win. +namespace mozilla { + +// You should normally not need to deal with this template parameter. If +// you enjoy esoteric event loop details, read on. +// +// If you specify that NS_ProcessNextEvent wait for an event, it is possible +// for NS_ProcessNextEvent to return false, i.e. to indicate that an event +// was not processed. This can only happen when the thread has been shut +// down by another thread, but is still attempting to process events outside +// of a nested event loop. +// +// This behavior is admittedly strange. The scenario it deals with is the +// following: +// +// * The current thread has been shut down by some owner thread. +// * The current thread is spinning an event loop waiting for some condition +// to become true. +// * Said condition is actually being fulfilled by another thread, so there +// are timing issues in play. +// +// Thus, there is a small window where the current thread's event loop +// spinning can check the condition, find it false, and call +// NS_ProcessNextEvent to wait for another event. But we don't actually +// want it to wait indefinitely, because there might not be any other events +// in the event loop, and the current thread can't accept dispatched events +// because it's being shut down. Thus, actually blocking would hang the +// thread, which is bad. The solution, then, is to detect such a scenario +// and not actually block inside NS_ProcessNextEvent. +// +// But this is a problem, because we want to return the status of +// NS_ProcessNextEvent to the caller of SpinEventLoopUntil if possible. In +// the above scenario, however, we'd stop spinning prematurely and cause +// all sorts of havoc. We therefore have this template parameter to +// control whether errors are ignored or passed out to the caller of +// SpinEventLoopUntil. The latter is the default; if you find yourself +// wanting to use the former, you should think long and hard before doing +// so, and write a comment like this defending your choice. + +enum class ProcessFailureBehavior { + IgnoreAndContinue, + ReportToCaller, +}; + +// SpinEventLoopUntil is a dangerous operation that can result in hangs. +// In particular during shutdown we want to know if we are hanging +// inside a nested event loop on the main thread. +// This is a helper annotation class to keep track of this. +struct MOZ_STACK_CLASS AutoNestedEventLoopAnnotation { + explicit AutoNestedEventLoopAnnotation(const nsACString& aEntry) + : mPrev(nullptr) { + if (NS_IsMainThread()) { + StaticMutexAutoLock lock(sStackMutex); + mPrev = sCurrent; + sCurrent = this; + if (mPrev) { + mStack = mPrev->mStack + "|"_ns + aEntry; + } else { + mStack = aEntry; + } + AnnotateXPCOMSpinEventLoopStack(mStack); + } + } + + ~AutoNestedEventLoopAnnotation() { + if (NS_IsMainThread()) { + StaticMutexAutoLock lock(sStackMutex); + MOZ_ASSERT(sCurrent == this); + sCurrent = mPrev; + if (mPrev) { + AnnotateXPCOMSpinEventLoopStack(mPrev->mStack); + } else { + AnnotateXPCOMSpinEventLoopStack(""_ns); + } + } + } + + static void CopyCurrentStack(nsCString& aNestedSpinStack) { + // We need to copy this behind a mutex as the + // memory for our instances is stack-bound and + // can go away at any time. + StaticMutexAutoLock lock(sStackMutex); + if (sCurrent) { + aNestedSpinStack = sCurrent->mStack; + } else { + aNestedSpinStack = "(no nested event loop active)"_ns; + } + } + + private: + AutoNestedEventLoopAnnotation(const AutoNestedEventLoopAnnotation&) = delete; + AutoNestedEventLoopAnnotation& operator=( + const AutoNestedEventLoopAnnotation&) = delete; + + // The declarations of these statics live in nsThreadManager.cpp. + static AutoNestedEventLoopAnnotation* sCurrent MOZ_GUARDED_BY(sStackMutex); + static StaticMutex sStackMutex; + + // We need this to avoid the inclusion of nsExceptionHandler.h here + // which can include windows.h which disturbs some dom/media/gtest. + // The implementation lives in nsThreadManager.cpp. + static void AnnotateXPCOMSpinEventLoopStack(const nsACString& aStack); + + AutoNestedEventLoopAnnotation* mPrev MOZ_GUARDED_BY(sStackMutex); + nsCString mStack MOZ_GUARDED_BY(sStackMutex); +}; + +// Please see the above notes for the Behavior template parameter. +// +// aVeryGoodReasonToDoThis is usually a literal string unique to each +// caller that can be recognized in the XPCOMSpinEventLoopStack +// annotation. +// aPredicate is the condition we wait for. +// aThread can be used to specify a thread, see the above introduction. +// It defaults to the current thread. +template < + ProcessFailureBehavior Behavior = ProcessFailureBehavior::ReportToCaller, + typename Pred> +bool SpinEventLoopUntil(const nsACString& aVeryGoodReasonToDoThis, + Pred&& aPredicate, nsIThread* aThread = nullptr) { + // Prepare the annotations + AutoNestedEventLoopAnnotation annotation(aVeryGoodReasonToDoThis); + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE( + "SpinEventLoopUntil", OTHER, aVeryGoodReasonToDoThis); + AUTO_PROFILER_MARKER_TEXT("SpinEventLoop", OTHER, MarkerStack::Capture(), + aVeryGoodReasonToDoThis); + + nsIThread* thread = aThread ? aThread : NS_GetCurrentThread(); + + // From a latency perspective, spinning the event loop is like leaving script + // and returning to the event loop. Tell the watchdog we stopped running + // script (until we return). + mozilla::Maybe asa; + if (NS_IsMainThread()) { + asa.emplace(false); + } + + while (!aPredicate()) { + bool didSomething = NS_ProcessNextEvent(thread, true); + + if (Behavior == ProcessFailureBehavior::IgnoreAndContinue) { + // Don't care what happened, continue on. + continue; + } else if (!didSomething) { + return false; + } + } + + return true; +} + +} // namespace mozilla + +#endif // xpcom_threads_SpinEventLoopUntil_h__ diff --git a/xpcom/threads/StateMirroring.h b/xpcom/threads/StateMirroring.h new file mode 100644 index 0000000000..c233116962 --- /dev/null +++ b/xpcom/threads/StateMirroring.h @@ -0,0 +1,393 @@ +/* -*- 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/. */ + +#if !defined(StateMirroring_h_) +# define StateMirroring_h_ + +# include +# include "mozilla/AbstractThread.h" +# include "mozilla/AlreadyAddRefed.h" +# include "mozilla/Assertions.h" +# include "mozilla/Logging.h" +# include "mozilla/Maybe.h" +# include "mozilla/RefPtr.h" +# include "mozilla/StateWatching.h" +# include "nsCOMPtr.h" +# include "nsIRunnable.h" +# include "nsISupports.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +/* + * The state-mirroring machinery allows pieces of interesting state to be + * observed on multiple thread without locking. The basic strategy is to track + * changes in a canonical value and post updates to other threads that hold + * mirrors for that value. + * + * One problem with the naive implementation of such a system is that some + * pieces of state need to be updated atomically, and certain other operations + * need to wait for these atomic updates to complete before executing. The + * state-mirroring machinery solves this problem by requiring that its owner + * thread uses tail dispatch, and posting state update events (which should + * always be run first by TaskDispatcher implementations) to that tail + * dispatcher. This ensures that state changes are always atomic from the + * perspective of observing threads. + * + * Given that state-mirroring is an automatic background process, we try to + * avoid burdening the caller with worrying too much about teardown. To that + * end, we don't assert dispatch success for any of the notifications, and + * assume that any canonical or mirror owned by a thread for whom dispatch fails + * will soon be disconnected by its holder anyway. + * + * Given that semantics may change and comments tend to go out of date, we + * deliberately don't provide usage examples here. Grep around to find them. + */ + +namespace mozilla { + +// Mirror and Canonical inherit WatchTarget, so we piggy-back on the +// logging that WatchTarget already does. Given that, it makes sense to share +// the same log module. +# define MIRROR_LOG(x, ...) \ + MOZ_ASSERT(gStateWatchingLog); \ + MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__)) + +template +class AbstractMirror; + +/* + * AbstractCanonical is a superclass from which all Canonical values must + * inherit. It serves as the interface of operations which may be performed (via + * asynchronous dispatch) by other threads, in particular by the corresponding + * Mirror value. + */ +template +class AbstractCanonical { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractCanonical) + AbstractCanonical(AbstractThread* aThread) : mOwnerThread(aThread) {} + virtual void AddMirror(AbstractMirror* aMirror) = 0; + virtual void RemoveMirror(AbstractMirror* aMirror) = 0; + + AbstractThread* OwnerThread() const { return mOwnerThread; } + + protected: + virtual ~AbstractCanonical() {} + RefPtr mOwnerThread; +}; + +/* + * AbstractMirror is a superclass from which all Mirror values must + * inherit. It serves as the interface of operations which may be performed (via + * asynchronous dispatch) by other threads, in particular by the corresponding + * Canonical value. + */ +template +class AbstractMirror { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractMirror) + AbstractMirror(AbstractThread* aThread) : mOwnerThread(aThread) {} + virtual void UpdateValue(const T& aNewValue) = 0; + virtual void NotifyDisconnected() = 0; + + AbstractThread* OwnerThread() const { return mOwnerThread; } + + protected: + virtual ~AbstractMirror() {} + RefPtr mOwnerThread; +}; + +/* + * Canonical is a wrapper class that allows a given value to be mirrored by + * other threads. It maintains a list of active mirrors, and queues updates for + * them when the internal value changes. When changing the value, the caller + * needs to pass a TaskDispatcher object, which fires the updates at the + * appropriate time. Canonical is also a WatchTarget, and may be set up to + * trigger other routines (on the same thread) when the canonical value changes. + * + * Canonical is intended to be used as a member variable, so it doesn't + * actually inherit AbstractCanonical (a refcounted type). Rather, it + * contains an inner class called |Impl| that implements most of the interesting + * logic. + */ +template +class Canonical { + public: + Canonical(AbstractThread* aThread, const T& aInitialValue, + const char* aName) { + mImpl = new Impl(aThread, aInitialValue, aName); + } + + ~Canonical() {} + + private: + class Impl : public AbstractCanonical, public WatchTarget { + public: + using AbstractCanonical::OwnerThread; + + Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName) + : AbstractCanonical(aThread), + WatchTarget(aName), + mValue(aInitialValue) { + MIRROR_LOG("%s [%p] initialized", mName, this); + MOZ_ASSERT(aThread->SupportsTailDispatch(), + "Can't get coherency without tail dispatch"); + } + + void AddMirror(AbstractMirror* aMirror) override { + MIRROR_LOG("%s [%p] adding mirror %p", mName, this, aMirror); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(!mMirrors.Contains(aMirror)); + mMirrors.AppendElement(aMirror); + aMirror->OwnerThread()->DispatchStateChange(MakeNotifier(aMirror)); + } + + void RemoveMirror(AbstractMirror* aMirror) override { + MIRROR_LOG("%s [%p] removing mirror %p", mName, this, aMirror); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(mMirrors.Contains(aMirror)); + mMirrors.RemoveElement(aMirror); + } + + void DisconnectAll() { + MIRROR_LOG("%s [%p] Disconnecting all mirrors", mName, this); + for (size_t i = 0; i < mMirrors.Length(); ++i) { + mMirrors[i]->OwnerThread()->Dispatch( + NewRunnableMethod("AbstractMirror::NotifyDisconnected", mMirrors[i], + &AbstractMirror::NotifyDisconnected)); + } + mMirrors.Clear(); + } + + operator const T&() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + return mValue; + } + + void Set(const T& aNewValue) { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + + if (aNewValue == mValue) { + return; + } + + // Notify same-thread watchers. The state watching machinery will make + // sure that notifications run at the right time. + NotifyWatchers(); + + // Check if we've already got a pending update. If so we won't schedule + // another one. + bool alreadyNotifying = mInitialValue.isSome(); + + // Stash the initial value if needed, then update to the new value. + if (mInitialValue.isNothing()) { + mInitialValue.emplace(mValue); + } + mValue = aNewValue; + + // We wait until things have stablized before sending state updates so + // that we can avoid sending multiple updates, and possibly avoid sending + // any updates at all if the value ends up where it started. + if (!alreadyNotifying) { + AbstractThread::DispatchDirectTask(NewRunnableMethod( + "Canonical::Impl::DoNotify", this, &Impl::DoNotify)); + } + } + + Impl& operator=(const T& aNewValue) { + Set(aNewValue); + return *this; + } + Impl& operator=(const Impl& aOther) { + Set(aOther); + return *this; + } + Impl(const Impl& aOther) = delete; + + protected: + ~Impl() { MOZ_DIAGNOSTIC_ASSERT(mMirrors.IsEmpty()); } + + private: + void DoNotify() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(mInitialValue.isSome()); + bool same = mInitialValue.ref() == mValue; + mInitialValue.reset(); + + if (same) { + MIRROR_LOG("%s [%p] unchanged - not sending update", mName, this); + return; + } + + for (size_t i = 0; i < mMirrors.Length(); ++i) { + mMirrors[i]->OwnerThread()->DispatchStateChange( + MakeNotifier(mMirrors[i])); + } + } + + already_AddRefed MakeNotifier(AbstractMirror* aMirror) { + return NewRunnableMethod("AbstractMirror::UpdateValue", aMirror, + &AbstractMirror::UpdateValue, mValue); + ; + } + + T mValue; + Maybe mInitialValue; + nsTArray>> mMirrors; + }; + + public: + // NB: Because mirror-initiated disconnection can race with canonical- + // initiated disconnection, a canonical should never be reinitialized. + // Forward control operations to the Impl. + void DisconnectAll() { return mImpl->DisconnectAll(); } + + // Access to the Impl. + operator Impl&() { return *mImpl; } + Impl* operator&() { return mImpl; } + + // Access to the T. + const T& Ref() const { return *mImpl; } + operator const T&() const { return Ref(); } + void Set(const T& aNewValue) { mImpl->Set(aNewValue); } + Canonical& operator=(const T& aNewValue) { + Set(aNewValue); + return *this; + } + Canonical& operator=(const Canonical& aOther) { + Set(aOther); + return *this; + } + Canonical(const Canonical& aOther) = delete; + + private: + RefPtr mImpl; +}; + +/* + * Mirror is a wrapper class that allows a given value to mirror that of a + * Canonical owned by another thread. It registers itself with a + * Canonical, and is periodically updated with new values. Mirror is also + * a WatchTarget, and may be set up to trigger other routines (on the same + * thread) when the mirrored value changes. + * + * Mirror is intended to be used as a member variable, so it doesn't actually + * inherit AbstractMirror (a refcounted type). Rather, it contains an inner + * class called |Impl| that implements most of the interesting logic. + */ +template +class Mirror { + public: + Mirror(AbstractThread* aThread, const T& aInitialValue, const char* aName) { + mImpl = new Impl(aThread, aInitialValue, aName); + } + + ~Mirror() { + // As a member of complex objects, a Mirror may be destroyed on a + // different thread than its owner, or late in shutdown during CC. Given + // that, we require manual disconnection so that callers can put things in + // the right place. + MOZ_DIAGNOSTIC_ASSERT(!mImpl->IsConnected()); + } + + private: + class Impl : public AbstractMirror, public WatchTarget { + public: + using AbstractMirror::OwnerThread; + + Impl(AbstractThread* aThread, const T& aInitialValue, const char* aName) + : AbstractMirror(aThread), + WatchTarget(aName), + mValue(aInitialValue) { + MIRROR_LOG("%s [%p] initialized", mName, this); + MOZ_ASSERT(aThread->SupportsTailDispatch(), + "Can't get coherency without tail dispatch"); + } + + operator const T&() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + return mValue; + } + + virtual void UpdateValue(const T& aNewValue) override { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + if (mValue != aNewValue) { + mValue = aNewValue; + WatchTarget::NotifyWatchers(); + } + } + + virtual void NotifyDisconnected() override { + MIRROR_LOG("%s [%p] Notifed of disconnection from %p", mName, this, + mCanonical.get()); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + mCanonical = nullptr; + } + + bool IsConnected() const { return !!mCanonical; } + + void Connect(AbstractCanonical* aCanonical) { + MIRROR_LOG("%s [%p] Connecting to %p", mName, this, aCanonical); + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + MOZ_ASSERT(!IsConnected()); + MOZ_ASSERT(OwnerThread()->RequiresTailDispatch(aCanonical->OwnerThread()), + "Can't get coherency without tail dispatch"); + + nsCOMPtr r = + NewRunnableMethod>>( + "AbstractCanonical::AddMirror", aCanonical, + &AbstractCanonical::AddMirror, this); + aCanonical->OwnerThread()->Dispatch(r.forget()); + mCanonical = aCanonical; + } + + public: + void DisconnectIfConnected() { + MOZ_ASSERT(OwnerThread()->IsCurrentThreadIn()); + if (!IsConnected()) { + return; + } + + MIRROR_LOG("%s [%p] Disconnecting from %p", mName, this, + mCanonical.get()); + nsCOMPtr r = + NewRunnableMethod>>( + "AbstractCanonical::RemoveMirror", mCanonical, + &AbstractCanonical::RemoveMirror, this); + mCanonical->OwnerThread()->Dispatch(r.forget()); + mCanonical = nullptr; + } + + protected: + ~Impl() { MOZ_DIAGNOSTIC_ASSERT(!IsConnected()); } + + private: + T mValue; + RefPtr> mCanonical; + }; + + public: + // Forward control operations to the Impl. + void Connect(AbstractCanonical* aCanonical) { mImpl->Connect(aCanonical); } + void DisconnectIfConnected() { mImpl->DisconnectIfConnected(); } + + // Access to the Impl. + operator Impl&() { return *mImpl; } + Impl* operator&() { return mImpl; } + + // Access to the T. + const T& Ref() const { return *mImpl; } + operator const T&() const { return Ref(); } + + private: + RefPtr mImpl; +}; + +# undef MIRROR_LOG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/StateWatching.h b/xpcom/threads/StateWatching.h new file mode 100644 index 0000000000..3da0c63bfe --- /dev/null +++ b/xpcom/threads/StateWatching.h @@ -0,0 +1,302 @@ +/* -*- 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/. */ + +#if !defined(StateWatching_h_) +# define StateWatching_h_ + +# include +# include +# include +# include "mozilla/AbstractThread.h" +# include "mozilla/Assertions.h" +# include "mozilla/Logging.h" +# include "mozilla/RefPtr.h" +# include "nsISupports.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +/* + * The state-watching machinery automates the process of responding to changes + * in various pieces of state. + * + * A standard programming pattern is as follows: + * + * mFoo = ...; + * NotifyStuffChanged(); + * ... + * mBar = ...; + * NotifyStuffChanged(); + * + * This pattern is error-prone and difficult to audit because it requires the + * programmer to manually trigger the update routine. This can be especially + * problematic when the update routine depends on numerous pieces of state, and + * when that state is modified across a variety of helper methods. In these + * cases the responsibility for invoking the routine is often unclear, causing + * developers to scatter calls to it like pixie dust. This can result in + * duplicate invocations (which is wasteful) and missing invocations in corner- + * cases (which is a source of bugs). + * + * This file provides a set of primitives that automatically handle updates and + * allow the programmers to explicitly construct a graph of state dependencies. + * When used correctly, it eliminates the guess-work and wasted cycles described + * above. + * + * There are two basic pieces: + * (1) Objects that can be watched for updates. These inherit WatchTarget. + * (2) Objects that receive objects and trigger processing. These inherit + * AbstractWatcher. In the current machinery, these exist only internally + * within the WatchManager, though that could change. + * + * Note that none of this machinery is thread-safe - it must all happen on the + * same owning thread. To solve multi-threaded use-cases, use state mirroring + * and watch the mirrored value. + * + * Given that semantics may change and comments tend to go out of date, we + * deliberately don't provide usage examples here. Grep around to find them. + */ + +namespace mozilla { + +extern LazyLogModule gStateWatchingLog; + +# define WATCH_LOG(x, ...) \ + MOZ_LOG(gStateWatchingLog, LogLevel::Debug, (x, ##__VA_ARGS__)) + +/* + * AbstractWatcher is a superclass from which all watchers must inherit. + */ +class AbstractWatcher { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AbstractWatcher) + AbstractWatcher() : mDestroyed(false) {} + bool IsDestroyed() { return mDestroyed; } + virtual void Notify() = 0; + + protected: + virtual ~AbstractWatcher() { MOZ_ASSERT(mDestroyed); } + bool mDestroyed; +}; + +/* + * WatchTarget is a superclass from which all watchable things must inherit. + * Unlike AbstractWatcher, it is a fully-implemented Mix-in, and the subclass + * needs only to invoke NotifyWatchers when something changes. + * + * The functionality that this class provides is not threadsafe, and should only + * be used on the thread that owns that WatchTarget. + */ +class WatchTarget { + public: + explicit WatchTarget(const char* aName) : mName(aName) {} + + void AddWatcher(AbstractWatcher* aWatcher) { + MOZ_ASSERT(!mWatchers.Contains(aWatcher)); + mWatchers.AppendElement(aWatcher); + } + + void RemoveWatcher(AbstractWatcher* aWatcher) { + MOZ_ASSERT(mWatchers.Contains(aWatcher)); + mWatchers.RemoveElement(aWatcher); + } + + protected: + void NotifyWatchers() { + WATCH_LOG("%s[%p] notifying watchers\n", mName, this); + PruneWatchers(); + for (size_t i = 0; i < mWatchers.Length(); ++i) { + mWatchers[i]->Notify(); + } + } + + private: + // We don't have Watchers explicitly unregister themselves when they die, + // because then they'd need back-references to all the WatchTargets they're + // subscribed to, and WatchTargets aren't reference-counted. So instead we + // just prune dead ones at appropriate times, which works just fine. + void PruneWatchers() { + mWatchers.RemoveElementsBy( + [](const auto& watcher) { return watcher->IsDestroyed(); }); + } + + nsTArray> mWatchers; + + protected: + const char* mName; +}; + +/* + * Watchable is a wrapper class that turns any primitive into a WatchTarget. + */ +template +class Watchable : public WatchTarget { + public: + Watchable(const T& aInitialValue, const char* aName) + : WatchTarget(aName), mValue(aInitialValue) {} + + const T& Ref() const { return mValue; } + operator const T&() const { return Ref(); } + Watchable& operator=(const T& aNewValue) { + if (aNewValue != mValue) { + mValue = aNewValue; + NotifyWatchers(); + } + + return *this; + } + + private: + Watchable(const Watchable& aOther) = delete; + Watchable& operator=(const Watchable& aOther) = delete; + + T mValue; +}; + +// Manager class for state-watching. Declare one of these in any class for which +// you want to invoke method callbacks. +// +// Internally, WatchManager maintains one AbstractWatcher per callback method. +// Consumers invoke Watch/Unwatch on a particular (WatchTarget, Callback) tuple. +// This causes an AbstractWatcher for |Callback| to be instantiated if it +// doesn't already exist, and registers it with |WatchTarget|. +// +// Using Direct Tasks on the TailDispatcher, WatchManager ensures that we fire +// watch callbacks no more than once per task, once all other operations for +// that task have been completed. +// +// WatchManager is intended to be declared as a member of |OwnerType| +// objects. Given that, it and its owned objects can't hold permanent strong +// refs to the owner, since that would keep the owner alive indefinitely. +// Instead, it _only_ holds strong refs while waiting for Direct Tasks to fire. +// This ensures that everything is kept alive just long enough. +template +class WatchManager { + public: + typedef void (OwnerType::*CallbackMethod)(); + explicit WatchManager(OwnerType* aOwner, AbstractThread* aOwnerThread) + : mOwner(aOwner), mOwnerThread(aOwnerThread) {} + + ~WatchManager() { + if (!IsShutdown()) { + Shutdown(); + } + } + + bool IsShutdown() const { return !mOwner; } + + // Shutdown needs to happen on mOwnerThread. If the WatchManager will be + // destroyed on a different thread, Shutdown() must be called manually. + void Shutdown() { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + for (auto& watcher : mWatchers) { + watcher->Destroy(); + } + mWatchers.Clear(); + mOwner = nullptr; + } + + void Watch(WatchTarget& aTarget, CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + aTarget.AddWatcher(&EnsureWatcher(aMethod)); + } + + void Unwatch(WatchTarget& aTarget, CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + MOZ_ASSERT(watcher); + aTarget.RemoveWatcher(watcher); + } + + void ManualNotify(CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + MOZ_ASSERT(watcher); + watcher->Notify(); + } + + private: + class PerCallbackWatcher : public AbstractWatcher { + public: + PerCallbackWatcher(OwnerType* aOwner, AbstractThread* aOwnerThread, + CallbackMethod aMethod) + : mOwner(aOwner), + mOwnerThread(aOwnerThread), + mCallbackMethod(aMethod) {} + + void Destroy() { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + mDestroyed = true; + mOwner = nullptr; + } + + void Notify() override { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + MOZ_DIAGNOSTIC_ASSERT(mOwner, + "mOwner is only null after destruction, " + "at which point we shouldn't be notified"); + if (mNotificationPending) { + // We've already got a notification job in the pipe. + return; + } + mNotificationPending = true; + + // Queue up our notification jobs to run in a stable state. + AbstractThread::DispatchDirectTask( + NS_NewRunnableFunction("WatchManager::PerCallbackWatcher::Notify", + [self = RefPtr(this), + owner = RefPtr(mOwner)]() { + if (!self->mDestroyed) { + ((*owner).*(self->mCallbackMethod))(); + } + self->mNotificationPending = false; + })); + } + + bool CallbackMethodIs(CallbackMethod aMethod) const { + return mCallbackMethod == aMethod; + } + + private: + ~PerCallbackWatcher() = default; + + OwnerType* mOwner; // Never null. + bool mNotificationPending = false; + RefPtr mOwnerThread; + CallbackMethod mCallbackMethod; + }; + + PerCallbackWatcher* GetWatcher(CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + for (auto& watcher : mWatchers) { + if (watcher->CallbackMethodIs(aMethod)) { + return watcher; + } + } + return nullptr; + } + + PerCallbackWatcher& EnsureWatcher(CallbackMethod aMethod) { + MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn()); + PerCallbackWatcher* watcher = GetWatcher(aMethod); + if (watcher) { + return *watcher; + } + watcher = mWatchers + .AppendElement(MakeAndAddRef( + mOwner, mOwnerThread, aMethod)) + ->get(); + return *watcher; + } + + nsTArray> mWatchers; + OwnerType* mOwner; + RefPtr mOwnerThread; +}; + +# undef WATCH_LOG + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/SyncRunnable.h b/xpcom/threads/SyncRunnable.h new file mode 100644 index 0000000000..77f82ba313 --- /dev/null +++ b/xpcom/threads/SyncRunnable.h @@ -0,0 +1,157 @@ +/* -*- 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/. */ + +#ifndef mozilla_SyncRunnable_h +#define mozilla_SyncRunnable_h + +#include + +#include "mozilla/AbstractThread.h" +#include "mozilla/Monitor.h" +#include "mozilla/dom/JSExecutionManager.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +/** + * This class will wrap a nsIRunnable and dispatch it to the target thread + * synchronously. This is different from + * NS_DispatchAndSpinEventLoopUntilComplete: this class does not spin the event + * loop waiting for the event to be dispatched. This means that you don't risk + * reentrance from pending messages, but you must be sure that the target thread + * does not ever block on this thread, or else you will deadlock. + * + * Typical usage: + * RefPtr sr = new SyncRunnable(new myrunnable...()); + * sr->DispatchToThread(t); + * + * We also provide convenience wrappers: + * SyncRunnable::DispatchToThread(pThread, new myrunnable...()); + * SyncRunnable::DispatchToThread(pThread, NS_NewRunnableFunction(...)); + * + */ +class SyncRunnable : public Runnable { + public: + explicit SyncRunnable(nsIRunnable* aRunnable) + : Runnable("SyncRunnable"), + mRunnable(aRunnable), + mMonitor("SyncRunnable"), + mDone(false) {} + + explicit SyncRunnable(already_AddRefed aRunnable) + : Runnable("SyncRunnable"), + mRunnable(std::move(aRunnable)), + mMonitor("SyncRunnable"), + mDone(false) {} + + nsresult DispatchToThread(nsIEventTarget* aThread, + bool aForceDispatch = false) { + nsresult rv; + bool on; + + if (!aForceDispatch) { + rv = aThread->IsOnCurrentThread(&on); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_SUCCEEDED(rv) && on) { + mRunnable->Run(); + return NS_OK; + } + } + + rv = aThread->Dispatch(this, NS_DISPATCH_NORMAL); + if (NS_SUCCEEDED(rv)) { + mozilla::MonitorAutoLock lock(mMonitor); + // This could be synchronously dispatching to a thread currently waiting + // for JS execution clearance. Yield JS execution. + dom::AutoYieldJSThreadExecution yield; + + while (!mDone) { + lock.Wait(); + } + } + return rv; + } + + nsresult DispatchToThread(AbstractThread* aThread, + bool aForceDispatch = false) { + if (!aForceDispatch && aThread->IsCurrentThreadIn()) { + mRunnable->Run(); + return NS_OK; + } + + // Check we don't have tail dispatching here. Otherwise we will deadlock + // ourself when spinning the loop below. + MOZ_ASSERT(!aThread->RequiresTailDispatchFromCurrentThread()); + + nsresult rv = aThread->Dispatch(RefPtr(this).forget()); + if (NS_SUCCEEDED(rv)) { + mozilla::MonitorAutoLock lock(mMonitor); + while (!mDone) { + lock.Wait(); + } + } + return rv; + } + + static nsresult DispatchToThread(nsIEventTarget* aThread, + nsIRunnable* aRunnable, + bool aForceDispatch = false) { + RefPtr s(new SyncRunnable(aRunnable)); + return s->DispatchToThread(aThread, aForceDispatch); + } + + static nsresult DispatchToThread(AbstractThread* aThread, + nsIRunnable* aRunnable, + bool aForceDispatch = false) { + RefPtr s(new SyncRunnable(aRunnable)); + return s->DispatchToThread(aThread, aForceDispatch); + } + + static nsresult DispatchToThread(nsIEventTarget* aThread, + already_AddRefed aRunnable, + bool aForceDispatch = false) { + RefPtr s(new SyncRunnable(std::move(aRunnable))); + return s->DispatchToThread(aThread, aForceDispatch); + } + + static nsresult DispatchToThread(AbstractThread* aThread, + already_AddRefed aRunnable, + bool aForceDispatch = false) { + RefPtr s(new SyncRunnable(std::move(aRunnable))); + return s->DispatchToThread(aThread, aForceDispatch); + } + + // These deleted overloads prevent accidentally (if harmlessly) double- + // wrapping SyncRunnable, which was previously a common anti-pattern. + static nsresult DispatchToThread(nsIEventTarget* aThread, + SyncRunnable* aRunnable, + bool aForceDispatch = false) = delete; + static nsresult DispatchToThread(AbstractThread* aThread, + SyncRunnable* aRunnable, + bool aForceDispatch = false) = delete; + + protected: + NS_IMETHOD Run() override { + mRunnable->Run(); + + mozilla::MonitorAutoLock lock(mMonitor); + MOZ_ASSERT(!mDone); + + mDone = true; + mMonitor.Notify(); + + return NS_OK; + } + + private: + nsCOMPtr mRunnable; + mozilla::Monitor mMonitor; + bool mDone MOZ_GUARDED_BY(mMonitor); +}; + +} // namespace mozilla + +#endif // mozilla_SyncRunnable_h diff --git a/xpcom/threads/SynchronizedEventQueue.cpp b/xpcom/threads/SynchronizedEventQueue.cpp new file mode 100644 index 0000000000..59161b7f9d --- /dev/null +++ b/xpcom/threads/SynchronizedEventQueue.cpp @@ -0,0 +1,26 @@ +/* -*- 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 "SynchronizedEventQueue.h" +#include "nsIThreadInternal.h" + +using namespace mozilla; + +void SynchronizedEventQueue::AddObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(aObserver); + MOZ_ASSERT(!mEventObservers.Contains(aObserver)); + mEventObservers.AppendElement(aObserver); +} + +void SynchronizedEventQueue::RemoveObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(aObserver); + MOZ_ALWAYS_TRUE(mEventObservers.RemoveElement(aObserver)); +} + +const nsTObserverArray>& +SynchronizedEventQueue::EventObservers() { + return mEventObservers; +} diff --git a/xpcom/threads/SynchronizedEventQueue.h b/xpcom/threads/SynchronizedEventQueue.h new file mode 100644 index 0000000000..e4cf1a62af --- /dev/null +++ b/xpcom/threads/SynchronizedEventQueue.h @@ -0,0 +1,131 @@ +/* -*- 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/. */ + +#ifndef mozilla_SynchronizedEventQueue_h +#define mozilla_SynchronizedEventQueue_h + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/EventQueue.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "nsIThreadInternal.h" +#include "nsCOMPtr.h" +#include "nsTObserverArray.h" + +class nsIEventTarget; +class nsISerialEventTarget; +class nsIThreadObserver; + +namespace mozilla { + +// A SynchronizedEventQueue is an abstract class for event queues that can be +// used across threads. A SynchronizedEventQueue implementation will typically +// use locks and condition variables to guarantee consistency. The methods of +// SynchronizedEventQueue are split between ThreadTargetSink (which contains +// methods for posting events) and SynchronizedEventQueue (which contains +// methods for getting events). This split allows event targets (specifically +// ThreadEventTarget) to use a narrow interface, since they only need to post +// events. +// +// ThreadEventQueue is the canonical implementation of +// SynchronizedEventQueue. When Quantum DOM is implemented, we will use a +// different synchronized queue on the main thread, SchedulerEventQueue, which +// will handle the cooperative threading model. + +class ThreadTargetSink { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadTargetSink) + + virtual bool PutEvent(already_AddRefed&& aEvent, + EventQueuePriority aPriority) = 0; + + // After this method is called, no more events can be posted. + virtual void Disconnect(const MutexAutoLock& aProofOfLock) = 0; + + virtual nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) = 0; + virtual nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) = 0; + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + // Not const because overrides may need to take a lock + virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) = 0; + + protected: + virtual ~ThreadTargetSink() = default; +}; + +class SynchronizedEventQueue : public ThreadTargetSink { + public: + virtual already_AddRefed GetEvent( + bool aMayWait, mozilla::TimeDuration* aLastEventDelay = nullptr) = 0; + virtual bool HasPendingEvent() = 0; + + // This method atomically checks if there are pending events and, if there are + // none, forbids future events from being posted. It returns true if there + // were no pending events. + virtual bool ShutdownIfNoPendingEvents() = 0; + + // These methods provide access to an nsIThreadObserver, whose methods are + // called when posting and processing events. SetObserver should only be + // called on the thread that processes events. GetObserver can be called from + // any thread. GetObserverOnThread must be used from the thread that processes + // events; it does not acquire a lock. + virtual already_AddRefed GetObserver() = 0; + virtual already_AddRefed GetObserverOnThread() = 0; + virtual void SetObserver(nsIThreadObserver* aObserver) = 0; + + void AddObserver(nsIThreadObserver* aObserver); + void RemoveObserver(nsIThreadObserver* aObserver); + const nsTObserverArray>& EventObservers(); + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) override { + // Normally we'd return + // mEventObservers.ShallowSizeOfExcludingThis(aMallocSizeOf); However, + // mEventObservers may be being mutated on another thread, and we don't lock + // around access, so locking here wouldn't help. They're small, so + return 0; + } + + /** + * This method causes any events currently enqueued on the thread to be + * suppressed until PopEventQueue is called, and any event dispatched to this + * thread's nsIEventTarget will queue as well. Calls to PushEventQueue may be + * nested and must each be paired with a call to PopEventQueue in order to + * restore the original state of the thread. The returned nsIEventTarget may + * be used to push events onto the nested queue. Dispatching will be disabled + * once the event queue is popped. The thread will only ever process pending + * events for the innermost event queue. Must only be called on the target + * thread. + */ + virtual already_AddRefed PushEventQueue() = 0; + + /** + * Revert a call to PushEventQueue. When an event queue is popped, any events + * remaining in the queue are appended to the elder queue. This also causes + * the nsIEventTarget returned from PushEventQueue to stop dispatching events. + * Must only be called on the target thread, and with the innermost event + * queue. + */ + virtual void PopEventQueue(nsIEventTarget* aTarget) = 0; + + /** + * Flush the list of shutdown tasks which were previously registered. After + * this is called, new shutdown tasks cannot be registered. + */ + virtual void RunShutdownTasks() = 0; + + protected: + virtual ~SynchronizedEventQueue() = default; + + private: + nsTObserverArray> mEventObservers; +}; + +} // namespace mozilla + +#endif // mozilla_SynchronizedEventQueue_h diff --git a/xpcom/threads/TaskCategory.h b/xpcom/threads/TaskCategory.h new file mode 100644 index 0000000000..8f189c8737 --- /dev/null +++ b/xpcom/threads/TaskCategory.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#ifndef mozilla_TaskCategory_h +#define mozilla_TaskCategory_h + +namespace mozilla { + +// The different kinds of tasks we can dispatch to a SystemGroup, TabGroup, or +// DocGroup. +enum class TaskCategory { + // User input (clicks, keypresses, etc.) + UI, + + // Data from the network + Network, + + // setTimeout, setInterval + Timer, + + // Runnables posted from a worker to the main thread + Worker, + + // requestIdleCallback + IdleCallback, + + // Vsync notifications + RefreshDriver, + + // GC/CC-related tasks + GarbageCollection, + + // Most DOM events (postMessage, media, plugins) + Other, + + // Runnables related to Performance Counting + Performance, + + Count +}; + +} // namespace mozilla + +#endif // mozilla_TaskCategory_h diff --git a/xpcom/threads/TaskController.cpp b/xpcom/threads/TaskController.cpp new file mode 100644 index 0000000000..37410bda1c --- /dev/null +++ b/xpcom/threads/TaskController.cpp @@ -0,0 +1,1072 @@ +/* -*- 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 "TaskController.h" +#include "nsIIdleRunnable.h" +#include "nsIRunnable.h" +#include "nsThreadUtils.h" +#include +#include +#include "GeckoProfiler.h" +#include "mozilla/EventQueue.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/VsyncTaskManager.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Unused.h" +#include "nsIThreadInternal.h" +#include "nsQueryObject.h" +#include "nsThread.h" +#include "prenv.h" +#include "prsystem.h" + +namespace mozilla { + +std::unique_ptr TaskController::sSingleton; +thread_local size_t mThreadPoolIndex = -1; +std::atomic Task::sCurrentTaskSeqNo = 0; + +const int32_t kMinimumPoolThreadCount = 2; +const int32_t kMaximumPoolThreadCount = 8; + +/* static */ +int32_t TaskController::GetPoolThreadCount() { + if (PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT")) { + return strtol(PR_GetEnv("MOZ_TASKCONTROLLER_THREADCOUNT"), nullptr, 0); + } + + int32_t numCores = std::max(1, PR_GetNumberOfProcessors()); + + return std::clamp(numCores, kMinimumPoolThreadCount, + kMaximumPoolThreadCount); +} + +#if defined(MOZ_COLLECTING_RUNNABLE_TELEMETRY) + +struct TaskMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("Task"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + const nsCString& aName, uint32_t aPriority) { + aWriter.StringProperty("name", aName); + aWriter.IntProperty("priority", aPriority); + +# define EVENT_PRIORITY(NAME, VALUE) \ + if (aPriority == (VALUE)) { \ + aWriter.StringProperty("priorityName", #NAME); \ + } else + EVENT_QUEUE_PRIORITY_LIST(EVENT_PRIORITY) +# undef EVENT_PRIORITY + { + aWriter.StringProperty("priorityName", "Invalid Value"); + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.SetChartLabel("{marker.data.name}"); + schema.SetTableLabel( + "{marker.name} - {marker.data.name} - priority: " + "{marker.data.priorityName} ({marker.data.priority})"); + schema.AddKeyLabelFormatSearchable("name", "Task Name", MS::Format::String, + MS::Searchable::Searchable); + schema.AddKeyLabelFormat("priorityName", "Priority Name", + MS::Format::String); + schema.AddKeyLabelFormat("priority", "Priority level", MS::Format::Integer); + return schema; + } +}; + +class MOZ_RAII AutoProfileTask { + public: + explicit AutoProfileTask(nsACString& aName, uint64_t aPriority) + : mName(aName), mPriority(aPriority) { + if (profiler_is_active()) { + mStartTime = TimeStamp::Now(); + } + } + + ~AutoProfileTask() { + if (!profiler_thread_is_being_profiled_for_markers()) { + return; + } + + AUTO_PROFILER_LABEL("AutoProfileTask", PROFILER); + AUTO_PROFILER_STATS(AUTO_PROFILE_TASK); + profiler_add_marker("Runnable", ::mozilla::baseprofiler::category::OTHER, + mStartTime.IsNull() + ? MarkerTiming::IntervalEnd() + : MarkerTiming::IntervalUntilNowFrom(mStartTime), + TaskMarker{}, mName, mPriority); + } + + private: + TimeStamp mStartTime; + nsAutoCString mName; + uint32_t mPriority; +}; + +# define AUTO_PROFILE_FOLLOWING_TASK(task) \ + nsAutoCString name; \ + (task)->GetName(name); \ + AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_NONSENSITIVE("Task", OTHER, name); \ + mozilla::AutoProfileTask PROFILER_RAII(name, (task)->GetPriority()); +#else +# define AUTO_PROFILE_FOLLOWING_TASK(task) +#endif + +bool TaskManager:: + UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + const MutexAutoLock& aProofOfLock, IterationType aIterationType) { + mCurrentSuspended = IsSuspended(aProofOfLock); + + if (aIterationType == IterationType::EVENT_LOOP_TURN && !mCurrentSuspended) { + int32_t oldModifier = mCurrentPriorityModifier; + mCurrentPriorityModifier = + GetPriorityModifierForEventLoopTurn(aProofOfLock); + + if (mCurrentPriorityModifier != oldModifier) { + return true; + } + } + return false; +} + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +class MOZ_RAII AutoSetMainThreadRunnableName { + public: + explicit AutoSetMainThreadRunnableName(const nsCString& aName) { + MOZ_ASSERT(NS_IsMainThread()); + // We want to record our current runnable's name in a static so + // that BHR can record it. + mRestoreRunnableName = nsThread::sMainThreadRunnableName; + + // Copy the name into sMainThreadRunnableName's buffer, and append a + // terminating null. + uint32_t length = std::min((uint32_t)nsThread::kRunnableNameBufSize - 1, + (uint32_t)aName.Length()); + memcpy(nsThread::sMainThreadRunnableName.begin(), aName.BeginReading(), + length); + nsThread::sMainThreadRunnableName[length] = '\0'; + } + + ~AutoSetMainThreadRunnableName() { + nsThread::sMainThreadRunnableName = mRestoreRunnableName; + } + + private: + Array mRestoreRunnableName; +}; +#endif + +Task* Task::GetHighestPriorityDependency() { + Task* currentTask = this; + + while (!currentTask->mDependencies.empty()) { + auto iter = currentTask->mDependencies.begin(); + + while (iter != currentTask->mDependencies.end()) { + if ((*iter)->mCompleted) { + auto oldIter = iter; + iter++; + // Completed tasks are removed here to prevent needlessly keeping them + // alive or iterating over them in the future. + currentTask->mDependencies.erase(oldIter); + continue; + } + + currentTask = iter->get(); + break; + } + } + + return currentTask == this ? nullptr : currentTask; +} + +TaskController* TaskController::Get() { + MOZ_ASSERT(sSingleton.get()); + return sSingleton.get(); +} + +void TaskController::Initialize() { + MOZ_ASSERT(!sSingleton); + sSingleton = std::make_unique(); +} + +void ThreadFuncPoolThread(void* aIndex) { + mThreadPoolIndex = *reinterpret_cast(aIndex); + delete reinterpret_cast(aIndex); + TaskController::Get()->RunPoolThread(); +} + +TaskController::TaskController() + : mGraphMutex("TaskController::mGraphMutex"), + mThreadPoolCV(mGraphMutex, "TaskController::mThreadPoolCV"), + mMainThreadCV(mGraphMutex, "TaskController::mMainThreadCV"), + mRunOutOfMTTasksCounter(0) { + InputTaskManager::Init(); + VsyncTaskManager::Init(); + mMTProcessingRunnable = NS_NewRunnableFunction( + "TaskController::ExecutePendingMTTasks()", + []() { TaskController::Get()->ProcessPendingMTTask(); }); + mMTBlockingProcessingRunnable = NS_NewRunnableFunction( + "TaskController::ExecutePendingMTTasks()", + []() { TaskController::Get()->ProcessPendingMTTask(true); }); +} + +// We want our default stack size limit to be approximately 2MB, to be safe for +// JS helper tasks that can use a lot of stack, but expect most threads to use +// much less. On Linux, however, requesting a stack of 2MB or larger risks the +// kernel allocating an entire 2MB huge page for it on first access, which we do +// not want. To avoid this possibility, we subtract 2 standard VM page sizes +// from our default. +constexpr PRUint32 sBaseStackSize = 2048 * 1024 - 2 * 4096; + +// TSan enforces a minimum stack size that's just slightly larger than our +// default helper stack size. It does this to store blobs of TSan-specific data +// on each thread's stack. Unfortunately, that means that even though we'll +// actually receive a larger stack than we requested, the effective usable space +// of that stack is significantly less than what we expect. To offset TSan +// stealing our stack space from underneath us, double the default. +// +// Similarly, ASan requires more stack space due to red-zones. +#if defined(MOZ_TSAN) || defined(MOZ_ASAN) +constexpr PRUint32 sStackSize = 2 * sBaseStackSize; +#else +constexpr PRUint32 sStackSize = sBaseStackSize; +#endif + +void TaskController::InitializeThreadPool() { + mPoolInitializationMutex.AssertCurrentThreadOwns(); + MOZ_ASSERT(!mThreadPoolInitialized); + mThreadPoolInitialized = true; + + int32_t poolSize = GetPoolThreadCount(); + for (int32_t i = 0; i < poolSize; i++) { + int32_t* index = new int32_t(i); + mPoolThreads.push_back( + {PR_CreateThread(PR_USER_THREAD, ThreadFuncPoolThread, index, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, sStackSize), + nullptr}); + } +} + +/* static */ +size_t TaskController::GetThreadStackSize() { return sStackSize; } + +void TaskController::SetPerformanceCounterState( + PerformanceCounterState* aPerformanceCounterState) { + mPerformanceCounterState = aPerformanceCounterState; +} + +/* static */ +void TaskController::Shutdown() { + InputTaskManager::Cleanup(); + VsyncTaskManager::Cleanup(); + if (sSingleton) { + sSingleton->ShutdownThreadPoolInternal(); + sSingleton->ShutdownInternal(); + } + MOZ_ASSERT(!sSingleton); +} + +void TaskController::ShutdownThreadPoolInternal() { + { + // Prevent racecondition on mShuttingDown and wait. + MutexAutoLock lock(mGraphMutex); + + mShuttingDown = true; + mThreadPoolCV.NotifyAll(); + } + for (PoolThread& thread : mPoolThreads) { + PR_JoinThread(thread.mThread); + } +} + +void TaskController::ShutdownInternal() { sSingleton = nullptr; } + +void TaskController::RunPoolThread() { + IOInterposer::RegisterCurrentThread(); + + // This is used to hold on to a task to make sure it is released outside the + // lock. This is required since it's perfectly feasible for task destructors + // to post events themselves. + RefPtr lastTask; + + nsAutoCString threadName; + threadName.AppendLiteral("TaskController #"); + threadName.AppendInt(static_cast(mThreadPoolIndex)); + AUTO_PROFILER_REGISTER_THREAD(threadName.BeginReading()); + + MutexAutoLock lock(mGraphMutex); + while (true) { + bool ranTask = false; + + if (!mThreadableTasks.empty()) { + for (auto iter = mThreadableTasks.begin(); iter != mThreadableTasks.end(); + ++iter) { + // Search for the highest priority dependency of the highest priority + // task. + + // We work with rawptrs to avoid needless refcounting. All our tasks + // are always kept alive by the graph. If one is removed from the graph + // it is kept alive by mPoolThreads[mThreadPoolIndex].mCurrentTask. + Task* task = iter->get(); + + MOZ_ASSERT(!task->mTaskManager); + + mPoolThreads[mThreadPoolIndex].mEffectiveTaskPriority = + task->GetPriority(); + + Task* nextTask; + while ((nextTask = task->GetHighestPriorityDependency())) { + task = nextTask; + } + + if (task->IsMainThreadOnly() || task->mInProgress) { + continue; + } + + mPoolThreads[mThreadPoolIndex].mCurrentTask = task; + mThreadableTasks.erase(task->mIterator); + task->mIterator = mThreadableTasks.end(); + task->mInProgress = true; + + if (!mThreadableTasks.empty()) { + // Ensure at least one additional thread is woken up if there are + // more threadable tasks to process. Notifying all threads at once + // isn't actually better for performance since they all need the + // GraphMutex to proceed anyway. + mThreadPoolCV.Notify(); + } + + bool taskCompleted = false; + { + MutexAutoUnlock unlock(mGraphMutex); + lastTask = nullptr; + AUTO_PROFILE_FOLLOWING_TASK(task); + taskCompleted = task->Run(); + ranTask = true; + } + + task->mInProgress = false; + + if (!taskCompleted) { + // Presumably this task was interrupted, leave its dependencies + // unresolved and reinsert into the queue. + auto insertion = mThreadableTasks.insert( + mPoolThreads[mThreadPoolIndex].mCurrentTask); + MOZ_ASSERT(insertion.second); + task->mIterator = insertion.first; + } else { + task->mCompleted = true; +#ifdef DEBUG + task->mIsInGraph = false; +#endif + task->mDependencies.clear(); + // This may have unblocked a main thread task. We could do this only + // if there was a main thread task before this one in the dependency + // chain. + mMayHaveMainThreadTask = true; + // Since this could have multiple dependencies thare are restricted + // to the main thread. Let's make sure that's awake. + EnsureMainThreadTasksScheduled(); + + MaybeInterruptTask(GetHighestPriorityMTTask()); + } + + // Store last task for release next time we release the lock or enter + // wait state. + lastTask = mPoolThreads[mThreadPoolIndex].mCurrentTask.forget(); + break; + } + } + + // Ensure the last task is released before we enter the wait state. + if (lastTask) { + MutexAutoUnlock unlock(mGraphMutex); + lastTask = nullptr; + + // Run another loop iteration, while we were unlocked there was an + // opportunity for another task to be posted or shutdown to be initiated. + continue; + } + + if (!ranTask) { + if (mShuttingDown) { + IOInterposer::UnregisterCurrentThread(); + MOZ_ASSERT(mThreadableTasks.empty()); + return; + } + + AUTO_PROFILER_LABEL("TaskController::RunPoolThread", IDLE); + mThreadPoolCV.Wait(); + } + } +} + +void TaskController::AddTask(already_AddRefed&& aTask) { + RefPtr task(aTask); + + if (!task->IsMainThreadOnly()) { + MutexAutoLock lock(mPoolInitializationMutex); + if (!mThreadPoolInitialized) { + InitializeThreadPool(); + } + } + + MutexAutoLock lock(mGraphMutex); + + if (TaskManager* manager = task->GetManager()) { + if (manager->mTaskCount == 0) { + mTaskManagers.insert(manager); + } + manager->DidQueueTask(); + + // Set this here since if this manager's priority modifier doesn't change + // we will not reprioritize when iterating over the queue. + task->mPriorityModifier = manager->mCurrentPriorityModifier; + } + + if (profiler_is_active_and_unpaused()) { + task->mInsertionTime = TimeStamp::Now(); + } + +#ifdef DEBUG + task->mIsInGraph = true; + + for (const RefPtr& otherTask : task->mDependencies) { + MOZ_ASSERT(!otherTask->mTaskManager || + otherTask->mTaskManager == task->mTaskManager); + } +#endif + + LogTask::LogDispatch(task); + + std::pair, Task::PriorityCompare>::iterator, bool> + insertion; + if (task->IsMainThreadOnly()) { + insertion = mMainThreadTasks.insert(std::move(task)); + } else { + insertion = mThreadableTasks.insert(std::move(task)); + } + (*insertion.first)->mIterator = insertion.first; + MOZ_ASSERT(insertion.second); + + MaybeInterruptTask(*insertion.first); +} + +void TaskController::WaitForTaskOrMessage() { + MutexAutoLock lock(mGraphMutex); + while (!mMayHaveMainThreadTask) { + AUTO_PROFILER_LABEL("TaskController::WaitForTaskOrMessage", IDLE); + mMainThreadCV.Wait(); + } +} + +void TaskController::ExecuteNextTaskOnlyMainThread() { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mGraphMutex); + ExecuteNextTaskOnlyMainThreadInternal(lock); +} + +void TaskController::ProcessPendingMTTask(bool aMayWait) { + MOZ_ASSERT(NS_IsMainThread()); + MutexAutoLock lock(mGraphMutex); + + for (;;) { + // We only ever process one event here. However we may sometimes + // not actually process a real event because of suspended tasks. + // This loop allows us to wait until we've processed something + // in that scenario. + + mMTTaskRunnableProcessedTask = ExecuteNextTaskOnlyMainThreadInternal(lock); + + if (mMTTaskRunnableProcessedTask || !aMayWait) { + break; + } + +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + // Unlock before calling into the BackgroundHangMonitor API as it uses + // the timer API. + { + MutexAutoUnlock unlock(mGraphMutex); + BackgroundHangMonitor().NotifyWait(); + } +#endif + + { + // ProcessNextEvent will also have attempted to wait, however we may have + // given it a Runnable when all the tasks in our task graph were suspended + // but we weren't able to cheaply determine that. + AUTO_PROFILER_LABEL("TaskController::ProcessPendingMTTask", IDLE); + mMainThreadCV.Wait(); + } + +#ifdef MOZ_ENABLE_BACKGROUND_HANG_MONITOR + { + MutexAutoUnlock unlock(mGraphMutex); + BackgroundHangMonitor().NotifyActivity(); + } +#endif + } + + if (mMayHaveMainThreadTask) { + EnsureMainThreadTasksScheduled(); + } +} + +void TaskController::ReprioritizeTask(Task* aTask, uint32_t aPriority) { + MutexAutoLock lock(mGraphMutex); + std::set, Task::PriorityCompare>* queue = &mMainThreadTasks; + if (!aTask->IsMainThreadOnly()) { + queue = &mThreadableTasks; + } + + MOZ_ASSERT(aTask->mIterator != queue->end()); + queue->erase(aTask->mIterator); + + aTask->mPriority = aPriority; + + auto insertion = queue->insert(aTask); + MOZ_ASSERT(insertion.second); + aTask->mIterator = insertion.first; + + MaybeInterruptTask(aTask); +} + +// Code supporting runnable compatibility. +// Task that wraps a runnable. +class RunnableTask : public Task { + public: + RunnableTask(already_AddRefed&& aRunnable, int32_t aPriority, + bool aMainThread = true) + : Task(aMainThread, aPriority), mRunnable(aRunnable) {} + + virtual bool Run() override { + mRunnable->Run(); + mRunnable = nullptr; + return true; + } + + void SetIdleDeadline(TimeStamp aDeadline) override { + nsCOMPtr idleRunnable = do_QueryInterface(mRunnable); + if (idleRunnable) { + idleRunnable->SetDeadline(aDeadline); + } + } + + PerformanceCounter* GetPerformanceCounter() const override { + return nsThread::GetPerformanceCounterBase(mRunnable); + } + + virtual bool GetName(nsACString& aName) override { +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + nsThread::GetLabeledRunnableName(mRunnable, aName, + EventQueuePriority(GetPriority())); + return true; +#else + return false; +#endif + } + + private: + RefPtr mRunnable; +}; + +void TaskController::DispatchRunnable(already_AddRefed&& aRunnable, + uint32_t aPriority, + TaskManager* aManager) { + RefPtr task = new RunnableTask(std::move(aRunnable), aPriority); + + task->SetManager(aManager); + TaskController::Get()->AddTask(task.forget()); +} + +nsIRunnable* TaskController::GetRunnableForMTTask(bool aReallyWait) { + MutexAutoLock lock(mGraphMutex); + + while (mMainThreadTasks.empty()) { + if (!aReallyWait) { + return nullptr; + } + + AUTO_PROFILER_LABEL("TaskController::GetRunnableForMTTask::Wait", IDLE); + mMainThreadCV.Wait(); + } + + return aReallyWait ? mMTBlockingProcessingRunnable : mMTProcessingRunnable; +} + +bool TaskController::HasMainThreadPendingTasks() { + MOZ_ASSERT(NS_IsMainThread()); + auto resetIdleState = MakeScopeExit([&idleManager = mIdleTaskManager] { + if (idleManager) { + idleManager->State().ClearCachedIdleDeadline(); + } + }); + + for (bool considerIdle : {false, true}) { + if (considerIdle && !mIdleTaskManager) { + continue; + } + + MutexAutoLock lock(mGraphMutex); + + if (considerIdle) { + mIdleTaskManager->State().ForgetPendingTaskGuarantee(); + // Temporarily unlock so we can peek our idle deadline. + // XXX We could do this _before_ we take the lock if the API would let us. + // We do want to do this before looking at mMainThreadTasks, in case + // someone adds one while we're unlocked. + { + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().CachePeekedIdleDeadline(unlock); + } + } + + // Return early if there's no tasks at all. + if (mMainThreadTasks.empty()) { + return false; + } + + // We can cheaply count how many tasks are suspended. + uint64_t totalSuspended = 0; + for (TaskManager* manager : mTaskManagers) { + DebugOnly modifierChanged = + manager + ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + lock, TaskManager::IterationType::NOT_EVENT_LOOP_TURN); + MOZ_ASSERT(!modifierChanged); + + // The idle manager should be suspended unless we're doing the idle pass. + MOZ_ASSERT(manager != mIdleTaskManager || manager->mCurrentSuspended || + considerIdle, + "Why are idle tasks not suspended here?"); + + if (manager->mCurrentSuspended) { + // XXX - If managers manage off-main-thread tasks this breaks! This + // scenario is explicitly not supported. + // + // This is only incremented inside the lock -or- decremented on the main + // thread so this is safe. + totalSuspended += manager->mTaskCount; + } + } + + // This would break down if we have a non-suspended task depending on a + // suspended task. This is why for the moment we do not allow tasks + // to be dependent on tasks managed by another taskmanager. + if (mMainThreadTasks.size() > totalSuspended) { + // If mIdleTaskManager->mTaskCount is 0, we never updated the suspended + // state of mIdleTaskManager above, hence shouldn't even check it here. + // But in that case idle tasks are not contributing to our suspended task + // count anyway. + if (mIdleTaskManager && mIdleTaskManager->mTaskCount && + !mIdleTaskManager->mCurrentSuspended) { + MOZ_ASSERT(considerIdle, "Why is mIdleTaskManager not suspended?"); + // Check whether the idle tasks were really needed to make our "we have + // an unsuspended task" decision. If they were, we need to force-enable + // idle tasks until we run our next task. + if (mMainThreadTasks.size() - mIdleTaskManager->mTaskCount <= + totalSuspended) { + mIdleTaskManager->State().EnforcePendingTaskGuarantee(); + } + } + return true; + } + } + return false; +} + +uint64_t TaskController::PendingMainthreadTaskCountIncludingSuspended() { + MutexAutoLock lock(mGraphMutex); + return mMainThreadTasks.size(); +} + +bool TaskController::ExecuteNextTaskOnlyMainThreadInternal( + const MutexAutoLock& aProofOfLock) { + MOZ_ASSERT(NS_IsMainThread()); + mGraphMutex.AssertCurrentThreadOwns(); + // Block to make it easier to jump to our cleanup. + bool taskRan = false; + do { + taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock); + if (taskRan) { + if (mIdleTaskManager && mIdleTaskManager->mTaskCount && + mIdleTaskManager->IsSuspended(aProofOfLock)) { + uint32_t activeTasks = mMainThreadTasks.size(); + for (TaskManager* manager : mTaskManagers) { + if (manager->IsSuspended(aProofOfLock)) { + activeTasks -= manager->mTaskCount; + } else { + break; + } + } + + if (!activeTasks) { + // We have only idle (and maybe other suspended) tasks left, so need + // to update the idle state. We need to temporarily release the lock + // while we do that. + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().RequestIdleDeadlineIfNeeded(unlock); + } + } + break; + } + + if (!mIdleTaskManager) { + break; + } + + if (mIdleTaskManager->mTaskCount) { + // We have idle tasks that we may not have gotten above because + // our idle state is not up to date. We need to update the idle state + // and try again. We need to temporarily release the lock while we do + // that. + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().UpdateCachedIdleDeadline(unlock); + } else { + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().RanOutOfTasks(unlock); + } + + // When we unlocked, someone may have queued a new task on us. So try to + // see whether we can run things again. + taskRan = DoExecuteNextTaskOnlyMainThreadInternal(aProofOfLock); + } while (false); + + if (mIdleTaskManager) { + // The pending task guarantee is not needed anymore, since we just tried + // running a task + mIdleTaskManager->State().ForgetPendingTaskGuarantee(); + + if (mMainThreadTasks.empty()) { + ++mRunOutOfMTTasksCounter; + + // XXX the IdlePeriodState API demands we have a MutexAutoUnlock for it. + // Otherwise we could perhaps just do this after we exit the locked block, + // by pushing the lock down into this method. Though it's not clear that + // we could check mMainThreadTasks.size() once we unlock, and whether we + // could maybe substitute mMayHaveMainThreadTask for that check. + MutexAutoUnlock unlock(mGraphMutex); + mIdleTaskManager->State().RanOutOfTasks(unlock); + } + } + + return taskRan; +} + +bool TaskController::DoExecuteNextTaskOnlyMainThreadInternal( + const MutexAutoLock& aProofOfLock) { + mGraphMutex.AssertCurrentThreadOwns(); + + nsCOMPtr mainIThread; + NS_GetMainThread(getter_AddRefs(mainIThread)); + + nsThread* mainThread = static_cast(mainIThread.get()); + if (mainThread) { + mainThread->SetRunningEventDelay(TimeDuration(), TimeStamp()); + } + + uint32_t totalSuspended = 0; + for (TaskManager* manager : mTaskManagers) { + bool modifierChanged = + manager + ->UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + aProofOfLock, TaskManager::IterationType::EVENT_LOOP_TURN); + if (modifierChanged) { + ProcessUpdatedPriorityModifier(manager); + } + if (manager->mCurrentSuspended) { + totalSuspended += manager->mTaskCount; + } + } + + MOZ_ASSERT(mMainThreadTasks.size() >= totalSuspended); + + // This would break down if we have a non-suspended task depending on a + // suspended task. This is why for the moment we do not allow tasks + // to be dependent on tasks managed by another taskmanager. + if (mMainThreadTasks.size() > totalSuspended) { + for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end(); + iter++) { + Task* task = iter->get(); + + if (task->mTaskManager && task->mTaskManager->mCurrentSuspended) { + // Even though we may want to run some dependencies of this task, we + // will run them at their own priority level and not the priority + // level of their dependents. + continue; + } + + task = GetFinalDependency(task); + + if (!task->IsMainThreadOnly() || task->mInProgress || + (task->mTaskManager && task->mTaskManager->mCurrentSuspended)) { + continue; + } + + mCurrentTasksMT.push(task); + mMainThreadTasks.erase(task->mIterator); + task->mIterator = mMainThreadTasks.end(); + task->mInProgress = true; + TaskManager* manager = task->GetManager(); + bool result = false; + + { + MutexAutoUnlock unlock(mGraphMutex); + if (manager) { + manager->WillRunTask(); + if (manager != mIdleTaskManager) { + // Notify the idle period state that we're running a non-idle task. + // This needs to happen while our mutex is not locked! + mIdleTaskManager->State().FlagNotIdle(); + } else { + TimeStamp idleDeadline = + mIdleTaskManager->State().GetCachedIdleDeadline(); + MOZ_ASSERT( + idleDeadline, + "How can we not have a deadline if our manager is enabled?"); + task->SetIdleDeadline(idleDeadline); + } + } + if (mIdleTaskManager) { + // We found a task to run; we can clear the idle deadline on our idle + // task manager. This _must_ be done before we actually run the task, + // because running the task could reenter via spinning the event loop + // and we want to make sure there's no cached idle deadline at that + // point. But we have to make sure we do it after out SetIdleDeadline + // call above, in the case when the task is actually an idle task. + mIdleTaskManager->State().ClearCachedIdleDeadline(); + } + + TimeStamp now = TimeStamp::Now(); + + if (mainThread) { + if (task->GetPriority() < uint32_t(EventQueuePriority::InputHigh) || + task->mInsertionTime.IsNull()) { + mainThread->SetRunningEventDelay(TimeDuration(), now); + } else { + mainThread->SetRunningEventDelay(now - task->mInsertionTime, now); + } + } + + nsAutoCString name; +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + task->GetName(name); +#endif + + PerformanceCounterState::Snapshot snapshot = + mPerformanceCounterState->RunnableWillRun( + task->GetPerformanceCounter(), now, + manager == mIdleTaskManager); + + { + LogTask::Run log(task); +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + AutoSetMainThreadRunnableName nameGuard(name); +#endif + AUTO_PROFILE_FOLLOWING_TASK(task); + result = task->Run(); + } + + // Task itself should keep manager alive. + if (manager) { + manager->DidRunTask(); + } + + mPerformanceCounterState->RunnableDidRun(name, std::move(snapshot)); + } + + // Task itself should keep manager alive. + if (manager && result && manager->mTaskCount == 0) { + mTaskManagers.erase(manager); + } + + task->mInProgress = false; + + if (!result) { + // Presumably this task was interrupted, leave its dependencies + // unresolved and reinsert into the queue. + auto insertion = + mMainThreadTasks.insert(std::move(mCurrentTasksMT.top())); + MOZ_ASSERT(insertion.second); + task->mIterator = insertion.first; + manager->WillRunTask(); + } else { + task->mCompleted = true; +#ifdef DEBUG + task->mIsInGraph = false; +#endif + // Clear dependencies to release references. + task->mDependencies.clear(); + + if (!mThreadableTasks.empty()) { + // We're going to wake up a single thread in our pool. This thread + // is responsible for waking up additional threads in the situation + // where more than one task became available. + mThreadPoolCV.Notify(); + } + } + + mCurrentTasksMT.pop(); + return true; + } + } + + mMayHaveMainThreadTask = false; + if (mIdleTaskManager) { + // We did not find a task to run. We still need to clear the cached idle + // deadline on our idle state, because that deadline was only relevant to + // the execution of this function. Had we found a task, we would have + // cleared the deadline before running that task. + mIdleTaskManager->State().ClearCachedIdleDeadline(); + } + return false; +} + +Task* TaskController::GetFinalDependency(Task* aTask) { + Task* nextTask; + + while ((nextTask = aTask->GetHighestPriorityDependency())) { + aTask = nextTask; + } + + return aTask; +} + +void TaskController::MaybeInterruptTask(Task* aTask) { + mGraphMutex.AssertCurrentThreadOwns(); + + if (!aTask) { + return; + } + + // This optimization prevents many slow lookups in long chains of similar + // priority. + if (!aTask->mDependencies.empty()) { + Task* firstDependency = aTask->mDependencies.begin()->get(); + if (aTask->GetPriority() <= firstDependency->GetPriority() && + !firstDependency->mCompleted && + aTask->IsMainThreadOnly() == firstDependency->IsMainThreadOnly()) { + // This task has the same or a higher priority as one of its dependencies, + // never any need to interrupt. + return; + } + } + + Task* finalDependency = GetFinalDependency(aTask); + + if (finalDependency->mInProgress) { + // No need to wake anything, we can't schedule this task right now anyway. + return; + } + + if (aTask->IsMainThreadOnly()) { + mMayHaveMainThreadTask = true; + + EnsureMainThreadTasksScheduled(); + + if (mCurrentTasksMT.empty()) { + return; + } + + // We could go through the steps above here and interrupt an off main + // thread task in case it has a lower priority. + if (!finalDependency->IsMainThreadOnly()) { + return; + } + + if (mCurrentTasksMT.top()->GetPriority() < aTask->GetPriority()) { + mCurrentTasksMT.top()->RequestInterrupt(aTask->GetPriority()); + } + } else { + Task* lowestPriorityTask = nullptr; + for (PoolThread& thread : mPoolThreads) { + if (!thread.mCurrentTask) { + mThreadPoolCV.Notify(); + // There's a free thread, no need to interrupt anything. + return; + } + + if (!lowestPriorityTask) { + lowestPriorityTask = thread.mCurrentTask.get(); + continue; + } + + // This should possibly select the lowest priority task which was started + // the latest. But for now we ignore that optimization. + // This also doesn't guarantee a task is interruptable, so that's an + // avenue for improvements as well. + if (lowestPriorityTask->GetPriority() > thread.mEffectiveTaskPriority) { + lowestPriorityTask = thread.mCurrentTask.get(); + } + } + + if (lowestPriorityTask->GetPriority() < aTask->GetPriority()) { + lowestPriorityTask->RequestInterrupt(aTask->GetPriority()); + } + + // We choose not to interrupt main thread tasks for tasks which may be + // executed off the main thread. + } +} + +Task* TaskController::GetHighestPriorityMTTask() { + mGraphMutex.AssertCurrentThreadOwns(); + + if (!mMainThreadTasks.empty()) { + return mMainThreadTasks.begin()->get(); + } + return nullptr; +} + +void TaskController::EnsureMainThreadTasksScheduled() { + if (mObserver) { + mObserver->OnDispatchedEvent(); + } + if (mExternalCondVar) { + mExternalCondVar->Notify(); + } + mMainThreadCV.Notify(); +} + +void TaskController::ProcessUpdatedPriorityModifier(TaskManager* aManager) { + mGraphMutex.AssertCurrentThreadOwns(); + + MOZ_ASSERT(NS_IsMainThread()); + + int32_t modifier = aManager->mCurrentPriorityModifier; + + std::vector> storedTasks; + // Find all relevant tasks. + for (auto iter = mMainThreadTasks.begin(); iter != mMainThreadTasks.end();) { + if ((*iter)->mTaskManager == aManager) { + storedTasks.push_back(*iter); + iter = mMainThreadTasks.erase(iter); + } else { + iter++; + } + } + + // Reinsert found tasks with their new priorities. + for (RefPtr& ref : storedTasks) { + // Kept alive at first by the vector and then by mMainThreadTasks. + Task* task = ref; + task->mPriorityModifier = modifier; + auto insertion = mMainThreadTasks.insert(std::move(ref)); + MOZ_ASSERT(insertion.second); + task->mIterator = insertion.first; + } +} + +} // namespace mozilla diff --git a/xpcom/threads/TaskController.h b/xpcom/threads/TaskController.h new file mode 100644 index 0000000000..184080002a --- /dev/null +++ b/xpcom/threads/TaskController.h @@ -0,0 +1,445 @@ +/* -*- 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/. */ + +#ifndef mozilla_TaskController_h +#define mozilla_TaskController_h + +#include "MainThreadUtils.h" +#include "mozilla/CondVar.h" +#include "mozilla/IdlePeriodState.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/EventQueue.h" +#include "nsISupportsImpl.h" +#include "nsIEventTarget.h" + +#include +#include +#include +#include +#include +#include + +class nsIRunnable; +class nsIThreadObserver; + +namespace mozilla { + +class Task; +class TaskController; +class PerformanceCounter; +class PerformanceCounterState; + +const EventQueuePriority kDefaultPriorityValue = EventQueuePriority::Normal; + +// This file contains the core classes to access the Gecko scheduler. The +// scheduler forms a graph of prioritize tasks, and is responsible for ensuring +// the execution of tasks or their dependencies in order of inherited priority. +// +// The core class is the 'Task' class. The task class describes a single unit of +// work. Users scheduling work implement this class and are required to +// reimplement the 'Run' function in order to do work. +// +// The TaskManager class is reimplemented by users that require +// the ability to reprioritize or suspend tasks. +// +// The TaskController is responsible for scheduling the work itself. The AddTask +// function is used to schedule work. The ReprioritizeTask function may be used +// to change the priority of a task already in the task graph, without +// unscheduling it. + +// The TaskManager is the baseclass used to atomically manage a large set of +// tasks. API users reimplementing TaskManager may reimplement a number of +// functions that they may use to indicate to the scheduler changes in the state +// for any tasks they manage. They may be used to reprioritize or suspend tasks +// under their control, and will also be notified before and after tasks under +// their control are executed. Their methods will only be called once per event +// loop turn, however they may still incur some performance overhead. In +// addition to this frequent reprioritizations may incur a significant +// performance overhead and are discouraged. A TaskManager may currently only be +// used to manage tasks that are bound to the Gecko Main Thread. +class TaskManager { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TaskManager) + + TaskManager() : mTaskCount(0) {} + + // Subclasses implementing task manager will have this function called to + // determine whether their associated tasks are currently suspended. This + // will only be called once per iteration of the task queue, this means that + // suspension of tasks managed by a single TaskManager may be assumed to + // occur atomically. + virtual bool IsSuspended(const MutexAutoLock& aProofOfLock) { return false; } + + // Subclasses may implement this in order to supply a priority adjustment + // to their managed tasks. This is called once per iteration of the task + // queue, and may be assumed to occur atomically for all managed tasks. + virtual int32_t GetPriorityModifierForEventLoopTurn( + const MutexAutoLock& aProofOfLock) { + return 0; + } + + void DidQueueTask() { ++mTaskCount; } + // This is called when a managed task is about to be executed by the + // scheduler. Anyone reimplementing this should ensure to call the parent or + // decrement mTaskCount. + virtual void WillRunTask() { --mTaskCount; } + // This is called when a managed task has finished being executed by the + // scheduler. + virtual void DidRunTask() {} + uint32_t PendingTaskCount() { return mTaskCount; } + + protected: + virtual ~TaskManager() {} + + private: + friend class TaskController; + + enum class IterationType { NOT_EVENT_LOOP_TURN, EVENT_LOOP_TURN }; + bool UpdateCachesForCurrentIterationAndReportPriorityModifierChanged( + const MutexAutoLock& aProofOfLock, IterationType aIterationType); + + bool mCurrentSuspended = false; + int32_t mCurrentPriorityModifier = 0; + + std::atomic mTaskCount; +}; + +// A Task is the the base class for any unit of work that may be scheduled. +// Subclasses may specify their priority and whether they should be bound to +// the Gecko Main thread. When not bound to the main thread tasks may be +// executed on any available thread (including the main thread), but they may +// also be executed in parallel to any other task they do not have a dependency +// relationship with. Tasks will be run in order of object creation. +class Task { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Task) + + bool IsMainThreadOnly() { return mMainThreadOnly; } + + // This returns the current task priority with its modifier applied. + uint32_t GetPriority() { return mPriority + mPriorityModifier; } + uint64_t GetSeqNo() { return mSeqNo; } + + // Callee needs to assume this may be called on any thread. + // aInterruptPriority passes the priority of the higher priority task that + // is ready to be executed. The task may safely ignore this function, or + // interrupt any work being done. It may return 'false' from its run function + // in order to be run automatically in the future, or true if it will + // reschedule incomplete work manually. + virtual void RequestInterrupt(uint32_t aInterruptPriority) {} + + // At the moment this -must- be called before the task is added to the + // controller. Calling this after tasks have been added to the controller + // results in undefined behavior! + // At submission, tasks must depend only on tasks managed by the same, or + // no idle manager. + void AddDependency(Task* aTask) { + MOZ_ASSERT(aTask); + MOZ_ASSERT(!mIsInGraph); + mDependencies.insert(aTask); + } + + // This sets the TaskManager for the current task. Calling this after the + // task has been added to the TaskController results in undefined behavior. + void SetManager(TaskManager* aManager) { + MOZ_ASSERT(mMainThreadOnly); + MOZ_ASSERT(!mIsInGraph); + mTaskManager = aManager; + } + TaskManager* GetManager() { return mTaskManager; } + + struct PriorityCompare { + bool operator()(const RefPtr& aTaskA, + const RefPtr& aTaskB) const { + uint32_t prioA = aTaskA->GetPriority(); + uint32_t prioB = aTaskB->GetPriority(); + return (prioA > prioB) || + (prioA == prioB && (aTaskA->GetSeqNo() < aTaskB->GetSeqNo())); + } + }; + + // Tell the task about its idle deadline. Will only be called for + // tasks managed by an IdleTaskManager, right before the task runs. + virtual void SetIdleDeadline(TimeStamp aDeadline) {} + + virtual PerformanceCounter* GetPerformanceCounter() const { return nullptr; } + + // Get a name for this task. This returns false if the task has no name. +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + virtual bool GetName(nsACString& aName) = 0; +#else + virtual bool GetName(nsACString& aName) { return false; } +#endif + + protected: + Task(bool aMainThreadOnly, + uint32_t aPriority = static_cast(kDefaultPriorityValue)) + : mMainThreadOnly(aMainThreadOnly), + mSeqNo(sCurrentTaskSeqNo++), + mPriority(aPriority) {} + + Task(bool aMainThreadOnly, + EventQueuePriority aPriority = kDefaultPriorityValue) + : mMainThreadOnly(aMainThreadOnly), + mSeqNo(sCurrentTaskSeqNo++), + mPriority(static_cast(aPriority)) {} + + virtual ~Task() {} + + friend class TaskController; + + // When this returns false, the task is considered incomplete and will be + // rescheduled at the current 'mPriority' level. + virtual bool Run() = 0; + + private: + Task* GetHighestPriorityDependency(); + + // Iterator pointing to this task's position in + // mThreadableTasks/mMainThreadTasks if, and only if this task is currently + // scheduled to be executed. This allows fast access to the task's position + // in the set, allowing for fast removal. + // This is safe, and remains valid unless the task is removed from the set. + // See also iterator invalidation in: + // https://en.cppreference.com/w/cpp/container + // + // Or the spec: + // "All Associative Containers: The insert and emplace members shall not + // affect the validity of iterators and references to the container + // [26.2.6/9]" "All Associative Containers: The erase members shall invalidate + // only iterators and references to the erased elements [26.2.6/9]" + std::set, PriorityCompare>::iterator mIterator; + std::set, PriorityCompare> mDependencies; + + RefPtr mTaskManager; + + // Access to these variables is protected by the GraphMutex. + bool mMainThreadOnly; + bool mCompleted = false; + bool mInProgress = false; +#ifdef DEBUG + bool mIsInGraph = false; +#endif + + static std::atomic sCurrentTaskSeqNo; + int64_t mSeqNo; + uint32_t mPriority; + // Modifier currently being applied to this task by its taskmanager. + int32_t mPriorityModifier = 0; + // Time this task was inserted into the task graph, this is used by the + // profiler. + mozilla::TimeStamp mInsertionTime; +}; + +struct PoolThread { + PRThread* mThread; + RefPtr mCurrentTask; + // This may be higher than mCurrentTask's priority due to priority + // propagation. This is -only- valid when mCurrentTask != nullptr. + uint32_t mEffectiveTaskPriority; +}; + +// A task manager implementation for priority levels that should only +// run during idle periods. +class IdleTaskManager : public TaskManager { + public: + explicit IdleTaskManager(already_AddRefed&& aIdlePeriod) + : mIdlePeriodState(std::move(aIdlePeriod)), mProcessedTaskCount(0) {} + + IdlePeriodState& State() { return mIdlePeriodState; } + + bool IsSuspended(const MutexAutoLock& aProofOfLock) override { + TimeStamp idleDeadline = State().GetCachedIdleDeadline(); + return !idleDeadline; + } + + void DidRunTask() override { + TaskManager::DidRunTask(); + ++mProcessedTaskCount; + } + + uint64_t ProcessedTaskCount() { return mProcessedTaskCount; } + + private: + // Tracking of our idle state of various sorts. + IdlePeriodState mIdlePeriodState; + + std::atomic mProcessedTaskCount; +}; + +// The TaskController is the core class of the scheduler. It is used to +// schedule tasks to be executed, as well as to reprioritize tasks that have +// already been scheduled. The core functions to do this are AddTask and +// ReprioritizeTask. +class TaskController { + public: + TaskController(); + + static TaskController* Get(); + + static void Initialize(); + + void SetThreadObserver(nsIThreadObserver* aObserver) { + MutexAutoLock lock(mGraphMutex); + mObserver = aObserver; + } + void SetConditionVariable(CondVar* aExternalCondVar) { + MutexAutoLock lock(mGraphMutex); + mExternalCondVar = aExternalCondVar; + } + + void SetIdleTaskManager(IdleTaskManager* aIdleTaskManager) { + mIdleTaskManager = aIdleTaskManager; + } + IdleTaskManager* GetIdleTaskManager() { return mIdleTaskManager.get(); } + + uint64_t RunOutOfMTTasksCount() { return mRunOutOfMTTasksCounter; } + + // Initialization and shutdown code. + void SetPerformanceCounterState( + PerformanceCounterState* aPerformanceCounterState); + + static void Shutdown(); + + // This adds a task to the TaskController graph. + // This may be called on any thread. + void AddTask(already_AddRefed&& aTask); + + // This wait function is the theoretical function you would need if our main + // thread needs to also process OS messages or something along those lines. + void WaitForTaskOrMessage(); + + // This gets the next (highest priority) task that is only allowed to execute + // on the main thread. + void ExecuteNextTaskOnlyMainThread(); + + // Process all pending main thread tasks. + void ProcessPendingMTTask(bool aMayWait = false); + + // This allows reprioritization of a task already in the task graph. + // This may be called on any thread. + void ReprioritizeTask(Task* aTask, uint32_t aPriority); + + void DispatchRunnable(already_AddRefed&& aRunnable, + uint32_t aPriority, TaskManager* aManager = nullptr); + + nsIRunnable* GetRunnableForMTTask(bool aReallyWait); + + bool HasMainThreadPendingTasks(); + + uint64_t PendingMainthreadTaskCountIncludingSuspended(); + + // Let users know whether the last main thread task runnable did work. + bool MTTaskRunnableProcessedTask() { + MOZ_ASSERT(NS_IsMainThread()); + return mMTTaskRunnableProcessedTask; + } + + static int32_t GetPoolThreadCount(); + static size_t GetThreadStackSize(); + + private: + friend void ThreadFuncPoolThread(void* aIndex); + + void InitializeThreadPool(); + + // This gets the next (highest priority) task that is only allowed to execute + // on the main thread, if any, and executes it. + // Returns true if it succeeded. + bool ExecuteNextTaskOnlyMainThreadInternal(const MutexAutoLock& aProofOfLock); + + // The guts of ExecuteNextTaskOnlyMainThreadInternal, which get idle handling + // wrapped around them. Returns whether a task actually ran. + bool DoExecuteNextTaskOnlyMainThreadInternal( + const MutexAutoLock& aProofOfLock); + + Task* GetFinalDependency(Task* aTask); + void MaybeInterruptTask(Task* aTask); + Task* GetHighestPriorityMTTask(); + + void EnsureMainThreadTasksScheduled(); + + void ProcessUpdatedPriorityModifier(TaskManager* aManager); + + void ShutdownThreadPoolInternal(); + void ShutdownInternal(); + + void RunPoolThread(); + + static std::unique_ptr sSingleton; + static StaticMutex sSingletonMutex MOZ_UNANNOTATED; + + // This protects access to the task graph. + Mutex mGraphMutex MOZ_UNANNOTATED; + + // This protects thread pool initialization. We cannot do this from within + // the GraphMutex, since thread creation on Windows can generate events on + // the main thread that need to be handled. + Mutex mPoolInitializationMutex = + Mutex("TaskController::mPoolInitializationMutex"); + // Created under the PoolInitialization mutex, then never extended, and + // only freed when the object is freed. mThread is set at creation time; + // mCurrentTask and mEffectiveTaskPriority are only accessed from the + // thread, so no locking is needed to access this. + std::vector mPoolThreads; + + CondVar mThreadPoolCV; + CondVar mMainThreadCV; + + // Variables below are protected by mGraphMutex. + + std::stack> mCurrentTasksMT; + + // A list of all tasks ordered by priority. + std::set, Task::PriorityCompare> mThreadableTasks; + std::set, Task::PriorityCompare> mMainThreadTasks; + + // TaskManagers currently active. + // We can use a raw pointer since tasks always hold on to their TaskManager. + std::set mTaskManagers; + + // This ensures we keep running the main thread if we processed a task there. + bool mMayHaveMainThreadTask = true; + bool mShuttingDown = false; + + // This stores whether the last main thread task runnable did work. + // Accessed only on MainThread + bool mMTTaskRunnableProcessedTask = false; + + // Whether our thread pool is initialized. We use this currently to avoid + // starting the threads in processes where it's never used. This is protected + // by mPoolInitializationMutex. + bool mThreadPoolInitialized = false; + + // Whether we have scheduled a runnable on the main thread event loop. + // This is used for nsIRunnable compatibility. + RefPtr mMTProcessingRunnable; + RefPtr mMTBlockingProcessingRunnable; + + // XXX - Thread observer to notify when a new event has been dispatched + // Set immediately, then simply accessed from any thread + nsIThreadObserver* mObserver = nullptr; + // XXX - External condvar to notify when we have received an event + CondVar* mExternalCondVar = nullptr; + // Idle task manager so we can properly do idle state stuff. + RefPtr mIdleTaskManager; + + // How many times the main thread was empty. + std::atomic mRunOutOfMTTasksCounter; + + // Our tracking of our performance counter and long task state, + // shared with nsThread. + // Set once when MainThread is created, never changed, only accessed from + // DoExecuteNextTaskOnlyMainThreadInternal() + PerformanceCounterState* mPerformanceCounterState = nullptr; +}; + +} // namespace mozilla + +#endif // mozilla_TaskController_h diff --git a/xpcom/threads/TaskDispatcher.h b/xpcom/threads/TaskDispatcher.h new file mode 100644 index 0000000000..1f27c32c7d --- /dev/null +++ b/xpcom/threads/TaskDispatcher.h @@ -0,0 +1,304 @@ +/* -*- 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/. */ + +#if !defined(TaskDispatcher_h_) +# define TaskDispatcher_h_ + +# include + +# include "mozilla/AbstractThread.h" +# include "mozilla/Maybe.h" +# include "mozilla/ProfilerRunnable.h" +# include "mozilla/UniquePtr.h" +# include "nsIDirectTaskDispatcher.h" +# include "nsISupportsImpl.h" +# include "nsTArray.h" +# include "nsThreadUtils.h" + +namespace mozilla { + +class SimpleTaskQueue { + public: + SimpleTaskQueue() = default; + virtual ~SimpleTaskQueue() = default; + + void AddTask(already_AddRefed aRunnable) { + if (!mTasks) { + mTasks.emplace(); + } + mTasks->push(std::move(aRunnable)); + } + + void DrainTasks() { + if (!mTasks) { + return; + } + auto& queue = mTasks.ref(); + while (!queue.empty()) { + nsCOMPtr r = std::move(queue.front()); + queue.pop(); + AUTO_PROFILE_FOLLOWING_RUNNABLE(r); + r->Run(); + } + } + + bool HaveTasks() const { return mTasks && !mTasks->empty(); } + + private: + // We use a Maybe<> because (a) when used for DirectTasks it often doesn't get + // anything put into it, and (b) the std::queue implementation in GNU + // libstdc++ does two largish heap allocations when creating a new std::queue. + Maybe>> mTasks; +}; + +/* + * A classic approach to cross-thread communication is to dispatch asynchronous + * runnables to perform updates on other threads. This generally works well, but + * there are sometimes reasons why we might want to delay the actual dispatch of + * these tasks until a specified moment. At present, this is primarily useful to + * ensure that mirrored state gets updated atomically - but there may be other + * applications as well. + * + * TaskDispatcher is a general abstract class that accepts tasks and dispatches + * them at some later point. These groups of tasks are per-target-thread, and + * contain separate queues for several kinds of tasks (see comments below). - + * "state change tasks" (which run first, and are intended to be used to update + * the value held by mirrors), and regular tasks, which are other arbitrary + * operations that the are gated to run after all the state changes have + * completed. + */ +class TaskDispatcher { + public: + TaskDispatcher() = default; + virtual ~TaskDispatcher() = default; + + // Direct tasks are run directly (rather than dispatched asynchronously) when + // the tail dispatcher fires. A direct task may cause other tasks to be added + // to the tail dispatcher. + virtual void AddDirectTask(already_AddRefed aRunnable) = 0; + + // State change tasks are dispatched asynchronously always run before regular + // tasks. They are intended to be used to update the value held by mirrors + // before any other dispatched tasks are run on the target thread. + virtual void AddStateChangeTask(AbstractThread* aThread, + already_AddRefed aRunnable) = 0; + + // Regular tasks are dispatched asynchronously, and run after state change + // tasks. + virtual nsresult AddTask(AbstractThread* aThread, + already_AddRefed aRunnable) = 0; + + virtual nsresult DispatchTasksFor(AbstractThread* aThread) = 0; + virtual bool HasTasksFor(AbstractThread* aThread) = 0; + virtual void DrainDirectTasks() = 0; +}; + +/* + * AutoTaskDispatcher is a stack-scoped TaskDispatcher implementation that fires + * its queued tasks when it is popped off the stack. + */ +class AutoTaskDispatcher : public TaskDispatcher { + public: + explicit AutoTaskDispatcher(nsIDirectTaskDispatcher* aDirectTaskDispatcher, + bool aIsTailDispatcher = false) + : mDirectTaskDispatcher(aDirectTaskDispatcher), + mIsTailDispatcher(aIsTailDispatcher) {} + + ~AutoTaskDispatcher() { + // Given that direct tasks may trigger other code that uses the tail + // dispatcher, it's better to avoid processing them in the tail dispatcher's + // destructor. So we require TailDispatchers to manually invoke + // DrainDirectTasks before the AutoTaskDispatcher gets destroyed. In truth, + // this is only necessary in the case where this AutoTaskDispatcher can be + // accessed by the direct tasks it dispatches (true for TailDispatchers, but + // potentially not true for other hypothetical AutoTaskDispatchers). Feel + // free to loosen this restriction to apply only to mIsTailDispatcher if a + // use-case requires it. + MOZ_ASSERT(!HaveDirectTasks()); + + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + DispatchTaskGroup(std::move(mTaskGroups[i])); + } + } + + bool HaveDirectTasks() { + return mDirectTaskDispatcher && mDirectTaskDispatcher->HaveDirectTasks(); + } + + void DrainDirectTasks() override { + if (mDirectTaskDispatcher) { + mDirectTaskDispatcher->DrainDirectTasks(); + } + } + + void AddDirectTask(already_AddRefed aRunnable) override { + MOZ_ASSERT(mDirectTaskDispatcher); + mDirectTaskDispatcher->DispatchDirectTask(std::move(aRunnable)); + } + + void AddStateChangeTask(AbstractThread* aThread, + already_AddRefed aRunnable) override { + nsCOMPtr r = aRunnable; + MOZ_RELEASE_ASSERT(r); + EnsureTaskGroup(aThread).mStateChangeTasks.AppendElement(r.forget()); + } + + nsresult AddTask(AbstractThread* aThread, + already_AddRefed aRunnable) override { + nsCOMPtr r = aRunnable; + MOZ_RELEASE_ASSERT(r); + // To preserve the event order, we need to append a new group if the last + // group is not targeted for |aThread|. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1318226&mark=0-3#c0 + // for the details of the issue. + if (mTaskGroups.Length() == 0 || + mTaskGroups.LastElement()->mThread != aThread) { + mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread)); + } + + PerThreadTaskGroup& group = *mTaskGroups.LastElement(); + group.mRegularTasks.AppendElement(r.forget()); + + return NS_OK; + } + + bool HasTasksFor(AbstractThread* aThread) override { + return !!GetTaskGroup(aThread) || + (aThread == AbstractThread::GetCurrent() && HaveDirectTasks()); + } + + nsresult DispatchTasksFor(AbstractThread* aThread) override { + nsresult rv = NS_OK; + + // Dispatch all groups that match |aThread|. + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + if (mTaskGroups[i]->mThread == aThread) { + nsresult rv2 = DispatchTaskGroup(std::move(mTaskGroups[i])); + + if (NS_WARN_IF(NS_FAILED(rv2)) && NS_SUCCEEDED(rv)) { + // We should try our best to call DispatchTaskGroup() as much as + // possible and return an error if any of DispatchTaskGroup() calls + // failed. + rv = rv2; + } + + mTaskGroups.RemoveElementAt(i--); + } + } + + return rv; + } + + private: + struct PerThreadTaskGroup { + public: + explicit PerThreadTaskGroup(AbstractThread* aThread) : mThread(aThread) { + MOZ_COUNT_CTOR(PerThreadTaskGroup); + } + + MOZ_COUNTED_DTOR(PerThreadTaskGroup) + + RefPtr mThread; + nsTArray> mStateChangeTasks; + nsTArray> mRegularTasks; + }; + + class TaskGroupRunnable : public Runnable { + public: + explicit TaskGroupRunnable(UniquePtr&& aTasks) + : Runnable("AutoTaskDispatcher::TaskGroupRunnable"), + mTasks(std::move(aTasks)) {} + + NS_IMETHOD Run() override { + // State change tasks get run all together before any code is run, so + // that all state changes are made in an atomic unit. + for (size_t i = 0; i < mTasks->mStateChangeTasks.Length(); ++i) { + mTasks->mStateChangeTasks[i]->Run(); + } + + // Once the state changes have completed, drain any direct tasks + // generated by those state changes (i.e. watcher notification tasks). + // This needs to be outside the loop because we don't want to run code + // that might observe intermediate states. + MaybeDrainDirectTasks(); + + for (size_t i = 0; i < mTasks->mRegularTasks.Length(); ++i) { + AUTO_PROFILE_FOLLOWING_RUNNABLE(mTasks->mRegularTasks[i]); + mTasks->mRegularTasks[i]->Run(); + + // Scope direct tasks tightly to the task that generated them. + MaybeDrainDirectTasks(); + } + + return NS_OK; + } + + private: + void MaybeDrainDirectTasks() { + AbstractThread* currentThread = AbstractThread::GetCurrent(); + if (currentThread && currentThread->MightHaveTailTasks()) { + currentThread->TailDispatcher().DrainDirectTasks(); + } + } + + UniquePtr mTasks; + }; + + PerThreadTaskGroup& EnsureTaskGroup(AbstractThread* aThread) { + PerThreadTaskGroup* existing = GetTaskGroup(aThread); + if (existing) { + return *existing; + } + + mTaskGroups.AppendElement(new PerThreadTaskGroup(aThread)); + return *mTaskGroups.LastElement(); + } + + PerThreadTaskGroup* GetTaskGroup(AbstractThread* aThread) { + for (size_t i = 0; i < mTaskGroups.Length(); ++i) { + if (mTaskGroups[i]->mThread == aThread) { + return mTaskGroups[i].get(); + } + } + + // Not found. + return nullptr; + } + + nsresult DispatchTaskGroup(UniquePtr aGroup) { + RefPtr thread = aGroup->mThread; + + AbstractThread::DispatchReason reason = + mIsTailDispatcher ? AbstractThread::TailDispatch + : AbstractThread::NormalDispatch; + nsCOMPtr r = new TaskGroupRunnable(std::move(aGroup)); + return thread->Dispatch(r.forget(), reason); + } + + // Task groups, organized by thread. + nsTArray> mTaskGroups; + + nsCOMPtr mDirectTaskDispatcher; + // True if this TaskDispatcher represents the tail dispatcher for the thread + // upon which it runs. + const bool mIsTailDispatcher; +}; + +// Little utility class to allow declaring AutoTaskDispatcher as a default +// parameter for methods that take a TaskDispatcher&. +template +class PassByRef { + public: + PassByRef() = default; + operator T&() { return mVal; } + + private: + T mVal; +}; + +} // namespace mozilla + +#endif diff --git a/xpcom/threads/TaskQueue.cpp b/xpcom/threads/TaskQueue.cpp new file mode 100644 index 0000000000..9c2019de95 --- /dev/null +++ b/xpcom/threads/TaskQueue.cpp @@ -0,0 +1,347 @@ +/* -*- 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/TaskQueue.h" + +#include "mozilla/DelayedRunnable.h" +#include "mozilla/ProfilerRunnable.h" +#include "nsIEventTarget.h" +#include "nsITargetShutdownTask.h" +#include "nsThreadUtils.h" +#include "nsQueryObject.h" + +namespace mozilla { + +// Handle for a TaskQueue being tracked by a TaskQueueTracker. When created, +// it is registered with the TaskQueueTracker, and when destroyed it is +// unregistered. Holds a threadsafe weak reference to the TaskQueue. +class TaskQueueTrackerEntry final + : private LinkedListElement { + public: + TaskQueueTrackerEntry(TaskQueueTracker* aTracker, + const RefPtr& aQueue) + : mTracker(aTracker), mQueue(aQueue) { + MutexAutoLock lock(mTracker->mMutex); + mTracker->mEntries.insertFront(this); + } + ~TaskQueueTrackerEntry() { + MutexAutoLock lock(mTracker->mMutex); + removeFrom(mTracker->mEntries); + } + + TaskQueueTrackerEntry(const TaskQueueTrackerEntry&) = delete; + TaskQueueTrackerEntry(TaskQueueTrackerEntry&&) = delete; + TaskQueueTrackerEntry& operator=(const TaskQueueTrackerEntry&) = delete; + TaskQueueTrackerEntry& operator=(TaskQueueTrackerEntry&&) = delete; + + RefPtr GetQueue() const { return RefPtr(mQueue); } + + private: + friend class LinkedList; + friend class LinkedListElement; + + const RefPtr mTracker; + const ThreadSafeWeakPtr mQueue; +}; + +RefPtr TaskQueue::Create(already_AddRefed aTarget, + const char* aName, + bool aSupportsTailDispatch) { + nsCOMPtr target(std::move(aTarget)); + RefPtr queue = + new TaskQueue(do_AddRef(target), aName, aSupportsTailDispatch); + + // If |target| is a TaskQueueTracker, register this TaskQueue with it. It will + // be unregistered when the TaskQueue is destroyed or shut down. + if (RefPtr tracker = do_QueryObject(target)) { + MonitorAutoLock lock(queue->mQueueMonitor); + queue->mTrackerEntry = MakeUnique(tracker, queue); + } + + return queue; +} + +TaskQueue::TaskQueue(already_AddRefed aTarget, + const char* aName, bool aSupportsTailDispatch) + : AbstractThread(aSupportsTailDispatch), + mTarget(aTarget), + mQueueMonitor("TaskQueue::Queue"), + mTailDispatcher(nullptr), + mIsRunning(false), + mIsShutdown(false), + mName(aName) {} + +TaskQueue::~TaskQueue() { + // We should never free the TaskQueue if it was destroyed abnormally, meaning + // that all cleanup tasks should be complete if we do. + MOZ_ASSERT(mShutdownTasks.IsEmpty()); +} + +NS_IMPL_ADDREF_INHERITED(TaskQueue, SupportsThreadSafeWeakPtr) +NS_IMPL_RELEASE_INHERITED(TaskQueue, SupportsThreadSafeWeakPtr) +NS_IMPL_QUERY_INTERFACE(TaskQueue, nsIDirectTaskDispatcher, + nsISerialEventTarget, nsIEventTarget) + +TaskDispatcher& TaskQueue::TailDispatcher() { + MOZ_ASSERT(IsCurrentThreadIn()); + MOZ_ASSERT(mTailDispatcher); + return *mTailDispatcher; +} + +// Note aRunnable is passed by ref to support conditional ownership transfer. +// See Dispatch() in TaskQueue.h for more details. +nsresult TaskQueue::DispatchLocked(nsCOMPtr& aRunnable, + uint32_t aFlags, DispatchReason aReason) { + mQueueMonitor.AssertCurrentThreadOwns(); + + // Continue to allow dispatches after shutdown until the last message has been + // processed, at which point no more messages will be accepted. + if (mIsShutdown && !mIsRunning) { + return NS_ERROR_UNEXPECTED; + } + + AbstractThread* currentThread; + if (aReason != TailDispatch && (currentThread = GetCurrent()) && + RequiresTailDispatch(currentThread) && + currentThread->IsTailDispatcherAvailable()) { + MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL, + "Tail dispatch doesn't support flags"); + return currentThread->TailDispatcher().AddTask(this, aRunnable.forget()); + } + + LogRunnable::LogDispatch(aRunnable); + mTasks.Push({std::move(aRunnable), aFlags}); + + if (mIsRunning) { + return NS_OK; + } + RefPtr runner(new Runner(this)); + nsresult rv = mTarget->Dispatch(runner.forget(), aFlags); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to dispatch runnable to run TaskQueue"); + return rv; + } + mIsRunning = true; + + return NS_OK; +} + +nsresult TaskQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + + MonitorAutoLock mon(mQueueMonitor); + if (mIsShutdown) { + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(!mShutdownTasks.Contains(aTask)); + mShutdownTasks.AppendElement(aTask); + return NS_OK; +} + +nsresult TaskQueue::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + + MonitorAutoLock mon(mQueueMonitor); + if (mIsShutdown) { + return NS_ERROR_UNEXPECTED; + } + + return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED; +} + +void TaskQueue::AwaitIdle() { + MonitorAutoLock mon(mQueueMonitor); + AwaitIdleLocked(); +} + +void TaskQueue::AwaitIdleLocked() { + // Make sure there are no tasks for this queue waiting in the caller's tail + // dispatcher. + MOZ_ASSERT_IF(AbstractThread::GetCurrent(), + !AbstractThread::GetCurrent()->HasTailTasksFor(this)); + + mQueueMonitor.AssertCurrentThreadOwns(); + MOZ_ASSERT(mIsRunning || mTasks.IsEmpty()); + while (mIsRunning) { + mQueueMonitor.Wait(); + } +} + +void TaskQueue::AwaitShutdownAndIdle() { + MOZ_ASSERT(!IsCurrentThreadIn()); + // Make sure there are no tasks for this queue waiting in the caller's tail + // dispatcher. + MOZ_ASSERT_IF(AbstractThread::GetCurrent(), + !AbstractThread::GetCurrent()->HasTailTasksFor(this)); + + MonitorAutoLock mon(mQueueMonitor); + while (!mIsShutdown) { + mQueueMonitor.Wait(); + } + AwaitIdleLocked(); +} +RefPtr TaskQueue::BeginShutdown() { + // Dispatch any tasks for this queue waiting in the caller's tail dispatcher, + // since this is the last opportunity to do so. + if (AbstractThread* currentThread = AbstractThread::GetCurrent()) { + currentThread->TailDispatchTasksFor(this); + } + + MonitorAutoLock mon(mQueueMonitor); + // Dispatch any cleanup tasks to the queue before we put it into full + // shutdown. + for (auto& task : mShutdownTasks) { + nsCOMPtr runnable{task->AsRunnable()}; + MOZ_ALWAYS_SUCCEEDS( + DispatchLocked(runnable, NS_DISPATCH_NORMAL, TailDispatch)); + } + mShutdownTasks.Clear(); + mIsShutdown = true; + + RefPtr p = mShutdownPromise.Ensure(__func__); + MaybeResolveShutdown(); + mon.NotifyAll(); + return p; +} + +void TaskQueue::MaybeResolveShutdown() { + mQueueMonitor.AssertCurrentThreadOwns(); + if (mIsShutdown && !mIsRunning) { + mShutdownPromise.ResolveIfExists(true, __func__); + // Disconnect from our target as we won't try to dispatch any more events. + mTrackerEntry = nullptr; + mTarget = nullptr; + } +} + +bool TaskQueue::IsEmpty() { + MonitorAutoLock mon(mQueueMonitor); + return mTasks.IsEmpty(); +} + +bool TaskQueue::IsCurrentThreadIn() const { + bool in = mRunningThread == PR_GetCurrentThread(); + return in; +} + +nsresult TaskQueue::Runner::Run() { + TaskStruct event; + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + MOZ_ASSERT(mQueue->mIsRunning); + if (mQueue->mTasks.IsEmpty()) { + mQueue->mIsRunning = false; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + return NS_OK; + } + event = std::move(mQueue->mTasks.FirstElement()); + mQueue->mTasks.Pop(); + } + MOZ_ASSERT(event.event); + + // Note that dropping the queue monitor before running the task, and + // taking the monitor again after the task has run ensures we have memory + // fences enforced. This means that if the object we're calling wasn't + // designed to be threadsafe, it will be, provided we're only calling it + // in this task queue. + { + AutoTaskGuard g(mQueue); + SerialEventTargetGuard tg(mQueue); + { + LogRunnable::Run log(event.event); + + AUTO_PROFILE_FOLLOWING_RUNNABLE(event.event); + event.event->Run(); + + // Drop the reference to event. The event will hold a reference to the + // object it's calling, and we don't want to keep it alive, it may be + // making assumptions what holds references to it. This is especially + // the case if the object is waiting for us to shutdown, so that it + // can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case). + event.event = nullptr; + } + } + + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + if (mQueue->mTasks.IsEmpty()) { + // No more events to run. Exit the task runner. + mQueue->mIsRunning = false; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + return NS_OK; + } + } + + // There's at least one more event that we can run. Dispatch this Runner + // to the target again to ensure it runs again. Note that we don't just + // run in a loop here so that we don't hog the target. This means we may + // run on another thread next time, but we rely on the memory fences from + // mQueueMonitor for thread safety of non-threadsafe tasks. + nsresult rv; + { + MonitorAutoLock mon(mQueue->mQueueMonitor); + rv = mQueue->mTarget->Dispatch( + this, mQueue->mTasks.FirstElement().flags | NS_DISPATCH_AT_END); + } + if (NS_FAILED(rv)) { + // Failed to dispatch, shutdown! + MonitorAutoLock mon(mQueue->mQueueMonitor); + mQueue->mIsRunning = false; + mQueue->mIsShutdown = true; + mQueue->MaybeResolveShutdown(); + mon.NotifyAll(); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIDirectTaskDispatcher +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +TaskQueue::DispatchDirectTask(already_AddRefed aEvent) { + if (!IsCurrentThreadIn()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.AddTask(std::move(aEvent)); + return NS_OK; +} + +NS_IMETHODIMP TaskQueue::DrainDirectTasks() { + if (!IsCurrentThreadIn()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.DrainTasks(); + return NS_OK; +} + +NS_IMETHODIMP TaskQueue::HaveDirectTasks(bool* aValue) { + if (!IsCurrentThreadIn()) { + return NS_ERROR_FAILURE; + } + + *aValue = mDirectTasks.HaveTasks(); + return NS_OK; +} + +nsTArray> TaskQueueTracker::GetAllTrackedTaskQueues() { + MutexAutoLock lock(mMutex); + nsTArray> queues; + for (auto* entry : mEntries) { + if (auto queue = entry->GetQueue()) { + queues.AppendElement(queue); + } + } + return queues; +} + +TaskQueueTracker::~TaskQueueTracker() = default; + +} // namespace mozilla diff --git a/xpcom/threads/TaskQueue.h b/xpcom/threads/TaskQueue.h new file mode 100644 index 0000000000..b9321866a9 --- /dev/null +++ b/xpcom/threads/TaskQueue.h @@ -0,0 +1,281 @@ +/* -*- 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/. */ + +#ifndef TaskQueue_h_ +#define TaskQueue_h_ + +#include "mozilla/AbstractThread.h" +#include "mozilla/Maybe.h" +#include "mozilla/Monitor.h" +#include "mozilla/MozPromise.h" +#include "mozilla/Queue.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/ThreadSafeWeakPtr.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +typedef MozPromise ShutdownPromise; + +class TaskQueueTrackerEntry; + +// Abstracts executing runnables in order on an arbitrary event target. The +// runnables dispatched to the TaskQueue will be executed in the order in which +// they're received, and are guaranteed to not be executed concurrently. +// They may be executed on different threads, and a memory barrier is used +// to make this threadsafe for objects that aren't already threadsafe. +// +// Note, since a TaskQueue can also be converted to an nsIEventTarget using +// WrapAsEventTarget() its possible to construct a hierarchy of TaskQueues. +// Consider these three TaskQueues: +// +// TQ1 dispatches to the main thread +// TQ2 dispatches to TQ1 +// TQ3 dispatches to TQ1 +// +// This ensures there is only ever a single runnable from the entire chain on +// the main thread. It also ensures that TQ2 and TQ3 only have a single +// runnable in TQ1 at any time. +// +// This arrangement lets you prioritize work by dispatching runnables directly +// to TQ1. You can issue many runnables for important work. Meanwhile the TQ2 +// and TQ3 work will always execute at most one runnable and then yield. +// +// A TaskQueue does not require explicit shutdown, however it provides a +// BeginShutdown() method that places TaskQueue in a shut down state and returns +// a promise that gets resolved once all pending tasks have completed +class TaskQueue final : public AbstractThread, + public nsIDirectTaskDispatcher, + public SupportsThreadSafeWeakPtr { + class EventTargetWrapper; + + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIDIRECTTASKDISPATCHER + MOZ_DECLARE_REFCOUNTED_TYPENAME(TaskQueue) + + static RefPtr Create(already_AddRefed aTarget, + const char* aName, + bool aSupportsTailDispatch = false); + + TaskDispatcher& TailDispatcher() override; + + NS_IMETHOD Dispatch(already_AddRefed aEvent, + uint32_t aFlags) override { + nsCOMPtr runnable = aEvent; + { + MonitorAutoLock mon(mQueueMonitor); + return DispatchLocked(/* passed by ref */ runnable, aFlags, + NormalDispatch); + } + // If the ownership of |r| is not transferred in DispatchLocked() due to + // dispatch failure, it will be deleted here outside the lock. We do so + // since the destructor of the runnable might access TaskQueue and result + // in deadlocks. + } + + [[nodiscard]] nsresult Dispatch( + already_AddRefed aRunnable, + DispatchReason aReason = NormalDispatch) override { + nsCOMPtr r = aRunnable; + { + MonitorAutoLock mon(mQueueMonitor); + return DispatchLocked(/* passed by ref */ r, NS_DISPATCH_NORMAL, aReason); + } + // If the ownership of |r| is not transferred in DispatchLocked() due to + // dispatch failure, it will be deleted here outside the lock. We do so + // since the destructor of the runnable might access TaskQueue and result + // in deadlocks. + } + + // So we can access nsIEventTarget::Dispatch(nsIRunnable*, uint32_t aFlags) + using nsIEventTarget::Dispatch; + + NS_IMETHOD RegisterShutdownTask(nsITargetShutdownTask* aTask) override; + NS_IMETHOD UnregisterShutdownTask(nsITargetShutdownTask* aTask) override; + + using CancelPromise = MozPromise; + + // Dispatches a task to cancel any pending DelayedRunnables. Idempotent. Only + // dispatches the task on the first call. Creating DelayedRunnables after this + // is called will result in assertion failures. + RefPtr CancelDelayedRunnables(); + + // Puts the queue in a shutdown state and returns immediately. The queue will + // remain alive at least until all the events are drained, because the Runners + // hold a strong reference to the task queue, and one of them is always held + // by the target event queue when the task queue is non-empty. + // + // The returned promise is resolved when the queue goes empty. + RefPtr BeginShutdown(); + + // Blocks until all task finish executing. + void AwaitIdle(); + + // Blocks until the queue is flagged for shutdown and all tasks have finished + // executing. + void AwaitShutdownAndIdle(); + + bool IsEmpty(); + + // Returns true if the current thread is currently running a Runnable in + // the task queue. + bool IsCurrentThreadIn() const override; + using nsISerialEventTarget::IsOnCurrentThread; + + private: + friend class SupportsThreadSafeWeakPtr; + + TaskQueue(already_AddRefed aTarget, const char* aName, + bool aSupportsTailDispatch); + + virtual ~TaskQueue(); + + // Blocks until all task finish executing. Called internally by methods + // that need to wait until the task queue is idle. + // mQueueMonitor must be held. + void AwaitIdleLocked(); + + nsresult DispatchLocked(nsCOMPtr& aRunnable, uint32_t aFlags, + DispatchReason aReason = NormalDispatch); + + void MaybeResolveShutdown(); + + nsCOMPtr mTarget MOZ_GUARDED_BY(mQueueMonitor); + + // Handle for this TaskQueue being registered with our target if it implements + // TaskQueueTracker. + UniquePtr mTrackerEntry MOZ_GUARDED_BY(mQueueMonitor); + + // Monitor that protects the queue, mIsRunning, mIsShutdown and + // mShutdownTasks; + Monitor mQueueMonitor; + + typedef struct TaskStruct { + nsCOMPtr event; + uint32_t flags; + } TaskStruct; + + // Queue of tasks to run. + Queue mTasks MOZ_GUARDED_BY(mQueueMonitor); + + // List of tasks to run during shutdown. + nsTArray> mShutdownTasks + MOZ_GUARDED_BY(mQueueMonitor); + + // The thread currently running the task queue. We store a reference + // to this so that IsCurrentThreadIn() can tell if the current thread + // is the thread currently running in the task queue. + // + // This may be read on any thread, but may only be written on mRunningThread. + // The thread can't die while we're running in it, and we only use it for + // pointer-comparison with the current thread anyway - so we make it atomic + // and don't refcount it. + Atomic mRunningThread; + + // RAII class that gets instantiated for each dispatched task. + class AutoTaskGuard { + public: + explicit AutoTaskGuard(TaskQueue* aQueue) + : mQueue(aQueue), mLastCurrentThread(nullptr) { + // NB: We don't hold the lock to aQueue here. Don't do anything that + // might require it. + MOZ_ASSERT(!mQueue->mTailDispatcher); + mTaskDispatcher.emplace(aQueue, + /* aIsTailDispatcher = */ true); + mQueue->mTailDispatcher = mTaskDispatcher.ptr(); + + mLastCurrentThread = sCurrentThreadTLS.get(); + sCurrentThreadTLS.set(aQueue); + + MOZ_ASSERT(mQueue->mRunningThread == nullptr); + mQueue->mRunningThread = PR_GetCurrentThread(); + } + + ~AutoTaskGuard() { + mTaskDispatcher->DrainDirectTasks(); + mTaskDispatcher.reset(); + + MOZ_ASSERT(mQueue->mRunningThread == PR_GetCurrentThread()); + mQueue->mRunningThread = nullptr; + + sCurrentThreadTLS.set(mLastCurrentThread); + mQueue->mTailDispatcher = nullptr; + } + + private: + Maybe mTaskDispatcher; + TaskQueue* mQueue; + AbstractThread* mLastCurrentThread; + }; + + TaskDispatcher* mTailDispatcher; + + // True if we've dispatched an event to the target to execute events from + // the queue. + bool mIsRunning MOZ_GUARDED_BY(mQueueMonitor); + + // True if we've started our shutdown process. + bool mIsShutdown MOZ_GUARDED_BY(mQueueMonitor); + MozPromiseHolder mShutdownPromise + MOZ_GUARDED_BY(mQueueMonitor); + + // The name of this TaskQueue. Useful when debugging dispatch failures. + const char* const mName; + + SimpleTaskQueue mDirectTasks; + + class Runner : public Runnable { + public: + explicit Runner(TaskQueue* aQueue) + : Runnable("TaskQueue::Runner"), mQueue(aQueue) {} + NS_IMETHOD Run() override; + + private: + RefPtr mQueue; + }; +}; + +#define MOZILLA_TASKQUEUETRACKER_IID \ + { \ + 0x765c4b56, 0xd5f6, 0x4a9f, { \ + 0x91, 0xcf, 0x51, 0x47, 0xb3, 0xc1, 0x7e, 0xa6 \ + } \ + } + +// XPCOM "interface" which may be implemented by nsIEventTarget implementations +// which want to keep track of what TaskQueue instances are currently targeting +// them. This may be used to asynchronously shutdown TaskQueues targeting a +// threadpool or other event target before the threadpool goes away. +// +// This explicitly TaskQueue-aware tracker is used instead of +// `nsITargetShutdownTask` as the operations required to shut down a TaskQueue +// are asynchronous, which is not a requirement of that interface. +class TaskQueueTracker : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_TASKQUEUETRACKER_IID) + + // Get a strong reference to every TaskQueue currently tracked by this + // TaskQueueTracker. May be called from any thraed. + nsTArray> GetAllTrackedTaskQueues(); + + protected: + virtual ~TaskQueueTracker(); + + private: + friend class TaskQueueTrackerEntry; + + Mutex mMutex{"TaskQueueTracker"}; + LinkedList mEntries MOZ_GUARDED_BY(mMutex); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(TaskQueueTracker, MOZILLA_TASKQUEUETRACKER_IID) + +} // namespace mozilla + +#endif // TaskQueue_h_ diff --git a/xpcom/threads/ThreadBound.h b/xpcom/threads/ThreadBound.h new file mode 100644 index 0000000000..4d5e0088b5 --- /dev/null +++ b/xpcom/threads/ThreadBound.h @@ -0,0 +1,143 @@ +/* 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/. */ + +// A class for values only accessible from a single designated thread. + +#ifndef mozilla_ThreadBound_h +#define mozilla_ThreadBound_h + +#include "mozilla/Atomics.h" +#include "prthread.h" + +#include + +namespace mozilla { + +template +class ThreadBound; + +namespace detail { + +template +struct AddConstIf { + using type = T; +}; + +template +struct AddConstIf { + using type = typename std::add_const::type; +}; + +} // namespace detail + +// A ThreadBound is a T that can only be accessed by a specific +// thread. To enforce this rule, the inner T is only accessible +// through a non-copyable, immovable accessor object. +// Given a ThreadBound threadBoundData, it can be accessed like so: +// +// auto innerData = threadBoundData.Access(); +// innerData->DoStuff(); +// +// Trying to access a ThreadBound from a different thread will +// trigger a MOZ_DIAGNOSTIC_ASSERT. +// The encapsulated T is constructed during the construction of the +// enclosing ThreadBound by forwarding all of the latter's +// constructor parameters to the former. A newly constructed +// ThreadBound is bound to the thread it's constructed in. It's +// possible to rebind the data to some otherThread by calling +// +// threadBoundData.Transfer(otherThread); +// +// on the thread that threadBoundData is currently bound to, as long +// as it's not currently being accessed. (Trying to rebind from +// another thread or while an accessor exists will trigger an +// assertion.) +// +// Note: A ThreadBound may be destructed from any thread, not just +// its designated thread at the time the destructor is invoked. +template +class ThreadBound final { + public: + template + explicit ThreadBound(Args&&... aArgs) + : mData(std::forward(aArgs)...), + mThread(PR_GetCurrentThread()), + mAccessCount(0) {} + + ~ThreadBound() { AssertIsNotCurrentlyAccessed(); } + + void Transfer(const PRThread* const aDest) { + AssertIsCorrectThread(); + AssertIsNotCurrentlyAccessed(); + mThread = aDest; + } + + private: + T mData; + + // This member is (potentially) accessed by multiple threads and is + // thus the first point of synchronization between them. + Atomic mThread; + + // In order to support nested accesses (e.g. from different stack + // frames) it's necessary to maintain a counter of the existing + // accessor. Since it's possible to access a const ThreadBound, the + // counter is mutable. It's atomic because accessing it synchronizes + // access to mData (see comment in Accessor's constructor). + using AccessCountType = Atomic; + mutable AccessCountType mAccessCount; + + public: + template + class MOZ_STACK_CLASS Accessor final { + using DataType = typename detail::AddConstIf::type; + + public: + explicit Accessor( + typename detail::AddConstIf::type& aThreadBound) + : mData(aThreadBound.mData), mAccessCount(aThreadBound.mAccessCount) { + aThreadBound.AssertIsCorrectThread(); + + // This load/store serves as a memory fence that guards mData + // against accesses that would trip the thread assertion. + // (Otherwise one of the loads in the caller's instruction + // stream might be scheduled before the assertion.) + ++mAccessCount; + } + + Accessor(const Accessor&) = delete; + Accessor(Accessor&&) = delete; + Accessor& operator=(const Accessor&) = delete; + Accessor& operator=(Accessor&&) = delete; + + ~Accessor() { --mAccessCount; } + + DataType* operator->() { return &mData; } + + private: + DataType& mData; + AccessCountType& mAccessCount; + }; + + auto Access() { return Accessor{*this}; } + + auto Access() const { return Accessor{*this}; } + + private: + bool IsCorrectThread() const { return mThread == PR_GetCurrentThread(); } + + bool IsNotCurrentlyAccessed() const { return mAccessCount == 0; } + +#define MOZ_DEFINE_THREAD_BOUND_ASSERT(predicate) \ + void Assert##predicate() const { MOZ_DIAGNOSTIC_ASSERT(predicate()); } + + MOZ_DEFINE_THREAD_BOUND_ASSERT(IsCorrectThread) + MOZ_DEFINE_THREAD_BOUND_ASSERT(IsNotCurrentlyAccessed) + +#undef MOZ_DEFINE_THREAD_BOUND_ASSERT +}; + +} // namespace mozilla + +#endif // mozilla_ThreadBound_h diff --git a/xpcom/threads/ThreadDelay.cpp b/xpcom/threads/ThreadDelay.cpp new file mode 100644 index 0000000000..1c38e25510 --- /dev/null +++ b/xpcom/threads/ThreadDelay.cpp @@ -0,0 +1,38 @@ +/* -*- 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 "ThreadDelay.h" +#include "mozilla/Assertions.h" +#include "mozilla/ChaosMode.h" + +#if defined(XP_WIN) +# include +#else +# include +#endif + +namespace mozilla { + +void DelayForChaosMode(ChaosFeature aFeature, + const uint32_t aMicrosecondLimit) { + if (!ChaosMode::isActive(aFeature)) { + return; + } + + MOZ_ASSERT(aMicrosecondLimit <= 1000); +#if defined(XP_WIN) + // Windows doesn't support sleeping at less than millisecond resolution. + // We could spin here, or we could just sleep for one millisecond. + // Sleeping for a full millisecond causes heavy delays, so we don't do + // anything here for now until we have found a good way to sleep more + // precisely here. +#else + const uint32_t duration = ChaosMode::randomUint32LessThan(aMicrosecondLimit); + ::usleep(duration); +#endif +} + +} // namespace mozilla diff --git a/xpcom/threads/ThreadDelay.h b/xpcom/threads/ThreadDelay.h new file mode 100644 index 0000000000..5cfc116e4d --- /dev/null +++ b/xpcom/threads/ThreadDelay.h @@ -0,0 +1,16 @@ +/* -*- 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/ChaosMode.h" + +namespace mozilla { + +// Sleep for a random number of microseconds less than aMicrosecondLimit +// if aFeature is enabled. On Windows, the sleep will always be 1 millisecond +// due to platform limitations. +void DelayForChaosMode(ChaosFeature aFeature, const uint32_t aMicrosecondLimit); + +} // namespace mozilla diff --git a/xpcom/threads/ThreadEventQueue.cpp b/xpcom/threads/ThreadEventQueue.cpp new file mode 100644 index 0000000000..dec317beef --- /dev/null +++ b/xpcom/threads/ThreadEventQueue.cpp @@ -0,0 +1,324 @@ +/* -*- 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/ThreadEventQueue.h" +#include "mozilla/EventQueue.h" + +#include "LeakRefPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsITargetShutdownTask.h" +#include "nsIThreadInternal.h" +#include "nsThreadUtils.h" +#include "nsThread.h" +#include "ThreadEventTarget.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/TaskController.h" +#include "mozilla/StaticPrefs_threads.h" + +using namespace mozilla; + +class ThreadEventQueue::NestedSink : public ThreadTargetSink { + public: + NestedSink(EventQueue* aQueue, ThreadEventQueue* aOwner) + : mQueue(aQueue), mOwner(aOwner) {} + + bool PutEvent(already_AddRefed&& aEvent, + EventQueuePriority aPriority) final { + return mOwner->PutEventInternal(std::move(aEvent), aPriority, this); + } + + void Disconnect(const MutexAutoLock& aProofOfLock) final { mQueue = nullptr; } + + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) final { + return NS_ERROR_NOT_IMPLEMENTED; + } + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) final { + return NS_ERROR_NOT_IMPLEMENTED; + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + if (mQueue) { + return mQueue->SizeOfIncludingThis(aMallocSizeOf); + } + return 0; + } + + private: + friend class ThreadEventQueue; + + // This is a non-owning reference. It must live at least until Disconnect is + // called to clear it out. + EventQueue* mQueue; + RefPtr mOwner; +}; + +ThreadEventQueue::ThreadEventQueue(UniquePtr aQueue, + bool aIsMainThread) + : mBaseQueue(std::move(aQueue)), + mLock("ThreadEventQueue"), + mEventsAvailable(mLock, "EventsAvail"), + mIsMainThread(aIsMainThread) { + if (aIsMainThread) { + TaskController::Get()->SetConditionVariable(&mEventsAvailable); + } +} + +ThreadEventQueue::~ThreadEventQueue() { MOZ_ASSERT(mNestedQueues.IsEmpty()); } + +bool ThreadEventQueue::PutEvent(already_AddRefed&& aEvent, + EventQueuePriority aPriority) { + return PutEventInternal(std::move(aEvent), aPriority, nullptr); +} + +bool ThreadEventQueue::PutEventInternal(already_AddRefed&& aEvent, + EventQueuePriority aPriority, + NestedSink* aSink) { + // We want to leak the reference when we fail to dispatch it, so that + // we won't release the event in a wrong thread. + LeakRefPtr event(std::move(aEvent)); + nsCOMPtr obs; + + { + // Check if the runnable wants to override the passed-in priority. + // Do this outside the lock, so runnables implemented in JS can QI + // (and possibly GC) outside of the lock. + if (mIsMainThread) { + auto* e = event.get(); // can't do_QueryInterface on LeakRefPtr. + if (nsCOMPtr runnablePrio = do_QueryInterface(e)) { + uint32_t prio = nsIRunnablePriority::PRIORITY_NORMAL; + runnablePrio->GetPriority(&prio); + if (prio == nsIRunnablePriority::PRIORITY_CONTROL) { + aPriority = EventQueuePriority::Control; + } else if (prio == nsIRunnablePriority::PRIORITY_RENDER_BLOCKING) { + aPriority = EventQueuePriority::RenderBlocking; + } else if (prio == nsIRunnablePriority::PRIORITY_VSYNC) { + aPriority = EventQueuePriority::Vsync; + } else if (prio == nsIRunnablePriority::PRIORITY_INPUT_HIGH) { + aPriority = EventQueuePriority::InputHigh; + } else if (prio == nsIRunnablePriority::PRIORITY_MEDIUMHIGH) { + aPriority = EventQueuePriority::MediumHigh; + } else if (prio == nsIRunnablePriority::PRIORITY_DEFERRED_TIMERS) { + aPriority = EventQueuePriority::DeferredTimers; + } else if (prio == nsIRunnablePriority::PRIORITY_IDLE) { + aPriority = EventQueuePriority::Idle; + } else if (prio == nsIRunnablePriority::PRIORITY_LOW) { + aPriority = EventQueuePriority::Low; + } + } + + if (aPriority == EventQueuePriority::Control && + !StaticPrefs::threads_control_event_queue_enabled()) { + aPriority = EventQueuePriority::MediumHigh; + } + } + + MutexAutoLock lock(mLock); + + if (mEventsAreDoomed) { + return false; + } + + if (aSink) { + if (!aSink->mQueue) { + return false; + } + + aSink->mQueue->PutEvent(event.take(), aPriority, lock); + } else { + mBaseQueue->PutEvent(event.take(), aPriority, lock); + } + + mEventsAvailable.Notify(); + + // Make sure to grab the observer before dropping the lock, otherwise the + // event that we just placed into the queue could run and eventually delete + // this nsThread before the calling thread is scheduled again. We would then + // crash while trying to access a dead nsThread. + obs = mObserver; + } + + if (obs) { + obs->OnDispatchedEvent(); + } + + return true; +} + +already_AddRefed ThreadEventQueue::GetEvent( + bool aMayWait, mozilla::TimeDuration* aLastEventDelay) { + nsCOMPtr event; + { + // Scope for lock. When we are about to return, we will exit this + // scope so we can do some work after releasing the lock but + // before returning. + MutexAutoLock lock(mLock); + + for (;;) { + const bool noNestedQueue = mNestedQueues.IsEmpty(); + if (noNestedQueue) { + event = mBaseQueue->GetEvent(lock, aLastEventDelay); + } else { + // We always get events from the topmost queue when there are nested + // queues. + event = + mNestedQueues.LastElement().mQueue->GetEvent(lock, aLastEventDelay); + } + + if (event) { + break; + } + + // No runnable available. Sleep waiting for one if if we're supposed to. + // Otherwise just go ahead and return null. + if (!aMayWait) { + break; + } + + AUTO_PROFILER_LABEL("ThreadEventQueue::GetEvent::Wait", IDLE); + mEventsAvailable.Wait(); + } + } + + return event.forget(); +} + +bool ThreadEventQueue::HasPendingEvent() { + MutexAutoLock lock(mLock); + + // We always get events from the topmost queue when there are nested queues. + if (mNestedQueues.IsEmpty()) { + return mBaseQueue->HasReadyEvent(lock); + } else { + return mNestedQueues.LastElement().mQueue->HasReadyEvent(lock); + } +} + +bool ThreadEventQueue::ShutdownIfNoPendingEvents() { + MutexAutoLock lock(mLock); + if (mNestedQueues.IsEmpty() && mBaseQueue->IsEmpty(lock)) { + mEventsAreDoomed = true; + return true; + } + return false; +} + +already_AddRefed ThreadEventQueue::PushEventQueue() { + auto queue = MakeUnique(); + RefPtr sink = new NestedSink(queue.get(), this); + RefPtr eventTarget = + new ThreadEventTarget(sink, NS_IsMainThread(), false); + + MutexAutoLock lock(mLock); + + mNestedQueues.AppendElement(NestedQueueItem(std::move(queue), eventTarget)); + return eventTarget.forget(); +} + +void ThreadEventQueue::PopEventQueue(nsIEventTarget* aTarget) { + MutexAutoLock lock(mLock); + + MOZ_ASSERT(!mNestedQueues.IsEmpty()); + + NestedQueueItem& item = mNestedQueues.LastElement(); + + MOZ_ASSERT(aTarget == item.mEventTarget); + + // Disconnect the event target that will be popped. + item.mEventTarget->Disconnect(lock); + + EventQueue* prevQueue = + mNestedQueues.Length() == 1 + ? mBaseQueue.get() + : mNestedQueues[mNestedQueues.Length() - 2].mQueue.get(); + + // Move events from the old queue to the new one. + nsCOMPtr event; + TimeDuration delay; + while ((event = item.mQueue->GetEvent(lock, &delay))) { + // preserve the event delay so far + prevQueue->PutEvent(event.forget(), EventQueuePriority::Normal, lock, + &delay); + } + + mNestedQueues.RemoveLastElement(); +} + +size_t ThreadEventQueue::SizeOfExcludingThis( + mozilla::MallocSizeOf aMallocSizeOf) { + size_t n = 0; + + { + MutexAutoLock lock(mLock); + n += mBaseQueue->SizeOfIncludingThis(aMallocSizeOf); + n += mNestedQueues.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (auto& queue : mNestedQueues) { + n += queue.mEventTarget->SizeOfIncludingThis(aMallocSizeOf); + } + } + + return SynchronizedEventQueue::SizeOfExcludingThis(aMallocSizeOf) + n; +} + +already_AddRefed ThreadEventQueue::GetObserver() { + MutexAutoLock lock(mLock); + return do_AddRef(mObserver); +} + +already_AddRefed ThreadEventQueue::GetObserverOnThread() + MOZ_NO_THREAD_SAFETY_ANALYSIS { + // only written on this thread + return do_AddRef(mObserver); +} + +void ThreadEventQueue::SetObserver(nsIThreadObserver* aObserver) { + // Always called from the thread - single writer. + nsCOMPtr observer = aObserver; + { + MutexAutoLock lock(mLock); + mObserver.swap(observer); + } + if (NS_IsMainThread()) { + TaskController::Get()->SetThreadObserver(aObserver); + } +} + +nsresult ThreadEventQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + MutexAutoLock lock(mLock); + if (mEventsAreDoomed || mShutdownTasksRun) { + return NS_ERROR_UNEXPECTED; + } + MOZ_ASSERT(!mShutdownTasks.Contains(aTask)); + mShutdownTasks.AppendElement(aTask); + return NS_OK; +} + +nsresult ThreadEventQueue::UnregisterShutdownTask( + nsITargetShutdownTask* aTask) { + NS_ENSURE_ARG(aTask); + MutexAutoLock lock(mLock); + if (mEventsAreDoomed || mShutdownTasksRun) { + return NS_ERROR_UNEXPECTED; + } + return mShutdownTasks.RemoveElement(aTask) ? NS_OK : NS_ERROR_UNEXPECTED; +} + +void ThreadEventQueue::RunShutdownTasks() { + nsTArray> shutdownTasks; + { + MutexAutoLock lock(mLock); + shutdownTasks = std::move(mShutdownTasks); + mShutdownTasks.Clear(); + mShutdownTasksRun = true; + } + for (auto& task : shutdownTasks) { + task->TargetShutdown(); + } +} + +ThreadEventQueue::NestedQueueItem::NestedQueueItem( + UniquePtr aQueue, ThreadEventTarget* aEventTarget) + : mQueue(std::move(aQueue)), mEventTarget(aEventTarget) {} diff --git a/xpcom/threads/ThreadEventQueue.h b/xpcom/threads/ThreadEventQueue.h new file mode 100644 index 0000000000..5244acb3dc --- /dev/null +++ b/xpcom/threads/ThreadEventQueue.h @@ -0,0 +1,95 @@ +/* -*- 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/. */ + +#ifndef mozilla_ThreadEventQueue_h +#define mozilla_ThreadEventQueue_h + +#include "mozilla/EventQueue.h" +#include "mozilla/CondVar.h" +#include "mozilla/SynchronizedEventQueue.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" + +class nsIEventTarget; +class nsISerialEventTarget; +class nsIThreadObserver; + +namespace mozilla { + +class EventQueue; +class ThreadEventTarget; + +// A ThreadEventQueue implements normal monitor-style synchronization over the +// EventQueue. It also implements PushEventQueue and PopEventQueue for workers +// (see the documentation below for an explanation of those). All threads use a +// ThreadEventQueue as their event queue. Although for the main thread this +// simply forwards events to the TaskController. +class ThreadEventQueue final : public SynchronizedEventQueue { + public: + explicit ThreadEventQueue(UniquePtr aQueue, + bool aIsMainThread = false); + + bool PutEvent(already_AddRefed&& aEvent, + EventQueuePriority aPriority) final; + + already_AddRefed GetEvent( + bool aMayWait, mozilla::TimeDuration* aLastEventDelay = nullptr) final; + bool HasPendingEvent() final; + + bool ShutdownIfNoPendingEvents() final; + + void Disconnect(const MutexAutoLock& aProofOfLock) final {} + + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) final; + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) final; + void RunShutdownTasks() final; + + already_AddRefed PushEventQueue() final; + void PopEventQueue(nsIEventTarget* aTarget) final; + + already_AddRefed GetObserver() final; + already_AddRefed GetObserverOnThread() final; + void SetObserver(nsIThreadObserver* aObserver) final; + + Mutex& MutexRef() { return mLock; } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) override; + + private: + class NestedSink; + + virtual ~ThreadEventQueue(); + + bool PutEventInternal(already_AddRefed&& aEvent, + EventQueuePriority aPriority, NestedSink* aQueue); + + const UniquePtr mBaseQueue MOZ_GUARDED_BY(mLock); + + struct NestedQueueItem { + UniquePtr mQueue; + RefPtr mEventTarget; + + NestedQueueItem(UniquePtr aQueue, + ThreadEventTarget* aEventTarget); + }; + + nsTArray mNestedQueues MOZ_GUARDED_BY(mLock); + + Mutex mLock; + CondVar mEventsAvailable MOZ_GUARDED_BY(mLock); + + bool mEventsAreDoomed MOZ_GUARDED_BY(mLock) = false; + nsCOMPtr mObserver MOZ_GUARDED_BY(mLock); + nsTArray> mShutdownTasks + MOZ_GUARDED_BY(mLock); + bool mShutdownTasksRun MOZ_GUARDED_BY(mLock) = false; + + const bool mIsMainThread; +}; + +} // namespace mozilla + +#endif // mozilla_ThreadEventQueue_h diff --git a/xpcom/threads/ThreadEventTarget.cpp b/xpcom/threads/ThreadEventTarget.cpp new file mode 100644 index 0000000000..d5df2efda1 --- /dev/null +++ b/xpcom/threads/ThreadEventTarget.cpp @@ -0,0 +1,136 @@ +/* -*- 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 "ThreadEventTarget.h" +#include "mozilla/ThreadEventQueue.h" + +#include "LeakRefPtr.h" +#include "mozilla/DelayedRunnable.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/TimeStamp.h" +#include "nsComponentManagerUtils.h" +#include "nsITimer.h" +#include "nsThreadManager.h" +#include "nsThreadSyncDispatch.h" +#include "nsThreadUtils.h" +#include "ThreadDelay.h" + +using namespace mozilla; + +#ifdef DEBUG +// This flag will be set right after XPCOMShutdownThreads finished but before +// we continue with other processing. It is exclusively meant to prime the +// assertion of ThreadEventTarget::Dispatch as early as possible. +// Please use AppShutdown::IsInOrBeyond(ShutdownPhase::???) +// elsewhere to check for shutdown phases. +static mozilla::Atomic + gXPCOMThreadsShutDownNotified(false); +#endif + +ThreadEventTarget::ThreadEventTarget(ThreadTargetSink* aSink, + bool aIsMainThread, bool aBlockDispatch) + : mSink(aSink), +#ifdef DEBUG + mIsMainThread(aIsMainThread), +#endif + mBlockDispatch(aBlockDispatch) { + mThread = PR_GetCurrentThread(); +} + +ThreadEventTarget::~ThreadEventTarget() = default; + +void ThreadEventTarget::SetCurrentThread(PRThread* aThread) { + mThread = aThread; +} + +void ThreadEventTarget::ClearCurrentThread() { mThread = nullptr; } + +NS_IMPL_ISUPPORTS(ThreadEventTarget, nsIEventTarget, nsISerialEventTarget) + +NS_IMETHODIMP +ThreadEventTarget::DispatchFromScript(nsIRunnable* aRunnable, uint32_t aFlags) { + return Dispatch(do_AddRef(aRunnable), aFlags); +} + +#ifdef DEBUG +// static +void ThreadEventTarget::XPCOMShutdownThreadsNotificationFinished() { + gXPCOMThreadsShutDownNotified = true; +} +#endif + +NS_IMETHODIMP +ThreadEventTarget::Dispatch(already_AddRefed aEvent, + uint32_t aFlags) { + // We want to leak the reference when we fail to dispatch it, so that + // we won't release the event in a wrong thread. + LeakRefPtr event(std::move(aEvent)); + if (NS_WARN_IF(!event)) { + return NS_ERROR_INVALID_ARG; + } + + NS_ASSERTION(!gXPCOMThreadsShutDownNotified || mIsMainThread || + PR_GetCurrentThread() == mThread, + "Dispatch to non-main thread after xpcom-shutdown-threads"); + + if (mBlockDispatch && !(aFlags & NS_DISPATCH_IGNORE_BLOCK_DISPATCH)) { + MOZ_DIAGNOSTIC_ASSERT( + false, + "Attempt to dispatch to thread which does not usually process " + "dispatched runnables until shutdown"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + LogRunnable::LogDispatch(event.get()); + + NS_ASSERTION((aFlags & (NS_DISPATCH_AT_END | + NS_DISPATCH_IGNORE_BLOCK_DISPATCH)) == aFlags, + "unexpected dispatch flags"); + if (!mSink->PutEvent(event.take(), EventQueuePriority::Normal)) { + return NS_ERROR_UNEXPECTED; + } + // Delay to encourage the receiving task to run before we do work. + DelayForChaosMode(ChaosFeature::TaskDispatching, 1000); + return NS_OK; +} + +NS_IMETHODIMP +ThreadEventTarget::DelayedDispatch(already_AddRefed aEvent, + uint32_t aDelayMs) { + nsCOMPtr event = aEvent; + NS_ENSURE_TRUE(!!aDelayMs, NS_ERROR_UNEXPECTED); + + RefPtr r = + new DelayedRunnable(do_AddRef(this), event.forget(), aDelayMs); + nsresult rv = r->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + return Dispatch(r.forget(), NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +ThreadEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return mSink->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThreadEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return mSink->UnregisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThreadEventTarget::IsOnCurrentThread(bool* aIsOnCurrentThread) { + *aIsOnCurrentThread = IsOnCurrentThread(); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +ThreadEventTarget::IsOnCurrentThreadInfallible() { + // This method is only going to be called if `mThread` is null, which + // only happens when the thread has exited the event loop. Therefore, when + // we are called, we can never be on this thread. + return false; +} diff --git a/xpcom/threads/ThreadEventTarget.h b/xpcom/threads/ThreadEventTarget.h new file mode 100644 index 0000000000..b78411aa80 --- /dev/null +++ b/xpcom/threads/ThreadEventTarget.h @@ -0,0 +1,63 @@ +/* -*- 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/. */ + +#ifndef mozilla_ThreadEventTarget_h +#define mozilla_ThreadEventTarget_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/SynchronizedEventQueue.h" // for ThreadTargetSink +#include "nsISerialEventTarget.h" + +namespace mozilla { +class DelayedRunnable; + +// ThreadEventTarget handles the details of posting an event to a thread. It can +// be used with any ThreadTargetSink implementation. +class ThreadEventTarget final : public nsISerialEventTarget { + public: + ThreadEventTarget(ThreadTargetSink* aSink, bool aIsMainThread, + bool aBlockDispatch); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + // Disconnects the target so that it can no longer post events. + void Disconnect(const MutexAutoLock& aProofOfLock) { + mSink->Disconnect(aProofOfLock); + } + + // Sets the thread for which IsOnCurrentThread returns true to the current + // thread. + void SetCurrentThread(PRThread* aThread); + // Call ClearCurrentThread() before the PRThread is deleted on thread join. + void ClearCurrentThread(); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mSink) { + n += mSink->SizeOfIncludingThis(aMallocSizeOf); + } + return aMallocSizeOf(this) + n; + } + +#ifdef DEBUG + static void XPCOMShutdownThreadsNotificationFinished(); +#endif + + private: + ~ThreadEventTarget(); + + RefPtr mSink; +#ifdef DEBUG + const bool mIsMainThread; +#endif + const bool mBlockDispatch; +}; + +} // namespace mozilla + +#endif // mozilla_ThreadEventTarget_h diff --git a/xpcom/threads/ThreadLocalVariables.cpp b/xpcom/threads/ThreadLocalVariables.cpp new file mode 100644 index 0000000000..606c0c6384 --- /dev/null +++ b/xpcom/threads/ThreadLocalVariables.cpp @@ -0,0 +1,16 @@ +/* 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/ThreadLocal.h" + +// This variable is used to ensure creating new URI doesn't put us in an +// infinite loop +MOZ_THREAD_LOCAL(uint32_t) gTlsURLRecursionCount; + +void InitThreadLocalVariables() { + if (!gTlsURLRecursionCount.init()) { + MOZ_CRASH("Could not init gTlsURLRecursionCount"); + } + gTlsURLRecursionCount.set(0); +} diff --git a/xpcom/threads/ThrottledEventQueue.cpp b/xpcom/threads/ThrottledEventQueue.cpp new file mode 100644 index 0000000000..9e4219b305 --- /dev/null +++ b/xpcom/threads/ThrottledEventQueue.cpp @@ -0,0 +1,459 @@ +/* -*- 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 "ThrottledEventQueue.h" + +#include "mozilla/Atomics.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CondVar.h" +#include "mozilla/EventQueue.h" +#include "mozilla/Mutex.h" +#include "mozilla/Unused.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +namespace {} // anonymous namespace + +// The ThrottledEventQueue is designed with inner and outer objects: +// +// XPCOM code base event target +// | | +// v v +// +-------+ +--------+ +// | Outer | +-->|executor| +// +-------+ | +--------+ +// | | | +// | +-------+ | +// +-->| Inner |<--+ +// +-------+ +// +// Client code references the outer nsIEventTarget which in turn references +// an inner object, which actually holds the queue of runnables. +// +// Whenever the queue is non-empty (and not paused), it keeps an "executor" +// runnable dispatched to the base event target. Each time the executor is run, +// it draws the next event from Inner's queue and runs it. If that queue has +// more events, the executor is dispatched to the base again. +// +// The executor holds a strong reference to the Inner object. This means that if +// the outer object is dereferenced and destroyed, the Inner object will remain +// live for as long as the executor exists - that is, until the Inner's queue is +// empty. +// +// A Paused ThrottledEventQueue does not enqueue an executor when new events are +// added. Any executor previously queued on the base event target draws no +// events from a Paused ThrottledEventQueue, and returns without re-enqueueing +// itself. Since there is no executor keeping the Inner object alive until its +// queue is empty, dropping a Paused ThrottledEventQueue may drop the Inner +// while it still owns events. This is the correct behavior: if there are no +// references to it, it will never be Resumed, and thus it will never dispatch +// events again. +// +// Resuming a ThrottledEventQueue must dispatch an executor, so calls to Resume +// are fallible for the same reasons as calls to Dispatch. +// +// The xpcom shutdown process drains the main thread's event queue several +// times, so if a ThrottledEventQueue is being driven by the main thread, it +// should get emptied out by the time we reach the "eventq shutdown" phase. +class ThrottledEventQueue::Inner final : public nsISupports { + // The runnable which is dispatched to the underlying base target. Since + // we only execute one event at a time we just re-use a single instance + // of this class while there are events left in the queue. + class Executor final : public Runnable, public nsIRunnablePriority { + // The Inner whose runnables we execute. mInner->mExecutor points + // to this executor, forming a reference loop. + RefPtr mInner; + + ~Executor() = default; + + public: + explicit Executor(Inner* aInner) + : Runnable("ThrottledEventQueue::Inner::Executor"), mInner(aInner) {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHODIMP + Run() override { + mInner->ExecuteRunnable(); + return NS_OK; + } + + NS_IMETHODIMP + GetPriority(uint32_t* aPriority) override { + *aPriority = mInner->mPriority; + return NS_OK; + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHODIMP + GetName(nsACString& aName) override { return mInner->CurrentName(aName); } +#endif + }; + + mutable Mutex mMutex; + mutable CondVar mIdleCondVar MOZ_GUARDED_BY(mMutex); + + // As-of-yet unexecuted runnables queued on this ThrottledEventQueue. + // + // Used from any thread; protected by mMutex. Signals mIdleCondVar when + // emptied. + EventQueueSized<64> mEventQueue MOZ_GUARDED_BY(mMutex); + + // The event target we dispatch our events (actually, just our Executor) to. + // + // Written only during construction. Readable by any thread without locking. + const nsCOMPtr mBaseTarget; + + // The Executor that we dispatch to mBaseTarget to draw runnables from our + // queue. mExecutor->mInner points to this Inner, forming a reference loop. + // + // Used from any thread; protected by mMutex. + nsCOMPtr mExecutor MOZ_GUARDED_BY(mMutex); + + const char* const mName; + + const uint32_t mPriority; + + // True if this queue is currently paused. + // Used from any thread; protected by mMutex. + bool mIsPaused MOZ_GUARDED_BY(mMutex); + + explicit Inner(nsISerialEventTarget* aBaseTarget, const char* aName, + uint32_t aPriority) + : mMutex("ThrottledEventQueue"), + mIdleCondVar(mMutex, "ThrottledEventQueue:Idle"), + mBaseTarget(aBaseTarget), + mName(aName), + mPriority(aPriority), + mIsPaused(false) { + MOZ_ASSERT(mName, "Must pass a valid name!"); + } + + ~Inner() { +#ifdef DEBUG + MutexAutoLock lock(mMutex); + + // As long as an executor exists, it had better keep us alive, since it's + // going to call ExecuteRunnable on us. + MOZ_ASSERT(!mExecutor); + + // If we have any events in our queue, there should be an executor queued + // for them, and that should have kept us alive. The exception is that, if + // we're paused, we don't enqueue an executor. + MOZ_ASSERT(mEventQueue.IsEmpty(lock) || IsPaused(lock)); + + // Some runnables are only safe to drop on the main thread, so if our queue + // isn't empty, we'd better be on the main thread. + MOZ_ASSERT_IF(!mEventQueue.IsEmpty(lock), NS_IsMainThread()); +#endif + } + + // Make sure an executor has been queued on our base target. If we already + // have one, do nothing; otherwise, create and dispatch it. + nsresult EnsureExecutor(MutexAutoLock& lock) MOZ_REQUIRES(mMutex) { + if (mExecutor) return NS_OK; + + // Note, this creates a ref cycle keeping the inner alive + // until the queue is drained. + mExecutor = new Executor(this); + nsresult rv = mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + mExecutor = nullptr; + return rv; + } + + return NS_OK; + } + + nsresult CurrentName(nsACString& aName) { + nsCOMPtr event; + +#ifdef DEBUG + bool currentThread = false; + mBaseTarget->IsOnCurrentThread(¤tThread); + MOZ_ASSERT(currentThread); +#endif + + { + MutexAutoLock lock(mMutex); + event = mEventQueue.PeekEvent(lock); + // It is possible that mEventQueue wasn't empty when the executor + // was added to the queue, but someone processed events from mEventQueue + // before the executor, this is why mEventQueue is empty here + if (!event) { + aName.AssignLiteral("no runnables left in the ThrottledEventQueue"); + return NS_OK; + } + } + + if (nsCOMPtr named = do_QueryInterface(event)) { + nsresult rv = named->GetName(aName); + return rv; + } + + aName.AssignASCII(mName); + return NS_OK; + } + + void ExecuteRunnable() { + // Any thread + nsCOMPtr event; + +#ifdef DEBUG + bool currentThread = false; + mBaseTarget->IsOnCurrentThread(¤tThread); + MOZ_ASSERT(currentThread); +#endif + + { + MutexAutoLock lock(mMutex); + + // Normally, a paused queue doesn't dispatch any executor, but we might + // have been paused after the executor was already in flight. There's no + // way to yank the executor out of the base event target, so we just check + // for a paused queue here and return without running anything. We'll + // create a new executor when we're resumed. + if (IsPaused(lock)) { + // Note, this breaks a ref cycle. + mExecutor = nullptr; + return; + } + + // We only dispatch an executor runnable when we know there is something + // in the queue, so this should never fail. + event = mEventQueue.GetEvent(lock); + MOZ_ASSERT(event); + + // If there are more events in the queue, then dispatch the next + // executor. We do this now, before running the event, because + // the event might spin the event loop and we don't want to stall + // the queue. + if (mEventQueue.HasReadyEvent(lock)) { + // Dispatch the next base target runnable to attempt to execute + // the next throttled event. We must do this before executing + // the event in case the event spins the event loop. + MOZ_ALWAYS_SUCCEEDS( + mBaseTarget->Dispatch(mExecutor, NS_DISPATCH_NORMAL)); + } + + // Otherwise the queue is empty and we can stop dispatching the + // executor. + else { + // Break the Executor::mInner / Inner::mExecutor reference loop. + mExecutor = nullptr; + mIdleCondVar.NotifyAll(); + } + } + + // Execute the event now that we have unlocked. + LogRunnable::Run log(event); + Unused << event->Run(); + + // To cover the event's destructor code in the LogRunnable log + event = nullptr; + } + + public: + static already_AddRefed Create(nsISerialEventTarget* aBaseTarget, + const char* aName, uint32_t aPriority) { + MOZ_ASSERT(NS_IsMainThread()); + // FIXME: This assertion only worked when `sCurrentShutdownPhase` was not + // being updated. + // MOZ_ASSERT(ClearOnShutdown_Internal::sCurrentShutdownPhase == + // ShutdownPhase::NotInShutdown); + + RefPtr ref = new Inner(aBaseTarget, aName, aPriority); + return ref.forget(); + } + + bool IsEmpty() const { + // Any thread + return Length() == 0; + } + + uint32_t Length() const { + // Any thread + MutexAutoLock lock(mMutex); + return mEventQueue.Count(lock); + } + + already_AddRefed GetEvent() { + MutexAutoLock lock(mMutex); + return mEventQueue.GetEvent(lock); + } + + void AwaitIdle() const { + // Any thread, except the main thread or our base target. Blocking the + // main thread is forbidden. Blocking the base target is guaranteed to + // produce a deadlock. + MOZ_ASSERT(!NS_IsMainThread()); +#ifdef DEBUG + bool onBaseTarget = false; + Unused << mBaseTarget->IsOnCurrentThread(&onBaseTarget); + MOZ_ASSERT(!onBaseTarget); +#endif + + MutexAutoLock lock(mMutex); + while (mExecutor || IsPaused(lock)) { + mIdleCondVar.Wait(); + } + } + + bool IsPaused() const { + MutexAutoLock lock(mMutex); + return IsPaused(lock); + } + + bool IsPaused(const MutexAutoLock& aProofOfLock) const MOZ_REQUIRES(mMutex) { + return mIsPaused; + } + + nsresult SetIsPaused(bool aIsPaused) { + MutexAutoLock lock(mMutex); + + // If we will be unpaused, and we have events in our queue, make sure we + // have an executor queued on the base event target to run them. Do this + // before we actually change mIsPaused, since this is fallible. + if (!aIsPaused && !mEventQueue.IsEmpty(lock)) { + nsresult rv = EnsureExecutor(lock); + if (NS_FAILED(rv)) { + return rv; + } + } + + mIsPaused = aIsPaused; + return NS_OK; + } + + nsresult DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + // Any thread + nsCOMPtr r = aEvent; + return Dispatch(r.forget(), aFlags); + } + + nsresult Dispatch(already_AddRefed aEvent, uint32_t aFlags) { + MOZ_ASSERT(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END); + + // Any thread + MutexAutoLock lock(mMutex); + + if (!IsPaused(lock)) { + // Make sure we have an executor in flight to process events. This is + // fallible, so do it first. Our lock will prevent the executor from + // accessing the event queue before we add the event below. + nsresult rv = EnsureExecutor(lock); + if (NS_FAILED(rv)) return rv; + } + + // Only add the event to the underlying queue if are able to + // dispatch to our base target. + nsCOMPtr event(aEvent); + LogRunnable::LogDispatch(event); + mEventQueue.PutEvent(event.forget(), EventQueuePriority::Normal, lock); + return NS_OK; + } + + nsresult DelayedDispatch(already_AddRefed aEvent, + uint32_t aDelay) { + // The base target may implement this, but we don't. Always fail + // to provide consistent behavior. + return NS_ERROR_NOT_IMPLEMENTED; + } + + nsresult RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return mBaseTarget->RegisterShutdownTask(aTask); + } + + nsresult UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return mBaseTarget->UnregisterShutdownTask(aTask); + } + + bool IsOnCurrentThread() { return mBaseTarget->IsOnCurrentThread(); } + + NS_DECL_THREADSAFE_ISUPPORTS +}; + +NS_IMPL_ISUPPORTS(ThrottledEventQueue::Inner, nsISupports); + +NS_IMPL_ISUPPORTS_INHERITED(ThrottledEventQueue::Inner::Executor, Runnable, + nsIRunnablePriority) + +NS_IMPL_ISUPPORTS(ThrottledEventQueue, ThrottledEventQueue, nsIEventTarget, + nsISerialEventTarget); + +ThrottledEventQueue::ThrottledEventQueue(already_AddRefed aInner) + : mInner(aInner) { + MOZ_ASSERT(mInner); +} + +already_AddRefed ThrottledEventQueue::Create( + nsISerialEventTarget* aBaseTarget, const char* aName, uint32_t aPriority) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBaseTarget); + + RefPtr inner = Inner::Create(aBaseTarget, aName, aPriority); + + RefPtr ref = new ThrottledEventQueue(inner.forget()); + return ref.forget(); +} + +bool ThrottledEventQueue::IsEmpty() const { return mInner->IsEmpty(); } + +uint32_t ThrottledEventQueue::Length() const { return mInner->Length(); } + +// Get the next runnable from the queue +already_AddRefed ThrottledEventQueue::GetEvent() { + return mInner->GetEvent(); +} + +void ThrottledEventQueue::AwaitIdle() const { return mInner->AwaitIdle(); } + +nsresult ThrottledEventQueue::SetIsPaused(bool aIsPaused) { + return mInner->SetIsPaused(aIsPaused); +} + +bool ThrottledEventQueue::IsPaused() const { return mInner->IsPaused(); } + +NS_IMETHODIMP +ThrottledEventQueue::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + return mInner->DispatchFromScript(aEvent, aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::Dispatch(already_AddRefed aEvent, + uint32_t aFlags) { + return mInner->Dispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::DelayedDispatch(already_AddRefed aEvent, + uint32_t aFlags) { + return mInner->DelayedDispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +ThrottledEventQueue::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return mInner->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThrottledEventQueue::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return mInner->UnregisterShutdownTask(aTask); +} + +NS_IMETHODIMP +ThrottledEventQueue::IsOnCurrentThread(bool* aResult) { + *aResult = mInner->IsOnCurrentThread(); + return NS_OK; +} + +NS_IMETHODIMP_(bool) +ThrottledEventQueue::IsOnCurrentThreadInfallible() { + return mInner->IsOnCurrentThread(); +} + +} // namespace mozilla diff --git a/xpcom/threads/ThrottledEventQueue.h b/xpcom/threads/ThrottledEventQueue.h new file mode 100644 index 0000000000..cf37a10a6d --- /dev/null +++ b/xpcom/threads/ThrottledEventQueue.h @@ -0,0 +1,118 @@ +/* -*- 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/. */ + +// nsIEventTarget wrapper for throttling event dispatch. + +#ifndef mozilla_ThrottledEventQueue_h +#define mozilla_ThrottledEventQueue_h + +#include "nsISerialEventTarget.h" + +#define NS_THROTTLEDEVENTQUEUE_IID \ + { \ + 0x8f3cf7dc, 0xfc14, 0x4ad5, { \ + 0x9f, 0xd5, 0xdb, 0x79, 0xbc, 0xe6, 0xd5, 0x08 \ + } \ + } + +namespace mozilla { + +// A ThrottledEventQueue is an event target that can be used to throttle +// events being dispatched to another base target. It maintains its +// own queue of events and only dispatches one at a time to the wrapped +// target. This can be used to avoid flooding the base target. +// +// Flooding is avoided via a very simple principle. Runnables dispatched +// to the ThrottledEventQueue are only dispatched to the base target +// one at a time. Only once that runnable has executed will we dispatch +// the next runnable to the base target. This in effect makes all +// runnables passing through the ThrottledEventQueue yield to other work +// on the base target. +// +// ThrottledEventQueue keeps runnables waiting to be dispatched to the +// base in its own internal queue. Code can query the length of this +// queue using IsEmpty() and Length(). Further, code implement back +// pressure by checking the depth of the queue and deciding to stop +// issuing runnables if they see the ThrottledEventQueue is backed up. +// Code running on other threads could even use AwaitIdle() to block +// all operation until the ThrottledEventQueue drains. +// +// Note, this class is similar to TaskQueue, but also differs in a few +// ways. First, it is a very simple nsIEventTarget implementation. It +// does not use the AbstractThread API. +// +// In addition, ThrottledEventQueue currently dispatches its next +// runnable to the base target *before* running the current event. This +// allows the event code to spin the event loop without stalling the +// ThrottledEventQueue. In contrast, TaskQueue only dispatches its next +// runnable after running the current event. That approach is necessary +// for TaskQueue in order to work with thread pool targets. +// +// So, if you are targeting a thread pool you probably want a TaskQueue. +// If you are targeting a single thread or other non-concurrent event +// target, you probably want a ThrottledEventQueue. +// +// If you drop a ThrottledEventQueue while its queue still has events to be run, +// they will continue to be dispatched as usual to the base. Only once the last +// event has run will all the ThrottledEventQueue's memory be freed. +class ThrottledEventQueue final : public nsISerialEventTarget { + class Inner; + RefPtr mInner; + + explicit ThrottledEventQueue(already_AddRefed aInner); + ~ThrottledEventQueue() = default; + + public: + // Create a ThrottledEventQueue for the given target. + static already_AddRefed Create( + nsISerialEventTarget* aBaseTarget, const char* aName, + uint32_t aPriority = nsIRunnablePriority::PRIORITY_NORMAL); + + // Determine if there are any events pending in the queue. + bool IsEmpty() const; + + // Determine how many events are pending in the queue. + uint32_t Length() const; + + already_AddRefed GetEvent(); + + // Block the current thread until the queue is empty. This may not be called + // on the main thread or the base target. The ThrottledEventQueue must not be + // paused. + void AwaitIdle() const; + + // If |aIsPaused| is true, pause execution of events from this queue. No + // events from this queue will be run until this is called with |aIsPaused| + // false. + // + // To un-pause a ThrottledEventQueue, we need to dispatch a runnable to the + // underlying event target. That operation may fail, so this method is + // fallible as well. + // + // Note that, although ThrottledEventQueue's behavior is descibed as queueing + // events on the base target, an event queued on a TEQ is never actually moved + // to any other queue. What is actually dispatched to the base is an + // "executor" event which, when run, removes an event from the TEQ and runs it + // immediately. This means that you can pause a TEQ even after the executor + // has been queued on the base target, and even so, no events from the TEQ + // will run. When the base target gets around to running the executor, the + // executor will see that the TEQ is paused, and do nothing. + [[nodiscard]] nsresult SetIsPaused(bool aIsPaused); + + // Return true if this ThrottledEventQueue is paused. + bool IsPaused() const; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + NS_DECLARE_STATIC_IID_ACCESSOR(NS_THROTTLEDEVENTQUEUE_IID); +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(ThrottledEventQueue, NS_THROTTLEDEVENTQUEUE_IID); + +} // namespace mozilla + +#endif // mozilla_ThrottledEventQueue_h diff --git a/xpcom/threads/TimerThread.cpp b/xpcom/threads/TimerThread.cpp new file mode 100644 index 0000000000..0d672ac7b0 --- /dev/null +++ b/xpcom/threads/TimerThread.cpp @@ -0,0 +1,1512 @@ +/* -*- 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 "nsTimerImpl.h" +#include "TimerThread.h" + +#include "GeckoProfiler.h" +#include "nsThreadUtils.h" +#include "pratom.h" + +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/ArenaAllocator.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/BinarySearch.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/StaticPrefs_timer.h" + +#include "mozilla/glean/GleanMetrics.h" + +#include + +using namespace mozilla; + +// Bug 1829983 reports an assertion failure that (so far) has only failed once +// in over a month of the assert existing. This #define enables some additional +// output that should get printed out if the assert fails again. +#if defined(XP_WIN) && defined(DEBUG) +# define HACK_OUTPUT_FOR_BUG_1829983 +#endif + +// Uncomment the following line to enable runtime stats during development. +// #define TIMERS_RUNTIME_STATS + +#ifdef TIMERS_RUNTIME_STATS +// This class gathers durations and displays some basic stats when destroyed. +// It is intended to be used as a static variable (see `AUTO_TIMERS_STATS` +// below), to display stats at the end of the program. +class StaticTimersStats { + public: + explicit StaticTimersStats(const char* aName) : mName(aName) {} + + ~StaticTimersStats() { + // Using unsigned long long for computations and printfs. + using ULL = unsigned long long; + ULL n = static_cast(mCount); + if (n == 0) { + printf("[%d] Timers stats `%s`: (nothing)\n", + int(profiler_current_process_id().ToNumber()), mName); + } else if (ULL sumNs = static_cast(mSumDurationsNs); sumNs == 0) { + printf("[%d] Timers stats `%s`: %llu\n", + int(profiler_current_process_id().ToNumber()), mName, n); + } else { + printf("[%d] Timers stats `%s`: %llu ns / %llu = %llu ns, max %llu ns\n", + int(profiler_current_process_id().ToNumber()), mName, sumNs, n, + sumNs / n, static_cast(mLongestDurationNs)); + } + } + + void AddDurationFrom(TimeStamp aStart) { + // Duration between aStart and now, rounded to the nearest nanosecond. + DurationNs duration = static_cast( + (TimeStamp::Now() - aStart).ToMicroseconds() * 1000 + 0.5); + mSumDurationsNs += duration; + ++mCount; + // Update mLongestDurationNs if this one is longer. + for (;;) { + DurationNs longest = mLongestDurationNs; + if (MOZ_LIKELY(longest >= duration)) { + // This duration is not the longest, nothing to do. + break; + } + if (MOZ_LIKELY(mLongestDurationNs.compareExchange(longest, duration))) { + // Successfully updated `mLongestDurationNs` with the new value. + break; + } + // Otherwise someone else just updated `mLongestDurationNs`, we need to + // try again by looping. + } + } + + void AddCount() { + MOZ_ASSERT(mSumDurationsNs == 0, "Don't mix counts and durations"); + ++mCount; + } + + private: + using DurationNs = uint64_t; + using Count = uint32_t; + + Atomic mSumDurationsNs{0}; + Atomic mLongestDurationNs{0}; + Atomic mCount{0}; + const char* mName; +}; + +// RAII object that measures its scoped lifetime duration and reports it to a +// `StaticTimersStats`. +class MOZ_RAII AutoTimersStats { + public: + explicit AutoTimersStats(StaticTimersStats& aStats) + : mStats(aStats), mStart(TimeStamp::Now()) {} + + ~AutoTimersStats() { mStats.AddDurationFrom(mStart); } + + private: + StaticTimersStats& mStats; + TimeStamp mStart; +}; + +// Macro that should be used to collect basic statistics from measurements of +// block durations, from where this macro is, until the end of its enclosing +// scope. The name is used in the static variable name and when displaying stats +// at the end of the program; Another location could use the same name but their +// stats will not be combined, so use different name if these locations should +// be distinguished. +# define AUTO_TIMERS_STATS(name) \ + static ::StaticTimersStats sStat##name(#name); \ + ::AutoTimersStats autoStat##name(sStat##name); + +// This macro only counts the number of times it's used, not durations. +// Don't mix with AUTO_TIMERS_STATS! +# define COUNT_TIMERS_STATS(name) \ + static ::StaticTimersStats sStat##name(#name); \ + sStat##name.AddCount(); + +#else // TIMERS_RUNTIME_STATS + +# define AUTO_TIMERS_STATS(name) +# define COUNT_TIMERS_STATS(name) + +#endif // TIMERS_RUNTIME_STATS else + +NS_IMPL_ISUPPORTS_INHERITED(TimerThread, Runnable, nsIObserver) + +TimerThread::TimerThread() + : Runnable("TimerThread"), + mInitialized(false), + mMonitor("TimerThread.mMonitor"), + mShutdown(false), + mWaiting(false), + mNotified(false), + mSleeping(false), + mAllowedEarlyFiringMicroseconds(0) {} + +TimerThread::~TimerThread() { + mThread = nullptr; + + NS_ASSERTION(mTimers.IsEmpty(), "Timers remain in TimerThread::~TimerThread"); + +#if TIMER_THREAD_STATISTICS + { + MonitorAutoLock lock(mMonitor); + PrintStatistics(); + } +#endif +} + +namespace { + +class TimerObserverRunnable : public Runnable { + public: + explicit TimerObserverRunnable(nsIObserver* aObserver) + : mozilla::Runnable("TimerObserverRunnable"), mObserver(aObserver) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr mObserver; +}; + +NS_IMETHODIMP +TimerObserverRunnable::Run() { + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(mObserver, "sleep_notification", false); + observerService->AddObserver(mObserver, "wake_notification", false); + observerService->AddObserver(mObserver, "suspend_process_notification", + false); + observerService->AddObserver(mObserver, "resume_process_notification", + false); + } + return NS_OK; +} + +} // namespace + +namespace { + +// TimerEventAllocator is a thread-safe allocator used only for nsTimerEvents. +// It's needed to avoid contention over the default allocator lock when +// firing timer events (see bug 733277). The thread-safety is required because +// nsTimerEvent objects are allocated on the timer thread, and freed on another +// thread. Because TimerEventAllocator has its own lock, contention over that +// lock is limited to the allocation and deallocation of nsTimerEvent objects. +// +// Because this is layered over ArenaAllocator, it never shrinks -- even +// "freed" nsTimerEvents aren't truly freed, they're just put onto a free-list +// for later recycling. So the amount of memory consumed will always be equal +// to the high-water mark consumption. But nsTimerEvents are small and it's +// unusual to have more than a few hundred of them, so this shouldn't be a +// problem in practice. + +class TimerEventAllocator { + private: + struct FreeEntry { + FreeEntry* mNext; + }; + + ArenaAllocator<4096> mPool MOZ_GUARDED_BY(mMonitor); + FreeEntry* mFirstFree MOZ_GUARDED_BY(mMonitor); + mozilla::Monitor mMonitor; + + public: + TimerEventAllocator() + : mPool(), mFirstFree(nullptr), mMonitor("TimerEventAllocator") {} + + ~TimerEventAllocator() = default; + + void* Alloc(size_t aSize); + void Free(void* aPtr); +}; + +} // namespace + +// This is a nsICancelableRunnable because we can dispatch it to Workers and +// those can be shut down at any time, and in these cases, Cancel() is called +// instead of Run(). +class nsTimerEvent final : public CancelableRunnable { + public: + NS_IMETHOD Run() override; + + nsresult Cancel() override { + mTimer->Cancel(); + return NS_OK; + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override; +#endif + + explicit nsTimerEvent(already_AddRefed aTimer, + ProfilerThreadId aTimerThreadId) + : mozilla::CancelableRunnable("nsTimerEvent"), + mTimer(aTimer), + mGeneration(mTimer->GetGeneration()), + mTimerThreadId(aTimerThreadId) { + // Note: We override operator new for this class, and the override is + // fallible! + sAllocatorUsers++; + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug) || + profiler_thread_is_being_profiled_for_markers(mTimerThreadId)) { + mInitTime = TimeStamp::Now(); + } + } + + static void Init(); + static void Shutdown(); + static void DeleteAllocatorIfNeeded(); + + static void* operator new(size_t aSize) noexcept(true) { + return sAllocator->Alloc(aSize); + } + void operator delete(void* aPtr) { + sAllocator->Free(aPtr); + sAllocatorUsers--; + DeleteAllocatorIfNeeded(); + } + + already_AddRefed ForgetTimer() { return mTimer.forget(); } + + private: + nsTimerEvent(const nsTimerEvent&) = delete; + nsTimerEvent& operator=(const nsTimerEvent&) = delete; + nsTimerEvent& operator=(const nsTimerEvent&&) = delete; + + ~nsTimerEvent() { + MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0, + "This will result in us attempting to deallocate the " + "nsTimerEvent allocator twice"); + } + + TimeStamp mInitTime; + RefPtr mTimer; + const int32_t mGeneration; + ProfilerThreadId mTimerThreadId; + + static TimerEventAllocator* sAllocator; + + static Atomic sAllocatorUsers; + static Atomic sCanDeleteAllocator; +}; + +TimerEventAllocator* nsTimerEvent::sAllocator = nullptr; +Atomic nsTimerEvent::sAllocatorUsers; +Atomic nsTimerEvent::sCanDeleteAllocator; + +namespace { + +void* TimerEventAllocator::Alloc(size_t aSize) { + MOZ_ASSERT(aSize == sizeof(nsTimerEvent)); + + mozilla::MonitorAutoLock lock(mMonitor); + + void* p; + if (mFirstFree) { + p = mFirstFree; + mFirstFree = mFirstFree->mNext; + } else { + p = mPool.Allocate(aSize, fallible); + } + + return p; +} + +void TimerEventAllocator::Free(void* aPtr) { + mozilla::MonitorAutoLock lock(mMonitor); + + FreeEntry* entry = reinterpret_cast(aPtr); + + entry->mNext = mFirstFree; + mFirstFree = entry; +} + +} // namespace + +struct TimerMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("Timer"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + uint32_t aDelay, uint8_t aType, + MarkerThreadId aThreadId, bool aCanceled) { + aWriter.IntProperty("delay", aDelay); + if (!aThreadId.IsUnspecified()) { + // Tech note: If `ToNumber()` returns a uint64_t, the conversion to + // int64_t is "implementation-defined" before C++20. This is + // acceptable here, because this is a one-way conversion to a unique + // identifier that's used to visually separate data by thread on the + // front-end. + aWriter.IntProperty( + "threadId", static_cast(aThreadId.ThreadId().ToNumber())); + } + if (aCanceled) { + aWriter.BoolProperty("canceled", true); + // Show a red 'X' as a prefix on the marker chart for canceled timers. + aWriter.StringProperty("prefix", "âŒ"); + } + + // The string property for the timer type is not written when the type is + // one shot, as that's the type used almost all the time, and that would + // consume space in the profiler buffer and then in the profile JSON, + // getting in the way of capturing long power profiles. + // Bug 1815677 might make this cheap to capture. + if (aType != nsITimer::TYPE_ONE_SHOT) { + if (aType == nsITimer::TYPE_REPEATING_SLACK) { + aWriter.StringProperty("ttype", "repeating slack"); + } else if (aType == nsITimer::TYPE_REPEATING_PRECISE) { + aWriter.StringProperty("ttype", "repeating precise"); + } else if (aType == nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP) { + aWriter.StringProperty("ttype", "repeating precise can skip"); + } else if (aType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY) { + aWriter.StringProperty("ttype", "repeating slack low priority"); + } else if (aType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY) { + aWriter.StringProperty("ttype", "low priority"); + } + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormat("delay", "Delay", MS::Format::Milliseconds); + schema.AddKeyLabelFormat("ttype", "Timer Type", MS::Format::String); + schema.AddKeyLabelFormat("canceled", "Canceled", MS::Format::String); + schema.SetChartLabel("{marker.data.prefix} {marker.data.delay}"); + schema.SetTableLabel( + "{marker.name} - {marker.data.prefix} {marker.data.delay}"); + return schema; + } +}; + +struct AddRemoveTimerMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("AddRemoveTimer"); + } + static void StreamJSONMarkerData(baseprofiler::SpliceableJSONWriter& aWriter, + const ProfilerString8View& aTimerName, + uint32_t aDelay, MarkerThreadId aThreadId) { + aWriter.StringProperty("name", aTimerName); + aWriter.IntProperty("delay", aDelay); + if (!aThreadId.IsUnspecified()) { + // Tech note: If `ToNumber()` returns a uint64_t, the conversion to + // int64_t is "implementation-defined" before C++20. This is + // acceptable here, because this is a one-way conversion to a unique + // identifier that's used to visually separate data by thread on the + // front-end. + aWriter.IntProperty( + "threadId", static_cast(aThreadId.ThreadId().ToNumber())); + } + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormatSearchable("name", "Name", MS::Format::String, + MS::Searchable::Searchable); + schema.AddKeyLabelFormat("delay", "Delay", MS::Format::Milliseconds); + schema.SetTableLabel( + "{marker.name} - {marker.data.name} - {marker.data.delay}"); + return schema; + } +}; + +void nsTimerEvent::Init() { sAllocator = new TimerEventAllocator(); } + +void nsTimerEvent::Shutdown() { + sCanDeleteAllocator = true; + DeleteAllocatorIfNeeded(); +} + +void nsTimerEvent::DeleteAllocatorIfNeeded() { + if (sCanDeleteAllocator && sAllocatorUsers == 0) { + delete sAllocator; + sAllocator = nullptr; + } +} + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +nsTimerEvent::GetName(nsACString& aName) { + bool current; + MOZ_RELEASE_ASSERT( + NS_SUCCEEDED(mTimer->mEventTarget->IsOnCurrentThread(¤t)) && + current); + + mTimer->GetName(aName); + return NS_OK; +} +#endif + +NS_IMETHODIMP +nsTimerEvent::Run() { + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + TimeStamp now = TimeStamp::Now(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] time between PostTimerEvent() and Fire(): %fms\n", this, + (now - mInitTime).ToMilliseconds())); + } + + if (profiler_thread_is_being_profiled_for_markers(mTimerThreadId)) { + MutexAutoLock lock(mTimer->mMutex); + nsAutoCString name; + mTimer->GetName(name, lock); + // This adds a marker with the timer name as the marker name, to make it + // obvious which timers are being used. This marker will be useful to + // understand which timers might be added and firing excessively often. + profiler_add_marker( + name, geckoprofiler::category::TIMER, + MarkerOptions(MOZ_LIKELY(mInitTime) + ? MarkerTiming::Interval( + mTimer->mTimeout - mTimer->mDelay, mInitTime) + : MarkerTiming::IntervalUntilNowFrom( + mTimer->mTimeout - mTimer->mDelay), + MarkerThreadId(mTimerThreadId)), + TimerMarker{}, mTimer->mDelay.ToMilliseconds(), mTimer->mType, + MarkerThreadId::CurrentThread(), false); + // This marker is meant to help understand the behavior of the timer thread. + profiler_add_marker( + "PostTimerEvent", geckoprofiler::category::OTHER, + MarkerOptions(MOZ_LIKELY(mInitTime) + ? MarkerTiming::IntervalUntilNowFrom(mInitTime) + : MarkerTiming::InstantNow(), + MarkerThreadId(mTimerThreadId)), + AddRemoveTimerMarker{}, name, mTimer->mDelay.ToMilliseconds(), + MarkerThreadId::CurrentThread()); + } + + mTimer->Fire(mGeneration); + + return NS_OK; +} + +nsresult TimerThread::Init() { + mMonitor.AssertCurrentThreadOwns(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("TimerThread::Init [%d]\n", mInitialized)); + + if (!mInitialized) { + nsTimerEvent::Init(); + + // We hold on to mThread to keep the thread alive. + nsresult rv = + NS_NewNamedThread("Timer", getter_AddRefs(mThread), this, + {.stackSize = nsIThreadManager::DEFAULT_STACK_SIZE, + .blockDispatch = true}); + if (NS_FAILED(rv)) { + mThread = nullptr; + } else { + RefPtr r = new TimerObserverRunnable(this); + if (NS_IsMainThread()) { + r->Run(); + } else { + NS_DispatchToMainThread(r); + } + } + + mInitialized = true; + } + + if (!mThread) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult TimerThread::Shutdown() { + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown begin\n")); + + if (!mThread) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsTArray> timers; + { + // lock scope + MonitorAutoLock lock(mMonitor); + + mShutdown = true; + + // notify the cond var so that Run() can return + if (mWaiting) { + mNotified = true; + mMonitor.Notify(); + } + + // Need to copy content of mTimers array to a local array + // because call to timers' Cancel() (and release its self) + // must not be done under the lock. Destructor of a callback + // might potentially call some code reentering the same lock + // that leads to unexpected behavior or deadlock. + // See bug 422472. + timers.SetCapacity(mTimers.Length()); + for (Entry& entry : mTimers) { + if (entry.Value()) { + timers.AppendElement(entry.Take()); + } + } + + mTimers.Clear(); + } + + for (const RefPtr& timer : timers) { + MOZ_ASSERT(timer); + timer->Cancel(); + } + + mThread->Shutdown(); // wait for the thread to die + + nsTimerEvent::Shutdown(); + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("TimerThread::Shutdown end\n")); + return NS_OK; +} + +namespace { + +struct MicrosecondsToInterval { + PRIntervalTime operator[](size_t aMs) const { + return PR_MicrosecondsToInterval(aMs); + } +}; + +struct IntervalComparator { + int operator()(PRIntervalTime aInterval) const { + return (0 < aInterval) ? -1 : 1; + } +}; + +} // namespace + +#ifdef DEBUG +void TimerThread::VerifyTimerListConsistency() const { + mMonitor.AssertCurrentThreadOwns(); + + // Find the first non-canceled timer (and check its cached timeout if we find + // it). + const size_t timerCount = mTimers.Length(); + size_t lastNonCanceledTimerIndex = 0; + while (lastNonCanceledTimerIndex < timerCount && + !mTimers[lastNonCanceledTimerIndex].Value()) { + ++lastNonCanceledTimerIndex; + } + MOZ_ASSERT(lastNonCanceledTimerIndex == timerCount || + mTimers[lastNonCanceledTimerIndex].Value()); + MOZ_ASSERT(lastNonCanceledTimerIndex == timerCount || + mTimers[lastNonCanceledTimerIndex].Value()->mTimeout == + mTimers[lastNonCanceledTimerIndex].Timeout()); + + // Verify that mTimers is sorted and the cached timeouts are consistent. + for (size_t timerIndex = lastNonCanceledTimerIndex + 1; + timerIndex < timerCount; ++timerIndex) { + if (mTimers[timerIndex].Value()) { + MOZ_ASSERT(mTimers[timerIndex].Timeout() == + mTimers[timerIndex].Value()->mTimeout); + MOZ_ASSERT(mTimers[timerIndex].Timeout() >= + mTimers[lastNonCanceledTimerIndex].Timeout()); + lastNonCanceledTimerIndex = timerIndex; + } + } +} +#endif + +size_t TimerThread::ComputeTimerInsertionIndex(const TimeStamp& timeout) const { + mMonitor.AssertCurrentThreadOwns(); + + const size_t timerCount = mTimers.Length(); + + size_t firstGtIndex = 0; + while (firstGtIndex < timerCount && + (!mTimers[firstGtIndex].Value() || + mTimers[firstGtIndex].Timeout() <= timeout)) { + ++firstGtIndex; + } + + return firstGtIndex; +} + +TimeStamp TimerThread::ComputeWakeupTimeFromTimers() const { + mMonitor.AssertCurrentThreadOwns(); + + // Timer list should be non-empty and first timer should always be + // non-canceled at this point and we rely on that here. + MOZ_ASSERT(!mTimers.IsEmpty()); + MOZ_ASSERT(mTimers[0].Value()); + + // Overview: Find the last timer in the list that can be "bundled" together in + // the same wake-up with mTimers[0] and use its timeout as our target wake-up + // time. + + // bundleWakeup is when we should wake up in order to be able to fire all of + // the timers in our selected bundle. It will always be the timeout of the + // last timer in the bundle. + TimeStamp bundleWakeup = mTimers[0].Timeout(); + + // cutoffTime is the latest that we can wake up for the timers currently + // accepted into the bundle. These needs to be updated as we go through the + // list because later timers may have more strict delay tolerances. + const TimeDuration minTimerDelay = TimeDuration::FromMilliseconds( + StaticPrefs::timer_minimum_firing_delay_tolerance_ms()); + const TimeDuration maxTimerDelay = TimeDuration::FromMilliseconds( + StaticPrefs::timer_maximum_firing_delay_tolerance_ms()); + TimeStamp cutoffTime = + bundleWakeup + ComputeAcceptableFiringDelay(mTimers[0].Delay(), + minTimerDelay, maxTimerDelay); + + const size_t timerCount = mTimers.Length(); + for (size_t entryIndex = 1; entryIndex < timerCount; ++entryIndex) { + const Entry& curEntry = mTimers[entryIndex]; + const nsTimerImpl* curTimer = curEntry.Value(); + if (!curTimer) { + // Canceled timer - skip it + continue; + } + + const TimeStamp curTimerDue = curEntry.Timeout(); + if (curTimerDue > cutoffTime) { + // Can't include this timer in the bundle - it fires too late. + break; + } + + // This timer can be included in the bundle. Update bundleWakeup and + // cutoffTime. + bundleWakeup = curTimerDue; + cutoffTime = std::min( + curTimerDue + ComputeAcceptableFiringDelay( + curEntry.Delay(), minTimerDelay, maxTimerDelay), + cutoffTime); + MOZ_ASSERT(bundleWakeup <= cutoffTime); + } + +#ifdef HACK_OUTPUT_FOR_BUG_1829983 + const bool assertCondition = + bundleWakeup - mTimers[0].Timeout() <= + ComputeAcceptableFiringDelay(mTimers[0].Delay(), minTimerDelay, + maxTimerDelay); + if (!assertCondition) { + printf_stderr("*** Special TimerThread debug output ***\n"); + const int64_t tDMin = minTimerDelay.GetValue(); + const int64_t tDMax = maxTimerDelay.GetValue(); + printf_stderr("%16llx / %16llx\n", tDMin, tDMax); + const size_t l = mTimers.Length(); + for (size_t i = 0; i < l; ++i) { + const Entry& e = mTimers[i]; + const TimeStamp tS = e.Timeout(); + const TimeStampValue tSV = tS.GetValue(); + const TimeDuration d = e.Delay(); + printf_stderr("[%5zu] %16llx / %16llx / %d / %d / %16llx\n", i, tSV.GTC(), + tSV.QPC(), (int)tSV.IsNull(), (int)tSV.HasQPC(), + d.GetValue()); + } + } +#endif + MOZ_ASSERT(bundleWakeup - mTimers[0].Timeout() <= + ComputeAcceptableFiringDelay(mTimers[0].Delay(), minTimerDelay, + maxTimerDelay)); + + return bundleWakeup; +} + +TimeDuration TimerThread::ComputeAcceptableFiringDelay( + TimeDuration timerDuration, TimeDuration minDelay, + TimeDuration maxDelay) const { + // Use the timer's duration divided by this value as a base for how much + // firing delay a timer can accept. 8 was chosen specifically because it is a + // power of two which means that this division turns nicely into a shift. + constexpr int64_t timerDurationDivider = 8; + static_assert(IsPowerOfTwo(static_cast(timerDurationDivider))); + const TimeDuration tmp = timerDuration / timerDurationDivider; + return std::min(std::max(minDelay, tmp), maxDelay); +} + +NS_IMETHODIMP +TimerThread::Run() { + MonitorAutoLock lock(mMonitor); + + mProfilerThreadId = profiler_current_thread_id(); + + // We need to know how many microseconds give a positive PRIntervalTime. This + // is platform-dependent and we calculate it at runtime, finding a value |v| + // such that |PR_MicrosecondsToInterval(v) > 0| and then binary-searching in + // the range [0, v) to find the ms-to-interval scale. + uint32_t usForPosInterval = 1; + while (PR_MicrosecondsToInterval(usForPosInterval) == 0) { + usForPosInterval <<= 1; + } + + size_t usIntervalResolution; + BinarySearchIf(MicrosecondsToInterval(), 0, usForPosInterval, + IntervalComparator(), &usIntervalResolution); + MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution - 1) == 0); + MOZ_ASSERT(PR_MicrosecondsToInterval(usIntervalResolution) == 1); + + // Half of the amount of microseconds needed to get positive PRIntervalTime. + // We use this to decide how to round our wait times later + mAllowedEarlyFiringMicroseconds = usIntervalResolution / 2; + bool forceRunNextTimer = false; + + // Queue for tracking of how many timers are fired on each wake-up. We need to + // buffer these locally and only send off to glean occasionally to avoid + // performance hit. + static constexpr size_t kMaxQueuedTimerFired = 128; + size_t queuedTimerFiredCount = 0; + AutoTArray queuedTimersFiredPerWakeup; + queuedTimersFiredPerWakeup.SetLengthAndRetainStorage(kMaxQueuedTimerFired); + + uint64_t timersFiredThisWakeup = 0; + while (!mShutdown) { + // Have to use PRIntervalTime here, since PR_WaitCondVar takes it + TimeDuration waitFor; + bool forceRunThisTimer = forceRunNextTimer; + forceRunNextTimer = false; + +#ifdef DEBUG + VerifyTimerListConsistency(); +#endif + + if (mSleeping) { + // Sleep for 0.1 seconds while not firing timers. + uint32_t milliseconds = 100; + if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { + milliseconds = ChaosMode::randomUint32LessThan(200); + } + waitFor = TimeDuration::FromMilliseconds(milliseconds); + } else { + waitFor = TimeDuration::Forever(); + TimeStamp now = TimeStamp::Now(); + +#if TIMER_THREAD_STATISTICS + if (!mNotified && !mIntendedWakeupTime.IsNull() && + now < mIntendedWakeupTime) { + ++mEarlyWakeups; + const double earlinessms = (mIntendedWakeupTime - now).ToMilliseconds(); + mTotalEarlyWakeupTime += earlinessms; + } +#endif + + RemoveLeadingCanceledTimersInternal(); + + if (!mTimers.IsEmpty()) { + if (now >= mTimers[0].Value()->mTimeout || forceRunThisTimer) { + next: + // NB: AddRef before the Release under RemoveTimerInternal to avoid + // mRefCnt passing through zero, in case all other refs than the one + // from mTimers have gone away (the last non-mTimers[i]-ref's Release + // must be racing with us, blocked in gThread->RemoveTimer waiting + // for TimerThread::mMonitor, under nsTimerImpl::Release. + + RefPtr timerRef(mTimers[0].Take()); + RemoveFirstTimerInternal(); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("Timer thread woke up %fms from when it was supposed to\n", + fabs((now - timerRef->mTimeout).ToMilliseconds()))); + + // We are going to let the call to PostTimerEvent here handle the + // release of the timer so that we don't end up releasing the timer + // on the TimerThread instead of on the thread it targets. + { + ++timersFiredThisWakeup; + LogTimerEvent::Run run(timerRef.get()); + PostTimerEvent(timerRef.forget()); + } + + if (mShutdown) { + break; + } + + // Update now, as PostTimerEvent plus the locking may have taken a + // tick or two, and we may goto next below. + now = TimeStamp::Now(); + } + } + + RemoveLeadingCanceledTimersInternal(); + + if (!mTimers.IsEmpty()) { + TimeStamp timeout = mTimers[0].Value()->mTimeout; + + // Don't wait at all (even for PR_INTERVAL_NO_WAIT) if the next timer + // is due now or overdue. + // + // Note that we can only sleep for integer values of a certain + // resolution. We use mAllowedEarlyFiringMicroseconds, calculated + // before, to do the optimal rounding (i.e., of how to decide what + // interval is so small we should not wait at all). + double microseconds = (timeout - now).ToMicroseconds(); + + // The mean value of sFractions must be 1 to ensure that the average of + // a long sequence of timeouts converges to the actual sum of their + // times. + static constexpr double sChaosFractions[] = {0.0, 0.25, 0.5, 0.75, + 1.0, 1.75, 2.75}; + if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { + microseconds *= sChaosFractions[ChaosMode::randomUint32LessThan( + ArrayLength(sChaosFractions))]; + forceRunNextTimer = true; + } + + if (microseconds < mAllowedEarlyFiringMicroseconds) { + forceRunNextTimer = false; + goto next; // round down; execute event now + } + + // TECHNICAL NOTE: Determining waitFor (by subtracting |now| from our + // desired wake-up time) at this point is not ideal. For one thing, the + // |now| that we have at this point is somewhat old. Secondly, there is + // quite a bit of code between here and where we actually use waitFor to + // request sleep. If I am thinking about this correctly, both of these + // will contribute to us requesting more sleep than is actually needed + // to wake up at our desired time. We could avoid this problem by only + // determining our desired wake-up time here and then calculating the + // wait time when we're actually about to sleep. + const TimeStamp wakeupTime = ComputeWakeupTimeFromTimers(); + waitFor = wakeupTime - now; + + // If this were to fail that would mean that we had more timers that we + // should have fired. + MOZ_ASSERT(!waitFor.IsZero()); + + if (ChaosMode::isActive(ChaosFeature::TimerScheduling)) { + // If chaos mode is active then mess with the amount of time that we + // request to sleep (without changing what we record as our expected + // wake-up time). This will simulate unintended early/late wake-ups. + const double waitInMs = waitFor.ToMilliseconds(); + const double chaosWaitInMs = + waitInMs * sChaosFractions[ChaosMode::randomUint32LessThan( + ArrayLength(sChaosFractions))]; + waitFor = TimeDuration::FromMilliseconds(chaosWaitInMs); + } + + mIntendedWakeupTime = wakeupTime; + } else { + mIntendedWakeupTime = TimeStamp{}; + } + + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + if (waitFor == TimeDuration::Forever()) + MOZ_LOG(GetTimerLog(), LogLevel::Debug, ("waiting forever\n")); + else + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("waiting for %f\n", waitFor.ToMilliseconds())); + } + } + + { + // About to sleep - let's make note of how many timers we processed and + // see if we should send out a new batch of telemetry. + queuedTimersFiredPerWakeup[queuedTimerFiredCount] = timersFiredThisWakeup; + ++queuedTimerFiredCount; + if (queuedTimerFiredCount == kMaxQueuedTimerFired) { + glean::timer_thread::timers_fired_per_wakeup.AccumulateSamples( + queuedTimersFiredPerWakeup); + queuedTimerFiredCount = 0; + } + } + +#if TIMER_THREAD_STATISTICS + { + size_t bucketIndex = 0; + while (bucketIndex < sTimersFiredPerWakeupBucketCount - 1 && + timersFiredThisWakeup > + sTimersFiredPerWakeupThresholds[bucketIndex]) { + ++bucketIndex; + } + MOZ_ASSERT(bucketIndex < sTimersFiredPerWakeupBucketCount); + ++mTimersFiredPerWakeup[bucketIndex]; + + ++mTotalWakeupCount; + if (mNotified) { + ++mTimersFiredPerNotifiedWakeup[bucketIndex]; + ++mTotalNotifiedWakeupCount; + } else { + ++mTimersFiredPerUnnotifiedWakeup[bucketIndex]; + ++mTotalUnnotifiedWakeupCount; + } + } +#endif + + timersFiredThisWakeup = 0; + + mWaiting = true; + mNotified = false; + + { + AUTO_PROFILER_TRACING_MARKER("TimerThread", "Wait", OTHER); + mMonitor.Wait(waitFor); + } + if (mNotified) { + forceRunNextTimer = false; + } + mWaiting = false; + } + + // About to shut down - let's send out the final batch of timers fired counts. + if (queuedTimerFiredCount != 0) { + queuedTimersFiredPerWakeup.SetLengthAndRetainStorage(queuedTimerFiredCount); + glean::timer_thread::timers_fired_per_wakeup.AccumulateSamples( + queuedTimersFiredPerWakeup); + } + + return NS_OK; +} + +nsresult TimerThread::AddTimer(nsTimerImpl* aTimer, + const MutexAutoLock& aProofOfLock) { + MonitorAutoLock lock(mMonitor); + AUTO_TIMERS_STATS(TimerThread_AddTimer); + + if (!aTimer->mEventTarget) { + return NS_ERROR_NOT_INITIALIZED; + } + + nsresult rv = Init(); + if (NS_FAILED(rv)) { + return rv; + } + + // Awaken the timer thread if: + // - This timer needs to fire *before* the Timer Thread is scheduled to wake + // up. + // AND/OR + // - The delay is 0, which is usually meant to be run as soon as possible. + // Note: Even if the thread is scheduled to wake up now/soon, on some + // systems there could be a significant delay compared to notifying, which + // is almost immediate; and some users of 0-delay depend on it being this + // fast! + const TimeDuration minTimerDelay = TimeDuration::FromMilliseconds( + StaticPrefs::timer_minimum_firing_delay_tolerance_ms()); + const TimeDuration maxTimerDelay = TimeDuration::FromMilliseconds( + StaticPrefs::timer_maximum_firing_delay_tolerance_ms()); + const TimeDuration firingDelay = ComputeAcceptableFiringDelay( + aTimer->mDelay, minTimerDelay, maxTimerDelay); + const bool firingBeforeNextWakeup = + mIntendedWakeupTime.IsNull() || + (aTimer->mTimeout + firingDelay < mIntendedWakeupTime); + const bool wakeUpTimerThread = + mWaiting && (firingBeforeNextWakeup || aTimer->mDelay.IsZero()); + +#if TIMER_THREAD_STATISTICS + if (mTotalTimersAdded == 0) { + mFirstTimerAdded = TimeStamp::Now(); + } + ++mTotalTimersAdded; +#endif + + // Add the timer to our list. + if (!AddTimerInternal(*aTimer)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + if (wakeUpTimerThread) { + mNotified = true; + mMonitor.Notify(); + } + + if (profiler_thread_is_being_profiled_for_markers(mProfilerThreadId)) { + nsAutoCString name; + aTimer->GetName(name, aProofOfLock); + + nsLiteralCString prefix("Anonymous_"); + profiler_add_marker( + "AddTimer", geckoprofiler::category::OTHER, + MarkerOptions(MarkerThreadId(mProfilerThreadId), + MarkerStack::MaybeCapture( + name.Equals("nonfunction:JS") || + StringHead(name, prefix.Length()) == prefix)), + AddRemoveTimerMarker{}, name, aTimer->mDelay.ToMilliseconds(), + MarkerThreadId::CurrentThread()); + } + + return NS_OK; +} + +nsresult TimerThread::RemoveTimer(nsTimerImpl* aTimer, + const MutexAutoLock& aProofOfLock) { + MonitorAutoLock lock(mMonitor); + AUTO_TIMERS_STATS(TimerThread_RemoveTimer); + + // Remove the timer from our array. Tell callers that aTimer was not found + // by returning NS_ERROR_NOT_AVAILABLE. + + if (!RemoveTimerInternal(*aTimer)) { + return NS_ERROR_NOT_AVAILABLE; + } + +#if TIMER_THREAD_STATISTICS + ++mTotalTimersRemoved; +#endif + + // Note: The timer thread is *not* awoken. + // The removed-timer entry is just left null, and will be reused (by a new or + // re-set timer) or discarded (when the timer thread logic handles non-null + // timers around it). + // If this was the front timer, and in the unlikely case that its entry is not + // soon reused by a re-set timer, the timer thread will wake up at the + // previously-scheduled time, but will quickly notice that there is no actual + // pending timer, and will restart its wait until the following real timeout. + + if (profiler_thread_is_being_profiled_for_markers(mProfilerThreadId)) { + nsAutoCString name; + aTimer->GetName(name, aProofOfLock); + + nsLiteralCString prefix("Anonymous_"); + // This marker is meant to help understand the behavior of the timer thread. + profiler_add_marker( + "RemoveTimer", geckoprofiler::category::OTHER, + MarkerOptions(MarkerThreadId(mProfilerThreadId), + MarkerStack::MaybeCapture( + name.Equals("nonfunction:JS") || + StringHead(name, prefix.Length()) == prefix)), + AddRemoveTimerMarker{}, name, aTimer->mDelay.ToMilliseconds(), + MarkerThreadId::CurrentThread()); + // This adds a marker with the timer name as the marker name, to make it + // obvious which timers are being used. This marker will be useful to + // understand which timers might be added and removed excessively often. + profiler_add_marker(name, geckoprofiler::category::TIMER, + MarkerOptions(MarkerTiming::IntervalUntilNowFrom( + aTimer->mTimeout - aTimer->mDelay), + MarkerThreadId(mProfilerThreadId)), + TimerMarker{}, aTimer->mDelay.ToMilliseconds(), + aTimer->mType, MarkerThreadId::CurrentThread(), true); + } + + return NS_OK; +} + +TimeStamp TimerThread::FindNextFireTimeForCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound) { + MonitorAutoLock lock(mMonitor); + AUTO_TIMERS_STATS(TimerThread_FindNextFireTimeForCurrentThread); + + for (const Entry& entry : mTimers) { + const nsTimerImpl* timer = entry.Value(); + if (timer) { + if (entry.Timeout() > aDefault) { + return aDefault; + } + + // Don't yield to timers created with the *_LOW_PRIORITY type. + if (!timer->IsLowPriority()) { + bool isOnCurrentThread = false; + nsresult rv = + timer->mEventTarget->IsOnCurrentThread(&isOnCurrentThread); + if (NS_SUCCEEDED(rv) && isOnCurrentThread) { + return entry.Timeout(); + } + } + + if (aSearchBound == 0) { + // Return the currently highest timeout when we reach the bound. + // This won't give accurate information if we stop before finding + // any timer for the current thread, but at least won't report too + // long idle period. + return timer->mTimeout; + } + + --aSearchBound; + } + } + + // No timers for this thread, return the default. + return aDefault; +} + +// This function must be called from within a lock +// Also: we hold the mutex for the nsTimerImpl. +bool TimerThread::AddTimerInternal(nsTimerImpl& aTimer) { + mMonitor.AssertCurrentThreadOwns(); + aTimer.mMutex.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal); + if (mShutdown) { + return false; + } + + LogTimerEvent::LogDispatch(&aTimer); + + const TimeStamp& timeout = aTimer.mTimeout; + const size_t insertionIndex = ComputeTimerInsertionIndex(timeout); + + if (insertionIndex != 0 && !mTimers[insertionIndex - 1].Value()) { + // Very common scenario in practice: The timer just before the insertion + // point is canceled, overwrite it. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_overwrite_before); + mTimers[insertionIndex - 1] = Entry{aTimer}; + return true; + } + + const size_t length = mTimers.Length(); + if (insertionIndex == length) { + // We're at the end (including it's the very first insertion), add new timer + // at the end. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_append); + return mTimers.AppendElement(Entry{aTimer}, mozilla::fallible); + } + + if (!mTimers[insertionIndex].Value()) { + // The timer at the insertion point is canceled, overwrite it. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_overwrite); + mTimers[insertionIndex] = Entry{aTimer}; + return true; + } + + // The new timer has to be inserted. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_insert); + // The capacity should be checked first, because if it needs to be increased + // and the memory allocation fails, only the new timer should be lost. + if (length == mTimers.Capacity() && mTimers[length - 1].Value()) { + // We have reached capacity, and the last entry is not canceled, so we + // really want to increase the capacity in case the extra slot is required. + // To force-expand the array, append a canceled-timer entry with a timestamp + // far in the future. + // This empty Entry may be used below to receive the moved-from previous + // entry. If not, it may be used in a later call if we need to append a new + // timer at the end. + AUTO_TIMERS_STATS(TimerThread_AddTimerInternal_insert_expand); + if (!mTimers.AppendElement( + Entry{mTimers[length - 1].Timeout() + + TimeDuration::FromSeconds(365.0 * 24.0 * 60.0 * 60.0)}, + mozilla::fallible)) { + return false; + } + } + + // Extract the timer at the insertion point, and put the new timer in its + // place. + Entry extractedEntry = std::exchange(mTimers[insertionIndex], Entry{aTimer}); + // Following entries can be pushed until we hit a canceled timer or the end. + for (size_t i = insertionIndex + 1; i < length; ++i) { + Entry& entryRef = mTimers[i]; + if (!entryRef.Value()) { + // Canceled entry, overwrite it with the extracted entry from before. + COUNT_TIMERS_STATS(TimerThread_AddTimerInternal_insert_overwrite); + entryRef = std::move(extractedEntry); + return true; + } + // Write extracted entry from before, and extract current entry. + COUNT_TIMERS_STATS(TimerThread_AddTimerInternal_insert_shifts); + std::swap(entryRef, extractedEntry); + } + // We've reached the end of the list, with still one extracted entry to + // re-insert. We've checked the capacity above, this cannot fail. + COUNT_TIMERS_STATS(TimerThread_AddTimerInternal_insert_append); + mTimers.AppendElement(std::move(extractedEntry)); + return true; +} + +// This function must be called from within a lock +// Also: we hold the mutex for the nsTimerImpl. +bool TimerThread::RemoveTimerInternal(nsTimerImpl& aTimer) { + mMonitor.AssertCurrentThreadOwns(); + aTimer.mMutex.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_RemoveTimerInternal); + if (!aTimer.IsInTimerThread()) { + COUNT_TIMERS_STATS(TimerThread_RemoveTimerInternal_not_in_list); + return false; + } + AUTO_TIMERS_STATS(TimerThread_RemoveTimerInternal_in_list); + for (auto& entry : mTimers) { + if (entry.Value() == &aTimer) { + entry.Forget(); + return true; + } + } + MOZ_ASSERT(!aTimer.IsInTimerThread(), + "Not found in the list but it should be!?"); + return false; +} + +void TimerThread::RemoveLeadingCanceledTimersInternal() { + mMonitor.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_RemoveLeadingCanceledTimersInternal); + + size_t toRemove = 0; + while (toRemove < mTimers.Length() && !mTimers[toRemove].Value()) { + ++toRemove; + } + mTimers.RemoveElementsAt(0, toRemove); +} + +void TimerThread::RemoveFirstTimerInternal() { + mMonitor.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_RemoveFirstTimerInternal); + MOZ_ASSERT(!mTimers.IsEmpty()); + mTimers.RemoveElementAt(0); +} + +void TimerThread::PostTimerEvent(already_AddRefed aTimerRef) { + mMonitor.AssertCurrentThreadOwns(); + AUTO_TIMERS_STATS(TimerThread_PostTimerEvent); + + RefPtr timer(aTimerRef); + +#if TIMER_THREAD_STATISTICS + const double actualFiringDelay = + std::max((TimeStamp::Now() - timer->mTimeout).ToMilliseconds(), 0.0); + if (mNotified) { + ++mTotalTimersFiredNotified; + mTotalActualTimerFiringDelayNotified += actualFiringDelay; + } else { + ++mTotalTimersFiredUnnotified; + mTotalActualTimerFiringDelayUnnotified += actualFiringDelay; + } +#endif + + if (!timer->mEventTarget) { + NS_ERROR("Attempt to post timer event to NULL event target"); + return; + } + + // XXX we may want to reuse this nsTimerEvent in the case of repeating timers. + + // Since we already addref'd 'timer', we don't need to addref here. + // We will release either in ~nsTimerEvent(), or pass the reference back to + // the caller. We need to copy the generation number from this timer into the + // event, so we can avoid firing a timer that was re-initialized after being + // canceled. + + nsCOMPtr target = timer->mEventTarget; + + void* p = nsTimerEvent::operator new(sizeof(nsTimerEvent)); + if (!p) { + return; + } + RefPtr event = + ::new (KnownNotNull, p) nsTimerEvent(timer.forget(), mProfilerThreadId); + + nsresult rv; + { + // We release mMonitor around the Dispatch because if the Dispatch interacts + // with the timer API we'll deadlock. + MonitorAutoUnlock unlock(mMonitor); + rv = target->Dispatch(event, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + timer = event->ForgetTimer(); + // We do this to avoid possible deadlock by taking the two locks in a + // different order than is used in RemoveTimer(). RemoveTimer() has + // aTimer->mMutex first. We use timer.get() to keep static analysis + // happy + // NOTE: I'm not sure that any of the below is actually necessary. It + // seems to me that the timer that we're trying to fire will have already + // been removed prior to this. + MutexAutoLock lock1(timer.get()->mMutex); + MonitorAutoLock lock2(mMonitor); + RemoveTimerInternal(*timer); + } + } +} + +void TimerThread::DoBeforeSleep() { + // Mainthread + MonitorAutoLock lock(mMonitor); + mSleeping = true; +} + +// Note: wake may be notified without preceding sleep notification +void TimerThread::DoAfterSleep() { + // Mainthread + MonitorAutoLock lock(mMonitor); + mSleeping = false; + + // Wake up the timer thread to re-process the array to ensure the sleep delay + // is correct, and fire any expired timers (perhaps quite a few) + mNotified = true; + PROFILER_MARKER_UNTYPED("AfterSleep", OTHER, + MarkerThreadId(mProfilerThreadId)); + mMonitor.Notify(); +} + +NS_IMETHODIMP +TimerThread::Observe(nsISupports* /* aSubject */, const char* aTopic, + const char16_t* /* aData */) { + if (StaticPrefs::timer_ignore_sleep_wake_notifications()) { + return NS_OK; + } + + if (strcmp(aTopic, "sleep_notification") == 0 || + strcmp(aTopic, "suspend_process_notification") == 0) { + DoBeforeSleep(); + } else if (strcmp(aTopic, "wake_notification") == 0 || + strcmp(aTopic, "resume_process_notification") == 0) { + DoAfterSleep(); + } + + return NS_OK; +} + +uint32_t TimerThread::AllowedEarlyFiringMicroseconds() { + MonitorAutoLock lock(mMonitor); + return mAllowedEarlyFiringMicroseconds; +} + +#if TIMER_THREAD_STATISTICS +void TimerThread::PrintStatistics() const { + mMonitor.AssertCurrentThreadOwns(); + + const TimeStamp freshNow = TimeStamp::Now(); + const double timeElapsed = mFirstTimerAdded.IsNull() + ? 0.0 + : (freshNow - mFirstTimerAdded).ToSeconds(); + printf_stderr("TimerThread Stats (Total time %8.2fs)\n", timeElapsed); + + printf_stderr("Added: %6llu Removed: %6llu Fired: %6llu\n", mTotalTimersAdded, + mTotalTimersRemoved, + mTotalTimersFiredNotified + mTotalTimersFiredUnnotified); + + auto PrintTimersFiredBucket = + [](const AutoTArray& buckets, + const size_t wakeupCount, const size_t timersFiredCount, + const double totalTimerDelay, const char* label) { + printf_stderr("%s : [", label); + for (size_t bucketVal : buckets) { + printf_stderr(" %5llu", bucketVal); + } + printf_stderr( + " ] Wake-ups/timer %6llu / %6llu (%7.4f) Avg Timer Delay %7.4f\n", + wakeupCount, timersFiredCount, + static_cast(wakeupCount) / timersFiredCount, + totalTimerDelay / timersFiredCount); + }; + + printf_stderr("Wake-ups:\n"); + PrintTimersFiredBucket( + mTimersFiredPerWakeup, mTotalWakeupCount, + mTotalTimersFiredNotified + mTotalTimersFiredUnnotified, + mTotalActualTimerFiringDelayNotified + + mTotalActualTimerFiringDelayUnnotified, + "Total "); + PrintTimersFiredBucket(mTimersFiredPerNotifiedWakeup, + mTotalNotifiedWakeupCount, mTotalTimersFiredNotified, + mTotalActualTimerFiringDelayNotified, "Notified "); + PrintTimersFiredBucket(mTimersFiredPerUnnotifiedWakeup, + mTotalUnnotifiedWakeupCount, + mTotalTimersFiredUnnotified, + mTotalActualTimerFiringDelayUnnotified, "Unnotified "); + + printf_stderr("Early Wake-ups: %6llu Avg: %7.4fms\n", mEarlyWakeups, + mTotalEarlyWakeupTime / mEarlyWakeups); +} +#endif + +/* This nsReadOnlyTimer class is used for the values returned by the + * TimerThread::GetTimers method. + * It is not possible to return a strong reference to the nsTimerImpl + * instance (that could extend the lifetime of the timer and cause it to fire + * a callback pointing to already freed memory) or a weak reference + * (nsSupportsWeakReference doesn't support freeing the referee on a thread + * that isn't the thread that owns the weak reference), so instead the timer + * name, delay and type are copied to a new object. */ +class nsReadOnlyTimer final : public nsITimer { + public: + explicit nsReadOnlyTimer(const nsACString& aName, uint32_t aDelay, + uint32_t aType) + : mName(aName), mDelay(aDelay), mType(aType) {} + NS_DECL_ISUPPORTS + + NS_IMETHOD Init(nsIObserver* aObserver, uint32_t aDelayInMs, + uint32_t aType) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD InitWithCallback(nsITimerCallback* aCallback, uint32_t aDelayInMs, + uint32_t aType) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD InitHighResolutionWithCallback(nsITimerCallback* aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD Cancel(void) override { return NS_ERROR_NOT_IMPLEMENTED; } + NS_IMETHOD InitWithNamedFuncCallback(nsTimerCallbackFunc aCallback, + void* aClosure, uint32_t aDelay, + uint32_t aType, + const char* aName) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD InitHighResolutionWithNamedFuncCallback( + nsTimerCallbackFunc aCallback, void* aClosure, + const mozilla::TimeDuration& aDelay, uint32_t aType, + const char* aName) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD GetName(nsACString& aName) override { + aName = mName; + return NS_OK; + } + NS_IMETHOD GetDelay(uint32_t* aDelay) override { + *aDelay = mDelay; + return NS_OK; + } + NS_IMETHOD SetDelay(uint32_t aDelay) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetType(uint32_t* aType) override { + *aType = mType; + return NS_OK; + } + NS_IMETHOD SetType(uint32_t aType) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetClosure(void** aClosure) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetCallback(nsITimerCallback** aCallback) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetTarget(nsIEventTarget** aTarget) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD SetTarget(nsIEventTarget* aTarget) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + NS_IMETHOD GetAllowedEarlyFiringMicroseconds( + uint32_t* aAllowedEarlyFiringMicroseconds) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) override { + return sizeof(*this); + } + + private: + nsCString mName; + uint32_t mDelay; + uint32_t mType; + ~nsReadOnlyTimer() = default; +}; + +NS_IMPL_ISUPPORTS(nsReadOnlyTimer, nsITimer) + +nsresult TimerThread::GetTimers(nsTArray>& aRetVal) { + nsTArray> timers; + { + MonitorAutoLock lock(mMonitor); + for (const auto& entry : mTimers) { + nsTimerImpl* timer = entry.Value(); + if (!timer) { + continue; + } + timers.AppendElement(timer); + } + } + + for (nsTimerImpl* timer : timers) { + nsAutoCString name; + timer->GetName(name); + + uint32_t delay; + timer->GetDelay(&delay); + + uint32_t type; + timer->GetType(&type); + + aRetVal.AppendElement(new nsReadOnlyTimer(name, delay, type)); + } + + return NS_OK; +} diff --git a/xpcom/threads/TimerThread.h b/xpcom/threads/TimerThread.h new file mode 100644 index 0000000000..ec138efca6 --- /dev/null +++ b/xpcom/threads/TimerThread.h @@ -0,0 +1,243 @@ +/* -*- 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/. */ + +#ifndef TimerThread_h___ +#define TimerThread_h___ + +#include "nsIObserver.h" +#include "nsIRunnable.h" +#include "nsIThread.h" + +#include "nsTimerImpl.h" +#include "nsThreadUtils.h" + +#include "nsTArray.h" + +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/Monitor.h" +#include "mozilla/ProfilerUtils.h" +#include "mozilla/UniquePtr.h" + +#include + +namespace mozilla { +class TimeStamp; +} // namespace mozilla + +// Enable this to compute lots of interesting statistics and print them out when +// PrintStatistics() is called. +#define TIMER_THREAD_STATISTICS 0 + +class TimerThread final : public mozilla::Runnable, public nsIObserver { + public: + typedef mozilla::Monitor Monitor; + typedef mozilla::MutexAutoLock MutexAutoLock; + typedef mozilla::TimeStamp TimeStamp; + typedef mozilla::TimeDuration TimeDuration; + + TimerThread(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIOBSERVER + + nsresult Shutdown(); + + nsresult AddTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(aTimer->mMutex); + nsresult RemoveTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(aTimer->mMutex); + // Considering only the first 'aSearchBound' timers (in firing order), returns + // the timeout of the first non-low-priority timer, on the current thread, + // that will fire before 'aDefault'. If no such timer exists, 'aDefault' is + // returned. + TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound); + + void DoBeforeSleep(); + void DoAfterSleep(); + + bool IsOnTimerThread() const { return mThread->IsOnCurrentThread(); } + + uint32_t AllowedEarlyFiringMicroseconds(); + nsresult GetTimers(nsTArray>& aRetVal); + + private: + ~TimerThread(); + + bool mInitialized; + + // These internal helper methods must be called while mMonitor is held. + // AddTimerInternal returns false if the insertion failed. + bool AddTimerInternal(nsTimerImpl& aTimer) MOZ_REQUIRES(mMonitor); + bool RemoveTimerInternal(nsTimerImpl& aTimer) + MOZ_REQUIRES(mMonitor, aTimer.mMutex); + void RemoveLeadingCanceledTimersInternal() MOZ_REQUIRES(mMonitor); + void RemoveFirstTimerInternal() MOZ_REQUIRES(mMonitor); + nsresult Init() MOZ_REQUIRES(mMonitor); + + void PostTimerEvent(already_AddRefed aTimerRef) + MOZ_REQUIRES(mMonitor); + + nsCOMPtr mThread; + // Lock ordering requirements: + // (optional) ThreadWrapper::sMutex -> + // (optional) nsTimerImpl::mMutex -> + // TimerThread::mMonitor + Monitor mMonitor; + + bool mShutdown MOZ_GUARDED_BY(mMonitor); + bool mWaiting MOZ_GUARDED_BY(mMonitor); + bool mNotified MOZ_GUARDED_BY(mMonitor); + bool mSleeping MOZ_GUARDED_BY(mMonitor); + + class Entry final { + public: + explicit Entry(nsTimerImpl& aTimerImpl) + : mTimeout(aTimerImpl.mTimeout), + mDelay(aTimerImpl.mDelay), + mTimerImpl(&aTimerImpl) { + aTimerImpl.SetIsInTimerThread(true); + } + + // Create an already-canceled entry with the given timeout. + explicit Entry(TimeStamp aTimeout) + : mTimeout(std::move(aTimeout)), mTimerImpl(nullptr) {} + + // Don't allow copies, otherwise which one would manage `IsInTimerThread`? + Entry(const Entry&) = delete; + Entry& operator=(const Entry&) = delete; + + // Move-only. + Entry(Entry&&) = default; + Entry& operator=(Entry&&) = default; + + ~Entry() { + if (mTimerImpl) { + mTimerImpl->mMutex.AssertCurrentThreadOwns(); + mTimerImpl->SetIsInTimerThread(false); + } + } + + nsTimerImpl* Value() const { return mTimerImpl; } + + void Forget() { + if (MOZ_UNLIKELY(!mTimerImpl)) { + return; + } + mTimerImpl->mMutex.AssertCurrentThreadOwns(); + mTimerImpl->SetIsInTimerThread(false); + mTimerImpl = nullptr; + } + + // Called with the Monitor held, but not the TimerImpl's mutex + already_AddRefed Take() { + if (MOZ_LIKELY(mTimerImpl)) { + MOZ_ASSERT(mTimerImpl->IsInTimerThread()); + mTimerImpl->SetIsInTimerThread(false); + } + return mTimerImpl.forget(); + } + + const TimeStamp& Timeout() const { return mTimeout; } + const TimeDuration& Delay() const { return mDelay; } + + private: + // These values are simply cached from the timer. Keeping them here is good + // for cache usage and allows us to avoid worrying about locking conflicts + // with the timer. + TimeStamp mTimeout; + TimeDuration mDelay; + + RefPtr mTimerImpl; + }; + + // Computes and returns the index in mTimers at which a new timer with the + // specified timeout should be inserted in order to maintain "sorted" order. + size_t ComputeTimerInsertionIndex(const TimeStamp& timeout) const + MOZ_REQUIRES(mMonitor); + + // Computes and returns when we should next try to wake up in order to handle + // the triggering of the timers in mTimers. Currently this is very simple and + // we always just plan to wake up for the next timer in the list. In the + // future this will be more sophisticated. + TimeStamp ComputeWakeupTimeFromTimers() const MOZ_REQUIRES(mMonitor); + + // Computes how late a timer can acceptably fire. + // timerDuration is the duration of the timer whose delay we are calculating. + // Longer timers can tolerate longer firing delays. + // minDelay is an amount by which any timer can be delayed. + // This function will never return a value smaller than minDelay (unless this + // conflicts with maxDelay). maxDelay is the upper limit on the amount by + // which we will ever delay any timer. Takes precedence over minDelay if there + // is a conflict. (Zero will effectively disable timer coalescing.) + TimeDuration ComputeAcceptableFiringDelay(TimeDuration timerDuration, + TimeDuration minDelay, + TimeDuration maxDelay) const; + +#ifdef DEBUG + // Checks mTimers to see if any entries are out of order or any cached + // timeouts are incorrect and will assert if any inconsistency is found. Has + // no side effects other than asserting so has no use in non-DEBUG builds. + void VerifyTimerListConsistency() const MOZ_REQUIRES(mMonitor); +#endif + + // mTimers is maintained in a "pseudo-sorted" order wrt the timeouts. + // Specifcally, mTimers is sorted according to the timeouts *if you ignore the + // canceled entries* (those whose mTimerImpl is nullptr). Notably this means + // that you cannot use a binary search on this list. + nsTArray mTimers MOZ_GUARDED_BY(mMonitor); + + // Set only at the start of the thread's Run(): + uint32_t mAllowedEarlyFiringMicroseconds MOZ_GUARDED_BY(mMonitor); + + ProfilerThreadId mProfilerThreadId MOZ_GUARDED_BY(mMonitor); + + // Time at which we were intending to wake up the last time that we slept. + // Is "null" if we have never slept or if our last sleep was "forever". + TimeStamp mIntendedWakeupTime; + +#if TIMER_THREAD_STATISTICS + static constexpr size_t sTimersFiredPerWakeupBucketCount = 16; + static inline constexpr std::array + sTimersFiredPerWakeupThresholds = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 12, 20, 30, 40, 50, 70, (size_t)(-1)}; + + mutable AutoTArray + mTimersFiredPerWakeup MOZ_GUARDED_BY(mMonitor) = {0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0}; + mutable AutoTArray + mTimersFiredPerUnnotifiedWakeup MOZ_GUARDED_BY(mMonitor) = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + mutable AutoTArray + mTimersFiredPerNotifiedWakeup MOZ_GUARDED_BY(mMonitor) = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + mutable size_t mTotalTimersAdded MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalTimersRemoved MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalTimersFiredNotified MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalTimersFiredUnnotified MOZ_GUARDED_BY(mMonitor) = 0; + + mutable size_t mTotalWakeupCount MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalUnnotifiedWakeupCount MOZ_GUARDED_BY(mMonitor) = 0; + mutable size_t mTotalNotifiedWakeupCount MOZ_GUARDED_BY(mMonitor) = 0; + + mutable double mTotalActualTimerFiringDelayNotified MOZ_GUARDED_BY(mMonitor) = + 0.0; + mutable double mTotalActualTimerFiringDelayUnnotified + MOZ_GUARDED_BY(mMonitor) = 0.0; + + mutable TimeStamp mFirstTimerAdded MOZ_GUARDED_BY(mMonitor); + + mutable size_t mEarlyWakeups MOZ_GUARDED_BY(mMonitor) = 0; + mutable double mTotalEarlyWakeupTime MOZ_GUARDED_BY(mMonitor) = 0.0; + + void PrintStatistics() const; +#endif +}; + +#endif /* TimerThread_h___ */ diff --git a/xpcom/threads/VsyncTaskManager.cpp b/xpcom/threads/VsyncTaskManager.cpp new file mode 100644 index 0000000000..ba4201af45 --- /dev/null +++ b/xpcom/threads/VsyncTaskManager.cpp @@ -0,0 +1,22 @@ +/* -*- 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 "VsyncTaskManager.h" +#include "InputTaskManager.h" + +namespace mozilla { + +StaticRefPtr VsyncTaskManager::gHighPriorityTaskManager; + +void VsyncTaskManager::Init() { + gHighPriorityTaskManager = new VsyncTaskManager(); +} + +void VsyncTaskManager::WillRunTask() { + TaskManager::WillRunTask(); + InputTaskManager::Get()->NotifyVsync(); +}; +} // namespace mozilla diff --git a/xpcom/threads/VsyncTaskManager.h b/xpcom/threads/VsyncTaskManager.h new file mode 100644 index 0000000000..e284ebf47b --- /dev/null +++ b/xpcom/threads/VsyncTaskManager.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef mozilla_VsyncTaskManager_h +#define mozilla_VsyncTaskManager_h + +#include "TaskController.h" +#include "mozilla/StaticPtr.h" + +namespace mozilla { +class VsyncTaskManager : public TaskManager { + public: + static VsyncTaskManager* Get() { return gHighPriorityTaskManager.get(); } + static void Cleanup() { gHighPriorityTaskManager = nullptr; } + static void Init(); + + void WillRunTask() override; + + private: + static StaticRefPtr gHighPriorityTaskManager; +}; +} // namespace mozilla +#endif diff --git a/xpcom/threads/WinHandleWatcher.cpp b/xpcom/threads/WinHandleWatcher.cpp new file mode 100644 index 0000000000..07d49e3730 --- /dev/null +++ b/xpcom/threads/WinHandleWatcher.cpp @@ -0,0 +1,303 @@ +/* -*- 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 +#include + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Assertions.h" +#include "mozilla/Logging.h" +#include "mozilla/Mutex.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ThreadSafety.h" +#include "mozilla/WinHandleWatcher.h" + +#include "nsCOMPtr.h" +#include "nsIRunnable.h" +#include "nsISerialEventTarget.h" +#include "nsISupportsImpl.h" +#include "nsITargetShutdownTask.h" +#include "nsIWeakReferenceUtils.h" +#include "nsThreadUtils.h" + +mozilla::LazyLogModule sHWLog("HandleWatcher"); + +namespace mozilla { +namespace details { +struct WaitHandleDeleter { + void operator()(PTP_WAIT waitHandle) { + MOZ_LOG(sHWLog, LogLevel::Debug, ("Closing PTP_WAIT %p", waitHandle)); + ::CloseThreadpoolWait(waitHandle); + } +}; +} // namespace details +using WaitHandlePtr = UniquePtr; + +// HandleWatcher::Impl +// +// The backing implementation of HandleWatcher is a PTP_WAIT, an OS-threadpool +// wait-object. Windows doesn't actually create a new thread per wait-object; +// OS-threadpool threads are assigned to wait-objects only when their associated +// handle become signaled -- although explicit documentation of this fact is +// somewhat obscurely placed. [1] +// +// Throughout this class, we use manual locking and unlocking guarded by Clang's +// thread-safety warnings, rather than scope-based lock-guards. See `Replace()` +// for an explanation and justification. +// +// [1]https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects#remarks +class HandleWatcher::Impl final : public nsITargetShutdownTask { + NS_DECL_THREADSAFE_ISUPPORTS + + public: + Impl() = default; + + private: + ~Impl() { MOZ_ASSERT(IsStopped()); } + + struct Data { + // The watched handle and its callback. + HANDLE handle; + RefPtr target; + nsCOMPtr runnable; + + // Handle to the threadpool wait-object. + WaitHandlePtr waitHandle; + // A pointer to ourselves, notionally owned by the wait-object. + RefPtr self; + + // (We can't actually do this because a) it has annoying consequences in + // C++20 thanks to P1008R1, and b) Clang just ignores it anyway.) + // + // ~Data() MOZ_EXCLUDES(mMutex) = default; + }; + + mozilla::Mutex mMutex{"HandleWatcher::Impl"}; + Data mData MOZ_GUARDED_BY(mMutex) = {}; + + // Callback from OS threadpool wait-object. + static void CALLBACK WaitCallback(PTP_CALLBACK_INSTANCE, void* ctx, + PTP_WAIT aWaitHandle, + TP_WAIT_RESULT aResult) { + static_cast(ctx)->OnWaitCompleted(aWaitHandle, aResult); + } + + void OnWaitCompleted(PTP_WAIT aWaitHandle, TP_WAIT_RESULT aResult) + MOZ_EXCLUDES(mMutex) { + MOZ_ASSERT(aResult == WAIT_OBJECT_0); + + mMutex.Lock(); + // If this callback is no longer the active callback, skip out. + // All cleanup is someone else's problem. + if (aWaitHandle != mData.waitHandle.get()) { + MOZ_LOG(sHWLog, LogLevel::Debug, + ("Recv'd already-stopped callback: HW %p | PTP_WAIT %p", this, + aWaitHandle)); + mMutex.Unlock(); + return; + } + + // Take our self-pointer so that we release it on exit. + RefPtr self = std::move(mData.self); + + MOZ_LOG(sHWLog, LogLevel::Info, + ("Recv'd callback: HW %p | handle %p | target %p | PTP_WAIT %p", + this, mData.handle, mData.target.get(), aWaitHandle)); + + // This may fail if (for example) `mData.target` is being shut down, but we + // have not yet received the shutdown callback. + mData.target->Dispatch(mData.runnable.forget()); + Replace(Data{}); + } + + public: + static RefPtr Create(HANDLE aHandle, nsIEventTarget* aTarget, + already_AddRefed aRunnable) { + auto impl = MakeRefPtr(); + bool const ok [[maybe_unused]] = + impl->Watch(aHandle, aTarget, std::move(aRunnable)); + MOZ_ASSERT(ok); + return impl; + } + + private: + bool Watch(HANDLE aHandle, nsIEventTarget* aTarget, + already_AddRefed aRunnable) MOZ_EXCLUDES(mMutex) { + MOZ_ASSERT(aHandle); + MOZ_ASSERT(aTarget); + + RefPtr target(aTarget); + + WaitHandlePtr waitHandle{ + ::CreateThreadpoolWait(&WaitCallback, this, nullptr)}; + if (!waitHandle) { + return false; + } + + { + mMutex.Lock(); + + nsresult const ret = aTarget->RegisterShutdownTask(this); + if (NS_FAILED(ret)) { + mMutex.Unlock(); + return false; + } + + MOZ_LOG(sHWLog, LogLevel::Info, + ("Setting callback: HW %p | handle %p | target %p | PTP_WAIT %p", + this, aHandle, aTarget, waitHandle.get())); + + // returns `void`; presumably always succeeds given a successful + // `::CreateThreadpoolWait()` + ::SetThreadpoolWait(waitHandle.get(), aHandle, nullptr); + // After this point, you must call `FlushWaitHandle(waitHandle.get())` + // before destroying the wait handle. (Note that this must be done while + // *not* holding `mMutex`!) + + Replace(Data{.handle = aHandle, + .target = std::move(target), + .runnable = aRunnable, + .waitHandle = std::move(waitHandle), + .self = this}); + } + + return true; + } + + void TargetShutdown() MOZ_EXCLUDES(mMutex) override final { + mMutex.Lock(); + + MOZ_LOG(sHWLog, LogLevel::Debug, + ("Target shutdown: HW %p | handle %p | target %p | PTP_WAIT %p", + this, mData.handle, mData.target.get(), mData.waitHandle.get())); + + // Clear mData.target, since there's no need to unregister the shutdown task + // anymore. Hold onto it until we release the mutex, though, to avoid any + // reentrancy issues. + // + // This is more for internal consistency than safety: someone has to be + // shutting `target` down, and that someone isn't us, so there's necessarily + // another reference out there. (Although decrementing the refcount might + // still have arbitrary effects if someone's been excessively clever with + // nsISupports::Release...) + auto const oldTarget = std::move(mData.target); + Replace(Data{}); + // (Static-assert that the mutex has indeed been released.) + ([&]() MOZ_EXCLUDES(mMutex) {})(); + } + + public: + void Stop() MOZ_EXCLUDES(mMutex) { + mMutex.Lock(); + Replace(Data{}); + } + + bool IsStopped() MOZ_EXCLUDES(mMutex) { + mozilla::MutexAutoLock lock(mMutex); + return !mData.handle; + } + + private: + // Throughout this class, we use manual locking and unlocking guarded by + // Clang's thread-safety warnings, rather than scope-based lock-guards. This + // is largely driven by `Replace()`, below, which performs both operations + // which require the mutex to be held and operations which require it to not + // be held, and therefore must explicitly sequence the mutex release. + // + // These explicit locks, unlocks, and annotations are both alien to C++ and + // offensively tedious; but they _are_ still checked for state consistency at + // scope boundaries. (The concerned reader is invited to test this by + // deliberately removing an `mMutex.Unlock()` call from anywhere in the class + // and viewing the resultant compiler diagnostics.) + // + // A more principled, or at least differently-principled, implementation might + // create a scope-based lock-guard and pass it to `Replace()` to dispose of at + // the proper time. Alas, it cannot be communicated to Clang's thread-safety + // checker that such a guard is associated with `mMutex`. + // + void Replace(Data&& aData) MOZ_CAPABILITY_RELEASE(mMutex) { + // either both handles are NULL, or neither is + MOZ_ASSERT(!!aData.handle == !!aData.waitHandle); + + if (mData.handle) { + MOZ_LOG(sHWLog, LogLevel::Info, + ("Stop callback: HW %p | handle %p | target %p | PTP_WAIT %p", + this, mData.handle, mData.target.get(), mData.waitHandle.get())); + } + + if (mData.target) { + mData.target->UnregisterShutdownTask(this); + } + + // Extract the old data and insert the new -- but hold onto the old data for + // now. (See [1] and [2], below.) + Data oldData = std::exchange(mData, std::move(aData)); + + //////////////////////////////////////////////////////////////////////////// + // Release the mutex. + mMutex.Unlock(); + //////////////////////////////////////////////////////////////////////////// + + // [1] `oldData.self` will be unset if the old callback already ran (or if + // there was no old callback in the first place). If it's set, though, we + // need to explicitly clear out the wait-object first. + if (oldData.self) { + MOZ_ASSERT(oldData.waitHandle); + FlushWaitHandle(oldData.waitHandle.get()); + } + + // [2] oldData also includes several other reference-counted pointers. It's + // possible that these may be the last pointer to something, so releasing + // them may have arbitrary side-effects -- like calling this->Stop(), which + // will try to reacquire the mutex. + // + // Now that we've released the mutex, we can (implicitly) release them all + // here. + } + + // Either confirm as complete or cancel any callbacks on aWaitHandle. Block + // until this is done. (See documentation for ::CloseThreadpoolWait().) + void FlushWaitHandle(PTP_WAIT aWaitHandle) MOZ_EXCLUDES(mMutex) { + ::SetThreadpoolWait(aWaitHandle, nullptr, nullptr); + // This might block on `OnWaitCompleted()`, so we can't hold `mMutex` here. + ::WaitForThreadpoolWaitCallbacks(aWaitHandle, TRUE); + // ::CloseThreadpoolWait() itself is the caller's responsibility. + } +}; + +NS_IMPL_ISUPPORTS(HandleWatcher::Impl, nsITargetShutdownTask) + +////// +// HandleWatcher member function implementations + +HandleWatcher::HandleWatcher() : mImpl{} {} +HandleWatcher::~HandleWatcher() { + if (mImpl) { + MOZ_ASSERT(mImpl->IsStopped()); + mImpl->Stop(); // just in case, in release + } +} + +void HandleWatcher::Watch(HANDLE aHandle, nsIEventTarget* aTarget, + already_AddRefed aRunnable) { + auto impl = Impl::Create(aHandle, aTarget, std::move(aRunnable)); + MOZ_ASSERT(impl); + + if (mImpl) { + mImpl->Stop(); + } + mImpl = std::move(impl); +} + +void HandleWatcher::Stop() { + if (mImpl) { + mImpl->Stop(); + } +} + +bool HandleWatcher::IsStopped() { return !mImpl || mImpl->IsStopped(); } + +} // namespace mozilla diff --git a/xpcom/threads/WinHandleWatcher.h b/xpcom/threads/WinHandleWatcher.h new file mode 100644 index 0000000000..3e9b4f1f22 --- /dev/null +++ b/xpcom/threads/WinHandleWatcher.h @@ -0,0 +1,117 @@ +/* -*- 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/. */ + +#ifndef WinHandleWatcher_h__ +#define WinHandleWatcher_h__ + +#include + +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UniquePtr.h" + +#include "nsIEventTarget.h" +#include "nsIRunnable.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +namespace mozilla { +/////////////////////////////////////////////////////////////////////// +// HandleWatcher +// +// Enqueues a task onto an event target when a watched Win32 synchronization +// object [1] enters the signaled state. +// +// The HandleWatcher must be stopped before either it or the synchronization +// object is destroyed. +// +////// +// +// Example of use: +// +// ``` +// class MyClass { +// /* ... */ +// +// HANDLE CreateThing(); +// void OnComplete(); +// public: +// void Fire() { +// mHandle.set(CreateThing()); +// mWatcher.Watch( +// mHandle.get(), NS_GetCurrentThread(), // (or any other thread) +// NS_NewRunnableFunction("OnComplete", [this] { OnComplete(); })); +// } +// +// ~MyClass() { mWatcher.Stop(); } +// +// HandleWatcher mWatcher; +// HandlePtr mHandle; // calls ::CloseHandle() on destruction +// }; +// ``` +// +// Note: this example demonstrates why an explicit `Stop()` is necessary in +// MyClass's destructor. Without it, the `HandlePtr` would destroy the HANDLE -- +// and possibly whatever other data `OnComplete()` depends on -- before the +// watch was stopped! +// +// Rather than make code correctness silently dependent on member object order, +// we require that HandleWatcher already be stopped at its destruction time. +// (This does not guarantee correctness, as the task may still reference a +// partially-destroyed transitive owner; but, short of RIIR, a guarantee of +// correctness is probably not possible here.) +// +////// +// +// [1]https://learn.microsoft.com/en-us/windows/win32/sync/synchronization-objects +class HandleWatcher { + public: + class Impl; + + HandleWatcher(); + ~HandleWatcher(); + + HandleWatcher(HandleWatcher const&) = delete; + HandleWatcher& operator=(HandleWatcher const&) = delete; + + HandleWatcher(HandleWatcher&&) = default; + HandleWatcher& operator=(HandleWatcher&&) = default; + + // Watches the given Win32 HANDLE, which must be a synchronization object. As + // soon as the HANDLE is signaled, posts `aRunnable` to `aTarget`. + // + // `aHandle` is merely borrowed for the duration of the watch: the + // HandleWatcher does not attempt to close it, and its lifetime must exceed + // that of the watch. + // + // If the watch is stopped for any reason other than completion, `aRunnable` + // is released immediately, on the same thread from which the Watch was + // stopped. + // + // The watch is stopped when any of the following occurs: + // * `Stop()` is called. + // * `Watch()` is called again, even without an intervening `Stop()`. + // * This object is destroyed. + // * `aTarget` shuts down. + // * `aHandle` becomes signaled. + // + void Watch(HANDLE aHandle, nsIEventTarget* aTarget, + already_AddRefed aRunnable); + + // Cancels the current watch, if any. + // + // Idempotent. Thread-safe with respect to other calls of `Stop()`. + void Stop(); + + // Potentially racy. Only intended for tests. + bool IsStopped(); + + private: + RefPtr mImpl; +}; +} // namespace mozilla + +#endif // WinHandleWatcher_h__ diff --git a/xpcom/threads/components.conf b/xpcom/threads/components.conf new file mode 100644 index 0000000000..53f76d3b89 --- /dev/null +++ b/xpcom/threads/components.conf @@ -0,0 +1,29 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{03d68f92-9513-4e25-9be9-7cb239874172}', + 'contract_ids': ['@mozilla.org/process/environment;1'], + 'legacy_constructor': 'nsEnvironment::Create', + 'headers': ['/xpcom/threads/nsEnvironment.h'], + 'js_name': 'env', + 'interfaces': ['nsIEnvironment'], + }, + { + 'cid': '{5ff24248-1dd2-11b2-8427-fbab44f29bc8}', + 'contract_ids': ['@mozilla.org/timer;1'], + 'legacy_constructor': 'nsTimer::XPCOMConstructor', + 'headers': ['/xpcom/threads/nsTimerImpl.h'], + 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_SOCKET_AND_UTILITY_PROCESS, + }, + { + 'cid': '{d39a8904-2e09-4a3a-a273-c3bec7db2bfe}', + 'contract_ids': ['@mozilla.org/timer-manager;1'], + 'headers': ['/xpcom/threads/nsTimerImpl.h'], + 'type': 'nsTimerManager', + }, +] diff --git a/xpcom/threads/moz.build b/xpcom/threads/moz.build new file mode 100644 index 0000000000..efdbb47304 --- /dev/null +++ b/xpcom/threads/moz.build @@ -0,0 +1,148 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +XPIDL_SOURCES += [ + "nsIDirectTaskDispatcher.idl", + "nsIEnvironment.idl", + "nsIEventTarget.idl", + "nsIIdlePeriod.idl", + "nsINamed.idl", + "nsIProcess.idl", + "nsIRunnable.idl", + "nsISerialEventTarget.idl", + "nsISupportsPriority.idl", + "nsIThread.idl", + "nsIThreadInternal.idl", + "nsIThreadManager.idl", + "nsIThreadPool.idl", + "nsIThreadShutdown.idl", + "nsITimer.idl", +] + +XPIDL_MODULE = "xpcom_threads" + +XPCOM_MANIFESTS += [ + "components.conf", +] + +EXPORTS += [ + "MainThreadUtils.h", + "nsICancelableRunnable.h", + "nsIDiscardableRunnable.h", + "nsIIdleRunnable.h", + "nsITargetShutdownTask.h", + "nsMemoryPressure.h", + "nsProcess.h", + "nsProxyRelease.h", + "nsThread.h", + "nsThreadManager.h", + "nsThreadPool.h", + "nsThreadUtils.h", +] + +EXPORTS.mozilla += [ + "AbstractThread.h", + "BlockingResourceBase.h", + "CondVar.h", + "CPUUsageWatcher.h", + "DataMutex.h", + "DeadlockDetector.h", + "DelayedRunnable.h", + "EventQueue.h", + "EventTargetCapability.h", + "IdlePeriodState.h", + "IdleTaskRunner.h", + "InputTaskManager.h", + "LazyIdleThread.h", + "MainThreadIdlePeriod.h", + "Monitor.h", + "MozPromise.h", + "MozPromiseInlines.h", + "Mutex.h", + "PerformanceCounter.h", + "Queue.h", + "RecursiveMutex.h", + "ReentrantMonitor.h", + "RWLock.h", + "SchedulerGroup.h", + "SharedThreadPool.h", + "SpinEventLoopUntil.h", + "StateMirroring.h", + "StateWatching.h", + "SynchronizedEventQueue.h", + "SyncRunnable.h", + "TaskCategory.h", + "TaskController.h", + "TaskDispatcher.h", + "TaskQueue.h", + "ThreadBound.h", + "ThreadEventQueue.h", + "ThrottledEventQueue.h", + "VsyncTaskManager.h", +] + +SOURCES += [ + "IdleTaskRunner.cpp", + "ThreadDelay.cpp", +] + +UNIFIED_SOURCES += [ + "AbstractThread.cpp", + "BlockingResourceBase.cpp", + "CPUUsageWatcher.cpp", + "DelayedRunnable.cpp", + "EventQueue.cpp", + "IdlePeriodState.cpp", + "InputTaskManager.cpp", + "LazyIdleThread.cpp", + "MainThreadIdlePeriod.cpp", + "nsEnvironment.cpp", + "nsMemoryPressure.cpp", + "nsProcessCommon.cpp", + "nsProxyRelease.cpp", + "nsThread.cpp", + "nsThreadManager.cpp", + "nsThreadPool.cpp", + "nsThreadUtils.cpp", + "nsTimerImpl.cpp", + "PerformanceCounter.cpp", + "RecursiveMutex.cpp", + "RWLock.cpp", + "SchedulerGroup.cpp", + "SharedThreadPool.cpp", + "SynchronizedEventQueue.cpp", + "TaskController.cpp", + "TaskQueue.cpp", + "ThreadEventQueue.cpp", + "ThreadEventTarget.cpp", + "ThreadLocalVariables.cpp", + "ThrottledEventQueue.cpp", + "TimerThread.cpp", + "VsyncTaskManager.cpp", +] + +if CONFIG["OS_ARCH"] == "WINNT": + EXPORTS.mozilla += ["WinHandleWatcher.h"] + UNIFIED_SOURCES += ["WinHandleWatcher.cpp"] + +# Should match the conditions in toolkit/components/backgroundhangmonitor/moz.build +if ( + CONFIG["NIGHTLY_BUILD"] + and not CONFIG["MOZ_DEBUG"] + and not CONFIG["MOZ_TSAN"] + and not CONFIG["MOZ_ASAN"] +): + DEFINES["MOZ_ENABLE_BACKGROUND_HANG_MONITOR"] = 1 + +LOCAL_INCLUDES += [ + "../build", + "/caps", + "/tools/profiler", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") diff --git a/xpcom/threads/nsEnvironment.cpp b/xpcom/threads/nsEnvironment.cpp new file mode 100644 index 0000000000..54efd9194a --- /dev/null +++ b/xpcom/threads/nsEnvironment.cpp @@ -0,0 +1,136 @@ +/* -*- 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 "nsEnvironment.h" +#include "prenv.h" +#include "nsBaseHashtable.h" +#include "nsHashKeys.h" +#include "nsPromiseFlatString.h" +#include "nsDependentString.h" +#include "nsNativeCharsetUtils.h" +#include "mozilla/Printf.h" +#include "mozilla/StaticMutex.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsEnvironment, nsIEnvironment) + +nsresult nsEnvironment::Create(REFNSIID aIID, void** aResult) { + nsresult rv; + *aResult = nullptr; + + nsEnvironment* obj = new nsEnvironment(); + + rv = obj->QueryInterface(aIID, aResult); + if (NS_FAILED(rv)) { + delete obj; + } + return rv; +} + +NS_IMETHODIMP +nsEnvironment::Exists(const nsAString& aName, bool* aOutValue) { + nsAutoCString nativeName; + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString nativeVal; +#if defined(XP_UNIX) + /* For Unix/Linux platforms we follow the Unix definition: + * An environment variable exists when |getenv()| returns a non-nullptr + * value. An environment variable does not exist when |getenv()| returns + * nullptr. + */ + const char* value = PR_GetEnv(nativeName.get()); + *aOutValue = value && *value; +#else + /* For non-Unix/Linux platforms we have to fall back to a + * "portable" definition (which is incorrect for Unix/Linux!!!!) + * which simply checks whether the string returned by |Get()| is empty + * or not. + */ + nsAutoString value; + Get(aName, value); + *aOutValue = !value.IsEmpty(); +#endif /* XP_UNIX */ + + return NS_OK; +} + +NS_IMETHODIMP +nsEnvironment::Get(const nsAString& aName, nsAString& aOutValue) { + nsAutoCString nativeName; + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsAutoCString nativeVal; + const char* value = PR_GetEnv(nativeName.get()); + if (value && *value) { + rv = NS_CopyNativeToUnicode(nsDependentCString(value), aOutValue); + } else { + aOutValue.Truncate(); + rv = NS_OK; + } + + return rv; +} + +/* Environment strings must have static duration; We're gonna leak all of this + * at shutdown: this is by design, caused how Unix/Linux implement environment + * vars. + */ + +typedef nsBaseHashtableET EnvEntryType; +typedef nsTHashtable EnvHashType; + +static StaticMutex gEnvHashMutex; +static EnvHashType* gEnvHash MOZ_GUARDED_BY(gEnvHashMutex) = nullptr; + +static EnvHashType* EnsureEnvHash() MOZ_REQUIRES(gEnvHashMutex) { + if (!gEnvHash) { + gEnvHash = new EnvHashType; + } + return gEnvHash; +} + +NS_IMETHODIMP +nsEnvironment::Set(const nsAString& aName, const nsAString& aValue) { + nsAutoCString nativeName; + nsAutoCString nativeVal; + + nsresult rv = NS_CopyUnicodeToNative(aName, nativeName); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = NS_CopyUnicodeToNative(aValue, nativeVal); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + StaticMutexAutoLock lock(gEnvHashMutex); + EnvEntryType* entry = EnsureEnvHash()->PutEntry(nativeName.get()); + if (!entry) { + return NS_ERROR_OUT_OF_MEMORY; + } + + SmprintfPointer newData = + mozilla::Smprintf("%s=%s", nativeName.get(), nativeVal.get()); + if (!newData) { + return NS_ERROR_OUT_OF_MEMORY; + } + + PR_SetEnv(newData.get()); + if (entry->GetData()) { + free(entry->GetData()); + } + entry->SetData(newData.release()); + return NS_OK; +} diff --git a/xpcom/threads/nsEnvironment.h b/xpcom/threads/nsEnvironment.h new file mode 100644 index 0000000000..d371050ec5 --- /dev/null +++ b/xpcom/threads/nsEnvironment.h @@ -0,0 +1,34 @@ +/* -*- 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/. */ + +#ifndef nsEnvironment_h__ +#define nsEnvironment_h__ + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsIEnvironment.h" + +#define NS_ENVIRONMENT_CID \ + { \ + 0X3D68F92UL, 0X9513, 0X4E25, { \ + 0X9B, 0XE9, 0X7C, 0XB2, 0X39, 0X87, 0X41, 0X72 \ + } \ + } +#define NS_ENVIRONMENT_CONTRACTID "@mozilla.org/process/environment;1" + +class nsEnvironment final : public nsIEnvironment { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIENVIRONMENT + + static nsresult Create(REFNSIID aIID, void** aResult); + + private: + nsEnvironment() {} + ~nsEnvironment() = default; +}; + +#endif /* !nsEnvironment_h__ */ diff --git a/xpcom/threads/nsICancelableRunnable.h b/xpcom/threads/nsICancelableRunnable.h new file mode 100644 index 0000000000..7aa98c86b6 --- /dev/null +++ b/xpcom/threads/nsICancelableRunnable.h @@ -0,0 +1,40 @@ +/* -*- 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/. */ + +#ifndef nsICancelableRunnable_h__ +#define nsICancelableRunnable_h__ + +#include "nsISupports.h" + +#define NS_ICANCELABLERUNNABLE_IID \ + { \ + 0xde93dc4c, 0x5eea, 0x4eb7, { \ + 0xb6, 0xd1, 0xdb, 0xf1, 0xe0, 0xce, 0xf6, 0x5c \ + } \ + } + +class nsICancelableRunnable : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICANCELABLERUNNABLE_IID) + + /* + * Cancels a pending task, so that calling run() on the task is a no-op. + * Calling cancel after the task execution has begun will be a no-op. + * Calling this method twice is considered an error. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that the runnable has already been canceled. + */ + virtual nsresult Cancel() = 0; + + protected: + nsICancelableRunnable() = default; + virtual ~nsICancelableRunnable() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsICancelableRunnable, NS_ICANCELABLERUNNABLE_IID) + +#endif // nsICancelableRunnable_h__ diff --git a/xpcom/threads/nsIDirectTaskDispatcher.idl b/xpcom/threads/nsIDirectTaskDispatcher.idl new file mode 100644 index 0000000000..7d44608708 --- /dev/null +++ b/xpcom/threads/nsIDirectTaskDispatcher.idl @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsIRunnable.idl" + +%{C++ +#include "mozilla/AlreadyAddRefed.h" +%} + +native alreadyAddRefed_nsIRunnable(already_AddRefed); + +/* + * The primary use of this interface is to allow any nsISerialEventTarget to + * provide Direct Task dispatching which is similar (but not identical to) the + * microtask semantics of JS promises. + * New direct task may be dispatched when a current direct task is running. In + * which case they will be run in FIFO order. + */ +[uuid(e05bf0fe-94b7-4e28-8462-a8368da9c136)] +interface nsIDirectTaskDispatcher : nsISupports +{ + /** + * Dispatch an event for the nsISerialEventTarget, using the direct task + * queue. + * + * This function must be called from the same nsISerialEventTarget + * implementing direct task dispatching. + * + * @param event + * The alreadyAddRefed<> event to dispatch. + * + */ + [noscript] void dispatchDirectTask(in alreadyAddRefed_nsIRunnable event); + + /** + * Synchronously run any pending direct tasks queued. + */ + [noscript] void drainDirectTasks(); + + /** + * Returns true if any direct tasks are pending. + */ + [noscript] bool haveDirectTasks(); + + %{C++ + // Infallible version of the above. Will assert that it is successful. + bool HaveDirectTasks() { + bool value = false; + MOZ_ALWAYS_SUCCEEDS(HaveDirectTasks(&value)); + return value; + } + %} + +}; diff --git a/xpcom/threads/nsIDiscardableRunnable.h b/xpcom/threads/nsIDiscardableRunnable.h new file mode 100644 index 0000000000..873b1f5d93 --- /dev/null +++ b/xpcom/threads/nsIDiscardableRunnable.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=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/. */ + +#ifndef XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_ +#define XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_ + +#include "nsISupports.h" + +/** + * An interface implemented by nsIRunnable tasks for which nsIRunnable::Run() + * might not be called. + */ +#define NS_IDISCARDABLERUNNABLE_IID \ + { \ + 0xde93dc4c, 0x755c, 0x4cdc, { \ + 0x96, 0x76, 0x35, 0xc6, 0x48, 0x81, 0x59, 0x78 \ + } \ + } + +class NS_NO_VTABLE nsIDiscardableRunnable : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDISCARDABLERUNNABLE_IID) + + /** + * Called exactly once on a queued task only if nsIRunnable::Run() is not + * called. + */ + virtual void OnDiscard() = 0; + + protected: + nsIDiscardableRunnable() = default; + virtual ~nsIDiscardableRunnable() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIDiscardableRunnable, + NS_IDISCARDABLERUNNABLE_IID) + +#endif // XPCOM_THREADS_NSIDISCARDABLERUNNABLE_H_ diff --git a/xpcom/threads/nsIEnvironment.idl b/xpcom/threads/nsIEnvironment.idl new file mode 100644 index 0000000000..60da8ba76a --- /dev/null +++ b/xpcom/threads/nsIEnvironment.idl @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * Scriptable access to the current process environment. + * + */ +[scriptable, uuid(101d5941-d820-4e85-a266-9a3469940807)] +interface nsIEnvironment : nsISupports +{ + /** + * Set the value of an environment variable. + * + * @param aName the variable name to set. + * @param aValue the value to set. + */ + void set(in AString aName, in AString aValue); + + /** + * Get the value of an environment variable. + * + * @param aName the variable name to retrieve. + * @return returns the value of the env variable. An empty string + * will be returned when the env variable does not exist or + * when the value itself is an empty string - please use + * |exists()| to probe whether the env variable exists + * or not. + */ + AString get(in AString aName); + + /** + * Check the existence of an environment variable. + * This method checks whether an environment variable is present in + * the environment or not. + * + * - For Unix/Linux platforms we follow the Unix definition: + * An environment variable exists when |getenv()| returns a non-NULL value. + * An environment variable does not exist when |getenv()| returns NULL. + * - For non-Unix/Linux platforms we have to fall back to a + * "portable" definition (which is incorrect for Unix/Linux!!!!) + * which simply checks whether the string returned by |Get()| is empty + * or not. + * + * @param aName the variable name to probe. + * @return if the variable has been set, the value returned is + * PR_TRUE. If the variable was not defined in the + * environment PR_FALSE will be returned. + */ + boolean exists(in AString aName); +}; diff --git a/xpcom/threads/nsIEventTarget.idl b/xpcom/threads/nsIEventTarget.idl new file mode 100644 index 0000000000..2139dc5ab3 --- /dev/null +++ b/xpcom/threads/nsIEventTarget.idl @@ -0,0 +1,227 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISupports.idl" +#include "nsIRunnable.idl" +%{C++ +#include "nsCOMPtr.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Atomics.h" + +class nsITargetShutdownTask; +%} + +native alreadyAddRefed_nsIRunnable(already_AddRefed); +[ptr] native nsITargetShutdownTask(nsITargetShutdownTask); + +[builtinclass, scriptable, uuid(a03b8b63-af8b-4164-b0e5-c41e8b2b7cfa)] +interface nsIEventTarget : nsISupports +{ + /* until we can get rid of all uses, keep the non-alreadyAddRefed<> version */ +%{C++ + nsresult Dispatch(nsIRunnable* aEvent, uint32_t aFlags) { + return Dispatch(nsCOMPtr(aEvent).forget(), aFlags); + } +%} + + /** + * This flag specifies the default mode of event dispatch, whereby the event + * is simply queued for later processing. When this flag is specified, + * dispatch returns immediately after the event is queued. + */ + const unsigned long DISPATCH_NORMAL = 0; + + // NOTE: 1 used to be DISPATCH_SYNC + + /** + * This flag specifies that the dispatch is occurring from a running event + * that was dispatched to the same event target, and that event is about to + * finish. + * + * A thread pool can use this as an optimization hint to not spin up + * another thread, since the current thread is about to become idle. + * + * These events are always async. + */ + const unsigned long DISPATCH_AT_END = 2; + + /** + * This flag specifies that the dispatched event may block the thread on + * which it executes, usually by doing some sort of I/O. This information + * may be used by the event target to execute the job on a thread + * specifically dedicated to doing I/O, leaving other threads available for + * CPU-intensive work. + */ + const unsigned long DISPATCH_EVENT_MAY_BLOCK = 4; + + /** + * This flag specifies that the dispatched event should be delivered to the + * target thread even if the thread has been configured to block dispatching + * of runnables. This is generally done for threads which have their own + * internal event loop, such as thread pools or the timer thread, and will not + * service runnables dispatched to them until shutdown. + */ + const unsigned long DISPATCH_IGNORE_BLOCK_DISPATCH = 8; + + /** + * IsOnCurrentThread() should return true if events dispatched to this target + * can possibly run on the current thread, and false otherwise. In the case + * of an nsIEventTarget for a thread pool, it should return true on all + * threads in the pool. In the case of a non-thread nsIEventTarget such as + * ThrottledEventQueue, it should return true on the thread where events are + * expected to be processed, even if no events from the queue are actually + * being processed right now. + * + * When called on an nsISerialEventTarget, IsOnCurrentThread can be used to + * ensure that no other thread has "ownership" of the event target. As such, + * it's useful for asserting that an object is only used on a particular + * thread. IsOnCurrentThread can't guarantee that the current event has been + * dispatched through a particular event target. + * + * The infallible version of IsOnCurrentThread() is optimized to avoid a + * virtual call for non-thread event targets. Thread targets should set + * mThread to their virtual PRThread. Non-thread targets should leave + * mThread null and implement IsOnCurrentThreadInfallible() to + * return the correct answer. + * + * The fallible version of IsOnCurrentThread may return errors, such as during + * shutdown. If it does not return an error, it should return the same result + * as the infallible version. The infallible method should return the correct + * result regardless of whether the fallible method returns an error. + */ + %{C++ +public: + // Infallible. Defined in nsThreadUtils.cpp. Delegates to + // IsOnCurrentThreadInfallible when mThread is null. + bool IsOnCurrentThread(); + +protected: + mozilla::Atomic mThread; + + nsIEventTarget() : mThread(nullptr) {} + %} + // Note that this method is protected. We define it through IDL, rather than + // in a %{C++ block, to ensure that the correct method indices are recorded + // for XPConnect purposes. + [noscript,notxpcom] boolean isOnCurrentThreadInfallible(); + %{C++ +public: + %} + + // Fallible version of IsOnCurrentThread. + boolean isOnCurrentThread(); + + /** + * Dispatch an event to this event target. This function may be called from + * any thread, and it may be called re-entrantly. + * + * @param event + * The alreadyAddRefed<> event to dispatch. + * NOTE that the event will be leaked if it fails to dispatch. + * @param flags + * The flags modifying event dispatch. The flags are described in detail + * below. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched. + */ + [noscript, binaryname(Dispatch)] void dispatchFromC(in alreadyAddRefed_nsIRunnable event, + [default(DISPATCH_NORMAL)] in unsigned long flags); + /** + * Version of Dispatch to expose to JS, which doesn't require an alreadyAddRefed<> + * (it will be converted to that internally) + * + * @param event + * The (raw) event to dispatch. + * @param flags + * The flags modifying event dispatch. The flags are described in detail + * below. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched. + */ + [binaryname(DispatchFromScript)] void dispatch(in nsIRunnable event, in unsigned long flags); + /** + * Dispatch an event to this event target, but do not run it before delay + * milliseconds have passed. This function may be called from any thread. + * + * @param event + * The alreadyAddrefed<> event to dispatch. + * @param delay + * The delay (in ms) before running the event. If event does not rise to + * the top of the event queue before the delay has passed, it will be set + * aside to execute once the delay has passed. Otherwise, it will be + * executed immediately. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched, or + * that delay is zero. + */ + [noscript] void delayedDispatch(in alreadyAddRefed_nsIRunnable event, in unsigned long delay); + + /** + * Register an task to be run on this event target when it begins shutting + * down. Shutdown tasks may be run in any order, and this function may be + * called from any thread. + * + * The event target may or may not continue accepting events during or after + * the shutdown task. The precise behaviour here depends on the event target. + * + * @param task + * The task to be registered to the target thread. + * NOTE that unlike `dispatch`, this will not leak the task if it fails. + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that task is null. + * @throws NS_ERROR_NOT_IMPLEMENTED + * Indicates that this event target doesn't support shutdown tasks. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is already shutting down, and no longer + * accepting events. + */ + [noscript] void registerShutdownTask(in nsITargetShutdownTask task); + + /** + * Unregisters an task previously registered with registerShutdownTask. This + * function may be called from any thread. + * + * @param task + * The task previously registered with registerShutdownTask + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that task is null. + * @throws NS_ERROR_NOT_IMPLEMENTED + * Indicates that this event target doesn't support shutdown tasks. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is already shutting down, and no longer + * accepting events, or that the shutdown task cannot be found. + */ + [noscript] void unregisterShutdownTask(in nsITargetShutdownTask task); +}; + +%{C++ +// convenient aliases: +#define NS_DISPATCH_NORMAL nsIEventTarget::DISPATCH_NORMAL +#define NS_DISPATCH_AT_END nsIEventTarget::DISPATCH_AT_END +#define NS_DISPATCH_EVENT_MAY_BLOCK nsIEventTarget::DISPATCH_EVENT_MAY_BLOCK +#define NS_DISPATCH_IGNORE_BLOCK_DISPATCH nsIEventTarget::DISPATCH_IGNORE_BLOCK_DISPATCH + +// Convenient NS_DECL variant that includes some C++-only methods. +#define NS_DECL_NSIEVENTTARGET_FULL \ + NS_DECL_NSIEVENTTARGET \ + /* Avoid hiding these methods */ \ + using nsIEventTarget::Dispatch; \ + using nsIEventTarget::IsOnCurrentThread; +%} diff --git a/xpcom/threads/nsIIdlePeriod.idl b/xpcom/threads/nsIIdlePeriod.idl new file mode 100644 index 0000000000..03ab45d80d --- /dev/null +++ b/xpcom/threads/nsIIdlePeriod.idl @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +%{C++ +namespace mozilla { +class TimeStamp; +} +%} + +native TimeStamp(mozilla::TimeStamp); + +/** + * An instance implementing nsIIdlePeriod is used by an associated + * nsIThread to estimate when it is likely that it will receive an + * event. + */ +[uuid(21dd35a2-eae9-4bd8-b470-0dfa35a0e3b9)] +interface nsIIdlePeriod : nsISupports +{ + /** + * Return an estimate of a point in time in the future when we + * think that the associated thread will become busy. Should + * return TimeStamp() (i.e. the null time) or a time less than + * TimeStamp::Now() if the thread is currently busy or will become + * busy very soon. + */ + TimeStamp getIdlePeriodHint(); +}; diff --git a/xpcom/threads/nsIIdleRunnable.h b/xpcom/threads/nsIIdleRunnable.h new file mode 100644 index 0000000000..7fe6149154 --- /dev/null +++ b/xpcom/threads/nsIIdleRunnable.h @@ -0,0 +1,48 @@ +/* -*- 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/. */ + +#ifndef nsIIdleRunnable_h__ +#define nsIIdleRunnable_h__ + +#include "nsISupports.h" +#include "mozilla/TimeStamp.h" + +#define NS_IIDLERUNNABLE_IID \ + { \ + 0x688be92e, 0x7ade, 0x4fdc, { \ + 0x9d, 0x83, 0x74, 0xcb, 0xef, 0xf4, 0xa5, 0x2c \ + } \ + } + +class nsIEventTarget; + +/** + * A task interface for tasks that can schedule their work to happen + * in increments bounded by a deadline. + */ +class nsIIdleRunnable : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IIDLERUNNABLE_IID) + + /** + * Notify the task of a point in time in the future when the task + * should stop executing. + */ + virtual void SetDeadline(mozilla::TimeStamp aDeadline){}; + virtual void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT_UNREACHABLE( + "The nsIIdleRunnable instance does not support " + "idle dispatch with timeout!"); + }; + + protected: + nsIIdleRunnable() = default; + virtual ~nsIIdleRunnable() = default; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIIdleRunnable, NS_IIDLERUNNABLE_IID) + +#endif // nsIIdleRunnable_h__ diff --git a/xpcom/threads/nsINamed.idl b/xpcom/threads/nsINamed.idl new file mode 100644 index 0000000000..cdb7d88f30 --- /dev/null +++ b/xpcom/threads/nsINamed.idl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * Represents an object with a name, such as a runnable or a timer. + */ + +[scriptable, uuid(0c5fe7de-7e83-4d0d-a8a6-4a6518b9a7b3)] +interface nsINamed : nsISupports +{ + /* + * A string describing the purpose of the runnable/timer/whatever. Useful + * for debugging. This attribute is read-only, but you can change it to a + * compile-time string literal with setName. + * + * WARNING: This attribute will be included in telemetry, so it should + * never contain privacy sensitive information. + */ + readonly attribute AUTF8String name; +}; diff --git a/xpcom/threads/nsIProcess.idl b/xpcom/threads/nsIProcess.idl new file mode 100644 index 0000000000..c15ded7a2f --- /dev/null +++ b/xpcom/threads/nsIProcess.idl @@ -0,0 +1,112 @@ +/* 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 "nsISupports.idl" + +interface nsIFile; +interface nsIObserver; + +[scriptable, uuid(609610de-9954-4a63-8a7c-346350a86403)] +interface nsIProcess : nsISupports +{ + /** + * Initialises the process with an executable to be run. Call the run method + * to run the executable. + * @param executable The executable to run. + */ + void init(in nsIFile executable); + + /** + * Kills the running process. After exiting the process will either have + * been killed or a failure will have been returned. + */ + void kill(); + + /** + * Executes the file this object was initialized with + * @param blocking Whether to wait until the process terminates before + returning or not. + * @param args An array of arguments to pass to the process in the + * native character set. + * @param count The length of the args array. + */ + void run(in boolean blocking, [array, size_is(count)] in string args, + in unsigned long count); + + /** + * Executes the file this object was initialized with optionally calling + * an observer after the process has finished running. + * @param args An array of arguments to pass to the process in the + * native character set. + * @param count The length of the args array. + * @param observer An observer to notify when the process has completed. It + * will receive this process instance as the subject and + * "process-finished" or "process-failed" as the topic. The + * observer will be notified on the main thread. + * @param holdWeak Whether to use a weak reference to hold the observer. + */ + void runAsync([array, size_is(count)] in string args, in unsigned long count, + [optional] in nsIObserver observer, [optional] in boolean holdWeak); + + /** + * Executes the file this object was initialized with + * @param blocking Whether to wait until the process terminates before + returning or not. + * @param args An array of arguments to pass to the process in UTF-16 + * @param count The length of the args array. + */ + void runw(in boolean blocking, [array, size_is(count)] in wstring args, + in unsigned long count); + + /** + * Executes the file this object was initialized with optionally calling + * an observer after the process has finished running. + * @param args An array of arguments to pass to the process in UTF-16 + * @param count The length of the args array. + * @param observer An observer to notify when the process has completed. It + * will receive this process instance as the subject and + * "process-finished" or "process-failed" as the topic. The + * observer will be notified on the main thread. + * @param holdWeak Whether to use a weak reference to hold the observer. + */ + void runwAsync([array, size_is(count)] in wstring args, + in unsigned long count, + [optional] in nsIObserver observer, [optional] in boolean holdWeak); + + /** + * When set to true the process will not open a new window when started and + * will run hidden from the user. This currently affects only the Windows + * platform. + */ + attribute boolean startHidden; + + /** + * When set to true the process will be launched directly without using the + * shell. This currently affects only the Windows platform. + */ + attribute boolean noShell; + + /** + * The process identifier of the currently running process. This will only + * be available after the process has started and may not be available on + * some platforms. + */ + readonly attribute unsigned long pid; + + /** + * The exit value of the process. This is only valid after the process has + * exited. + */ + readonly attribute long exitValue; + + /** + * Returns whether the process is currently running or not. + */ + readonly attribute boolean isRunning; +}; + +%{C++ + +#define NS_PROCESS_CONTRACTID "@mozilla.org/process/util;1" +%} diff --git a/xpcom/threads/nsIRunnable.idl b/xpcom/threads/nsIRunnable.idl new file mode 100644 index 0000000000..bfe9669a9f --- /dev/null +++ b/xpcom/threads/nsIRunnable.idl @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * Represents a task which can be dispatched to a thread for execution. + */ + +[scriptable, function, uuid(4a2abaf0-6886-11d3-9382-00104ba0fd40)] +interface nsIRunnable : nsISupports +{ + /** + * The function implementing the task to be run. + */ + void run(); +}; + +[scriptable, uuid(e75aa42a-80a9-11e6-afb5-e89d87348e2c)] +interface nsIRunnablePriority : nsISupports +{ + const unsigned long PRIORITY_IDLE = 0; + const unsigned long PRIORITY_DEFERRED_TIMERS = 1; + const unsigned long PRIORITY_LOW = 2; + // INPUT_LOW isn't supposed to be used directly. + // const unsigned long PRIORITY_INPUT_LOW = 3; + const unsigned long PRIORITY_NORMAL = 4; + const unsigned long PRIORITY_MEDIUMHIGH = 5; + const unsigned long PRIORITY_INPUT_HIGH = 6; + const unsigned long PRIORITY_VSYNC = 7; + // INPUT_HIGHEST is InputTaskManager's internal priority + //const unsigned long PRIORITY_INPUT_HIGHEST = 8; + const unsigned long PRIORITY_RENDER_BLOCKING = 9; + const unsigned long PRIORITY_CONTROL = 10; + + readonly attribute unsigned long priority; +}; + +[uuid(3114c36c-a482-4c6e-9523-1dcfc6f605b9)] +interface nsIRunnableIPCMessageType : nsISupports +{ + readonly attribute unsigned long type; +}; diff --git a/xpcom/threads/nsISerialEventTarget.idl b/xpcom/threads/nsISerialEventTarget.idl new file mode 100644 index 0000000000..9cf7768b37 --- /dev/null +++ b/xpcom/threads/nsISerialEventTarget.idl @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsIEventTarget.idl" + +/** + * A serial event target is an event dispatching interface like + * nsIEventTarget. Runnables dispatched to an nsISerialEventTarget are required + * to execute serially. That is, two different runnables dispatched to the + * target should never be allowed to execute simultaneously. One exception to + * this rule is nested event loops. If a runnable spins a nested event loop, + * causing another runnable dispatched to the target to run, the target may + * still be considered "serial". + * + * Examples: + * - nsIThread is a serial event target. + * - Thread pools are not serial event targets. + * - However, one can "convert" a thread pool into an nsISerialEventTarget + * by putting a TaskQueue in front of it. + */ +[builtinclass, scriptable, uuid(9f982380-24b4-49f3-88f6-45e2952036c7)] +interface nsISerialEventTarget : nsIEventTarget +{ +}; diff --git a/xpcom/threads/nsISupportsPriority.idl b/xpcom/threads/nsISupportsPriority.idl new file mode 100644 index 0000000000..d0b8b9a3dd --- /dev/null +++ b/xpcom/threads/nsISupportsPriority.idl @@ -0,0 +1,45 @@ +/* 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 "nsISupports.idl" + +/** + * This interface exposes the general notion of a scheduled object with a + * integral priority value. Following UNIX conventions, smaller (and possibly + * negative) values have higher priority. + * + * This interface does not strictly define what happens when the priority of an + * object is changed. An implementation of this interface is free to define + * the side-effects of changing the priority of an object. In some cases, + * changing the priority of an object may be disallowed (resulting in an + * exception being thrown) or may simply be ignored. + */ +[scriptable, uuid(aa578b44-abd5-4c19-8b14-36d4de6fdc36)] +interface nsISupportsPriority : nsISupports +{ + /** + * Typical priority values. + */ + const long PRIORITY_HIGHEST = -20; + const long PRIORITY_HIGH = -10; + const long PRIORITY_NORMAL = 0; + const long PRIORITY_LOW = 10; + const long PRIORITY_LOWEST = 20; + + /** + * This attribute may be modified to change the priority of this object. The + * implementation of this interface is free to truncate a given priority + * value to whatever limits are appropriate. Typically, this attribute is + * initialized to PRIORITY_NORMAL, but implementations may choose to assign a + * different initial value. + */ + attribute long priority; + + /** + * This method adjusts the priority attribute by a given delta. It helps + * reduce the amount of coding required to increment or decrement the value + * of the priority attribute. + */ + void adjustPriority(in long delta); +}; diff --git a/xpcom/threads/nsITargetShutdownTask.h b/xpcom/threads/nsITargetShutdownTask.h new file mode 100644 index 0000000000..09ac3c5e5f --- /dev/null +++ b/xpcom/threads/nsITargetShutdownTask.h @@ -0,0 +1,37 @@ +/* -*- 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/. */ + +#ifndef XPCOM_THREADS_NSITARGETSHUTDOWNTASK_H_ +#define XPCOM_THREADS_NSITARGETSHUTDOWNTASK_H_ + +#include "nsISupports.h" +#include "nsIEventTarget.h" +#include "nsThreadUtils.h" + +#define NS_ITARGETSHUTDOWNTASK_IID \ + { \ + 0xb08647aa, 0xcfb5, 0x4630, { \ + 0x8e, 0x26, 0x9a, 0xbe, 0xb3, 0x3f, 0x08, 0x40 \ + } \ + } + +class NS_NO_VTABLE nsITargetShutdownTask : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_ISHUTDOWNTASK_IID) + + virtual void TargetShutdown() = 0; + + already_AddRefed AsRunnable() { + // FIXME: Try QI to nsINamed if available? + return mozilla::NewRunnableMethod("nsITargetShutdownTask::TargetShutdown", + this, + &nsITargetShutdownTask::TargetShutdown); + } +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsITargetShutdownTask, NS_ITARGETSHUTDOWNTASK_IID) + +#endif diff --git a/xpcom/threads/nsIThread.idl b/xpcom/threads/nsIThread.idl new file mode 100644 index 0000000000..e6735d5d64 --- /dev/null +++ b/xpcom/threads/nsIThread.idl @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISerialEventTarget.idl" +#include "nsIThreadShutdown.idl" + +%{C++ +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla { +class TimeStamp; +class TimeDurationValueCalculator; +template class BaseTimeDuration; +typedef BaseTimeDuration TimeDuration; +enum class EventQueuePriority; +} +%} + +[ptr] native PRThread(PRThread); +native EventQueuePriority(mozilla::EventQueuePriority); + +native nsIEventTargetPtr(nsIEventTarget*); +native nsISerialEventTargetPtr(nsISerialEventTarget*); +native TimeStamp(mozilla::TimeStamp); +native TimeDuration(mozilla::TimeDuration); + +/** + * This interface provides a high-level abstraction for an operating system + * thread. + * + * Threads have a built-in event queue, and a thread is an event target that + * can receive nsIRunnable objects (events) to be processed on the thread. + * + * See nsIThreadManager for the API used to create and locate threads. + */ +[builtinclass, scriptable, uuid(5801d193-29d1-4964-a6b7-70eb697ddf2b)] +interface nsIThread : nsISerialEventTarget +{ + /** + * @returns + * The NSPR thread object corresponding to this nsIThread. + */ + [noscript] readonly attribute PRThread PRThread; + + /** + * @returns + * Whether or not this thread may call into JS. Used in the profiler + * to avoid some unnecessary locking. + */ + [noscript] attribute boolean CanInvokeJS; + + /** + * Thread QoS priorities. Currently only supported on MacOS. + */ + + cenum QoSPriority : 32 { + QOS_PRIORITY_NORMAL, + QOS_PRIORITY_LOW + }; + + /** + * Shutdown the thread. This method prevents further dispatch of events to + * the thread, and it causes any pending events to run to completion before + * the thread joins (see PR_JoinThread) with the current thread. During this + * method call, events for the current thread may be processed. + * + * This method MAY NOT be executed from the thread itself. Instead, it is + * meant to be executed from another thread (usually the thread that created + * this thread or the main application thread). When this function returns, + * the thread will be shutdown, and it will no longer be possible to dispatch + * events to the thread. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * the current thread, that this thread was not created with a call to + * nsIThreadManager::NewThread, or if this method was called more than once + * on the thread object. + */ + void shutdown(); + + /** + * This method may be called to determine if there are any events ready to be + * processed. It may only be called when this thread is the current thread. + * + * Because events may be added to this thread by another thread, a "false" + * result does not mean that this thread has no pending events. It only + * means that there were no pending events when this method was called. + * + * @returns + * A boolean value that if "true" indicates that this thread has one or + * more pending events. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * not the current thread. + */ + boolean hasPendingEvents(); + + /** + * Similar to above, but checks only possible high priority queue. + */ + boolean hasPendingHighPriorityEvents(); + + /** + * Process the next event. If there are no pending events, then this method + * may wait -- depending on the value of the mayWait parameter -- until an + * event is dispatched to this thread. This method is re-entrant but may + * only be called if this thread is the current thread. + * + * @param mayWait + * A boolean parameter that if "true" indicates that the method may block + * the calling thread to wait for a pending event. + * + * @returns + * A boolean value that if "true" indicates that an event was processed. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * not the current thread. + */ + boolean processNextEvent(in boolean mayWait); + + /** + * Shutdown the thread asynchronously. This method immediately prevents + * further dispatch of events to the thread, and it causes any pending events + * to run to completion before this thread joins with the current thread. + * + * UNLIKE shutdown() this does not process events on the current thread. + * Instead it merely ensures that the current thread continues running until + * this thread has shut down. + * + * This method MAY NOT be executed from the thread itself. Instead, it is + * meant to be executed from another thread (usually the thread that created + * this thread or the main application thread). When this function returns, + * the thread will continue running until it exhausts its event queue. + * + * @throws NS_ERROR_UNEXPECTED + * Indicates that this method was erroneously called when this thread was + * the current thread, that this thread was not created with a call to + * nsIThreadManager::NewNamedThread, or that this method was called more + * than once on the thread object. + */ + void asyncShutdown(); + + /** + * Like `asyncShutdown`, but also returns a nsIThreadShutdown instance to + * allow observing and controlling the thread's async shutdown progress. + */ + nsIThreadShutdown beginShutdown(); + + /** + * Dispatch an event to a specified queue for the thread. This function + * may be called from any thread, and it may be called re-entrantly. + * Most users should use the NS_Dispatch*() functions in nsThreadUtils instead + * of calling this directly. + * + * @param event + * The alreadyAddRefed<> event to dispatch. + * NOTE that the event will be leaked if it fails to dispatch. + * @param queue + * Which event priority queue this should be added to + * + * @throws NS_ERROR_INVALID_ARG + * Indicates that event is null. + * @throws NS_ERROR_UNEXPECTED + * Indicates that the thread is shutting down and has finished processing + * events, so this event would never run and has not been dispatched. + */ + [noscript] void dispatchToQueue(in alreadyAddRefed_nsIRunnable event, + in EventQueuePriority queue); + + /** + * This is set to the end of the last 50+ms event that was executed on + * this thread (for MainThread only). Otherwise returns a null TimeStamp. + */ + [noscript] readonly attribute TimeStamp lastLongTaskEnd; + [noscript] readonly attribute TimeStamp lastLongNonIdleTaskEnd; + + /** + * Get information on the timing of the currently-running event. + * + * @param delay + * The amount of time the current running event in the specified queue waited + * to run. Will return TimeDuration() if the queue is empty or has not run any + * new events since event delay monitoring started. NOTE: delay will be + * TimeDuration() if this thread uses a PrioritizedEventQueue (i.e. MainThread) + * and the event priority is below Input. + * @param start + * The time the currently running event began to run, or TimeStamp() if no + * event is running. + */ + [noscript] void getRunningEventDelay(out TimeDuration delay, out TimeStamp start); + + /** + * Set information on the timing of the currently-running event. + * Overrides the values returned by getRunningEventDelay + * + * @param delay + * Delay the running event spent in queues, or TimeDuration() if + * there's no running event. + * @param start + * The time the currently running event began to run, or TimeStamp() if no + * event is running. + */ + [noscript] void setRunningEventDelay(in TimeDuration delay, in TimeStamp start); + + [noscript] void setNameForWakeupTelemetry(in ACString name); + + /** + * Set the QoS priority of threads where this may be available. Currently + * restricted to MacOS. Must be on the thread to call this method. + * + * @param aPriority + * The specified priority we will adjust to. Can be low (background) or + * normal (default / user-interactive) + */ + [noscript] void setThreadQoS(in nsIThread_QoSPriority aPriority); + +}; diff --git a/xpcom/threads/nsIThreadInternal.idl b/xpcom/threads/nsIThreadInternal.idl new file mode 100644 index 0000000000..fca9e711b0 --- /dev/null +++ b/xpcom/threads/nsIThreadInternal.idl @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsIThread.idl" + +interface nsIRunnable; +interface nsIThreadObserver; + +/** + * The XPCOM thread object implements this interface, which allows a consumer + * to observe dispatch activity on the thread. + */ +[builtinclass, scriptable, uuid(a3a72e5f-71d9-4add-8f30-59a78fb6d5eb)] +interface nsIThreadInternal : nsIThread +{ + /** + * Get/set the current thread observer (may be null). This attribute may be + * read from any thread, but must only be set on the thread corresponding to + * this thread object. The observer will be released on the thread + * corresponding to this thread object after all other events have been + * processed during a call to Shutdown. + */ + attribute nsIThreadObserver observer; + + /** + * Add an observer that will *only* receive onProcessNextEvent, + * beforeProcessNextEvent. and afterProcessNextEvent callbacks. Always called + * on the target thread, and the implementation does not have to be + * threadsafe. Order of callbacks is not guaranteed (i.e. + * afterProcessNextEvent may be called first depending on whether or not the + * observer is added in a nested loop). Holds a strong ref. + */ + void addObserver(in nsIThreadObserver observer); + + /** + * Remove an observer added via the addObserver call. Once removed the + * observer will never be called again by the thread. + */ + void removeObserver(in nsIThreadObserver observer); +}; + +/** + * This interface provides the observer with hooks to implement a layered + * event queue. For example, it is possible to overlay processing events + * for a GUI toolkit on top of the events for a thread: + * + * var NativeQueue; + * Observer = { + * onDispatchedEvent() { + * NativeQueue.signal(); + * } + * onProcessNextEvent(thread, mayWait) { + * if (NativeQueue.hasNextEvent()) + * NativeQueue.processNextEvent(); + * while (mayWait && !thread.hasPendingEvent()) { + * NativeQueue.wait(); + * NativeQueue.processNextEvent(); + * } + * } + * }; + * + * NOTE: The implementation of this interface must be threadsafe. + * + * NOTE: It is valid to change the thread's observer during a call to an + * observer method. + * + * NOTE: Will be split into two interfaces soon: one for onProcessNextEvent and + * afterProcessNextEvent, then another that inherits the first and adds + * onDispatchedEvent. + */ +[uuid(cc8da053-1776-44c2-9199-b5a629d0a19d)] +interface nsIThreadObserver : nsISupports +{ + /** + * This method is called after an event has been dispatched to the thread. + * This method may be called from any thread. + */ + void onDispatchedEvent(); + + /** + * This method is called when nsIThread::ProcessNextEvent is called. It does + * not guarantee that an event is actually going to be processed. This method + * is only called on the target thread. + * + * @param thread + * The thread being asked to process another event. + * @param mayWait + * Indicates whether or not the method is allowed to block the calling + * thread. For example, this parameter is false during thread shutdown. + */ + void onProcessNextEvent(in nsIThreadInternal thread, in boolean mayWait); + + /** + * This method is called (from nsIThread::ProcessNextEvent) after an event + * is processed. It does not guarantee that an event was actually processed + * (depends on the value of |eventWasProcessed|. This method is only called + * on the target thread. DO NOT EVER RUN SCRIPT FROM THIS CALLBACK!!! + * + * @param thread + * The thread that processed another event. + * @param eventWasProcessed + * Indicates whether an event was actually processed. May be false if the + * |mayWait| flag was false when calling nsIThread::ProcessNextEvent(). + */ + void afterProcessNextEvent(in nsIThreadInternal thread, + in bool eventWasProcessed); +}; diff --git a/xpcom/threads/nsIThreadManager.idl b/xpcom/threads/nsIThreadManager.idl new file mode 100644 index 0000000000..1001436364 --- /dev/null +++ b/xpcom/threads/nsIThreadManager.idl @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISupports.idl" + +[ptr] native PRThread(PRThread); + native ThreadCreationOptions(nsIThreadManager::ThreadCreationOptions); + +interface nsIEventTarget; +interface nsIRunnable; +interface nsIThread; + +[scriptable, function, uuid(039a227d-0cb7-44a5-a8f9-dbb7071979f2)] +interface nsINestedEventLoopCondition : nsISupports +{ + /** + * Returns true if the current nested event loop should stop spinning. + */ + bool isDone(); +}; + +/** + * An interface for creating and locating nsIThread instances. + */ +[scriptable, uuid(1be89eca-e2f7-453b-8d38-c11ba247f6f3)] +interface nsIThreadManager : nsISupports +{ + /** + * Default number of bytes reserved for a thread's stack, if no stack size + * is specified in newThread(). + * + * Defaults can be a little overzealous for many platforms. + * + * On Linux and OS X, for instance, the default thread stack size is whatever + * getrlimit(RLIMIT_STACK) returns, which is often set at 8MB. Or, on Linux, + * if the stack size is unlimited, we fall back to 2MB. This causes particular + * problems on Linux, which allocates 2MB huge VM pages, and will often + * immediately allocate them for any stacks which are 2MB or larger. + * + * The default on Windows is 1MB, which is a little more reasonable. But the + * vast majority of our threads don't need anywhere near that much space. + * + * ASan, TSan and non-opt builds, however, often need a bit more, so give + * them the platform default. + */ +%{C++ +#if defined(MOZ_ASAN) || defined(MOZ_TSAN) || !defined(__OPTIMIZE__) + static constexpr uint32_t DEFAULT_STACK_SIZE = 0; +#else + static constexpr uint32_t DEFAULT_STACK_SIZE = 256 * 1024; +#endif + + static const uint32_t kThreadPoolStackSize = DEFAULT_STACK_SIZE; + + struct ThreadCreationOptions { + // The size in bytes to reserve for the thread's stack. A value of `0` means + // to use the platform default. + uint32_t stackSize = nsIThreadManager::DEFAULT_STACK_SIZE; + + // If set to `true`, any attempts to dispatch runnables to this thread + // without `DISPATCH_IGNORE_BLOCK_DISPATCH` will fail. + // + // This is intended to be used for threads which are expected to generally + // only service a single runnable (other than thread lifecycle runnables), + // and perform their own event dispatching internaly, such as thread pool + // threads or the timer thread. + bool blockDispatch = false; + + // (Windows-only) Whether the thread should have a MessageLoop capable of + // processing native UI events. Defaults to false. + bool isUiThread = false; + }; +%} + + /** + * Create a new thread (a global, user PRThread) with the specified name. + * + * @param name + * The name of the thread. If it is empty the thread will not be named. + * @param options + * Configuration options for the newly created thread. + * + * @returns + * The newly created nsIThread object. + */ + [noscript] nsIThread newNamedThread(in ACString name, in ThreadCreationOptions options); + + /** + * Get the main thread. + */ + readonly attribute nsIThread mainThread; + + /** + * Get the current thread. If the calling thread does not already have a + * nsIThread associated with it, then a new nsIThread will be created and + * associated with the current PRThread. + */ + readonly attribute nsIThread currentThread; + + /** + * This queues a runnable to the main thread. It's a shortcut for JS callers + * to be used instead of + * .mainThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL); + * or + * .currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL); + * C++ callers should instead use NS_DispatchToMainThread. + */ + [optional_argc] + void dispatchToMainThread(in nsIRunnable event, [optional] in uint32_t priority); + + /** + * Similar to dispatchToMainThread, but wraps the event with extra + * runnable that allocates nsAutoMicroTask. + */ + [optional_argc] + void dispatchToMainThreadWithMicroTask(in nsIRunnable event, [optional] in uint32_t priority); + + /** + * This queues a runnable to the main thread's idle queue. + * + * @param event + * The event to dispatch. + * @param timeout + * The time in milliseconds until this event should be moved from the idle + * queue to the regular queue if it hasn't been executed by then. If not + * passed or a zero value is specified, the event will never be moved to + * the regular queue. + */ + void idleDispatchToMainThread(in nsIRunnable event, + [optional] in uint32_t timeout); + + /* + * A helper method to dispatch a task through nsIDirectTaskDispatcher to the + * current thread. + */ + void dispatchDirectTaskToCurrentThread(in nsIRunnable event); + + /** + * Enter a nested event loop on the current thread, waiting on, and + * processing events until condition.isDone() returns true. + * + * If condition.isDone() throws, this function will throw as well. + * + * C++ code should not use this function, instead preferring + * mozilla::SpinEventLoopUntil. + */ + void spinEventLoopUntil(in ACString aVeryGoodReasonToDoThis, in nsINestedEventLoopCondition condition); + + /** + * Similar to the previous method, but the spinning of the event loop + * terminates when the quit application shutting down starts. + * + * C++ code should not use this function, instead preferring + * mozilla::SpinEventLoopUntil. + */ + void spinEventLoopUntilOrQuit(in ACString aVeryGoodReasonToDoThis, in nsINestedEventLoopCondition condition); + + /** + * Spin the current thread's event loop until there are no more pending + * events. This could be done with spinEventLoopUntil, but that would + * require access to the current thread from JavaScript, which we are + * moving away from. + */ + void spinEventLoopUntilEmpty(); + + /** + * Return the EventTarget for the main thread. + */ + readonly attribute nsIEventTarget mainThreadEventTarget; +}; diff --git a/xpcom/threads/nsIThreadPool.idl b/xpcom/threads/nsIThreadPool.idl new file mode 100644 index 0000000000..65162779fa --- /dev/null +++ b/xpcom/threads/nsIThreadPool.idl @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsIEventTarget.idl" +#include "nsIThread.idl" + +[uuid(ef194cab-3f86-4b61-b132-e5e96a79e5d1)] +interface nsIThreadPoolListener : nsISupports +{ + /** + * Called when a new thread is created by the thread pool. The notification + * happens on the newly-created thread. + */ + void onThreadCreated(); + + /** + * Called when a thread is about to be destroyed by the thread pool. The + * notification happens on the thread that is about to be destroyed. + */ + void onThreadShuttingDown(); +}; + +/** + * An interface to a thread pool. A thread pool creates a limited number of + * anonymous (unnamed) worker threads. An event dispatched to the thread pool + * will be run on the next available worker thread. + */ +[uuid(76ce99c9-8e43-489a-9789-f27cc4424965)] +interface nsIThreadPool : nsIEventTarget +{ + /** + * Set the entire pool's QoS priority. If the priority has not changed, do nothing. + * Existing threads will update their QoS priority the next time they become + * active, and newly created threads will set this QoS priority upon creation. + */ + [noscript] void setQoSForThreads(in nsIThread_QoSPriority aPriority); + + /** + * Shutdown the thread pool. This method may not be executed from any thread + * in the thread pool. Instead, it is meant to be executed from another + * thread (usually the thread that created this thread pool). When this + * function returns, the thread pool and all of its threads will be shutdown, + * and it will no longer be possible to dispatch tasks to the thread pool. + * + * As a side effect, events on the current thread will be processed. + */ + void shutdown(); + + /** + * Shutdown the thread pool, but only wait for aTimeoutMs. After the timeout + * expires, any threads that have not shutdown yet are leaked and will not + * block shutdown. + * + * This method should only be used at during shutdown to cleanup threads that + * made blocking calls to code outside our control, and can't be safely + * terminated. We choose to leak them intentionally to avoid a shutdown hang. + */ + [noscript] void shutdownWithTimeout(in long aTimeoutMs); + + /** + * Get/set the maximum number of threads allowed at one time in this pool. + */ + attribute unsigned long threadLimit; + + /** + * Get/set the maximum number of idle threads kept alive. + */ + attribute unsigned long idleThreadLimit; + + /** + * Get/set the amount of time in milliseconds before an idle thread is + * destroyed. + */ + attribute unsigned long idleThreadTimeout; + + /** + * If set to true the idle timeout will be calculated as idleThreadTimeout + * divideded by the number of idle threads at the moment. This may help + * save memory allocations but still keep reasonable amount of idle threads. + * Default is false, use |idleThreadTimeout| for all threads. + */ + attribute boolean idleThreadTimeoutRegressive; + + /** + * Get/set the number of bytes reserved for the stack of all threads in + * the pool. By default this is nsIThreadManager::DEFAULT_STACK_SIZE. + */ + attribute unsigned long threadStackSize; + + /** + * An optional listener that will be notified when a thread is created or + * destroyed in the course of the thread pool's operation. + * + * A listener will only receive notifications about threads created after the + * listener is set so it is recommended that the consumer set the listener + * before dispatching the first event. A listener that receives an + * onThreadCreated() notification is guaranteed to always receive the + * corresponding onThreadShuttingDown() notification. + * + * The thread pool takes ownership of the listener and releases it when the + * shutdown() method is called. Threads created after the listener is set will + * also take ownership of the listener so that the listener will be kept alive + * long enough to receive the guaranteed onThreadShuttingDown() notification. + */ + attribute nsIThreadPoolListener listener; + + /** + * Set the label for threads in the pool. All threads will be named + * " #", where is a serial number. + */ + void setName(in ACString aName); +}; diff --git a/xpcom/threads/nsIThreadShutdown.idl b/xpcom/threads/nsIThreadShutdown.idl new file mode 100644 index 0000000000..a08d64165b --- /dev/null +++ b/xpcom/threads/nsIThreadShutdown.idl @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 "nsISupports.idl" + +interface nsIRunnable; + +/** + * Handle for the ongoing shutdown progress of a given thread which can be used + * to observe and interrupt async shutdown progress. Methods on this interface + * may generally only be used on the thread which called + * `nsIThread::beginShutdown`. + */ +[scriptable, builtinclass, uuid(70a43748-6130-4ea6-a440-7c74e1b7dd7c)] +interface nsIThreadShutdown : nsISupports +{ + /** + * Register a runnable to be executed when the thread has completed shutdown, + * or shutdown has been cancelled due to `stopWaitingAndLeakThread()`. + * + * If the thread has already completed or cancelled shutdown, the runnable + * may be executed synchronously. + * + * May only be called on the thread which invoked `nsIThread::beginShutdown`. + */ + void onCompletion(in nsIRunnable aEvent); + + /** + * Check if the target thread has completed shutdown. + * + * May only be accessed on the thread which called `nsIThread::beginShutdown`. + */ + [infallible] readonly attribute boolean completed; + + /** + * Give up on waiting for the shutting down thread to exit. Calling this + * method will allow the thread to continue running, no longer block shutdown, + * and the thread will never be joined or have its resources reclaimed. + * + * Completion callbacks attached to this `nsIThreadShutdown` may be executed + * during this call. + * + * This method should NOT be called except in exceptional circumstances during + * shutdown, as it will cause resources for the shutting down thread to be + * leaked. + * + * May only be called on the thread which called `nsIThread::beginShutdown` + * + * @throws NS_ERROR_NOT_AVAILABLE + * Indicates that the target thread has already stopped running and a + * request to be joined is already being dispatched to the waiting thread. + */ + void stopWaitingAndLeakThread(); +}; diff --git a/xpcom/threads/nsITimer.idl b/xpcom/threads/nsITimer.idl new file mode 100644 index 0000000000..5d20c315b4 --- /dev/null +++ b/xpcom/threads/nsITimer.idl @@ -0,0 +1,376 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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 "nsISupports.idl" +#include "nsINamed.idl" + +interface nsIObserver; +interface nsIEventTarget; + +%{C++ +#include "mozilla/MemoryReporting.h" +#include "mozilla/TimeStamp.h" +#include + +/** + * The signature of the timer callback function passed to initWithFuncCallback. + * This is the function that will get called when the timer expires if the + * timer is initialized via initWithFuncCallback. + * + * @param aTimer the timer which has expired + * @param aClosure opaque parameter passed to initWithFuncCallback + */ +class nsITimer; +typedef void (*nsTimerCallbackFunc) (nsITimer *aTimer, void *aClosure); +%} + +native MallocSizeOf(mozilla::MallocSizeOf); +native nsTimerCallbackFunc(nsTimerCallbackFunc); +[ref] native TimeDuration(mozilla::TimeDuration); + +/** + * The callback interface for timers. + */ +interface nsITimer; + +[function, scriptable, uuid(a796816d-7d47-4348-9ab8-c7aeb3216a7d)] +interface nsITimerCallback : nsISupports +{ + /** + * @param aTimer the timer which has expired + */ + void notify(in nsITimer timer); +}; + +%{C++ +// Two timer deadlines must differ by less than half the PRIntervalTime domain. +#define DELAY_INTERVAL_LIMIT PR_BIT(8 * sizeof(PRIntervalTime) - 1) +%} + +/** + * nsITimer instances must be initialized by calling one of the "init" methods + * documented below. You may also re-initialize (using one of the init() + * methods) an existing instance to avoid the overhead of destroying and + * creating a timer. It is not necessary to cancel the timer in that case. + * + * By default a timer will fire on the thread that created it. Set the .target + * attribute to fire on a different thread. Once you have set a timer's .target + * and called one of its init functions, any further interactions with the timer + * (calling cancel(), changing member fields, etc) should only be done by the + * target thread, or races may occur with bad results like timers firing after + * they've been canceled, and/or not firing after re-initiatization. + */ +[scriptable, builtinclass, uuid(3de4b105-363c-482c-a409-baac83a01bfc)] +interface nsITimer : nsISupports +{ + /* Timer types */ + + /** + * Type of a timer that fires once only. + */ + const short TYPE_ONE_SHOT = 0; + + /** + * After firing, a TYPE_REPEATING_SLACK timer is stopped and not restarted + * until its callback completes. Specified timer period will be at least + * the time between when processing for last firing the callback completes + * and when the next firing occurs. + * + * This is the preferable repeating type for most situations. + */ + const short TYPE_REPEATING_SLACK = 1; + + /** + * TYPE_REPEATING_PRECISE is just a synonym for + * TYPE_REPEATING_PRECISE_CAN_SKIP. They used to be distinct, but the old + * TYPE_REPEATING_PRECISE kind was similar to TYPE_REPEATING_PRECISE_CAN_SKIP + * while also being less useful. So the distinction was removed. + */ + const short TYPE_REPEATING_PRECISE = 2; + + /** + * A TYPE_REPEATING_PRECISE_CAN_SKIP repeating timer aims to have constant + * period between firings. The processing time for each timer callback will + * not influence the timer period. If the callback finishes after the next + * firing(s) should have happened (either because the callback took a long + * time, or the callback was called extremely late), that firing(s) is + * skipped, but the following sequence of firing times will not be altered. + * This timer type guarantees that it will not queue up new events to fire + * the callback until the previous callback event finishes firing. This is + * the only non-slack timer available. + */ + const short TYPE_REPEATING_PRECISE_CAN_SKIP = 3; + + /** + * Same as TYPE_REPEATING_SLACK with the exception that idle events + * won't yield to timers with this type. Use this when you want an + * idle callback to be scheduled to run even though this timer is + * about to fire. + */ + const short TYPE_REPEATING_SLACK_LOW_PRIORITY = 4; + + /** + * Same as TYPE_ONE_SHOT with the exception that idle events won't + * yield to timers with this type. Use this when you want an idle + * callback to be scheduled to run even though this timer is about + * to fire. + */ + const short TYPE_ONE_SHOT_LOW_PRIORITY = 5; + + /** + * Initialize a timer that will fire after the said delay. + * A user must keep a reference to this timer till it is + * is no longer needed or has been cancelled. + * + * @param aObserver the callback object that observes the + * ``timer-callback'' topic with the subject being + * the timer itself when the timer fires: + * + * observe(nsISupports aSubject, => nsITimer + * string aTopic, => ``timer-callback'' + * wstring data => null + * + * @param aDelayInMs delay in milliseconds for timer to fire + * @param aType timer type per TYPE* consts defined above + */ + void init(in nsIObserver aObserver, in unsigned long aDelayInMs, + in unsigned long aType); + + + /** + * Initialize a timer to fire after the given millisecond interval. + * This version takes a callback object. + * + * @param aFunc nsITimerCallback interface to call when timer expires + * @param aDelayInMs The millisecond interval + * @param aType Timer type per TYPE* consts defined above + */ + void initWithCallback(in nsITimerCallback aCallback, + in unsigned long aDelayInMs, + in unsigned long aType); + + /** + * Initialize a timer to fire after the high resolution TimeDuration. + * This version takes a callback object. + * + * @param aFunc nsITimerCallback interface to call when timer expires + * @param aDelay The high resolution interval + * @param aType Timer type per TYPE* consts defined above + */ + [noscript] void initHighResolutionWithCallback(in nsITimerCallback aCallback, + [const] in TimeDuration aDelay, + in unsigned long aType); + + /** + * Cancel the timer. This method works on all types, not just on repeating + * timers -- you might want to cancel a TYPE_ONE_SHOT timer, and even reuse + * it by re-initializing it (to avoid object destruction and creation costs + * by conserving one timer instance). + */ + void cancel(); + + /** + * Like initWithFuncCallback, but also takes a name for the timer; the name + * will be used when timer profiling is enabled via the "TimerFirings" log + * module. + * + * @param aFunc The function to invoke + * @param aClosure An opaque pointer to pass to that function + * @param aDelay The millisecond interval + * @param aType Timer type per TYPE* consts defined above + * @param aName The timer's name + */ + [noscript] void initWithNamedFuncCallback(in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + in unsigned long aDelay, + in unsigned long aType, + in string aName); + + /** + * Initialize a timer to fire after the high resolution TimeDuration. + * This version takes a named function callback. + * + * @param aFunc The function to invoke + * @param aClosure An opaque pointer to pass to that function + * @param aDelay The high resolution interval + * @param aType Timer type per TYPE* consts defined above + * @param aName The timer's name + */ + [noscript] void initHighResolutionWithNamedFuncCallback( + in nsTimerCallbackFunc aCallback, + in voidPtr aClosure, + [const] in TimeDuration aDelay, + in unsigned long aType, + in string aName); + + /** + * The millisecond delay of the timeout. + * + * NOTE: Re-setting the delay on a one-shot timer that has already fired + * doesn't restart the timer. Call one of the init() methods to restart + * a one-shot timer. + */ + attribute unsigned long delay; + + /** + * The timer type - one of the above TYPE_* constants. + */ + attribute unsigned long type; + + /** + * The opaque pointer pass to initWithFuncCallback. + */ + [noscript] readonly attribute voidPtr closure; + + /** + * The nsITimerCallback object passed to initWithCallback. + */ + readonly attribute nsITimerCallback callback; + + /** + * The nsIEventTarget where the callback will be dispatched. Note that this + * target may only be set before the call to one of the init methods above. + * + * By default the target is the thread that created the timer. + */ + attribute nsIEventTarget target; + + readonly attribute ACString name; + + /** + * The number of microseconds this nsITimer implementation can possibly + * fire early. + */ + [noscript] readonly attribute unsigned long allowedEarlyFiringMicroseconds; + + [notxpcom, nostdcall] size_t sizeOfIncludingThis(in MallocSizeOf aMallocSizeOf); +}; + +%{C++ +#include "nsCOMPtr.h" + +already_AddRefed NS_NewTimer(); + +already_AddRefed NS_NewTimer(nsIEventTarget* aTarget); + +nsresult +NS_NewTimerWithObserver(nsITimer** aTimer, + nsIObserver* aObserver, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); +mozilla::Result, nsresult> +NS_NewTimerWithObserver(nsIObserver* aObserver, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + nsITimerCallback* aCallback, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); +mozilla::Result, nsresult> +NS_NewTimerWithCallback(nsITimerCallback* aCallback, + uint32_t aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + nsITimerCallback* aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); +mozilla::Result, nsresult> +NS_NewTimerWithCallback(nsITimerCallback* aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + std::function&& aCallback, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); +mozilla::Result, nsresult> +NS_NewTimerWithCallback(std::function&& aCallback, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithCallback(nsITimer** aTimer, + std::function&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); +mozilla::Result, nsresult> +NS_NewTimerWithCallback(std::function&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); +mozilla::Result, nsresult> +NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback, + void* aClosure, + uint32_t aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); + +nsresult +NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); +mozilla::Result, nsresult> +NS_NewTimerWithFuncCallback(nsTimerCallbackFunc aCallback, + void* aClosure, + const mozilla::TimeDuration& aDelay, + uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget = nullptr); + +#define NS_TIMER_CALLBACK_TOPIC "timer-callback" + +#ifndef RELEASE_OR_BETA +#undef NS_DECL_NSITIMERCALLBACK +#define NS_DECL_NSITIMERCALLBACK \ + NS_IMETHOD Notify(nsITimer *timer) override; \ + inline void _ensure_GetName_exists(void) { \ + static_assert(std::is_convertible::value, \ + "nsITimerCallback implementations must also implement nsINamed"); \ + } +#endif +%} + +[scriptable, builtinclass, uuid(5482506d-1d21-4d08-b01c-95c87e1295ad)] +interface nsITimerManager : nsISupports +{ + /** + * Returns a read-only list of nsITimer objects, implementing only the name, + * delay and type attribute getters. + * This is meant to be used for tests, to verify that no timer is leftover + * at the end of a test. */ + Array getTimers(); +}; diff --git a/xpcom/threads/nsMemoryPressure.cpp b/xpcom/threads/nsMemoryPressure.cpp new file mode 100644 index 0000000000..dbd3a92f79 --- /dev/null +++ b/xpcom/threads/nsMemoryPressure.cpp @@ -0,0 +1,104 @@ +/* -*- 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 "nsMemoryPressure.h" +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Services.h" + +#include "nsThreadUtils.h" +#include "nsIObserverService.h" + +using namespace mozilla; + +const char* const kTopicMemoryPressure = "memory-pressure"; +const char* const kTopicMemoryPressureStop = "memory-pressure-stop"; +const char16_t* const kSubTopicLowMemoryNew = u"low-memory"; +const char16_t* const kSubTopicLowMemoryOngoing = u"low-memory-ongoing"; + +// This is accessed from any thread through NS_NotifyOfEventualMemoryPressure +static Atomic sMemoryPressurePending( + MemoryPressureState::NoPressure); + +void NS_NotifyOfEventualMemoryPressure(MemoryPressureState aState) { + MOZ_ASSERT(aState != MemoryPressureState::None); + + /* + * A new memory pressure event erases an ongoing (or stop of) memory pressure, + * but an existing "new" memory pressure event takes precedence over a new + * "ongoing" or "stop" memory pressure event. + */ + switch (aState) { + case MemoryPressureState::None: + case MemoryPressureState::LowMemory: + sMemoryPressurePending = aState; + break; + case MemoryPressureState::NoPressure: + sMemoryPressurePending.compareExchange(MemoryPressureState::None, aState); + break; + } +} + +nsresult NS_NotifyOfMemoryPressure(MemoryPressureState aState) { + NS_NotifyOfEventualMemoryPressure(aState); + nsCOMPtr event = + new Runnable("NS_DispatchEventualMemoryPressure"); + return NS_DispatchToMainThread(event); +} + +void NS_DispatchMemoryPressure() { + MOZ_ASSERT(NS_IsMainThread()); + static MemoryPressureState sMemoryPressureStatus = + MemoryPressureState::NoPressure; + + MemoryPressureState mpPending = + sMemoryPressurePending.exchange(MemoryPressureState::None); + if (mpPending == MemoryPressureState::None) { + return; + } + + nsCOMPtr os = services::GetObserverService(); + if (!os) { + NS_WARNING("Can't get observer service!"); + return; + } + + switch (mpPending) { + case MemoryPressureState::None: + MOZ_ASSERT_UNREACHABLE("Already handled this case above."); + break; + case MemoryPressureState::LowMemory: + switch (sMemoryPressureStatus) { + case MemoryPressureState::None: + MOZ_ASSERT_UNREACHABLE("The internal status should never be None."); + break; + case MemoryPressureState::NoPressure: + sMemoryPressureStatus = MemoryPressureState::LowMemory; + os->NotifyObservers(nullptr, kTopicMemoryPressure, + kSubTopicLowMemoryNew); + break; + case MemoryPressureState::LowMemory: + os->NotifyObservers(nullptr, kTopicMemoryPressure, + kSubTopicLowMemoryOngoing); + break; + } + break; + case MemoryPressureState::NoPressure: + switch (sMemoryPressureStatus) { + case MemoryPressureState::None: + MOZ_ASSERT_UNREACHABLE("The internal status should never be None."); + break; + case MemoryPressureState::NoPressure: + // Already no pressure. Do nothing. + break; + case MemoryPressureState::LowMemory: + sMemoryPressureStatus = MemoryPressureState::NoPressure; + os->NotifyObservers(nullptr, kTopicMemoryPressureStop, nullptr); + break; + } + break; + } +} diff --git a/xpcom/threads/nsMemoryPressure.h b/xpcom/threads/nsMemoryPressure.h new file mode 100644 index 0000000000..5a68b0bce5 --- /dev/null +++ b/xpcom/threads/nsMemoryPressure.h @@ -0,0 +1,77 @@ +/* -*- 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/. */ + +#ifndef nsMemoryPressure_h__ +#define nsMemoryPressure_h__ + +#include "nscore.h" + +/* + * These pre-defined strings are the topic to pass to the observer + * service to declare the memory-pressure or lift the memory-pressure. + * + * 1. Notify kTopicMemoryPressure with kSubTopicLowMemoryNew + * New memory pressure deteced + * On a new memory pressure, we stop everything to start cleaning + * aggresively the memory used, in order to free as much memory as + * possible. + * + * 2. Notify kTopicMemoryPressure with kSubTopicLowMemoryOngoing + * Repeated memory pressure. + * A repeated memory pressure implies to clean softly recent allocations. + * It is supposed to happen after a new memory pressure which already + * cleaned aggressivley. So there is no need to damage the reactivity of + * Gecko by stopping the world again. + * + * In case of conflict with an new memory pressue, the new memory pressure + * takes precedence over an ongoing memory pressure. The reason being + * that if no events are processed between 2 notifications (new followed + * by ongoing, or ongoing followed by a new) we want to be as aggresive as + * possible on the clean-up of the memory. After all, we are trying to + * keep Gecko alive as long as possible. + * + * 3. Notify kTopicMemoryPressureStop with nullptr + * Memory pressure stopped. + * We're no longer under acute memory pressure, so we might want to have a + * chance of (cautiously) re-enabling some things we previously turned off. + * As above, an already enqueued new memory pressure event takes precedence. + * The priority ordering between concurrent attempts to queue both stopped + * and ongoing memory pressure is currently not defined. + */ +extern const char* const kTopicMemoryPressure; +extern const char* const kTopicMemoryPressureStop; +extern const char16_t* const kSubTopicLowMemoryNew; +extern const char16_t* const kSubTopicLowMemoryOngoing; + +enum class MemoryPressureState : uint32_t { + None, // For internal use. Don't use this value. + LowMemory, + NoPressure, +}; + +/** + * This function causes the main thread to fire a memory pressure event + * before processing the next event, but if there are no events pending in + * the main thread's event queue, the memory pressure event would not be + * dispatched until one is enqueued. It is infallible and does not allocate + * any memory. + * + * You may call this function from any thread. + */ +void NS_NotifyOfEventualMemoryPressure(MemoryPressureState aState); + +/** + * This function causes the main thread to fire a memory pressure event + * before processing the next event. We wake up the main thread by adding a + * dummy event to its event loop, so, unlike with + * NS_NotifyOfEventualMemoryPressure, this memory-pressure event is always + * fired relatively quickly, even if the event loop is otherwise empty. + * + * You may call this function from any thread. + */ +nsresult NS_NotifyOfMemoryPressure(MemoryPressureState aState); + +#endif // nsMemoryPressure_h__ diff --git a/xpcom/threads/nsProcess.h b/xpcom/threads/nsProcess.h new file mode 100644 index 0000000000..95d6748640 --- /dev/null +++ b/xpcom/threads/nsProcess.h @@ -0,0 +1,82 @@ +/* -*- 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/. */ + +#ifndef _nsPROCESSWIN_H_ +#define _nsPROCESSWIN_H_ + +#if defined(XP_WIN) +# define PROCESSMODEL_WINAPI +#endif + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "nsIProcess.h" +#include "nsIObserver.h" +#include "nsMaybeWeakPtr.h" +#include "nsString.h" +#ifndef XP_UNIX +# include "prproces.h" +#endif +#if defined(PROCESSMODEL_WINAPI) +# include +# include +#endif + +#define NS_PROCESS_CID \ + { \ + 0x7b4eeb20, 0xd781, 0x11d4, { \ + 0x8A, 0x83, 0x00, 0x10, 0xa4, 0xe0, 0xc9, 0xca \ + } \ + } + +class nsIFile; + +class nsProcess final : public nsIProcess, public nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROCESS + NS_DECL_NSIOBSERVER + + nsProcess(); + + private: + ~nsProcess(); + PRThread* CreateMonitorThread(); + static void Monitor(void* aArg); + void ProcessComplete(); + nsresult CopyArgsAndRunProcess(bool aBlocking, const char** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak); + nsresult CopyArgsAndRunProcessw(bool aBlocking, const char16_t** aArgs, + uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak); + // The 'args' array is null-terminated. + nsresult RunProcess(bool aBlocking, char** aArgs, nsIObserver* aObserver, + bool aHoldWeak, bool aArgsUTF8); + + PRThread* mThread; + mozilla::Mutex mLock; + bool mShutdown MOZ_GUARDED_BY(mLock); + bool mBlocking; + bool mStartHidden; + bool mNoShell; + + nsCOMPtr mExecutable; + nsString mTargetPath; + int32_t mPid; + nsMaybeWeakPtr mObserver; + + // These members are modified by multiple threads, any accesses should be + // protected with mLock. + int32_t mExitValue MOZ_GUARDED_BY(mLock); +#if defined(PROCESSMODEL_WINAPI) + HANDLE mProcess MOZ_GUARDED_BY(mLock); +#elif !defined(XP_UNIX) + PRProcess* mProcess MOZ_GUARDED_BY(mLock); +#endif +}; + +#endif diff --git a/xpcom/threads/nsProcessCommon.cpp b/xpcom/threads/nsProcessCommon.cpp new file mode 100644 index 0000000000..0a88488e5f --- /dev/null +++ b/xpcom/threads/nsProcessCommon.cpp @@ -0,0 +1,600 @@ +/* -*- 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/. */ + +/***************************************************************************** + * + * nsProcess is used to execute new processes and specify if you want to + * wait (blocking) or continue (non-blocking). + * + ***************************************************************************** + */ + +#include "mozilla/ArrayUtils.h" + +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsProcess.h" +#include "prio.h" +#include "prenv.h" +#include "nsCRT.h" +#include "nsThreadUtils.h" +#include "nsIObserverService.h" +#include "nsXULAppAPI.h" +#include "mozilla/Services.h" + +#include + +#if defined(PROCESSMODEL_WINAPI) +# include "nsString.h" +# include "nsLiteralString.h" +# include "nsReadableUtils.h" +# include "mozilla/AssembleCmdLine.h" +# include "mozilla/UniquePtrExtensions.h" +#else +# ifdef XP_MACOSX +# include +# include +# endif +# ifdef XP_UNIX +# ifndef XP_MACOSX +# include "base/process_util.h" +# endif +# include +# include +# endif +# include +# include +#endif + +using namespace mozilla; + +//-------------------------------------------------------------------// +// nsIProcess implementation +//-------------------------------------------------------------------// +NS_IMPL_ISUPPORTS(nsProcess, nsIProcess, nsIObserver) + +// Constructor +nsProcess::nsProcess() + : mThread(nullptr), + mLock("nsProcess.mLock"), + mShutdown(false), + mBlocking(false), + mStartHidden(false), + mNoShell(false), + mPid(-1), + mExitValue(-1) +#if !defined(XP_UNIX) + , + mProcess(nullptr) +#endif +{ +} + +// Destructor +nsProcess::~nsProcess() = default; + +NS_IMETHODIMP +nsProcess::Init(nsIFile* aExecutable) { + if (mExecutable) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (NS_WARN_IF(!aExecutable)) { + return NS_ERROR_INVALID_ARG; + } + bool isFile; + + // First make sure the file exists + nsresult rv = aExecutable->IsFile(&isFile); + if (NS_FAILED(rv)) { + return rv; + } + if (!isFile) { + return NS_ERROR_FAILURE; + } + + // Store the nsIFile in mExecutable + mExecutable = aExecutable; + // Get the path because it is needed by the NSPR process creation +#ifdef XP_WIN + rv = mExecutable->GetTarget(mTargetPath); + if (NS_FAILED(rv) || mTargetPath.IsEmpty()) +#endif + rv = mExecutable->GetPath(mTargetPath); + + return rv; +} + +void nsProcess::Monitor(void* aArg) { + RefPtr process = dont_AddRef(static_cast(aArg)); + + if (!process->mBlocking) { + NS_SetCurrentThreadName("RunProcess"); + } + +#if defined(PROCESSMODEL_WINAPI) + HANDLE processHandle; + { + // The mutex region cannot include WaitForSingleObject otherwise we'll + // block calls such as Kill. So lock on access and store a local. + MutexAutoLock lock(process->mLock); + processHandle = process->mProcess; + } + + DWORD dwRetVal; + unsigned long exitCode = -1; + + dwRetVal = WaitForSingleObject(processHandle, INFINITE); + if (dwRetVal != WAIT_FAILED) { + if (GetExitCodeProcess(processHandle, &exitCode) == FALSE) { + exitCode = -1; + } + } + + // Lock in case Kill or GetExitCode are called during this. + { + MutexAutoLock lock(process->mLock); + CloseHandle(process->mProcess); + process->mProcess = nullptr; + process->mExitValue = exitCode; + if (process->mShutdown) { + return; + } + } +#else +# ifdef XP_UNIX + int exitCode = -1; + int status = 0; + pid_t result; + do { + result = waitpid(process->mPid, &status, 0); + } while (result == -1 && errno == EINTR); + if (result == process->mPid) { + if (WIFEXITED(status)) { + exitCode = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + exitCode = 256; // match NSPR's signal exit status + } + } +# else + int32_t exitCode = -1; + PRProcess* prProcess; + { + // The mutex region cannot include PR_WaitProcess otherwise we'll + // block calls such as Kill. So lock on access and store a local. + MutexAutoLock lock(process->mLock); + prProcess = process->mProcess; + } + if (PR_WaitProcess(prProcess, &exitCode) != PR_SUCCESS) { + exitCode = -1; + } +# endif + + // Lock in case Kill or GetExitCode are called during this + { + MutexAutoLock lock(process->mLock); +# if !defined(XP_UNIX) + process->mProcess = nullptr; +# endif + process->mExitValue = exitCode; + if (process->mShutdown) { + return; + } + } +#endif + + // If we ran a background thread for the monitor then notify on the main + // thread + if (NS_IsMainThread()) { + process->ProcessComplete(); + } else { + NS_DispatchToMainThread(NewRunnableMethod( + "nsProcess::ProcessComplete", process, &nsProcess::ProcessComplete)); + } +} + +void nsProcess::ProcessComplete() { + if (mThread) { + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + PR_JoinThread(mThread); + mThread = nullptr; + } + + const char* topic; + { + MutexAutoLock lock(mLock); + if (mExitValue != 0) { + topic = "process-failed"; + } else { + topic = "process-finished"; + } + } + + mPid = -1; + nsCOMPtr observer = mObserver.GetValue(); + mObserver = nullptr; + + if (observer) { + observer->Observe(NS_ISUPPORTS_CAST(nsIProcess*, this), topic, nullptr); + } +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::Run(bool aBlocking, const char** aArgs, uint32_t aCount) { + return CopyArgsAndRunProcess(aBlocking, aArgs, aCount, nullptr, false); +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::RunAsync(const char** aArgs, uint32_t aCount, nsIObserver* aObserver, + bool aHoldWeak) { + return CopyArgsAndRunProcess(false, aArgs, aCount, aObserver, aHoldWeak); +} + +nsresult nsProcess::CopyArgsAndRunProcess(bool aBlocking, const char** aArgs, + uint32_t aCount, + nsIObserver* aObserver, + bool aHoldWeak) { + // Add one to the aCount for the program name and one for null termination. + char** my_argv = nullptr; + my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2)); + + my_argv[0] = ToNewUTF8String(mTargetPath); + + for (uint32_t i = 0; i < aCount; ++i) { + my_argv[i + 1] = const_cast(aArgs[i]); + } + + my_argv[aCount + 1] = nullptr; + + nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, false); + + free(my_argv[0]); + free(my_argv); + return rv; +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::Runw(bool aBlocking, const char16_t** aArgs, uint32_t aCount) { + return CopyArgsAndRunProcessw(aBlocking, aArgs, aCount, nullptr, false); +} + +// XXXldb |aArgs| has the wrong const-ness +NS_IMETHODIMP +nsProcess::RunwAsync(const char16_t** aArgs, uint32_t aCount, + nsIObserver* aObserver, bool aHoldWeak) { + return CopyArgsAndRunProcessw(false, aArgs, aCount, aObserver, aHoldWeak); +} + +nsresult nsProcess::CopyArgsAndRunProcessw(bool aBlocking, + const char16_t** aArgs, + uint32_t aCount, + nsIObserver* aObserver, + bool aHoldWeak) { + // Add one to the aCount for the program name and one for null termination. + char** my_argv = nullptr; + my_argv = (char**)moz_xmalloc(sizeof(char*) * (aCount + 2)); + + my_argv[0] = ToNewUTF8String(mTargetPath); + + for (uint32_t i = 0; i < aCount; i++) { + my_argv[i + 1] = ToNewUTF8String(nsDependentString(aArgs[i])); + } + + my_argv[aCount + 1] = nullptr; + + nsresult rv = RunProcess(aBlocking, my_argv, aObserver, aHoldWeak, true); + + for (uint32_t i = 0; i <= aCount; ++i) { + free(my_argv[i]); + } + free(my_argv); + return rv; +} + +nsresult nsProcess::RunProcess(bool aBlocking, char** aMyArgv, + nsIObserver* aObserver, bool aHoldWeak, + bool aArgsUTF8) { + NS_WARNING_ASSERTION(!XRE_IsContentProcess(), + "No launching of new processes in the content process"); + + if (NS_WARN_IF(!mExecutable)) { + return NS_ERROR_NOT_INITIALIZED; + } + if (NS_WARN_IF(mThread)) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (aObserver) { + if (aHoldWeak) { + nsresult rv = NS_OK; + mObserver = do_GetWeakReference(aObserver, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mObserver = aObserver; + } + } + + { + MutexAutoLock lock(mLock); + mExitValue = -1; + mPid = -1; + } + +#if defined(PROCESSMODEL_WINAPI) + BOOL retVal; + UniqueFreePtr cmdLine; + + // |aMyArgv| is null-terminated and always starts with the program path. If + // the second slot is non-null then arguments are being passed. + if (aMyArgv[1] || mNoShell) { + // Pass the executable path as argv[0] to the launched program when calling + // CreateProcess(). + char** argv = mNoShell ? aMyArgv : aMyArgv + 1; + + wchar_t* assembledCmdLine = nullptr; + if (assembleCmdLine(argv, &assembledCmdLine, + aArgsUTF8 ? CP_UTF8 : CP_ACP) == -1) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + cmdLine.reset(assembledCmdLine); + } + + // The program name in aMyArgv[0] is always UTF-8 + NS_ConvertUTF8toUTF16 wideFile(aMyArgv[0]); + + if (mNoShell) { + STARTUPINFO startupInfo; + ZeroMemory(&startupInfo, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + startupInfo.dwFlags = STARTF_USESHOWWINDOW; + startupInfo.wShowWindow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL; + + PROCESS_INFORMATION processInfo; + retVal = CreateProcess(/* lpApplicationName = */ wideFile.get(), + /* lpCommandLine */ cmdLine.get(), + /* lpProcessAttributes = */ NULL, + /* lpThreadAttributes = */ NULL, + /* bInheritHandles = */ FALSE, + /* dwCreationFlags = */ 0, + /* lpEnvironment = */ NULL, + /* lpCurrentDirectory = */ NULL, + /* lpStartupInfo = */ &startupInfo, + /* lpProcessInformation */ &processInfo); + + if (!retVal) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + CloseHandle(processInfo.hThread); + + // TODO(bug 1763051): assess if we need further work around this locking. + MutexAutoLock lock(mLock); + mProcess = processInfo.hProcess; + } else { + SHELLEXECUTEINFOW sinfo; + memset(&sinfo, 0, sizeof(SHELLEXECUTEINFOW)); + sinfo.cbSize = sizeof(SHELLEXECUTEINFOW); + sinfo.hwnd = nullptr; + sinfo.lpFile = wideFile.get(); + sinfo.nShow = mStartHidden ? SW_HIDE : SW_SHOWNORMAL; + + /* The SEE_MASK_NO_CONSOLE flag is important to prevent console windows + * from appearing. This makes behavior the same on all platforms. The flag + * will not have any effect on non-console applications. + */ + sinfo.fMask = + SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NO_CONSOLE | SEE_MASK_NOCLOSEPROCESS; + + if (cmdLine) { + sinfo.lpParameters = cmdLine.get(); + } + + retVal = ShellExecuteExW(&sinfo); + if (!retVal) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + + MutexAutoLock lock(mLock); + mProcess = sinfo.hProcess; + } + + { + MutexAutoLock lock(mLock); + mPid = GetProcessId(mProcess); + } +#elif defined(XP_MACOSX) + // Note: |aMyArgv| is already null-terminated as required by posix_spawnp. + pid_t newPid = 0; + int result = posix_spawnp(&newPid, aMyArgv[0], nullptr, nullptr, aMyArgv, + *_NSGetEnviron()); + mPid = static_cast(newPid); + + if (result != 0) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_UNIX) + base::LaunchOptions options; + std::vector argvVec; + for (char** arg = aMyArgv; *arg != nullptr; ++arg) { + argvVec.push_back(*arg); + } + pid_t newPid; + if (base::LaunchApp(argvVec, options, &newPid).isOk()) { + static_assert(sizeof(pid_t) <= sizeof(int32_t), + "mPid is large enough to hold a pid"); + mPid = static_cast(newPid); + } else { + return NS_ERROR_FAILURE; + } +#else + { + PRProcess* prProcess = + PR_CreateProcess(aMyArgv[0], aMyArgv, nullptr, nullptr); + if (!prProcess) { + return NS_ERROR_FAILURE; + } + { + MutexAutoLock lock(mLock); + mProcess = prProcess; + } + struct MYProcess { + uint32_t pid; + }; + MYProcess* ptrProc = (MYProcess*)mProcess; + mPid = ptrProc->pid; + } +#endif + + NS_ADDREF_THIS(); + mBlocking = aBlocking; + if (aBlocking) { + Monitor(this); + MutexAutoLock lock(mLock); + if (mExitValue < 0) { + return NS_ERROR_FILE_EXECUTION_FAILED; + } + } else { + mThread = CreateMonitorThread(); + if (!mThread) { + NS_RELEASE_THIS(); + return NS_ERROR_FAILURE; + } + + // It isn't a failure if we just can't watch for shutdown + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + os->AddObserver(this, "xpcom-shutdown", false); + } + } + + return NS_OK; +} + +// We don't guarantee that monitor threads are joined before Gecko exits, which +// can cause TSAN to complain about thread leaks. We handle this with a TSAN +// suppression, and route thread creation through this helper so that the +// suppression is as narrowly-scoped as possible. +PRThread* nsProcess::CreateMonitorThread() { + return PR_CreateThread(PR_SYSTEM_THREAD, Monitor, this, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); +} + +NS_IMETHODIMP +nsProcess::GetIsRunning(bool* aIsRunning) { + if (mThread) { + *aIsRunning = true; + } else { + *aIsRunning = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetStartHidden(bool* aStartHidden) { + *aStartHidden = mStartHidden; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::SetStartHidden(bool aStartHidden) { + mStartHidden = aStartHidden; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetNoShell(bool* aNoShell) { + *aNoShell = mNoShell; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::SetNoShell(bool aNoShell) { + mNoShell = aNoShell; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetPid(uint32_t* aPid) { + if (!mThread) { + return NS_ERROR_FAILURE; + } + if (mPid < 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + *aPid = mPid; + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::Kill() { + if (!mThread) { + return NS_ERROR_FAILURE; + } + + { + MutexAutoLock lock(mLock); +#if defined(PROCESSMODEL_WINAPI) + if (TerminateProcess(mProcess, 0) == 0) { + return NS_ERROR_FAILURE; + } +#elif defined(XP_UNIX) + if (kill(mPid, SIGKILL) != 0) { + return NS_ERROR_FAILURE; + } +#else + if (!mProcess || (PR_KillProcess(mProcess) != PR_SUCCESS)) { + return NS_ERROR_FAILURE; + } +#endif + } + + // We must null out mThread if we want IsRunning to return false immediately + // after this call. + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + PR_JoinThread(mThread); + mThread = nullptr; + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::GetExitValue(int32_t* aExitValue) { + MutexAutoLock lock(mLock); + + *aExitValue = mExitValue; + + return NS_OK; +} + +NS_IMETHODIMP +nsProcess::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + // Shutting down, drop all references + if (mThread) { + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, "xpcom-shutdown"); + } + mThread = nullptr; + } + + mObserver = nullptr; + + MutexAutoLock lock(mLock); + mShutdown = true; + + return NS_OK; +} diff --git a/xpcom/threads/nsProxyRelease.cpp b/xpcom/threads/nsProxyRelease.cpp new file mode 100644 index 0000000000..86ed18c8b5 --- /dev/null +++ b/xpcom/threads/nsProxyRelease.cpp @@ -0,0 +1,30 @@ +/* -*- 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 "nsProxyRelease.h" +#include "nsThreadUtils.h" + +namespace detail { + +/* static */ nsresult ProxyReleaseChooser::ProxyReleaseISupports( + const char* aName, nsIEventTarget* aTarget, nsISupports* aDoomed, + bool aAlwaysProxy) { + return ::detail::ProxyRelease( + aName, aTarget, dont_AddRef(aDoomed), aAlwaysProxy); +} + +} // namespace detail + +extern "C" { + +// This function uses C linkage because it's exposed to Rust to support the +// `ThreadPtrHolder` wrapper in the `moz_task` crate. +void NS_ProxyReleaseISupports(const char* aName, nsIEventTarget* aTarget, + nsISupports* aDoomed, bool aAlwaysProxy) { + NS_ProxyRelease(aName, aTarget, dont_AddRef(aDoomed), aAlwaysProxy); +} + +} // extern "C" diff --git a/xpcom/threads/nsProxyRelease.h b/xpcom/threads/nsProxyRelease.h new file mode 100644 index 0000000000..00f69e3287 --- /dev/null +++ b/xpcom/threads/nsProxyRelease.h @@ -0,0 +1,390 @@ +/* -*- 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/. */ + +#ifndef nsProxyRelease_h__ +#define nsProxyRelease_h__ + +#include + +#include "MainThreadUtils.h" +#include "mozilla/Likely.h" +#include "mozilla/Unused.h" +#include "nsCOMPtr.h" +#include "nsIEventTarget.h" +#include "nsISerialEventTarget.h" +#include "nsIThread.h" +#include "nsPrintfCString.h" +#include "nsThreadUtils.h" + +#ifdef XPCOM_GLUE_AVOID_NSPR +# error NS_ProxyRelease implementation depends on NSPR. +#endif + +class nsIRunnable; + +namespace detail { + +template +class ProxyReleaseEvent : public mozilla::CancelableRunnable { + public: + ProxyReleaseEvent(const char* aName, already_AddRefed aDoomed) + : CancelableRunnable(aName), mDoomed(aDoomed.take()) {} + + NS_IMETHOD Run() override { + NS_IF_RELEASE(mDoomed); + return NS_OK; + } + + nsresult Cancel() override { return Run(); } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + if (mName) { + aName.Append(nsPrintfCString("ProxyReleaseEvent for %s", mName)); + } else { + aName.AssignLiteral("ProxyReleaseEvent"); + } + return NS_OK; + } +#endif + + private: + T* MOZ_OWNING_REF mDoomed; +}; + +template +nsresult ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed aDoomed, bool aAlwaysProxy) { + // Auto-managing release of the pointer. + RefPtr doomed = aDoomed; + nsresult rv; + + if (!doomed || !aTarget) { + return NS_ERROR_INVALID_ARG; + } + + if (!aAlwaysProxy) { + bool onCurrentThread = false; + rv = aTarget->IsOnCurrentThread(&onCurrentThread); + if (NS_SUCCEEDED(rv) && onCurrentThread) { + return NS_OK; + } + } + + nsCOMPtr ev = new ProxyReleaseEvent(aName, doomed.forget()); + + rv = aTarget->Dispatch(ev, NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + NS_WARNING(nsPrintfCString( + "failed to post proxy release event for %s, leaking!", aName) + .get()); + // It is better to leak the aDoomed object than risk crashing as + // a result of deleting it on the wrong thread. + } + return rv; +} + +template +struct ProxyReleaseChooser { + template + static nsresult ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed aDoomed, bool aAlwaysProxy) { + return ::detail::ProxyRelease(aName, aTarget, std::move(aDoomed), + aAlwaysProxy); + } +}; + +template <> +struct ProxyReleaseChooser { + // We need an intermediate step for handling classes with ambiguous + // inheritance to nsISupports. + template + static nsresult ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed aDoomed, bool aAlwaysProxy) { + return ProxyReleaseISupports(aName, aTarget, ToSupports(aDoomed.take()), + aAlwaysProxy); + } + + static nsresult ProxyReleaseISupports(const char* aName, + nsIEventTarget* aTarget, + nsISupports* aDoomed, + bool aAlwaysProxy); +}; + +} // namespace detail + +/** + * Ensures that the delete of a smart pointer occurs on the target thread. + * Note: The doomed object will be leaked if dispatch to the target thread + * fails, as releasing it on the current thread may be unsafe + * + * @param aName + * the labelling name of the runnable involved in the releasing. + * @param aTarget + * the target thread where the doomed object should be released. + * @param aDoomed + * the doomed object; the object to be released on the target thread. + * @param aAlwaysProxy + * normally, if NS_ProxyRelease is called on the target thread, then the + * doomed object will be released directly. However, if this parameter is + * true, then an event will always be posted to the target thread for + * asynchronous release. + * @return result of the task which is dispatched to delete the smart pointer + * on the target thread. + * Note: The caller should not attempt to recover from an + * error code returned by trying to perform the final ->Release() + * manually. + */ +template +inline NS_HIDDEN_(nsresult) + NS_ProxyRelease(const char* aName, nsIEventTarget* aTarget, + already_AddRefed aDoomed, bool aAlwaysProxy = false) { + return ::detail::ProxyReleaseChooser< + std::is_base_of::value>::ProxyRelease(aName, aTarget, + std::move(aDoomed), + aAlwaysProxy); +} + +/** + * Ensures that the delete of a smart pointer occurs on the main thread. + * + * @param aName + * the labelling name of the runnable involved in the releasing + * @param aDoomed + * the doomed object; the object to be released on the main thread. + * @param aAlwaysProxy + * normally, if NS_ReleaseOnMainThread is called on the main + * thread, then the doomed object will be released directly. However, if + * this parameter is true, then an event will always be posted to the + * main thread for asynchronous release. + */ +template +inline NS_HIDDEN_(void) + NS_ReleaseOnMainThread(const char* aName, already_AddRefed aDoomed, + bool aAlwaysProxy = false) { + RefPtr doomed = aDoomed; + if (!doomed) { + return; // Nothing to do. + } + + // NS_ProxyRelease treats a null event target as "the current thread". So a + // handle on the main thread is only necessary when we're not already on the + // main thread or the release must happen asynchronously. + nsCOMPtr target; + if (!NS_IsMainThread() || aAlwaysProxy) { + target = mozilla::GetMainThreadSerialEventTarget(); + + if (!target) { + MOZ_ASSERT_UNREACHABLE("Could not get main thread; leaking an object!"); + mozilla::Unused << doomed.forget().take(); + return; + } + } + + NS_ProxyRelease(aName, target, doomed.forget(), aAlwaysProxy); +} + +/** + * Class to safely handle main-thread-only pointers off the main thread. + * + * Classes like XPCWrappedJS are main-thread-only, which means that it is + * forbidden to call methods on instances of these classes off the main thread. + * For various reasons (see bug 771074), this restriction applies to + * AddRef/Release as well. + * + * This presents a problem for consumers that wish to hold a callback alive + * on non-main-thread code. A common example of this is the proxy callback + * pattern, where non-main-thread code holds a strong-reference to the callback + * object, and dispatches new Runnables (also with a strong reference) to the + * main thread in order to execute the callback. This involves several AddRef + * and Release calls on the other thread, which is verboten. + * + * The basic idea of this class is to introduce a layer of indirection. + * nsMainThreadPtrHolder is a threadsafe reference-counted class that internally + * maintains one strong reference to the main-thread-only object. It must be + * instantiated on the main thread (so that the AddRef of the underlying object + * happens on the main thread), but consumers may subsequently pass references + * to the holder anywhere they please. These references are meant to be opaque + * when accessed off-main-thread (assertions enforce this). + * + * The semantics of RefPtr> would be cumbersome, so we + * also introduce nsMainThreadPtrHandle, which is conceptually identical to + * the above (though it includes various convenience methods). The basic pattern + * is as follows. + * + * // On the main thread: + * nsCOMPtr callback = ...; + * nsMainThreadPtrHandle callbackHandle = + * new nsMainThreadPtrHolder(callback); + * // Pass callbackHandle to structs/classes that might be accessed on other + * // threads. + * + * All structs and classes that might be accessed on other threads should store + * an nsMainThreadPtrHandle rather than an nsCOMPtr. + */ +template +class MOZ_IS_SMARTPTR_TO_REFCOUNTED nsMainThreadPtrHolder final { + public: + // We can only acquire a pointer on the main thread. We want to fail fast for + // threading bugs, so by default we assert if our pointer is used or acquired + // off-main-thread. But some consumers need to use the same pointer for + // multiple classes, some of which are main-thread-only and some of which + // aren't. So we allow them to explicitly disable this strict checking. + nsMainThreadPtrHolder(const char* aName, T* aPtr, bool aStrict = true, + nsIEventTarget* aMainThreadEventTarget = nullptr) + : mRawPtr(aPtr), + mStrict(aStrict), + mMainThreadEventTarget(aMainThreadEventTarget) +#ifndef RELEASE_OR_BETA + , + mName(aName) +#endif + { + // We can only AddRef our pointer on the main thread, which means that the + // holder must be constructed on the main thread. + MOZ_ASSERT(!mStrict || NS_IsMainThread()); + NS_IF_ADDREF(mRawPtr); + } + nsMainThreadPtrHolder(const char* aName, already_AddRefed aPtr, + bool aStrict = true, + nsIEventTarget* aMainThreadEventTarget = nullptr) + : mRawPtr(aPtr.take()), + mStrict(aStrict), + mMainThreadEventTarget(aMainThreadEventTarget) +#ifndef RELEASE_OR_BETA + , + mName(aName) +#endif + { + // Since we don't need to AddRef the pointer, this constructor is safe to + // call on any thread. + } + + // Copy constructor and operator= deleted. Once constructed, the holder is + // immutable. + T& operator=(nsMainThreadPtrHolder& aOther) = delete; + nsMainThreadPtrHolder(const nsMainThreadPtrHolder& aOther) = delete; + + private: + // We can be released on any thread. + ~nsMainThreadPtrHolder() { + if (NS_IsMainThread()) { + NS_IF_RELEASE(mRawPtr); + } else if (mRawPtr) { + if (!mMainThreadEventTarget) { + mMainThreadEventTarget = do_GetMainThread(); + } + MOZ_ASSERT(mMainThreadEventTarget); + NS_ProxyRelease( +#ifdef RELEASE_OR_BETA + nullptr, +#else + mName, +#endif + mMainThreadEventTarget, dont_AddRef(mRawPtr)); + } + } + + public: + T* get() const { + // Nobody should be touching the raw pointer off-main-thread. + if (mStrict && MOZ_UNLIKELY(!NS_IsMainThread())) { + NS_ERROR("Can't dereference nsMainThreadPtrHolder off main thread"); + MOZ_CRASH(); + } + return mRawPtr; + } + + bool operator==(const nsMainThreadPtrHolder& aOther) const { + return mRawPtr == aOther.mRawPtr; + } + bool operator!() const { return !mRawPtr; } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsMainThreadPtrHolder) + + private: + // Our wrapped pointer. + T* mRawPtr = nullptr; + + // Whether to strictly enforce thread invariants in this class. + bool mStrict = true; + + nsCOMPtr mMainThreadEventTarget; + +#ifndef RELEASE_OR_BETA + const char* mName = nullptr; +#endif +}; + +template +class MOZ_IS_SMARTPTR_TO_REFCOUNTED nsMainThreadPtrHandle { + public: + nsMainThreadPtrHandle() : mPtr(nullptr) {} + MOZ_IMPLICIT nsMainThreadPtrHandle(decltype(nullptr)) : mPtr(nullptr) {} + explicit nsMainThreadPtrHandle(nsMainThreadPtrHolder* aHolder) + : mPtr(aHolder) {} + explicit nsMainThreadPtrHandle( + already_AddRefed> aHolder) + : mPtr(aHolder) {} + nsMainThreadPtrHandle(const nsMainThreadPtrHandle& aOther) = default; + nsMainThreadPtrHandle(nsMainThreadPtrHandle&& aOther) = default; + nsMainThreadPtrHandle& operator=(const nsMainThreadPtrHandle& aOther) = + default; + nsMainThreadPtrHandle& operator=(nsMainThreadPtrHandle&& aOther) = default; + nsMainThreadPtrHandle& operator=(nsMainThreadPtrHolder* aHolder) { + mPtr = aHolder; + return *this; + } + + // These all call through to nsMainThreadPtrHolder, and thus implicitly + // assert that we're on the main thread (if strict). Off-main-thread consumers + // must treat these handles as opaque. + T* get() const { + if (mPtr) { + return mPtr.get()->get(); + } + return nullptr; + } + + operator T*() const { return get(); } + T* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN { return get(); } + + // These are safe to call on other threads with appropriate external locking. + bool operator==(const nsMainThreadPtrHandle& aOther) const { + if (!mPtr || !aOther.mPtr) { + return mPtr == aOther.mPtr; + } + return *mPtr == *aOther.mPtr; + } + bool operator!=(const nsMainThreadPtrHandle& aOther) const { + return !operator==(aOther); + } + bool operator==(decltype(nullptr)) const { return mPtr == nullptr; } + bool operator!=(decltype(nullptr)) const { return mPtr != nullptr; } + bool operator!() const { return !mPtr || !*mPtr; } + + private: + RefPtr> mPtr; +}; + +class nsCycleCollectionTraversalCallback; +template +void CycleCollectionNoteChild(nsCycleCollectionTraversalCallback& aCallback, + T* aChild, const char* aName, uint32_t aFlags); + +template +inline void ImplCycleCollectionTraverse( + nsCycleCollectionTraversalCallback& aCallback, + nsMainThreadPtrHandle& aField, const char* aName, uint32_t aFlags = 0) { + CycleCollectionNoteChild(aCallback, aField.get(), aName, aFlags); +} + +template +inline void ImplCycleCollectionUnlink(nsMainThreadPtrHandle& aField) { + aField = nullptr; +} + +#endif diff --git a/xpcom/threads/nsThread.cpp b/xpcom/threads/nsThread.cpp new file mode 100644 index 0000000000..772f6b7738 --- /dev/null +++ b/xpcom/threads/nsThread.cpp @@ -0,0 +1,1609 @@ +/* -*- 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 "nsThread.h" + +#include "base/message_loop.h" +#include "base/platform_thread.h" + +// Chromium's logging can sometimes leak through... +#ifdef LOG +# undef LOG +#endif + +#include "mozilla/ReentrantMonitor.h" +#include "nsMemoryPressure.h" +#include "nsThreadManager.h" +#include "nsIClassInfoImpl.h" +#include "nsCOMPtr.h" +#include "nsQueryObject.h" +#include "pratom.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/CycleCollectedJSContext.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Logging.h" +#include "nsIObserverService.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/ipc/MessageChannel.h" +#include "mozilla/ipc/BackgroundChild.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerRunnable.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticLocalPtr.h" +#include "mozilla/StaticPrefs_threads.h" +#include "mozilla/TaskController.h" +#include "nsXPCOMPrivate.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Unused.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/ScriptSettings.h" +#include "nsThreadSyncDispatch.h" +#include "nsServiceManagerUtils.h" +#include "GeckoProfiler.h" +#include "ThreadEventQueue.h" +#include "ThreadEventTarget.h" +#include "ThreadDelay.h" + +#include + +#ifdef XP_LINUX +# ifdef __GLIBC__ +# include +# endif +# include +# include +# include +# include +# include +#endif + +#ifdef XP_WIN +# include "mozilla/DynamicallyLinkedFunctionPtr.h" + +# include + +using GetCurrentThreadStackLimitsFn = void(WINAPI*)(PULONG_PTR LowLimit, + PULONG_PTR HighLimit); +#endif + +#define HAVE_UALARM \ + _BSD_SOURCE || \ + (_XOPEN_SOURCE >= 500 || _XOPEN_SOURCE && _XOPEN_SOURCE_EXTENDED) && \ + !(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700) + +#if defined(XP_LINUX) && !defined(ANDROID) && defined(_GNU_SOURCE) +# define HAVE_SCHED_SETAFFINITY +#endif + +#ifdef XP_MACOSX +# include +# include +# include +#endif + +#ifdef MOZ_CANARY +# include +# include +# include +# include +# include "nsXULAppAPI.h" +#endif + +using namespace mozilla; + +extern void InitThreadLocalVariables(); + +static LazyLogModule sThreadLog("nsThread"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sThreadLog, mozilla::LogLevel::Debug, args) + +NS_DECL_CI_INTERFACE_GETTER(nsThread) + +Array nsThread::sMainThreadRunnableName; + +#ifdef EARLY_BETA_OR_EARLIER +const uint32_t kTelemetryWakeupCountLimit = 100; +#endif + +//----------------------------------------------------------------------------- +// Because we do not have our own nsIFactory, we have to implement nsIClassInfo +// somewhat manually. + +class nsThreadClassInfo : public nsIClassInfo { + public: + NS_DECL_ISUPPORTS_INHERITED // no mRefCnt + NS_DECL_NSICLASSINFO + + nsThreadClassInfo() = default; +}; + +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadClassInfo::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadClassInfo::Release() { return 1; } +NS_IMPL_QUERY_INTERFACE(nsThreadClassInfo, nsIClassInfo) + +NS_IMETHODIMP +nsThreadClassInfo::GetInterfaces(nsTArray& aArray) { + return NS_CI_INTERFACE_GETTER_NAME(nsThread)(aArray); +} + +NS_IMETHODIMP +nsThreadClassInfo::GetScriptableHelper(nsIXPCScriptable** aResult) { + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetContractID(nsACString& aResult) { + aResult.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassDescription(nsACString& aResult) { + aResult.SetIsVoid(true); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassID(nsCID** aResult) { + *aResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetFlags(uint32_t* aResult) { + *aResult = THREADSAFE; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadClassInfo::GetClassIDNoAlloc(nsCID* aResult) { + return NS_ERROR_NOT_AVAILABLE; +} + +//----------------------------------------------------------------------------- + +NS_IMPL_ADDREF(nsThread) +NS_IMPL_RELEASE(nsThread) +NS_INTERFACE_MAP_BEGIN(nsThread) + NS_INTERFACE_MAP_ENTRY(nsIThread) + NS_INTERFACE_MAP_ENTRY(nsIThreadInternal) + NS_INTERFACE_MAP_ENTRY(nsIEventTarget) + NS_INTERFACE_MAP_ENTRY(nsISerialEventTarget) + NS_INTERFACE_MAP_ENTRY(nsISupportsPriority) + NS_INTERFACE_MAP_ENTRY(nsIDirectTaskDispatcher) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIThread) + if (aIID.Equals(NS_GET_IID(nsIClassInfo))) { + static nsThreadClassInfo sThreadClassInfo; + foundInterface = static_cast(&sThreadClassInfo); + } else +NS_INTERFACE_MAP_END +NS_IMPL_CI_INTERFACE_GETTER(nsThread, nsIThread, nsIThreadInternal, + nsIEventTarget, nsISerialEventTarget, + nsISupportsPriority) + +//----------------------------------------------------------------------------- + +// This event is responsible for notifying nsThread::Shutdown that it is time +// to call PR_JoinThread. It implements nsICancelableRunnable so that it can +// run on a DOM Worker thread (where all events must implement +// nsICancelableRunnable.) +class nsThreadShutdownAckEvent : public CancelableRunnable { + public: + explicit nsThreadShutdownAckEvent(NotNull aCtx) + : CancelableRunnable("nsThreadShutdownAckEvent"), + mShutdownContext(aCtx) {} + NS_IMETHOD Run() override { + mShutdownContext->mTerminatingThread->ShutdownComplete(mShutdownContext); + return NS_OK; + } + nsresult Cancel() override { return Run(); } + + private: + virtual ~nsThreadShutdownAckEvent() = default; + + NotNull> mShutdownContext; +}; + +// This event is responsible for setting mShutdownContext +class nsThreadShutdownEvent : public Runnable { + public: + nsThreadShutdownEvent(NotNull aThr, + NotNull aCtx) + : Runnable("nsThreadShutdownEvent"), + mThread(aThr), + mShutdownContext(aCtx) {} + NS_IMETHOD Run() override { + // Creates a cycle between `mThread` and the shutdown context which will be + // broken when the thread exits. + mThread->mShutdownContext = mShutdownContext; + MessageLoop::current()->Quit(); +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // Let's leave a trace that we passed here in the thread's name. + nsAutoCString threadName(PR_GetThreadName(PR_GetCurrentThread())); + threadName.Append(",SHDRCV"_ns); + NS_SetCurrentThreadName(threadName.get()); +#endif + return NS_OK; + } + + private: + NotNull> mThread; + NotNull> mShutdownContext; +}; + +//----------------------------------------------------------------------------- + +static void SetThreadAffinity(unsigned int cpu) { +#ifdef HAVE_SCHED_SETAFFINITY + cpu_set_t cpus; + CPU_ZERO(&cpus); + CPU_SET(cpu, &cpus); + sched_setaffinity(0, sizeof(cpus), &cpus); + // Don't assert sched_setaffinity's return value because it intermittently (?) + // fails with EINVAL on Linux x64 try runs. +#elif defined(XP_MACOSX) + // OS X does not provide APIs to pin threads to specific processors, but you + // can tag threads as belonging to the same "affinity set" and the OS will try + // to run them on the same processor. To run threads on different processors, + // tag them as belonging to different affinity sets. Tag 0, the default, means + // "no affinity" so let's pretend each CPU has its own tag `cpu+1`. + thread_affinity_policy_data_t policy; + policy.affinity_tag = cpu + 1; + kern_return_t kr = thread_policy_set( + mach_thread_self(), THREAD_AFFINITY_POLICY, &policy.affinity_tag, 1); + // Setting the thread affinity is not supported on ARM. + MOZ_ALWAYS_TRUE(kr == KERN_SUCCESS || kr == KERN_NOT_SUPPORTED); +#elif defined(XP_WIN) + MOZ_ALWAYS_TRUE(SetThreadIdealProcessor(GetCurrentThread(), cpu) != + (DWORD)-1); +#endif +} + +static void SetupCurrentThreadForChaosMode() { + if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) { + return; + } + +#ifdef XP_LINUX + // PR_SetThreadPriority doesn't really work since priorities > + // PR_PRIORITY_NORMAL can't be set by non-root users. Instead we'll just use + // setpriority(2) to set random 'nice values'. In regular Linux this is only + // a dynamic adjustment so it still doesn't really do what we want, but tools + // like 'rr' can be more aggressive about honoring these values. + // Some of these calls may fail due to trying to lower the priority + // (e.g. something may have already called setpriority() for this thread). + // This makes it hard to have non-main threads with higher priority than the + // main thread, but that's hard to fix. Tools like rr can choose to honor the + // requested values anyway. + // Use just 4 priorities so there's a reasonable chance of any two threads + // having equal priority. + setpriority(PRIO_PROCESS, 0, ChaosMode::randomUint32LessThan(4)); +#else + // We should set the affinity here but NSPR doesn't provide a way to expose + // it. + uint32_t priority = ChaosMode::randomUint32LessThan(PR_PRIORITY_LAST + 1); + PR_SetThreadPriority(PR_GetCurrentThread(), PRThreadPriority(priority)); +#endif + + // Force half the threads to CPU 0 so they compete for CPU + if (ChaosMode::randomUint32LessThan(2)) { + SetThreadAffinity(0); + } +} + +namespace { + +struct ThreadInitData { + nsThread* thread; + nsCString name; +}; + +} // namespace + +/* static */ mozilla::OffTheBooksMutex& nsThread::ThreadListMutex() { + static StaticLocalAutoPtr sMutex( + new OffTheBooksMutex("nsThread::ThreadListMutex")); + return *sMutex; +} + +/* static */ LinkedList& nsThread::ThreadList() { + static StaticLocalAutoPtr> sList( + new LinkedList()); + return *sList; +} + +/* static */ +nsThreadEnumerator nsThread::Enumerate() { return {}; } + +void nsThread::AddToThreadList() { + OffTheBooksMutexAutoLock mal(ThreadListMutex()); + MOZ_ASSERT(!isInList()); + + ThreadList().insertBack(this); +} + +void nsThread::MaybeRemoveFromThreadList() { + OffTheBooksMutexAutoLock mal(ThreadListMutex()); + if (isInList()) { + removeFrom(ThreadList()); + } +} + +/*static*/ +void nsThread::ThreadFunc(void* aArg) { + using mozilla::ipc::BackgroundChild; + + UniquePtr initData(static_cast(aArg)); + nsThread* self = initData->thread; // strong reference + + MOZ_ASSERT(self->mEventTarget); + MOZ_ASSERT(self->mEvents); + + // Note: see the comment in nsThread::Init, where we set these same values. + DebugOnly prev = self->mThread.exchange(PR_GetCurrentThread()); + MOZ_ASSERT(!prev || prev == PR_GetCurrentThread()); + self->mEventTarget->SetCurrentThread(self->mThread); + SetupCurrentThreadForChaosMode(); + + if (!initData->name.IsEmpty()) { + NS_SetCurrentThreadName(initData->name.BeginReading()); + } + + self->InitCommon(); + + // Inform the ThreadManager + nsThreadManager::get().RegisterCurrentThread(*self); + + mozilla::IOInterposer::RegisterCurrentThread(); + + // This must come after the call to nsThreadManager::RegisterCurrentThread(), + // because that call is needed to properly set up this thread as an nsThread, + // which profiler_register_thread() requires. See bug 1347007. + const bool registerWithProfiler = !initData->name.IsEmpty(); + if (registerWithProfiler) { + PROFILER_REGISTER_THREAD(initData->name.BeginReading()); + } + + { + // Scope for MessageLoop. + MessageLoop loop( +#if defined(XP_WIN) || defined(XP_MACOSX) + self->mIsUiThread ? MessageLoop::TYPE_MOZILLA_NONMAINUITHREAD + : MessageLoop::TYPE_MOZILLA_NONMAINTHREAD, +#else + MessageLoop::TYPE_MOZILLA_NONMAINTHREAD, +#endif + self); + + // Now, process incoming events... + loop.Run(); + + self->mEvents->RunShutdownTasks(); + + BackgroundChild::CloseForCurrentThread(); + + // NB: The main thread does not shut down here! It shuts down via + // nsThreadManager::Shutdown. + + // Do NS_ProcessPendingEvents but with special handling to set + // mEventsAreDoomed atomically with the removal of the last event. The key + // invariant here is that we will never permit PutEvent to succeed if the + // event would be left in the queue after our final call to + // NS_ProcessPendingEvents. We also have to keep processing events as long + // as we have outstanding mRequestedShutdownContexts. + while (true) { + // Check and see if we're waiting on any threads. + self->WaitForAllAsynchronousShutdowns(); + + if (self->mEvents->ShutdownIfNoPendingEvents()) { + break; + } + NS_ProcessPendingEvents(self); + } + } + + mozilla::IOInterposer::UnregisterCurrentThread(); + + // Inform the threadmanager that this thread is going away + nsThreadManager::get().UnregisterCurrentThread(*self); + + // The thread should only unregister itself if it was registered above. + if (registerWithProfiler) { + PROFILER_UNREGISTER_THREAD(); + } + + NotNull> context = + WrapNotNull(self->mShutdownContext); + self->mShutdownContext = nullptr; + MOZ_ASSERT(context->mTerminatingThread == self); + + // Take the joining thread from our shutdown context. This may have been + // cleared by the joining thread if it decided to cancel waiting on us, in + // which case we won't notify our caller, and leak. + RefPtr joiningThread; + { + MutexAutoLock lock(context->mJoiningThreadMutex); + joiningThread = context->mJoiningThread.forget(); + MOZ_RELEASE_ASSERT(joiningThread || context->mThreadLeaked); + } + if (joiningThread) { + // Dispatch shutdown ACK + nsCOMPtr event = new nsThreadShutdownAckEvent(context); + nsresult dispatch_ack_rv = + joiningThread->Dispatch(event, NS_DISPATCH_NORMAL); + + // We do not expect this to ever happen, but If we cannot dispatch + // the ack event, someone probably blocks waiting on us and will + // crash with a hang later anyways. The best we can do is to tell + // the world what happened right here. + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(dispatch_ack_rv)); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + // Let's leave a trace that we passed here in the thread's name. + nsAutoCString threadName(PR_GetThreadName(PR_GetCurrentThread())); + threadName.Append(",SHDACK"_ns); + NS_SetCurrentThreadName(threadName.get()); +#endif + } else { + NS_WARNING( + "nsThread exiting after StopWaitingAndLeakThread was called, thread " + "resources will be leaked!"); + } + + // Release any observer of the thread here. + self->SetObserver(nullptr); + + // The PRThread will be deleted in PR_JoinThread(), so clear references. + self->mThread = nullptr; + self->mEventTarget->ClearCurrentThread(); + NS_RELEASE(self); +} + +void nsThread::InitCommon() { + mThreadId = uint32_t(PlatformThread::CurrentId()); + + { +#if defined(XP_LINUX) + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_getattr_np(pthread_self(), &attr); + + size_t stackSize; + pthread_attr_getstack(&attr, &mStackBase, &stackSize); + + // Glibc prior to 2.27 reports the stack size and base including the guard + // region, so we need to compensate for it to get accurate accounting. + // Also, this behavior difference isn't guarded by a versioned symbol, so we + // actually need to check the runtime glibc version, not the version we were + // compiled against. + static bool sAdjustForGuardSize = ({ +# ifdef __GLIBC__ + unsigned major, minor; + sscanf(gnu_get_libc_version(), "%u.%u", &major, &minor) < 2 || + major < 2 || (major == 2 && minor < 27); +# else + false; +# endif + }); + if (sAdjustForGuardSize) { + size_t guardSize; + pthread_attr_getguardsize(&attr, &guardSize); + + // Note: This assumes that the stack grows down, as is the case on all of + // our tier 1 platforms. On platforms where the stack grows up, the + // mStackBase adjustment is unnecessary, but doesn't cause any harm other + // than under-counting stack memory usage by one page. + mStackBase = reinterpret_cast(mStackBase) + guardSize; + stackSize -= guardSize; + } + + mStackSize = stackSize; + + // This is a bit of a hack. + // + // We really do want the NOHUGEPAGE flag on our thread stacks, since we + // don't expect any of them to need anywhere near 2MB of space. But setting + // it here is too late to have an effect, since the first stack page has + // already been faulted in existence, and NSPR doesn't give us a way to set + // it beforehand. + // + // What this does get us, however, is a different set of VM flags on our + // thread stacks compared to normal heap memory. Which makes the Linux + // kernel report them as separate regions, even when they are adjacent to + // heap memory. This allows us to accurately track the actual memory + // consumption of our allocated stacks. + madvise(mStackBase, stackSize, MADV_NOHUGEPAGE); + + pthread_attr_destroy(&attr); +#elif defined(XP_WIN) + static const StaticDynamicallyLinkedFunctionPtr< + GetCurrentThreadStackLimitsFn> + sGetStackLimits(L"kernel32.dll", "GetCurrentThreadStackLimits"); + + if (sGetStackLimits) { + ULONG_PTR stackBottom, stackTop; + sGetStackLimits(&stackBottom, &stackTop); + mStackBase = reinterpret_cast(stackBottom); + mStackSize = stackTop - stackBottom; + } +#endif + } + + InitThreadLocalVariables(); + AddToThreadList(); +} + +//----------------------------------------------------------------------------- + +#ifdef MOZ_CANARY +int sCanaryOutputFD = -1; +#endif + +nsThread::nsThread(NotNull aQueue, + MainThreadFlag aMainThread, + nsIThreadManager::ThreadCreationOptions aOptions) + : mEvents(aQueue.get()), + mEventTarget(new ThreadEventTarget( + mEvents.get(), aMainThread == MAIN_THREAD, aOptions.blockDispatch)), + mOutstandingShutdownContexts(0), + mShutdownContext(nullptr), + mScriptObserver(nullptr), + mThreadName(""), + mStackSize(aOptions.stackSize), + mNestedEventLoopDepth(0), + mShutdownRequired(false), + mPriority(PRIORITY_NORMAL), + mIsMainThread(aMainThread == MAIN_THREAD), + mUseHangMonitor(aMainThread == MAIN_THREAD), + mIsUiThread(aOptions.isUiThread), + mIsAPoolThreadFree(nullptr), + mCanInvokeJS(false), +#ifdef EARLY_BETA_OR_EARLIER + mLastWakeupCheckTime(TimeStamp::Now()), +#endif + mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread) { +#if !(defined(XP_WIN) || defined(XP_MACOSX)) + MOZ_ASSERT(!mIsUiThread, + "Non-main UI threads are only supported on Windows and macOS"); +#endif + if (mIsMainThread) { + MOZ_ASSERT(!mIsUiThread, + "Setting isUIThread is not supported for main threads"); + mozilla::TaskController::Get()->SetPerformanceCounterState( + &mPerformanceCounterState); + } +} + +nsThread::nsThread() + : mEvents(nullptr), + mEventTarget(nullptr), + mOutstandingShutdownContexts(0), + mShutdownContext(nullptr), + mScriptObserver(nullptr), + mThreadName(""), + mStackSize(0), + mNestedEventLoopDepth(0), + mShutdownRequired(false), + mPriority(PRIORITY_NORMAL), + mIsMainThread(false), + mUseHangMonitor(false), + mIsUiThread(false), + mCanInvokeJS(false), +#ifdef EARLY_BETA_OR_EARLIER + mLastWakeupCheckTime(TimeStamp::Now()), +#endif + mPerformanceCounterState(mNestedEventLoopDepth, mIsMainThread) { + MOZ_ASSERT(!NS_IsMainThread()); +} + +nsThread::~nsThread() { + NS_ASSERTION(mOutstandingShutdownContexts == 0, + "shouldn't be waiting on other threads to shutdown"); + + MaybeRemoveFromThreadList(); +} + +nsresult nsThread::Init(const nsACString& aName) { + MOZ_ASSERT(mEvents); + MOZ_ASSERT(mEventTarget); + MOZ_ASSERT(!mThread); + + NS_ADDREF_THIS(); + + SetThreadNameInternal(aName); + + mShutdownRequired = true; + + UniquePtr initData( + new ThreadInitData{this, nsCString(aName)}); + + PRThread* thread = nullptr; + // ThreadFunc is responsible for setting mThread + if (!(thread = PR_CreateThread(PR_USER_THREAD, ThreadFunc, initData.get(), + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, + PR_JOINABLE_THREAD, mStackSize))) { + NS_RELEASE_THIS(); + return NS_ERROR_OUT_OF_MEMORY; + } + + // The created thread now owns initData, so release our ownership of it. + Unused << initData.release(); + + // Note: we set these both here and inside ThreadFunc, to what should be + // the same value. This is because calls within ThreadFunc need these values + // to be set, and our callers need these values to be set. + DebugOnly prev = mThread.exchange(thread); + MOZ_ASSERT(!prev || prev == thread); + + mEventTarget->SetCurrentThread(thread); + return NS_OK; +} + +nsresult nsThread::InitCurrentThread() { + mThread = PR_GetCurrentThread(); + SetupCurrentThreadForChaosMode(); + InitCommon(); + + nsThreadManager::get().RegisterCurrentThread(*this); + return NS_OK; +} + +void nsThread::GetThreadName(nsACString& aNameBuffer) { + auto lock = mThreadName.Lock(); + aNameBuffer = lock.ref(); +} + +void nsThread::SetThreadNameInternal(const nsACString& aName) { + auto lock = mThreadName.Lock(); + lock->Assign(aName); +} + +//----------------------------------------------------------------------------- +// nsIEventTarget + +NS_IMETHODIMP +nsThread::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr event(aEvent); + return mEventTarget->Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +nsThread::Dispatch(already_AddRefed aEvent, uint32_t aFlags) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + LOG(("THRD(%p) Dispatch [%p %x]\n", this, /* XXX aEvent */ nullptr, aFlags)); + + return mEventTarget->Dispatch(std::move(aEvent), aFlags); +} + +NS_IMETHODIMP +nsThread::DelayedDispatch(already_AddRefed aEvent, + uint32_t aDelayMs) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + return mEventTarget->DelayedDispatch(std::move(aEvent), aDelayMs); +} + +NS_IMETHODIMP +nsThread::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + return mEventTarget->RegisterShutdownTask(aTask); +} + +NS_IMETHODIMP +nsThread::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + MOZ_ASSERT(mEventTarget); + NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_IMPLEMENTED); + + return mEventTarget->UnregisterShutdownTask(aTask); +} + +NS_IMETHODIMP +nsThread::GetRunningEventDelay(TimeDuration* aDelay, TimeStamp* aStart) { + if (mIsAPoolThreadFree && *mIsAPoolThreadFree) { + // if there are unstarted threads in the pool, a new event to the + // pool would not be delayed at all (beyond thread start time) + *aDelay = TimeDuration(); + *aStart = TimeStamp(); + } else { + *aDelay = mLastEventDelay; + *aStart = mLastEventStart; + } + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetRunningEventDelay(TimeDuration aDelay, TimeStamp aStart) { + mLastEventDelay = aDelay; + mLastEventStart = aStart; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::IsOnCurrentThread(bool* aResult) { + if (mEventTarget) { + return mEventTarget->IsOnCurrentThread(aResult); + } + *aResult = PR_GetCurrentThread() == mThread; + return NS_OK; +} + +NS_IMETHODIMP_(bool) +nsThread::IsOnCurrentThreadInfallible() { + // This method is only going to be called if `mThread` is null, which + // only happens when the thread has exited the event loop. Therefore, when + // we are called, we can never be on this thread. + return false; +} + +//----------------------------------------------------------------------------- +// nsIThread + +NS_IMETHODIMP +nsThread::GetPRThread(PRThread** aResult) { + PRThread* thread = mThread; // atomic load + *aResult = thread; + return thread ? NS_OK : NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +nsThread::GetCanInvokeJS(bool* aResult) { + *aResult = mCanInvokeJS; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetCanInvokeJS(bool aCanInvokeJS) { + mCanInvokeJS = aCanInvokeJS; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::GetLastLongTaskEnd(TimeStamp* _retval) { + *_retval = mPerformanceCounterState.LastLongTaskEnd(); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::GetLastLongNonIdleTaskEnd(TimeStamp* _retval) { + *_retval = mPerformanceCounterState.LastLongNonIdleTaskEnd(); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetNameForWakeupTelemetry(const nsACString& aName) { +#ifdef EARLY_BETA_OR_EARLIER + mNameForWakeupTelemetry = aName; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsThread::AsyncShutdown() { + LOG(("THRD(%p) async shutdown\n", this)); + + nsCOMPtr shutdown; + BeginShutdown(getter_AddRefs(shutdown)); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::BeginShutdown(nsIThreadShutdown** aShutdown) { + LOG(("THRD(%p) begin shutdown\n", this)); + + MOZ_ASSERT(mEvents); + MOZ_ASSERT(mEventTarget); + MOZ_ASSERT(mThread != PR_GetCurrentThread()); + if (NS_WARN_IF(mThread == PR_GetCurrentThread())) { + return NS_ERROR_UNEXPECTED; + } + + // Prevent multiple calls to this method. + if (!mShutdownRequired.compareExchange(true, false)) { + return NS_ERROR_UNEXPECTED; + } + MOZ_ASSERT(mThread); + + MaybeRemoveFromThreadList(); + + RefPtr currentThread = nsThreadManager::get().GetCurrentThread(); + + MOZ_DIAGNOSTIC_ASSERT(currentThread->EventQueue(), + "Shutdown() may only be called from an XPCOM thread"); + + // Allocate a shutdown context, and record that we're waiting for it. + RefPtr context = + new nsThreadShutdownContext(WrapNotNull(this), currentThread); + + ++currentThread->mOutstandingShutdownContexts; + nsCOMPtr clearOutstanding = NS_NewRunnableFunction( + "nsThread::ClearOutstandingShutdownContext", + [currentThread] { --currentThread->mOutstandingShutdownContexts; }); + context->OnCompletion(clearOutstanding); + + // Set mShutdownContext and wake up the thread in case it is waiting for + // events to process. + nsCOMPtr event = + new nsThreadShutdownEvent(WrapNotNull(this), WrapNotNull(context)); + if (!mEvents->PutEvent(event.forget(), EventQueuePriority::Normal)) { + // We do not expect this to happen. Let's collect some diagnostics. + nsAutoCString threadName; + GetThreadName(threadName); + MOZ_CRASH_UNSAFE_PRINTF("Attempt to shutdown an already dead thread: %s", + threadName.get()); + } + + // We could still end up with other events being added after the shutdown + // task, but that's okay because we process pending events in ThreadFunc + // after setting mShutdownContext just before exiting. + context.forget(aShutdown); + return NS_OK; +} + +void nsThread::ShutdownComplete(NotNull aContext) { + MOZ_ASSERT(mEvents); + MOZ_ASSERT(mEventTarget); + MOZ_ASSERT(aContext->mTerminatingThread == this); + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + { + MutexAutoLock lock(aContext->mJoiningThreadMutex); + + // StopWaitingAndLeakThread is explicitely meant to not cause a + // nsThreadShutdownAckEvent on the joining thread, which is the only + // caller of ShutdownComplete. + MOZ_DIAGNOSTIC_ASSERT(!aContext->mThreadLeaked); + } +#endif + + MaybeRemoveFromThreadList(); + + // Now, it should be safe to join without fear of dead-locking. + PR_JoinThread(aContext->mTerminatingPRThread); + MOZ_ASSERT(!mThread); + +#ifdef DEBUG + nsCOMPtr obs = mEvents->GetObserver(); + MOZ_ASSERT(!obs, "Should have been cleared at shutdown!"); +#endif + + aContext->MarkCompleted(); +} + +void nsThread::WaitForAllAsynchronousShutdowns() { + // This is the motivating example for why SpinEventLoopUntil + // has the template parameter we are providing here. + SpinEventLoopUntil( + "nsThread::WaitForAllAsynchronousShutdowns"_ns, + [&]() { return mOutstandingShutdownContexts == 0; }, this); +} + +NS_IMETHODIMP +nsThread::Shutdown() { + LOG(("THRD(%p) sync shutdown\n", this)); + + nsCOMPtr context; + nsresult rv = BeginShutdown(getter_AddRefs(context)); + if (NS_FAILED(rv)) { + return NS_OK; // The thread has already shut down. + } + + // If we are going to hang here we want to see the thread's name + nsAutoCString threadName; + GetThreadName(threadName); + + // Process events on the current thread until we receive a shutdown ACK. + // Allows waiting; ensure no locks are held that would deadlock us! + SpinEventLoopUntil("nsThread::Shutdown: "_ns + threadName, + [&]() { return context->GetCompleted(); }); + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::HasPendingEvents(bool* aResult) { + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + if (mIsMainThread) { + *aResult = TaskController::Get()->HasMainThreadPendingTasks(); + } else { + *aResult = mEvents->HasPendingEvent(); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThread::HasPendingHighPriorityEvents(bool* aResult) { + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + // This function appears to never be called anymore. + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::DispatchToQueue(already_AddRefed aEvent, + EventQueuePriority aQueue) { + nsCOMPtr event = aEvent; + + if (NS_WARN_IF(!event)) { + return NS_ERROR_INVALID_ARG; + } + + if (!mEvents->PutEvent(event.forget(), aQueue)) { + NS_WARNING( + "An idle event was posted to a thread that will never run it " + "(rejected)"); + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP nsThread::SetThreadQoS(nsIThread::QoSPriority aPriority) { + if (!StaticPrefs::threads_use_low_power_enabled()) { + return NS_OK; + } + // The approach here is to have a thread set itself for its QoS level, + // so we assert if we aren't on the current thread. + MOZ_ASSERT(IsOnCurrentThread(), "Can only change the current thread's QoS"); + +#if defined(XP_MACOSX) + // Only arm64 macs may possess heterogeneous cores. On these, we can tell + // a thread to set its own QoS status. On intel macs things should behave + // normally, and the OS will ignore the QoS state of the thread. + if (aPriority == nsIThread::QOS_PRIORITY_LOW) { + pthread_set_qos_class_self_np(QOS_CLASS_BACKGROUND, 0); + } else if (NS_IsMainThread()) { + // MacOS documentation specifies that a main thread should be initialized at + // the USER_INTERACTIVE priority, so when we restore thread priorities the + // main thread should be setting itself to this. + pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0); + } else { + pthread_set_qos_class_self_np(QOS_CLASS_DEFAULT, 0); + } +#endif + // Do nothing if an OS-specific implementation is unavailable. + return NS_OK; +} + +#ifdef MOZ_CANARY +void canary_alarm_handler(int signum); + +class Canary { + // XXX ToDo: support nested loops + public: + Canary() { + if (sCanaryOutputFD > 0 && EventLatencyIsImportant()) { + signal(SIGALRM, canary_alarm_handler); + ualarm(15000, 0); + } + } + + ~Canary() { + if (sCanaryOutputFD != 0 && EventLatencyIsImportant()) { + ualarm(0, 0); + } + } + + static bool EventLatencyIsImportant() { + return NS_IsMainThread() && XRE_IsParentProcess(); + } +}; + +void canary_alarm_handler(int signum) { + void* array[30]; + const char msg[29] = "event took too long to run:\n"; + // use write to be safe in the signal handler + write(sCanaryOutputFD, msg, sizeof(msg)); + backtrace_symbols_fd(array, backtrace(array, 30), sCanaryOutputFD); +} + +#endif + +#define NOTIFY_EVENT_OBSERVERS(observers_, func_, params_) \ + do { \ + if (!observers_.IsEmpty()) { \ + for (nsCOMPtr obs_ : observers_.ForwardRange()) { \ + obs_->func_ params_; \ + } \ + } \ + } while (0) + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +// static +bool nsThread::GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName, + EventQueuePriority aPriority) { + bool labeled = false; + if (RefPtr groupRunnable = do_QueryObject(aEvent)) { + labeled = true; + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(groupRunnable->GetName(aName))); + } else if (nsCOMPtr named = do_QueryInterface(aEvent)) { + MOZ_ALWAYS_TRUE(NS_SUCCEEDED(named->GetName(aName))); + } else { + aName.AssignLiteral("non-nsINamed runnable"); + } + if (aName.IsEmpty()) { + aName.AssignLiteral("anonymous runnable"); + } + + if (!labeled && aPriority > EventQueuePriority::InputHigh) { + aName.AppendLiteral("(unlabeled)"); + } + + return labeled; +} +#endif + +mozilla::PerformanceCounter* nsThread::GetPerformanceCounter( + nsIRunnable* aEvent) const { + return GetPerformanceCounterBase(aEvent); +} + +// static +mozilla::PerformanceCounter* nsThread::GetPerformanceCounterBase( + nsIRunnable* aEvent) { + RefPtr docRunnable = do_QueryObject(aEvent); + if (docRunnable) { + return docRunnable->GetPerformanceCounter(); + } + return nullptr; +} + +size_t nsThread::ShallowSizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mShutdownContext) { + n += aMallocSizeOf(mShutdownContext); + } + return aMallocSizeOf(this) + aMallocSizeOf(mThread) + n; +} + +size_t nsThread::SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + if (mEventTarget) { + // The size of mEvents is reported by mEventTarget. + n += mEventTarget->SizeOfIncludingThis(aMallocSizeOf); + } + return n; +} + +size_t nsThread::SizeOfIncludingThis( + mozilla::MallocSizeOf aMallocSizeOf) const { + return ShallowSizeOfIncludingThis(aMallocSizeOf) + + SizeOfEventQueues(aMallocSizeOf); +} + +NS_IMETHODIMP +nsThread::ProcessNextEvent(bool aMayWait, bool* aResult) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + LOG(("THRD(%p) ProcessNextEvent [%u %u]\n", this, aMayWait, + mNestedEventLoopDepth)); + + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + // The toplevel event loop normally blocks waiting for the next event, but + // if we're trying to shut this thread down, we must exit the event loop + // when the event queue is empty. This only applys to the toplevel event + // loop! Nested event loops (e.g. during sync dispatch) are waiting for + // some state change and must be able to block even if something has + // requested shutdown of the thread. Otherwise we'll just busywait as we + // endlessly look for an event, fail to find one, and repeat the nested + // event loop since its state change hasn't happened yet. + bool reallyWait = aMayWait && (mNestedEventLoopDepth > 0 || !ShuttingDown()); + + Maybe noJSAPI; + + if (mUseHangMonitor && reallyWait) { + BackgroundHangMonitor().NotifyWait(); + } + + if (mIsMainThread) { + DoMainThreadSpecificProcessing(); + } + + ++mNestedEventLoopDepth; + + // We only want to create an AutoNoJSAPI on threads that actually do DOM + // stuff (including workers). Those are exactly the threads that have an + // mScriptObserver. + bool callScriptObserver = !!mScriptObserver; + if (callScriptObserver) { + noJSAPI.emplace(); + mScriptObserver->BeforeProcessTask(reallyWait); + } + + DrainDirectTasks(); + +#ifdef EARLY_BETA_OR_EARLIER + // Need to capture mayWaitForWakeup state before OnProcessNextEvent, + // since on the main thread OnProcessNextEvent ends up waiting for the new + // events. + bool mayWaitForWakeup = reallyWait && !mEvents->HasPendingEvent(); +#endif + + nsCOMPtr obs = mEvents->GetObserverOnThread(); + if (obs) { + obs->OnProcessNextEvent(this, reallyWait); + } + + NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), OnProcessNextEvent, + (this, reallyWait)); + + DrainDirectTasks(); + +#ifdef MOZ_CANARY + Canary canary; +#endif + nsresult rv = NS_OK; + + { + // Scope for |event| to make sure that its destructor fires while + // mNestedEventLoopDepth has been incremented, since that destructor can + // also do work. + nsCOMPtr event; + bool usingTaskController = mIsMainThread; + if (usingTaskController) { + event = TaskController::Get()->GetRunnableForMTTask(reallyWait); + } else { + event = mEvents->GetEvent(reallyWait, &mLastEventDelay); + } + + *aResult = (event.get() != nullptr); + + if (event) { +#ifdef EARLY_BETA_OR_EARLIER + if (mayWaitForWakeup && mThread) { + ++mWakeupCount; + if (mWakeupCount == kTelemetryWakeupCountLimit) { + TimeStamp now = TimeStamp::Now(); + double ms = (now - mLastWakeupCheckTime).ToMilliseconds(); + if (ms < 0) { + ms = 0; + } + const char* name = !mNameForWakeupTelemetry.IsEmpty() + ? mNameForWakeupTelemetry.get() + : PR_GetThreadName(mThread); + if (!name) { + name = mIsMainThread ? "MainThread" : "(nameless thread)"; + } + nsDependentCString key(name); + Telemetry::Accumulate(Telemetry::THREAD_WAKEUP, key, + static_cast(ms)); + mLastWakeupCheckTime = now; + mWakeupCount = 0; + } + } +#endif + + LOG(("THRD(%p) running [%p]\n", this, event.get())); + + Maybe log; + + if (!usingTaskController) { + log.emplace(event); + } + + // Delay event processing to encourage whoever dispatched this event + // to run. + DelayForChaosMode(ChaosFeature::TaskRunning, 1000); + + mozilla::TimeStamp now = mozilla::TimeStamp::Now(); + + if (mUseHangMonitor) { + BackgroundHangMonitor().NotifyActivity(); + } + + Maybe snapshot; + if (!usingTaskController) { + snapshot.emplace(mPerformanceCounterState.RunnableWillRun( + GetPerformanceCounter(event), now, false)); + } + + mLastEventStart = now; + + if (!usingTaskController) { + AUTO_PROFILE_FOLLOWING_RUNNABLE(event); + event->Run(); + } else { + // Avoid generating "Runnable" profiler markers for the + // "TaskController::ExecutePendingMTTasks" runnables created + // by TaskController, which already adds "Runnable" markers + // when executing tasks. + event->Run(); + } + + if (usingTaskController) { + *aResult = TaskController::Get()->MTTaskRunnableProcessedTask(); + } else { + mPerformanceCounterState.RunnableDidRun(EmptyCString(), + std::move(snapshot.ref())); + } + + // To cover the event's destructor code inside the LogRunnable span. + event = nullptr; + } else { + mLastEventDelay = TimeDuration(); + mLastEventStart = TimeStamp(); + if (aMayWait) { + MOZ_ASSERT(ShuttingDown(), + "This should only happen when shutting down"); + rv = NS_ERROR_UNEXPECTED; + } + } + } + + DrainDirectTasks(); + + NOTIFY_EVENT_OBSERVERS(EventQueue()->EventObservers(), AfterProcessNextEvent, + (this, *aResult)); + + if (obs) { + obs->AfterProcessNextEvent(this, *aResult); + } + + // In case some EventObserver dispatched some direct tasks; process them + // now. + DrainDirectTasks(); + + if (callScriptObserver) { + if (mScriptObserver) { + mScriptObserver->AfterProcessTask(mNestedEventLoopDepth); + } + noJSAPI.reset(); + } + + --mNestedEventLoopDepth; + + return rv; +} + +//----------------------------------------------------------------------------- +// nsISupportsPriority + +NS_IMETHODIMP +nsThread::GetPriority(int32_t* aPriority) { + *aPriority = mPriority; + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetPriority(int32_t aPriority) { + if (NS_WARN_IF(!mThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + + // NSPR defines the following four thread priorities: + // PR_PRIORITY_LOW + // PR_PRIORITY_NORMAL + // PR_PRIORITY_HIGH + // PR_PRIORITY_URGENT + // We map the priority values defined on nsISupportsPriority to these + // values. + + mPriority = aPriority; + + PRThreadPriority pri; + if (mPriority <= PRIORITY_HIGHEST) { + pri = PR_PRIORITY_URGENT; + } else if (mPriority < PRIORITY_NORMAL) { + pri = PR_PRIORITY_HIGH; + } else if (mPriority > PRIORITY_NORMAL) { + pri = PR_PRIORITY_LOW; + } else { + pri = PR_PRIORITY_NORMAL; + } + // If chaos mode is active, retain the randomly chosen priority + if (!ChaosMode::isActive(ChaosFeature::ThreadScheduling)) { + PR_SetThreadPriority(mThread, pri); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::AdjustPriority(int32_t aDelta) { + return SetPriority(mPriority + aDelta); +} + +//----------------------------------------------------------------------------- +// nsIThreadInternal + +NS_IMETHODIMP +nsThread::GetObserver(nsIThreadObserver** aObs) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + nsCOMPtr obs = mEvents->GetObserver(); + obs.forget(aObs); + return NS_OK; +} + +NS_IMETHODIMP +nsThread::SetObserver(nsIThreadObserver* aObs) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + mEvents->SetObserver(aObs); + return NS_OK; +} + +uint32_t nsThread::RecursionDepth() const { + MOZ_ASSERT(PR_GetCurrentThread() == mThread); + return mNestedEventLoopDepth; +} + +NS_IMETHODIMP +nsThread::AddObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + if (NS_WARN_IF(!aObserver)) { + return NS_ERROR_INVALID_ARG; + } + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + EventQueue()->AddObserver(aObserver); + + return NS_OK; +} + +NS_IMETHODIMP +nsThread::RemoveObserver(nsIThreadObserver* aObserver) { + MOZ_ASSERT(mEvents); + NS_ENSURE_TRUE(mEvents, NS_ERROR_NOT_IMPLEMENTED); + + if (NS_WARN_IF(PR_GetCurrentThread() != mThread)) { + return NS_ERROR_NOT_SAME_THREAD; + } + + EventQueue()->RemoveObserver(aObserver); + + return NS_OK; +} + +void nsThread::SetScriptObserver( + mozilla::CycleCollectedJSContext* aScriptObserver) { + if (!aScriptObserver) { + mScriptObserver = nullptr; + return; + } + + MOZ_ASSERT(!mScriptObserver); + mScriptObserver = aScriptObserver; +} + +void NS_DispatchMemoryPressure(); + +void nsThread::DoMainThreadSpecificProcessing() const { + MOZ_ASSERT(mIsMainThread); + + ipc::CancelCPOWs(); + + // Fire a memory pressure notification, if one is pending. + if (!ShuttingDown()) { + NS_DispatchMemoryPressure(); + } +} + +//----------------------------------------------------------------------------- +// nsIDirectTaskDispatcher + +NS_IMETHODIMP +nsThread::DispatchDirectTask(already_AddRefed aEvent) { + if (!IsOnCurrentThread()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.AddTask(std::move(aEvent)); + return NS_OK; +} + +NS_IMETHODIMP nsThread::DrainDirectTasks() { + if (!IsOnCurrentThread()) { + return NS_ERROR_FAILURE; + } + mDirectTasks.DrainTasks(); + return NS_OK; +} + +NS_IMETHODIMP nsThread::HaveDirectTasks(bool* aValue) { + if (!IsOnCurrentThread()) { + return NS_ERROR_FAILURE; + } + + *aValue = mDirectTasks.HaveTasks(); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsThreadShutdownContext, nsIThreadShutdown) + +NS_IMETHODIMP +nsThreadShutdownContext::OnCompletion(nsIRunnable* aEvent) { + if (mCompleted) { + aEvent->Run(); + } else { + mCompletionCallbacks.AppendElement(aEvent); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadShutdownContext::GetCompleted(bool* aCompleted) { + *aCompleted = mCompleted; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadShutdownContext::StopWaitingAndLeakThread() { + // Take the joining thread from `mJoiningThread` so that the terminating + // thread won't try to dispatch nsThreadShutdownAckEvent to us anymore. + RefPtr joiningThread; + { + MutexAutoLock lock(mJoiningThreadMutex); + if (!mJoiningThread) { + // Shutdown is already being resolved, so there's nothing for us to do. + return NS_ERROR_NOT_AVAILABLE; + } + joiningThread = mJoiningThread.forget(); + mThreadLeaked = true; + } + + MOZ_DIAGNOSTIC_ASSERT(joiningThread->IsOnCurrentThread()); + + MarkCompleted(); + + return NS_OK; +} + +void nsThreadShutdownContext::MarkCompleted() { + MOZ_ASSERT(!mCompleted); + mCompleted = true; + nsTArray> callbacks(std::move(mCompletionCallbacks)); + for (auto& callback : callbacks) { + callback->Run(); + } +} + +namespace mozilla { +PerformanceCounterState::Snapshot PerformanceCounterState::RunnableWillRun( + PerformanceCounter* aCounter, TimeStamp aNow, bool aIsIdleRunnable) { + if (IsNestedRunnable()) { + // Flush out any accumulated time that should be accounted to the + // current runnable before we start running a nested runnable. + MaybeReportAccumulatedTime("nested runnable"_ns, aNow); + } + + Snapshot snapshot(mCurrentEventLoopDepth, mCurrentPerformanceCounter, + mCurrentRunnableIsIdleRunnable); + + mCurrentEventLoopDepth = mNestedEventLoopDepth; + mCurrentPerformanceCounter = aCounter; + mCurrentRunnableIsIdleRunnable = aIsIdleRunnable; + mCurrentTimeSliceStart = aNow; + + return snapshot; +} + +void PerformanceCounterState::RunnableDidRun(const nsCString& aName, + Snapshot&& aSnapshot) { + // First thing: Restore our mCurrentEventLoopDepth so we can use + // IsNestedRunnable(). + mCurrentEventLoopDepth = aSnapshot.mOldEventLoopDepth; + + // We may not need the current timestamp; don't bother computing it if we + // don't. + TimeStamp now; + if (mCurrentPerformanceCounter || mIsMainThread || IsNestedRunnable()) { + now = TimeStamp::Now(); + } + if (mCurrentPerformanceCounter || mIsMainThread) { + MaybeReportAccumulatedTime(aName, now); + } + + // And now restore the rest of our state. + mCurrentPerformanceCounter = std::move(aSnapshot.mOldPerformanceCounter); + mCurrentRunnableIsIdleRunnable = aSnapshot.mOldIsIdleRunnable; + if (IsNestedRunnable()) { + // Reset mCurrentTimeSliceStart to right now, so our parent runnable's + // next slice can be properly accounted for. + mCurrentTimeSliceStart = now; + } else { + // We are done at the outermost level; we are no longer in a timeslice. + mCurrentTimeSliceStart = TimeStamp(); + } +} + +void PerformanceCounterState::MaybeReportAccumulatedTime(const nsCString& aName, + TimeStamp aNow) { + MOZ_ASSERT(mCurrentTimeSliceStart, + "How did we get here if we're not in a timeslice?"); + + if (!mCurrentPerformanceCounter && !mIsMainThread) { + // No one cares about this timeslice. + return; + } + + TimeDuration duration = aNow - mCurrentTimeSliceStart; + if (mCurrentPerformanceCounter) { + mCurrentPerformanceCounter->IncrementExecutionDuration( + duration.ToMicroseconds()); + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + if (mIsMainThread && duration.ToMilliseconds() > LONGTASK_TELEMETRY_MS) { + Telemetry::Accumulate(Telemetry::EVENT_LONGTASK, aName, + duration.ToMilliseconds()); + } +#endif + + // Long tasks only matter on the main thread. + if (mIsMainThread && duration.ToMilliseconds() > LONGTASK_BUSY_WINDOW_MS) { + // Idle events (gc...) don't *really* count here + if (!mCurrentRunnableIsIdleRunnable) { + mLastLongNonIdleTaskEnd = aNow; + } + mLastLongTaskEnd = aNow; + + if (profiler_thread_is_being_profiled_for_markers()) { + struct LongTaskMarker { + static constexpr Span MarkerTypeName() { + return MakeStringSpan("MainThreadLongTask"); + } + static void StreamJSONMarkerData( + baseprofiler::SpliceableJSONWriter& aWriter) { + aWriter.StringProperty("category", "LongTask"); + } + static MarkerSchema MarkerTypeDisplay() { + using MS = MarkerSchema; + MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable}; + schema.AddKeyLabelFormatSearchable("category", "Type", + MS::Format::String, + MS::Searchable::Searchable); + return schema; + } + }; + + profiler_add_marker(mCurrentRunnableIsIdleRunnable + ? ProfilerString8View("LongIdleTask") + : ProfilerString8View("LongTask"), + geckoprofiler::category::OTHER, + MarkerTiming::Interval(mCurrentTimeSliceStart, aNow), + LongTaskMarker{}); + } + } +} + +} // namespace mozilla diff --git a/xpcom/threads/nsThread.h b/xpcom/threads/nsThread.h new file mode 100644 index 0000000000..e4b0eece51 --- /dev/null +++ b/xpcom/threads/nsThread.h @@ -0,0 +1,400 @@ +/* -*- 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/. */ + +#ifndef nsThread_h__ +#define nsThread_h__ + +#include "MainThreadUtils.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" +#include "mozilla/DataMutex.h" +#include "mozilla/EventQueue.h" +#include "mozilla/LinkedList.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Mutex.h" +#include "mozilla/NotNull.h" +#include "mozilla/PerformanceCounter.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TaskDispatcher.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsIDirectTaskDispatcher.h" +#include "nsIEventTarget.h" +#include "nsISerialEventTarget.h" +#include "nsISupportsPriority.h" +#include "nsIThread.h" +#include "nsIThreadInternal.h" +#include "nsTArray.h" + +namespace mozilla { +class CycleCollectedJSContext; +class DelayedRunnable; +class SynchronizedEventQueue; +class ThreadEventQueue; +class ThreadEventTarget; + +template +class Array; +} // namespace mozilla + +using mozilla::NotNull; + +class nsIRunnable; +class nsThreadEnumerator; +class nsThreadShutdownContext; + +// See https://www.w3.org/TR/longtasks +#define LONGTASK_BUSY_WINDOW_MS 50 + +// Time a Runnable executes before we accumulate telemetry on it +#define LONGTASK_TELEMETRY_MS 30 + +// A class for managing performance counter state. +namespace mozilla { +class PerformanceCounterState { + public: + explicit PerformanceCounterState(const uint32_t& aNestedEventLoopDepthRef, + bool aIsMainThread) + : mNestedEventLoopDepth(aNestedEventLoopDepthRef), + mIsMainThread(aIsMainThread), + // Does it really make sense to initialize these to "now" when we + // haven't run any tasks? + mLastLongTaskEnd(TimeStamp::Now()), + mLastLongNonIdleTaskEnd(mLastLongTaskEnd) {} + + class Snapshot { + public: + Snapshot(uint32_t aOldEventLoopDepth, PerformanceCounter* aCounter, + bool aOldIsIdleRunnable) + : mOldEventLoopDepth(aOldEventLoopDepth), + mOldPerformanceCounter(aCounter), + mOldIsIdleRunnable(aOldIsIdleRunnable) {} + + Snapshot(const Snapshot&) = default; + Snapshot(Snapshot&&) = default; + + private: + friend class PerformanceCounterState; + + const uint32_t mOldEventLoopDepth; + // Non-const so we can move out of it and avoid the extra refcounting. + RefPtr mOldPerformanceCounter; + const bool mOldIsIdleRunnable; + }; + + // Notification that a runnable is about to run. This captures a snapshot of + // our current state before we reset to prepare for the new runnable. This + // muast be called after mNestedEventLoopDepth has been incremented for the + // runnable execution. The performance counter passed in should be the one + // for the relevant runnable and may be null. aIsIdleRunnable should be true + // if and only if the runnable has idle priority. + Snapshot RunnableWillRun(PerformanceCounter* Counter, TimeStamp aNow, + bool aIsIdleRunnable); + + // Notification that a runnable finished executing. This must be passed the + // snapshot that RunnableWillRun returned for the same runnable. This must be + // called before mNestedEventLoopDepth is decremented after the runnable's + // execution. + void RunnableDidRun(const nsCString& aName, Snapshot&& aSnapshot); + + const TimeStamp& LastLongTaskEnd() const { return mLastLongTaskEnd; } + const TimeStamp& LastLongNonIdleTaskEnd() const { + return mLastLongNonIdleTaskEnd; + } + + private: + // Called to report accumulated time, as needed, when we're about to run a + // runnable or just finished running one. + void MaybeReportAccumulatedTime(const nsCString& aName, TimeStamp aNow); + + // Whether the runnable we are about to run, or just ran, is a nested + // runnable, in the sense that there is some other runnable up the stack + // spinning the event loop. This must be called before we change our + // mCurrentEventLoopDepth (when about to run a new event) or after we restore + // it (after we ran one). + bool IsNestedRunnable() const { + return mNestedEventLoopDepth > mCurrentEventLoopDepth; + } + + // The event loop depth of the currently running runnable. Set to the max + // value of a uint32_t when there is no runnable running, so when starting to + // run a toplevel (not nested) runnable IsNestedRunnable() will test false. + uint32_t mCurrentEventLoopDepth = std::numeric_limits::max(); + + // A reference to the nsThread's mNestedEventLoopDepth, so we can + // see what it is right now. + const uint32_t& mNestedEventLoopDepth; + + // A boolean that indicates whether the currently running runnable is an idle + // runnable. Only has a useful value between RunnableWillRun() being called + // and RunnableDidRun() returning. + bool mCurrentRunnableIsIdleRunnable = false; + + // Whether we're attached to the mainthread nsThread. + const bool mIsMainThread; + + // The timestamp from which time to be accounted for should be measured. This + // can be the start of a runnable running or the end of a nested runnable + // running. + TimeStamp mCurrentTimeSliceStart; + + // Information about when long tasks last ended. + TimeStamp mLastLongTaskEnd; + TimeStamp mLastLongNonIdleTaskEnd; + + // The performance counter to use for accumulating the runtime of + // the currently running event. May be null, in which case the + // event's running time should not be accounted to any performance + // counters. + RefPtr mCurrentPerformanceCounter; +}; +} // namespace mozilla + +// A native thread +class nsThread : public nsIThreadInternal, + public nsISupportsPriority, + public nsIDirectTaskDispatcher, + private mozilla::LinkedListElement { + friend mozilla::LinkedList; + friend mozilla::LinkedListElement; + + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSITHREAD + NS_DECL_NSITHREADINTERNAL + NS_DECL_NSISUPPORTSPRIORITY + NS_DECL_NSIDIRECTTASKDISPATCHER + + enum MainThreadFlag { MAIN_THREAD, NOT_MAIN_THREAD }; + + nsThread(NotNull aQueue, + MainThreadFlag aMainThread, + nsIThreadManager::ThreadCreationOptions aOptions); + + private: + nsThread(); + + public: + // Initialize this as a named wrapper for a new PRThread. + nsresult Init(const nsACString& aName); + + // Initialize this as a wrapper for the current PRThread. + nsresult InitCurrentThread(); + + // Get this thread's name, thread-safe. + void GetThreadName(nsACString& aNameBuffer); + + // Set this thread's name. Consider using + // NS_SetCurrentThreadName if you are not sure. + void SetThreadNameInternal(const nsACString& aName); + + private: + // Initializes the mThreadId and stack base/size members, and adds the thread + // to the ThreadList(). + void InitCommon(); + + public: + // The PRThread corresponding to this thread. + PRThread* GetPRThread() const { return mThread; } + + const void* StackBase() const { return mStackBase; } + size_t StackSize() const { return mStackSize; } + + uint32_t ThreadId() const { return mThreadId; } + + // If this flag is true, then the nsThread was created using + // nsIThreadManager::NewThread. + bool ShutdownRequired() { return mShutdownRequired; } + + // Lets GetRunningEventDelay() determine if the pool this is part + // of has an unstarted thread + void SetPoolThreadFreePtr(mozilla::Atomic* aPtr) { + mIsAPoolThreadFree = aPtr; + } + + void SetScriptObserver(mozilla::CycleCollectedJSContext* aScriptObserver); + + uint32_t RecursionDepth() const; + + void ShutdownComplete(NotNull aContext); + + void WaitForAllAsynchronousShutdowns(); + + static const uint32_t kRunnableNameBufSize = 1000; + static mozilla::Array sMainThreadRunnableName; + + mozilla::SynchronizedEventQueue* EventQueue() { return mEvents.get(); } + + bool ShuttingDown() const { return mShutdownContext != nullptr; } + + static bool GetLabeledRunnableName(nsIRunnable* aEvent, nsACString& aName, + mozilla::EventQueuePriority aPriority); + + virtual mozilla::PerformanceCounter* GetPerformanceCounter( + nsIRunnable* aEvent) const; + + static mozilla::PerformanceCounter* GetPerformanceCounterBase( + nsIRunnable* aEvent); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + // Returns the size of this object, its PRThread, and its shutdown contexts, + // but excluding its event queues. + size_t ShallowSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const; + + size_t SizeOfEventQueues(mozilla::MallocSizeOf aMallocSizeOf) const; + + static nsThreadEnumerator Enumerate(); + + void SetUseHangMonitor(bool aValue) { + MOZ_ASSERT(IsOnCurrentThread()); + mUseHangMonitor = aValue; + } + + private: + void DoMainThreadSpecificProcessing() const; + + protected: + friend class nsThreadShutdownEvent; + + friend class nsThreadEnumerator; + + virtual ~nsThread(); + + static void ThreadFunc(void* aArg); + + // Helper + already_AddRefed GetObserver() { + nsIThreadObserver* obs; + nsThread::GetObserver(&obs); + return already_AddRefed(obs); + } + + already_AddRefed ShutdownInternal(bool aSync); + + friend class nsThreadManager; + friend class nsThreadPool; + + static mozilla::OffTheBooksMutex& ThreadListMutex(); + static mozilla::LinkedList& ThreadList(); + + void AddToThreadList(); + void MaybeRemoveFromThreadList(); + + // Whether or not these members have a value determines whether the nsThread + // is treated as a full XPCOM thread or as a thin wrapper. + // + // For full nsThreads, they will always contain valid pointers. For thin + // wrappers around non-XPCOM threads, they will be null, and event dispatch + // methods which rely on them will fail (and assert) if called. + RefPtr mEvents; + RefPtr mEventTarget; + + // The number of outstanding nsThreadShutdownContext started by this thread. + // The thread will not be allowed to exit until this number reaches 0. + uint32_t mOutstandingShutdownContexts; + // The shutdown context for ourselves. + RefPtr mShutdownContext; + + mozilla::CycleCollectedJSContext* mScriptObserver; + + // Our name. + mozilla::DataMutex mThreadName; + + void* mStackBase = nullptr; + uint32_t mStackSize; + uint32_t mThreadId; + + uint32_t mNestedEventLoopDepth; + + mozilla::Atomic mShutdownRequired; + + int8_t mPriority; + + const bool mIsMainThread; + bool mUseHangMonitor; + const bool mIsUiThread; + mozilla::Atomic* mIsAPoolThreadFree; + + // Set to true if this thread creates a JSRuntime. + bool mCanInvokeJS; + + // The time the currently running event spent in event queues, and + // when it started running. If no event is running, they are + // TimeDuration() & TimeStamp(). + mozilla::TimeDuration mLastEventDelay; + mozilla::TimeStamp mLastEventStart; + +#ifdef EARLY_BETA_OR_EARLIER + nsCString mNameForWakeupTelemetry; + mozilla::TimeStamp mLastWakeupCheckTime; + uint32_t mWakeupCount = 0; +#endif + + mozilla::PerformanceCounterState mPerformanceCounterState; + + mozilla::SimpleTaskQueue mDirectTasks; +}; + +class nsThreadShutdownContext final : public nsIThreadShutdown { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSITHREADSHUTDOWN + + private: + friend class nsThread; + friend class nsThreadShutdownEvent; + friend class nsThreadShutdownAckEvent; + + nsThreadShutdownContext(NotNull aTerminatingThread, + nsThread* aJoiningThread) + : mTerminatingThread(aTerminatingThread), + mTerminatingPRThread(aTerminatingThread->GetPRThread()), + mJoiningThreadMutex("nsThreadShutdownContext::mJoiningThreadMutex"), + mJoiningThread(aJoiningThread) {} + + ~nsThreadShutdownContext() = default; + + // Must be called on the joining thread. + void MarkCompleted(); + + // NB: This may be the last reference. + NotNull> const mTerminatingThread; + PRThread* const mTerminatingPRThread; + + // May only be accessed on the joining thread. + bool mCompleted = false; + nsTArray> mCompletionCallbacks; + + // The thread waiting for this thread to shut down. Will either be cleared by + // the joining thread if `StopWaitingAndLeakThread` is called or by the + // terminating thread upon exiting and notifying the joining thread. + mozilla::Mutex mJoiningThreadMutex; + RefPtr mJoiningThread MOZ_GUARDED_BY(mJoiningThreadMutex); + bool mThreadLeaked MOZ_GUARDED_BY(mJoiningThreadMutex) = false; +}; + +class MOZ_STACK_CLASS nsThreadEnumerator final { + public: + nsThreadEnumerator() = default; + + auto begin() { return nsThread::ThreadList().begin(); } + auto end() { return nsThread::ThreadList().end(); } + + private: + mozilla::OffTheBooksMutexAutoLock mMal{nsThread::ThreadListMutex()}; +}; + +#if defined(XP_UNIX) && !defined(ANDROID) && !defined(DEBUG) && HAVE_UALARM && \ + defined(_GNU_SOURCE) +# define MOZ_CANARY + +extern int sCanaryOutputFD; +#endif + +#endif // nsThread_h__ diff --git a/xpcom/threads/nsThreadManager.cpp b/xpcom/threads/nsThreadManager.cpp new file mode 100644 index 0000000000..367b520a54 --- /dev/null +++ b/xpcom/threads/nsThreadManager.cpp @@ -0,0 +1,798 @@ +/* -*- 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 "nsThreadManager.h" +#include "nsThread.h" +#include "nsThreadPool.h" +#include "nsThreadUtils.h" +#include "nsIClassInfoImpl.h" +#include "nsExceptionHandler.h" +#include "nsTArray.h" +#include "nsXULAppAPI.h" +#include "nsExceptionHandler.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/CycleCollectedJSContext.h" // nsAutoMicroTask +#include "mozilla/EventQueue.h" +#include "mozilla/InputTaskManager.h" +#include "mozilla/Mutex.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/ThreadEventQueue.h" +#include "mozilla/ThreadLocal.h" +#include "TaskController.h" +#include "ThreadEventTarget.h" +#ifdef MOZ_CANARY +# include +# include +#endif + +#include "MainThreadIdlePeriod.h" + +using namespace mozilla; + +static MOZ_THREAD_LOCAL(bool) sTLSIsMainThread; + +bool NS_IsMainThreadTLSInitialized() { return sTLSIsMainThread.initialized(); } + +class BackgroundEventTarget final : public nsIEventTarget, + public TaskQueueTracker { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIEVENTTARGET_FULL + + BackgroundEventTarget() = default; + + nsresult Init(); + + already_AddRefed CreateBackgroundTaskQueue( + const char* aName); + + void BeginShutdown(nsTArray>&); + void FinishShutdown(); + + private: + ~BackgroundEventTarget() = default; + + nsCOMPtr mPool; + nsCOMPtr mIOPool; +}; + +NS_IMPL_ISUPPORTS(BackgroundEventTarget, nsIEventTarget, TaskQueueTracker) + +nsresult BackgroundEventTarget::Init() { + nsCOMPtr pool(new nsThreadPool()); + NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); + + nsresult rv = pool->SetName("BackgroundThreadPool"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Use potentially more conservative stack size. + rv = pool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Thread limit of 2 makes deadlock during synchronous dispatch less likely. + rv = pool->SetThreadLimit(2); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pool->SetIdleThreadLimit(1); + NS_ENSURE_SUCCESS(rv, rv); + + // Leave threads alive for up to 5 minutes + rv = pool->SetIdleThreadTimeout(300000); + NS_ENSURE_SUCCESS(rv, rv); + + // Initialize the background I/O event target. + nsCOMPtr ioPool(new nsThreadPool()); + NS_ENSURE_TRUE(pool, NS_ERROR_FAILURE); + + // The io pool spends a lot of its time blocking on io, so we want to offload + // these jobs on a lower priority if available. + rv = ioPool->SetQoSForThreads(nsIThread::QOS_PRIORITY_LOW); + NS_ENSURE_SUCCESS( + rv, rv); // note: currently infallible, keeping this for brevity. + + rv = ioPool->SetName("BgIOThreadPool"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Use potentially more conservative stack size. + rv = ioPool->SetThreadStackSize(nsIThreadManager::kThreadPoolStackSize); + NS_ENSURE_SUCCESS(rv, rv); + + // Thread limit of 4 makes deadlock during synchronous dispatch less likely. + rv = ioPool->SetThreadLimit(4); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ioPool->SetIdleThreadLimit(1); + NS_ENSURE_SUCCESS(rv, rv); + + // Leave threads alive for up to 5 minutes + rv = ioPool->SetIdleThreadTimeout(300000); + NS_ENSURE_SUCCESS(rv, rv); + + pool.swap(mPool); + ioPool.swap(mIOPool); + + return NS_OK; +} + +NS_IMETHODIMP_(bool) +BackgroundEventTarget::IsOnCurrentThreadInfallible() { + return mPool->IsOnCurrentThread() || mIOPool->IsOnCurrentThread(); +} + +NS_IMETHODIMP +BackgroundEventTarget::IsOnCurrentThread(bool* aValue) { + bool value = false; + if (NS_SUCCEEDED(mPool->IsOnCurrentThread(&value)) && value) { + *aValue = value; + return NS_OK; + } + return mIOPool->IsOnCurrentThread(aValue); +} + +NS_IMETHODIMP +BackgroundEventTarget::Dispatch(already_AddRefed aRunnable, + uint32_t aFlags) { + // We need to be careful here, because if an event is getting dispatched here + // from within TaskQueue::Runner::Run, it will be dispatched with + // NS_DISPATCH_AT_END, but we might not be running the event on the same + // pool, depending on which pool we were on and the dispatch flags. If we + // dispatch an event with NS_DISPATCH_AT_END to the wrong pool, the pool + // may not process the event in a timely fashion, which can lead to deadlock. + uint32_t flags = aFlags & ~NS_DISPATCH_EVENT_MAY_BLOCK; + bool mayBlock = bool(aFlags & NS_DISPATCH_EVENT_MAY_BLOCK); + nsCOMPtr& pool = mayBlock ? mIOPool : mPool; + + // If we're already running on the pool we want to dispatch to, we can + // unconditionally add NS_DISPATCH_AT_END to indicate that we shouldn't spin + // up a new thread. + // + // Otherwise, we should remove NS_DISPATCH_AT_END so we don't run into issues + // like those in the above comment. + if (pool->IsOnCurrentThread()) { + flags |= NS_DISPATCH_AT_END; + } else { + flags &= ~NS_DISPATCH_AT_END; + } + + return pool->Dispatch(std::move(aRunnable), flags); +} + +NS_IMETHODIMP +BackgroundEventTarget::DispatchFromScript(nsIRunnable* aRunnable, + uint32_t aFlags) { + nsCOMPtr runnable(aRunnable); + return Dispatch(runnable.forget(), aFlags); +} + +NS_IMETHODIMP +BackgroundEventTarget::DelayedDispatch(already_AddRefed aRunnable, + uint32_t) { + nsCOMPtr dropRunnable(aRunnable); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BackgroundEventTarget::RegisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +BackgroundEventTarget::UnregisterShutdownTask(nsITargetShutdownTask* aTask) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +void BackgroundEventTarget::BeginShutdown( + nsTArray>& promises) { + auto queues = GetAllTrackedTaskQueues(); + for (auto& queue : queues) { + promises.AppendElement(queue->BeginShutdown()); + } +} + +void BackgroundEventTarget::FinishShutdown() { + mPool->Shutdown(); + mIOPool->Shutdown(); +} + +already_AddRefed +BackgroundEventTarget::CreateBackgroundTaskQueue(const char* aName) { + return TaskQueue::Create(do_AddRef(this), aName).forget(); +} + +extern "C" { +// This uses the C language linkage because it's exposed to Rust +// via the xpcom/rust/moz_task crate. +bool NS_IsMainThread() { return sTLSIsMainThread.get(); } +} + +void NS_SetMainThread() { + if (!sTLSIsMainThread.init()) { + MOZ_CRASH(); + } + sTLSIsMainThread.set(true); + MOZ_ASSERT(NS_IsMainThread()); + // We initialize the SerialEventTargetGuard's TLS here for simplicity as it + // needs to be initialized around the same time you would initialize + // sTLSIsMainThread. + SerialEventTargetGuard::InitTLS(); + nsThreadPool::InitTLS(); +} + +#ifdef DEBUG + +namespace mozilla { + +void AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); } + +} // namespace mozilla + +#endif + +typedef nsTArray>> nsThreadArray; + +//----------------------------------------------------------------------------- + +/* static */ +void nsThreadManager::ReleaseThread(void* aData) { + static_cast(aData)->Release(); +} + +// statically allocated instance +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::AddRef() { return 2; } +NS_IMETHODIMP_(MozExternalRefCountType) +nsThreadManager::Release() { return 1; } +NS_IMPL_CLASSINFO(nsThreadManager, nullptr, + nsIClassInfo::THREADSAFE | nsIClassInfo::SINGLETON, + NS_THREADMANAGER_CID) +NS_IMPL_QUERY_INTERFACE_CI(nsThreadManager, nsIThreadManager) +NS_IMPL_CI_INTERFACE_GETTER(nsThreadManager, nsIThreadManager) + +//----------------------------------------------------------------------------- + +/*static*/ nsThreadManager& nsThreadManager::get() { + static nsThreadManager sInstance; + return sInstance; +} + +nsThreadManager::nsThreadManager() + : mCurThreadIndex(0), mMainPRThread(nullptr), mInitialized(false) {} + +nsThreadManager::~nsThreadManager() = default; + +nsresult nsThreadManager::Init() { + // Child processes need to initialize the thread manager before they + // initialize XPCOM in order to set up the crash reporter. This leads to + // situations where we get initialized twice. + if (mInitialized) { + return NS_OK; + } + + if (PR_NewThreadPrivateIndex(&mCurThreadIndex, ReleaseThread) == PR_FAILURE) { + return NS_ERROR_FAILURE; + } + +#ifdef MOZ_CANARY + const int flags = O_WRONLY | O_APPEND | O_CREAT | O_NONBLOCK; + const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + char* env_var_flag = getenv("MOZ_KILL_CANARIES"); + sCanaryOutputFD = + env_var_flag + ? (env_var_flag[0] ? open(env_var_flag, flags, mode) : STDERR_FILENO) + : 0; +#endif + + TaskController::Initialize(); + + // Initialize idle handling. + nsCOMPtr idlePeriod = new MainThreadIdlePeriod(); + TaskController::Get()->SetIdleTaskManager( + new IdleTaskManager(idlePeriod.forget())); + + // Create main thread queue that forwards events to TaskController and + // construct main thread. + UniquePtr queue = MakeUnique(true); + + RefPtr synchronizedQueue = + new ThreadEventQueue(std::move(queue), true); + + mMainThread = new nsThread(WrapNotNull(synchronizedQueue), + nsThread::MAIN_THREAD, {.stackSize = 0}); + + nsresult rv = mMainThread->InitCurrentThread(); + if (NS_FAILED(rv)) { + mMainThread = nullptr; + return rv; + } + + // We need to keep a pointer to the current thread, so we can satisfy + // GetIsMainThread calls that occur post-Shutdown. + mMainThread->GetPRThread(&mMainPRThread); + + // Init AbstractThread. + AbstractThread::InitTLS(); + AbstractThread::InitMainThread(); + + // Initialize the background event target. + RefPtr target(new BackgroundEventTarget()); + + rv = target->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + mBackgroundEventTarget = std::move(target); + + mInitialized = true; + + return NS_OK; +} + +void nsThreadManager::ShutdownNonMainThreads() { + MOZ_ASSERT(NS_IsMainThread(), "shutdown not called from main thread"); + + // Prevent further access to the thread manager (no more new threads!) + // + // What happens if shutdown happens before NewThread completes? + // We Shutdown() the new thread, and return error if we've started Shutdown + // between when NewThread started, and when the thread finished initializing + // and registering with ThreadManager. + // + mInitialized = false; + + // Empty the main thread event queue before we begin shutting down threads. + NS_ProcessPendingEvents(mMainThread); + + mMainThread->mEvents->RunShutdownTasks(); + + nsTArray> promises; + mBackgroundEventTarget->BeginShutdown(promises); + + bool taskQueuesShutdown = false; + // It's fine to capture everything by reference in the Then handler since it + // runs before we exit the nested event loop, thanks to the SpinEventLoopUntil + // below. + ShutdownPromise::All(mMainThread, promises)->Then(mMainThread, __func__, [&] { + mBackgroundEventTarget->FinishShutdown(); + taskQueuesShutdown = true; + }); + + // Wait for task queues to shutdown, so we don't shut down the underlying + // threads of the background event target in the block below, thereby + // preventing the task queues from emptying, preventing the shutdown promises + // from resolving, and prevent anything checking `taskQueuesShutdown` from + // working. + mozilla::SpinEventLoopUntil( + "nsThreadManager::Shutdown"_ns, [&]() { return taskQueuesShutdown; }, + mMainThread); + + { + // We gather the threads into a list, so that we avoid holding the + // enumerator lock while calling nsIThread::Shutdown. + nsTArray> threadsToShutdown; + for (auto* thread : nsThread::Enumerate()) { + if (thread->ShutdownRequired()) { + threadsToShutdown.AppendElement(thread); + } + } + + // It's tempting to walk the list of threads here and tell them each to stop + // accepting new events, but that could lead to badness if one of those + // threads is stuck waiting for a response from another thread. To do it + // right, we'd need some way to interrupt the threads. + // + // Instead, we process events on the current thread while waiting for + // threads to shutdown. This means that we have to preserve a mostly + // functioning world until such time as the threads exit. + + // Shutdown all threads that require it (join with threads that we created). + for (auto& thread : threadsToShutdown) { + thread->Shutdown(); + } + } + + // NB: It's possible that there are events in the queue that want to *start* + // an asynchronous shutdown. But we have already shutdown the threads above, + // so there's no need to worry about them. We only have to wait for all + // in-flight asynchronous thread shutdowns to complete. + mMainThread->WaitForAllAsynchronousShutdowns(); + + // There are no more background threads at this point. +} + +void nsThreadManager::ShutdownMainThread() { + MOZ_ASSERT(!mInitialized, "Must have called BeginShutdown"); + + // Do NS_ProcessPendingEvents but with special handling to set + // mEventsAreDoomed atomically with the removal of the last event. This means + // that PutEvent cannot succeed if the event would be left in the main thread + // queue after our final call to NS_ProcessPendingEvents. + // See comments in `nsThread::ThreadFunc` for a more detailed explanation. + while (true) { + if (mMainThread->mEvents->ShutdownIfNoPendingEvents()) { + break; + } + NS_ProcessPendingEvents(mMainThread); + } + + // Normally thread shutdown clears the observer for the thread, but since the + // main thread is special we do it manually here after we're sure all events + // have been processed. + mMainThread->SetObserver(nullptr); + + mBackgroundEventTarget = nullptr; +} + +void nsThreadManager::ReleaseMainThread() { + MOZ_ASSERT(!mInitialized, "Must have called BeginShutdown"); + MOZ_ASSERT(!mBackgroundEventTarget, "Must have called ShutdownMainThread"); + MOZ_ASSERT(mMainThread); + + // Release main thread object. + mMainThread = nullptr; + + // Remove the TLS entry for the main thread. + PR_SetThreadPrivate(mCurThreadIndex, nullptr); +} + +void nsThreadManager::RegisterCurrentThread(nsThread& aThread) { + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + aThread.AddRef(); // for TLS entry + PR_SetThreadPrivate(mCurThreadIndex, &aThread); +} + +void nsThreadManager::UnregisterCurrentThread(nsThread& aThread) { + MOZ_ASSERT(aThread.GetPRThread() == PR_GetCurrentThread(), "bad aThread"); + + PR_SetThreadPrivate(mCurThreadIndex, nullptr); + // Ref-count balanced via ReleaseThread +} + +nsThread* nsThreadManager::CreateCurrentThread( + SynchronizedEventQueue* aQueue, nsThread::MainThreadFlag aMainThread) { + // Make sure we don't have an nsThread yet. + MOZ_ASSERT(!PR_GetThreadPrivate(mCurThreadIndex)); + + if (!mInitialized) { + return nullptr; + } + + RefPtr thread = + new nsThread(WrapNotNull(aQueue), aMainThread, {.stackSize = 0}); + if (!thread || NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +nsresult nsThreadManager::DispatchToBackgroundThread(nsIRunnable* aEvent, + uint32_t aDispatchFlags) { + if (!mInitialized) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr backgroundTarget(mBackgroundEventTarget); + return backgroundTarget->Dispatch(aEvent, aDispatchFlags); +} + +already_AddRefed +nsThreadManager::CreateBackgroundTaskQueue(const char* aName) { + if (!mInitialized) { + return nullptr; + } + + return mBackgroundEventTarget->CreateBackgroundTaskQueue(aName); +} + +nsThread* nsThreadManager::GetCurrentThread() { + // read thread local storage + void* data = PR_GetThreadPrivate(mCurThreadIndex); + if (data) { + return static_cast(data); + } + + if (!mInitialized) { + return nullptr; + } + + // OK, that's fine. We'll dynamically create one :-) + // + // We assume that if we're implicitly creating a thread here that it doesn't + // want an event queue. Any thread which wants an event queue should + // explicitly create its nsThread wrapper. + RefPtr thread = new nsThread(); + if (!thread || NS_FAILED(thread->InitCurrentThread())) { + return nullptr; + } + + return thread.get(); // reference held in TLS +} + +bool nsThreadManager::IsNSThread() const { + if (!mInitialized) { + return false; + } + if (auto* thread = (nsThread*)PR_GetThreadPrivate(mCurThreadIndex)) { + return thread->EventQueue(); + } + return false; +} + +NS_IMETHODIMP +nsThreadManager::NewNamedThread( + const nsACString& aName, nsIThreadManager::ThreadCreationOptions aOptions, + nsIThread** aResult) { + // Note: can be called from arbitrary threads + + // No new threads during Shutdown + if (NS_WARN_IF(!mInitialized)) { + return NS_ERROR_NOT_INITIALIZED; + } + + [[maybe_unused]] TimeStamp startTime = TimeStamp::Now(); + + RefPtr queue = + new ThreadEventQueue(MakeUnique()); + RefPtr thr = + new nsThread(WrapNotNull(queue), nsThread::NOT_MAIN_THREAD, aOptions); + nsresult rv = + thr->Init(aName); // Note: blocks until the new thread has been set up + if (NS_FAILED(rv)) { + return rv; + } + + // At this point, we expect that the thread has been registered in + // mThreadByPRThread; however, it is possible that it could have also been + // replaced by now, so we cannot really assert that it was added. Instead, + // kill it if we entered Shutdown() during/before Init() + + if (NS_WARN_IF(!mInitialized)) { + if (thr->ShutdownRequired()) { + thr->Shutdown(); // ok if it happens multiple times + } + return NS_ERROR_NOT_INITIALIZED; + } + + PROFILER_MARKER_TEXT( + "NewThread", OTHER, + MarkerOptions(MarkerStack::Capture(), + MarkerTiming::IntervalUntilNowFrom(startTime)), + aName); + if (!NS_IsMainThread()) { + PROFILER_MARKER_TEXT( + "NewThread (non-main thread)", OTHER, + MarkerOptions(MarkerStack::Capture(), MarkerThreadId::MainThread(), + MarkerTiming::IntervalUntilNowFrom(startTime)), + aName); + } + + thr.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetMainThread(nsIThread** aResult) { + // Keep this functioning during Shutdown + if (!mMainThread) { + if (!NS_IsMainThread()) { + NS_WARNING( + "Called GetMainThread but there isn't a main thread and " + "we're not the main thread."); + } + return NS_ERROR_NOT_INITIALIZED; + } + NS_ADDREF(*aResult = mMainThread); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetCurrentThread(nsIThread** aResult) { + // Keep this functioning during Shutdown + if (!mMainThread) { + return NS_ERROR_NOT_INITIALIZED; + } + *aResult = GetCurrentThread(); + if (!*aResult) { + return NS_ERROR_OUT_OF_MEMORY; + } + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntil(const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition) { + return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition, + ShutdownPhase::NotInShutdown); +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntilOrQuit( + const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition) { + return SpinEventLoopUntilInternal(aVeryGoodReasonToDoThis, aCondition, + ShutdownPhase::AppShutdownConfirmed); +} + +// statics from SpinEventLoopUntil.h +AutoNestedEventLoopAnnotation* AutoNestedEventLoopAnnotation::sCurrent = + nullptr; +StaticMutex AutoNestedEventLoopAnnotation::sStackMutex; + +// static from SpinEventLoopUntil.h +void AutoNestedEventLoopAnnotation::AnnotateXPCOMSpinEventLoopStack( + const nsACString& aStack) { + if (aStack.Length() > 0) { + nsCString prefixedStack(XRE_GetProcessTypeString()); + prefixedStack += ": "_ns + aStack; + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::XPCOMSpinEventLoopStack, prefixedStack); + } else { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::XPCOMSpinEventLoopStack, ""_ns); + } +} + +nsresult nsThreadManager::SpinEventLoopUntilInternal( + const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition, + ShutdownPhase aShutdownPhaseToCheck) { + // XXX: We would want to AssertIsOnMainThread(); but that breaks some GTest. + nsCOMPtr condition(aCondition); + nsresult rv = NS_OK; + + if (!mozilla::SpinEventLoopUntil(aVeryGoodReasonToDoThis, [&]() -> bool { + // Check if an ongoing shutdown reached our limits. + if (aShutdownPhaseToCheck > ShutdownPhase::NotInShutdown && + AppShutdown::GetCurrentShutdownPhase() >= aShutdownPhaseToCheck) { + return true; + } + + bool isDone = false; + rv = condition->IsDone(&isDone); + // JS failure should be unusual, but we need to stop and propagate + // the error back to the caller. + if (NS_FAILED(rv)) { + return true; + } + + return isDone; + })) { + // We stopped early for some reason, which is unexpected. + return NS_ERROR_UNEXPECTED; + } + + // If we exited when the condition told us to, we need to return whether + // the condition encountered failure when executing. + return rv; +} + +NS_IMETHODIMP +nsThreadManager::SpinEventLoopUntilEmpty() { + nsIThread* thread = NS_GetCurrentThread(); + + while (NS_HasPendingEvents(thread)) { + (void)NS_ProcessNextEvent(thread, false); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::GetMainThreadEventTarget(nsIEventTarget** aTarget) { + nsCOMPtr target = GetMainThreadSerialEventTarget(); + target.forget(aTarget); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadManager::DispatchToMainThread(nsIRunnable* aEvent, uint32_t aPriority, + uint8_t aArgc) { + // Note: C++ callers should instead use NS_DispatchToMainThread. + MOZ_ASSERT(NS_IsMainThread()); + + // Keep this functioning during Shutdown + if (NS_WARN_IF(!mMainThread)) { + return NS_ERROR_NOT_INITIALIZED; + } + // If aPriority wasn't explicitly passed, that means it should be treated as + // PRIORITY_NORMAL. + if (aArgc > 0 && aPriority != nsIRunnablePriority::PRIORITY_NORMAL) { + nsCOMPtr event(aEvent); + return mMainThread->DispatchFromScript( + new PrioritizableRunnable(event.forget(), aPriority), 0); + } + return mMainThread->DispatchFromScript(aEvent, 0); +} + +class AutoMicroTaskWrapperRunnable final : public Runnable { + public: + explicit AutoMicroTaskWrapperRunnable(nsIRunnable* aEvent) + : Runnable("AutoMicroTaskWrapperRunnable"), mEvent(aEvent) { + MOZ_ASSERT(aEvent); + } + + private: + ~AutoMicroTaskWrapperRunnable() = default; + + NS_IMETHOD Run() override { + nsAutoMicroTask mt; + + return mEvent->Run(); + } + + RefPtr mEvent; +}; + +NS_IMETHODIMP +nsThreadManager::DispatchToMainThreadWithMicroTask(nsIRunnable* aEvent, + uint32_t aPriority, + uint8_t aArgc) { + RefPtr runnable = + new AutoMicroTaskWrapperRunnable(aEvent); + + return DispatchToMainThread(runnable, aPriority, aArgc); +} + +void nsThreadManager::EnableMainThreadEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->EnableInputEventPrioritization(); +} + +void nsThreadManager::FlushInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->FlushInputEventPrioritization(); +} + +void nsThreadManager::SuspendInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->SuspendInputEventPrioritization(); +} + +void nsThreadManager::ResumeInputEventPrioritization() { + MOZ_ASSERT(NS_IsMainThread()); + InputTaskManager::Get()->ResumeInputEventPrioritization(); +} + +// static +bool nsThreadManager::MainThreadHasPendingHighPriorityEvents() { + MOZ_ASSERT(NS_IsMainThread()); + bool retVal = false; + if (get().mMainThread) { + get().mMainThread->HasPendingHighPriorityEvents(&retVal); + } + return retVal; +} + +NS_IMETHODIMP +nsThreadManager::IdleDispatchToMainThread(nsIRunnable* aEvent, + uint32_t aTimeout) { + // Note: C++ callers should instead use NS_DispatchToThreadQueue or + // NS_DispatchToCurrentThreadQueue. + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr event(aEvent); + if (aTimeout) { + return NS_DispatchToThreadQueue(event.forget(), aTimeout, mMainThread, + EventQueuePriority::Idle); + } + + return NS_DispatchToThreadQueue(event.forget(), mMainThread, + EventQueuePriority::Idle); +} + +NS_IMETHODIMP +nsThreadManager::DispatchDirectTaskToCurrentThread(nsIRunnable* aEvent) { + NS_ENSURE_STATE(aEvent); + nsCOMPtr runnable = aEvent; + return GetCurrentThread()->DispatchDirectTask(runnable.forget()); +} diff --git a/xpcom/threads/nsThreadManager.h b/xpcom/threads/nsThreadManager.h new file mode 100644 index 0000000000..444cdb23d2 --- /dev/null +++ b/xpcom/threads/nsThreadManager.h @@ -0,0 +1,117 @@ +/* -*- 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/. */ + +#ifndef nsThreadManager_h__ +#define nsThreadManager_h__ + +#include "nsIThreadManager.h" +#include "nsThread.h" +#include "mozilla/ShutdownPhase.h" + +class nsIRunnable; +class nsIEventTarget; +class nsISerialEventTarget; +class nsIThread; + +namespace mozilla { +class IdleTaskManager; +class SynchronizedEventQueue; +} // namespace mozilla + +class BackgroundEventTarget; + +class nsThreadManager : public nsIThreadManager { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITHREADMANAGER + + static nsThreadManager& get(); + + nsresult Init(); + + // Shutdown all threads other than the main thread. This function should only + // be called on the main thread of the application process. + void ShutdownNonMainThreads(); + + // Finish shutting down all threads. This function must be called after + // ShutdownNonMainThreads and will delete the BackgroundEventTarget and + // take the main thread event target out of commission, but without + // releasing the underlying nsThread object. + void ShutdownMainThread(); + + // Release the underlying main thread nsThread object. + void ReleaseMainThread(); + + // Called by nsThread to inform the ThreadManager it exists. This method + // must be called when the given thread is the current thread. + void RegisterCurrentThread(nsThread& aThread); + + // Called by nsThread to inform the ThreadManager it is going away. This + // method must be called when the given thread is the current thread. + void UnregisterCurrentThread(nsThread& aThread); + + // Returns the current thread. Returns null if OOM or if ThreadManager isn't + // initialized. Creates the nsThread if one does not exist yet. + nsThread* GetCurrentThread(); + + // Returns true iff the currently running thread has an nsThread associated + // with it (ie; whether this is a thread that we can dispatch runnables to). + bool IsNSThread() const; + + // CreateCurrentThread sets up an nsThread for the current thread. It uses the + // event queue and main thread flags passed in. It should only be called once + // for the current thread. After it returns, GetCurrentThread() will return + // the thread that was created. GetCurrentThread() will also create a thread + // (lazily), but it doesn't allow the queue or main-thread attributes to be + // specified. + nsThread* CreateCurrentThread(mozilla::SynchronizedEventQueue* aQueue, + nsThread::MainThreadFlag aMainThread); + + nsresult DispatchToBackgroundThread(nsIRunnable* aEvent, + uint32_t aDispatchFlags); + + already_AddRefed CreateBackgroundTaskQueue( + const char* aName); + + ~nsThreadManager(); + + void EnableMainThreadEventPrioritization(); + void FlushInputEventPrioritization(); + void SuspendInputEventPrioritization(); + void ResumeInputEventPrioritization(); + + static bool MainThreadHasPendingHighPriorityEvents(); + + nsIThread* GetMainThreadWeak() { return mMainThread; } + + private: + nsThreadManager(); + + nsresult SpinEventLoopUntilInternal( + const nsACString& aVeryGoodReasonToDoThis, + nsINestedEventLoopCondition* aCondition, + mozilla::ShutdownPhase aShutdownPhaseToCheck); + + static void ReleaseThread(void* aData); + + unsigned mCurThreadIndex; // thread-local-storage index + RefPtr mIdleTaskManager; + RefPtr mMainThread; + PRThread* mMainPRThread; + mozilla::Atomic mInitialized; + + // Shared event target used for background runnables. + RefPtr mBackgroundEventTarget; +}; + +#define NS_THREADMANAGER_CID \ + { /* 7a4204c6-e45a-4c37-8ebb-6709a22c917c */ \ + 0x7a4204c6, 0xe45a, 0x4c37, { \ + 0x8e, 0xbb, 0x67, 0x09, 0xa2, 0x2c, 0x91, 0x7c \ + } \ + } + +#endif // nsThreadManager_h__ diff --git a/xpcom/threads/nsThreadPool.cpp b/xpcom/threads/nsThreadPool.cpp new file mode 100644 index 0000000000..362e18f5a7 --- /dev/null +++ b/xpcom/threads/nsThreadPool.cpp @@ -0,0 +1,611 @@ +/* -*- 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 "nsThreadPool.h" + +#include "nsCOMArray.h" +#include "ThreadDelay.h" +#include "nsThreadManager.h" +#include "nsThread.h" +#include "nsThreadUtils.h" +#include "prinrval.h" +#include "mozilla/Logging.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerRunnable.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "nsThreadSyncDispatch.h" + +#include + +using namespace mozilla; + +static LazyLogModule sThreadPoolLog("nsThreadPool"); +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sThreadPoolLog, mozilla::LogLevel::Debug, args) + +static MOZ_THREAD_LOCAL(nsThreadPool*) gCurrentThreadPool; + +void nsThreadPool::InitTLS() { gCurrentThreadPool.infallibleInit(); } + +// DESIGN: +// o Allocate anonymous threads. +// o Use nsThreadPool::Run as the main routine for each thread. +// o Each thread waits on the event queue's monitor, checking for +// pending events and rescheduling itself as an idle thread. + +#define DEFAULT_THREAD_LIMIT 4 +#define DEFAULT_IDLE_THREAD_LIMIT 1 +#define DEFAULT_IDLE_THREAD_TIMEOUT PR_SecondsToInterval(60) + +NS_IMPL_ISUPPORTS_INHERITED(nsThreadPool, Runnable, nsIThreadPool, + nsIEventTarget) + +nsThreadPool* nsThreadPool::GetCurrentThreadPool() { + return gCurrentThreadPool.get(); +} + +nsThreadPool::nsThreadPool() + : Runnable("nsThreadPool"), + mMutex("[nsThreadPool.mMutex]"), + mEventsAvailable(mMutex, "[nsThreadPool.mEventsAvailable]"), + mThreadLimit(DEFAULT_THREAD_LIMIT), + mIdleThreadLimit(DEFAULT_IDLE_THREAD_LIMIT), + mIdleThreadTimeout(DEFAULT_IDLE_THREAD_TIMEOUT), + mIdleCount(0), + mQoSPriority(nsIThread::QOS_PRIORITY_NORMAL), + mStackSize(nsIThreadManager::DEFAULT_STACK_SIZE), + mShutdown(false), + mRegressiveMaxIdleTime(false), + mIsAPoolThreadFree(true) { + LOG(("THRD-P(%p) constructor!!!\n", this)); +} + +nsThreadPool::~nsThreadPool() { + // Threads keep a reference to the nsThreadPool until they return from Run() + // after removing themselves from mThreads. + MOZ_ASSERT(mThreads.IsEmpty()); +} + +nsresult nsThreadPool::PutEvent(nsIRunnable* aEvent) { + nsCOMPtr event(aEvent); + return PutEvent(event.forget(), 0); +} + +nsresult nsThreadPool::PutEvent(already_AddRefed aEvent, + uint32_t aFlags) { + // Avoid spawning a new thread while holding the event queue lock... + + bool spawnThread = false; + uint32_t stackSize = 0; + nsCString name; + { + MutexAutoLock lock(mMutex); + + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } + LOG(("THRD-P(%p) put [%d %d %d]\n", this, mIdleCount, mThreads.Count(), + mThreadLimit)); + MOZ_ASSERT(mIdleCount <= (uint32_t)mThreads.Count(), "oops"); + + // Make sure we have a thread to service this event. + if (mThreads.Count() < (int32_t)mThreadLimit && + !(aFlags & NS_DISPATCH_AT_END) && + // Spawn a new thread if we don't have enough idle threads to serve + // pending events immediately. + mEvents.Count(lock) >= mIdleCount) { + spawnThread = true; + } + + nsCOMPtr event(aEvent); + LogRunnable::LogDispatch(event); + mEvents.PutEvent(event.forget(), EventQueuePriority::Normal, lock); + mEventsAvailable.Notify(); + stackSize = mStackSize; + name = mName; + } + + auto delay = MakeScopeExit([&]() { + // Delay to encourage the receiving task to run before we do work. + DelayForChaosMode(ChaosFeature::TaskDispatching, 1000); + }); + + LOG(("THRD-P(%p) put [spawn=%d]\n", this, spawnThread)); + if (!spawnThread) { + return NS_OK; + } + + nsCOMPtr thread; + nsresult rv = NS_NewNamedThread( + mThreadNaming.GetNextThreadName(name), getter_AddRefs(thread), nullptr, + {.stackSize = stackSize, .blockDispatch = true}); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NS_ERROR_UNEXPECTED; + } + + bool killThread = false; + { + MutexAutoLock lock(mMutex); + if (mShutdown) { + killThread = true; + } else if (mThreads.Count() < (int32_t)mThreadLimit) { + mThreads.AppendObject(thread); + if (mThreads.Count() >= (int32_t)mThreadLimit) { + mIsAPoolThreadFree = false; + } + } else { + // Someone else may have also been starting a thread + killThread = true; // okay, we don't need this thread anymore + } + } + LOG(("THRD-P(%p) put [%p kill=%d]\n", this, thread.get(), killThread)); + if (killThread) { + // We never dispatched any events to the thread, so we can shut it down + // asynchronously without worrying about anything. + ShutdownThread(thread); + } else { + thread->Dispatch(this, NS_DISPATCH_IGNORE_BLOCK_DISPATCH); + } + + return NS_OK; +} + +void nsThreadPool::ShutdownThread(nsIThread* aThread) { + LOG(("THRD-P(%p) shutdown async [%p]\n", this, aThread)); + + // This is either called by a threadpool thread that is out of work, or + // a thread that attempted to create a threadpool thread and raced in + // such a way that the newly created thread is no longer necessary. + // In the first case, we must go to another thread to shut aThread down + // (because it is the current thread). In the second case, we cannot + // synchronously shut down the current thread (because then Dispatch() would + // spin the event loop, and that could blow up the world), and asynchronous + // shutdown requires this thread have an event loop (and it may not, see bug + // 10204784). The simplest way to cover all cases is to asynchronously + // shutdown aThread from the main thread. + SchedulerGroup::Dispatch( + TaskCategory::Other, + NewRunnableMethod("nsIThread::AsyncShutdown", aThread, + &nsIThread::AsyncShutdown)); +} + +NS_IMETHODIMP +nsThreadPool::SetQoSForThreads(nsIThread::QoSPriority aPriority) { + MutexAutoLock lock(mMutex); + mQoSPriority = aPriority; + + // We don't notify threads here to observe the change, because we don't want + // to create spurious wakeups during idle. Rather, we want threads to simply + // observe the change on their own if they wake up to do some task. + + return NS_OK; +} + +// This event 'runs' for the lifetime of the worker thread. The actual +// eventqueue is mEvents, and is shared by all the worker threads. This +// means that the set of threads together define the delay seen by a new +// event sent to the pool. +// +// To model the delay experienced by the pool, we can have each thread in +// the pool report 0 if it's idle OR if the pool is below the threadlimit; +// or otherwise the current event's queuing delay plus current running +// time. +// +// To reconstruct the delays for the pool, the profiler can look at all the +// threads that are part of a pool (pools have defined naming patterns that +// can be user to connect them). If all threads have delays at time X, +// that means that all threads saturated at that point and any event +// dispatched to the pool would get a delay. +// +// The delay experienced by an event dispatched when all pool threads are +// busy is based on the calculations shown in platform.cpp. Run that +// algorithm for each thread in the pool, and the delay at time X is the +// longest value for time X of any of the threads, OR the time from X until +// any one of the threads reports 0 (i.e. it's not busy), whichever is +// shorter. + +// In order to record this when the profiler samples threads in the pool, +// each thread must (effectively) override GetRunnningEventDelay, by +// resetting the mLastEventDelay/Start values in the nsThread when we start +// to run an event (or when we run out of events to run). Note that handling +// the shutdown of a thread may be a little tricky. + +NS_IMETHODIMP +nsThreadPool::Run() { + nsCOMPtr current; + nsThreadManager::get().GetCurrentThread(getter_AddRefs(current)); + + bool shutdownThreadOnExit = false; + bool exitThread = false; + bool wasIdle = false; + TimeStamp idleSince; + nsIThread::QoSPriority threadPriority = nsIThread::QOS_PRIORITY_NORMAL; + + // This thread is an nsThread created below with NS_NewNamedThread() + static_cast(current.get()) + ->SetPoolThreadFreePtr(&mIsAPoolThreadFree); + + nsCOMPtr listener; + { + MutexAutoLock lock(mMutex); + listener = mListener; + LOG(("THRD-P(%p) enter %s\n", this, mName.BeginReading())); + + // Go ahead and check for thread priority. If priority is normal, do nothing + // because threads are created with default priority. + if (threadPriority != mQoSPriority) { + current->SetThreadQoS(threadPriority); + threadPriority = mQoSPriority; + } + } + + if (listener) { + listener->OnThreadCreated(); + } + + MOZ_ASSERT(!gCurrentThreadPool.get()); + gCurrentThreadPool.set(this); + + do { + nsCOMPtr event; + TimeDuration delay; + { + MutexAutoLock lock(mMutex); + + // Before getting the next event, we can adjust priority as needed. + if (threadPriority != mQoSPriority) { + current->SetThreadQoS(threadPriority); + threadPriority = mQoSPriority; + } + + event = mEvents.GetEvent(lock, &delay); + if (!event) { + TimeStamp now = TimeStamp::Now(); + uint32_t idleTimeoutDivider = + (mIdleCount && mRegressiveMaxIdleTime) ? mIdleCount : 1; + TimeDuration timeout = TimeDuration::FromMilliseconds( + static_cast(mIdleThreadTimeout) / idleTimeoutDivider); + + // If we are shutting down, then don't keep any idle threads. + if (mShutdown) { + exitThread = true; + } else { + if (wasIdle) { + // if too many idle threads or idle for too long, then bail. + if (mIdleCount > mIdleThreadLimit || + (mIdleThreadTimeout != UINT32_MAX && + (now - idleSince) >= timeout)) { + exitThread = true; + } + } else { + // if would be too many idle threads... + if (mIdleCount == mIdleThreadLimit) { + exitThread = true; + } else { + ++mIdleCount; + idleSince = now; + wasIdle = true; + } + } + } + + if (exitThread) { + if (wasIdle) { + --mIdleCount; + } + shutdownThreadOnExit = mThreads.RemoveObject(current); + + // keep track if there are threads available to start + mIsAPoolThreadFree = (mThreads.Count() < (int32_t)mThreadLimit); + } else { + current->SetRunningEventDelay(TimeDuration(), TimeStamp()); + + AUTO_PROFILER_LABEL("nsThreadPool::Run::Wait", IDLE); + + TimeDuration delta = timeout - (now - idleSince); + LOG(("THRD-P(%p) %s waiting [%f]\n", this, mName.BeginReading(), + delta.ToMilliseconds())); + mEventsAvailable.Wait(delta); + LOG(("THRD-P(%p) done waiting\n", this)); + } + } else if (wasIdle) { + wasIdle = false; + --mIdleCount; + } + } + if (event) { + if (MOZ_LOG_TEST(sThreadPoolLog, mozilla::LogLevel::Debug)) { + MutexAutoLock lock(mMutex); + LOG(("THRD-P(%p) %s running [%p]\n", this, mName.BeginReading(), + event.get())); + } + + // Delay event processing to encourage whoever dispatched this event + // to run. + DelayForChaosMode(ChaosFeature::TaskRunning, 1000); + + if (profiler_thread_is_being_profiled( + ThreadProfilingFeatures::Sampling)) { + // We'll handle the case of unstarted threads available + // when we sample. + current->SetRunningEventDelay(delay, TimeStamp::Now()); + } + + LogRunnable::Run log(event); + AUTO_PROFILE_FOLLOWING_RUNNABLE(event); + event->Run(); + // To cover the event's destructor code in the LogRunnable span + event = nullptr; + } + } while (!exitThread); + + if (listener) { + listener->OnThreadShuttingDown(); + } + + MOZ_ASSERT(gCurrentThreadPool.get() == this); + gCurrentThreadPool.set(nullptr); + + if (shutdownThreadOnExit) { + ShutdownThread(current); + } + + LOG(("THRD-P(%p) leave\n", this)); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::DispatchFromScript(nsIRunnable* aEvent, uint32_t aFlags) { + nsCOMPtr event(aEvent); + return Dispatch(event.forget(), aFlags); +} + +NS_IMETHODIMP +nsThreadPool::Dispatch(already_AddRefed aEvent, uint32_t aFlags) { + LOG(("THRD-P(%p) dispatch [%p %x]\n", this, /* XXX aEvent*/ nullptr, aFlags)); + + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ASSERTION(aFlags == NS_DISPATCH_NORMAL || aFlags == NS_DISPATCH_AT_END, + "unexpected dispatch flags"); + PutEvent(std::move(aEvent), aFlags); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::DelayedDispatch(already_AddRefed, uint32_t) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsThreadPool::RegisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsThreadPool::UnregisterShutdownTask(nsITargetShutdownTask*) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP_(bool) +nsThreadPool::IsOnCurrentThreadInfallible() { + return gCurrentThreadPool.get() == this; +} + +NS_IMETHODIMP +nsThreadPool::IsOnCurrentThread(bool* aResult) { + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(mShutdown)) { + return NS_ERROR_NOT_AVAILABLE; + } + + *aResult = IsOnCurrentThreadInfallible(); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::Shutdown() { return ShutdownWithTimeout(-1); } + +NS_IMETHODIMP +nsThreadPool::ShutdownWithTimeout(int32_t aTimeoutMs) { + nsCOMArray threads; + nsCOMPtr listener; + { + MutexAutoLock lock(mMutex); + if (mShutdown) { + return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; + } + mShutdown = true; + mEventsAvailable.NotifyAll(); + + threads.AppendObjects(mThreads); + mThreads.Clear(); + + // Swap in a null listener so that we release the listener at the end of + // this method. The listener will be kept alive as long as the other threads + // that were created when it was set. + mListener.swap(listener); + } + + nsTArray> contexts; + for (int32_t i = 0; i < threads.Count(); ++i) { + nsCOMPtr context; + if (NS_SUCCEEDED(threads[i]->BeginShutdown(getter_AddRefs(context)))) { + contexts.AppendElement(std::move(context)); + } + } + + // Start a timer which will stop waiting & leak the thread, forcing + // onCompletion to be called when it expires. + nsCOMPtr timer; + if (aTimeoutMs >= 0) { + NS_NewTimerWithCallback( + getter_AddRefs(timer), + [&](nsITimer*) { + for (auto& context : contexts) { + context->StopWaitingAndLeakThread(); + } + }, + aTimeoutMs, nsITimer::TYPE_ONE_SHOT, + "nsThreadPool::ShutdownWithTimeout"); + } + + // Start a counter and register a callback to decrement outstandingThreads + // when the threads finish exiting. We'll spin an event loop until + // outstandingThreads reaches 0. + uint32_t outstandingThreads = contexts.Length(); + RefPtr onCompletion = NS_NewCancelableRunnableFunction( + "nsThreadPool thread completion", [&] { --outstandingThreads; }); + for (auto& context : contexts) { + context->OnCompletion(onCompletion); + } + + mozilla::SpinEventLoopUntil("nsThreadPool::ShutdownWithTimeout"_ns, + [&] { return outstandingThreads == 0; }); + + if (timer) { + timer->Cancel(); + } + onCompletion->Cancel(); + + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetThreadLimit(uint32_t* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mThreadLimit; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetThreadLimit(uint32_t aValue) { + MutexAutoLock lock(mMutex); + LOG(("THRD-P(%p) thread limit [%u]\n", this, aValue)); + mThreadLimit = aValue; + if (mIdleThreadLimit > mThreadLimit) { + mIdleThreadLimit = mThreadLimit; + } + + if (static_cast(mThreads.Count()) > mThreadLimit) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadLimit(uint32_t* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mIdleThreadLimit; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetIdleThreadLimit(uint32_t aValue) { + MutexAutoLock lock(mMutex); + LOG(("THRD-P(%p) idle thread limit [%u]\n", this, aValue)); + mIdleThreadLimit = aValue; + if (mIdleThreadLimit > mThreadLimit) { + mIdleThreadLimit = mThreadLimit; + } + + // Do we need to kill some idle threads? + if (mIdleCount > mIdleThreadLimit) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadTimeout(uint32_t* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mIdleThreadTimeout; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetIdleThreadTimeout(uint32_t aValue) { + MutexAutoLock lock(mMutex); + uint32_t oldTimeout = mIdleThreadTimeout; + mIdleThreadTimeout = aValue; + + // Do we need to notify any idle threads that their sleep time has shortened? + if (mIdleThreadTimeout < oldTimeout && mIdleCount > 0) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetIdleThreadTimeoutRegressive(bool* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mRegressiveMaxIdleTime; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetIdleThreadTimeoutRegressive(bool aValue) { + MutexAutoLock lock(mMutex); + bool oldRegressive = mRegressiveMaxIdleTime; + mRegressiveMaxIdleTime = aValue; + + // Would setting regressive timeout effect idle threads? + if (mRegressiveMaxIdleTime > oldRegressive && mIdleCount > 1) { + mEventsAvailable + .NotifyAll(); // wake up threads so they observe this change + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetThreadStackSize(uint32_t* aValue) { + MutexAutoLock lock(mMutex); + *aValue = mStackSize; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetThreadStackSize(uint32_t aValue) { + MutexAutoLock lock(mMutex); + mStackSize = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::GetListener(nsIThreadPoolListener** aListener) { + MutexAutoLock lock(mMutex); + NS_IF_ADDREF(*aListener = mListener); + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetListener(nsIThreadPoolListener* aListener) { + nsCOMPtr swappedListener(aListener); + { + MutexAutoLock lock(mMutex); + mListener.swap(swappedListener); + } + return NS_OK; +} + +NS_IMETHODIMP +nsThreadPool::SetName(const nsACString& aName) { + MutexAutoLock lock(mMutex); + if (mThreads.Count()) { + return NS_ERROR_NOT_AVAILABLE; + } + mName = aName; + return NS_OK; +} diff --git a/xpcom/threads/nsThreadPool.h b/xpcom/threads/nsThreadPool.h new file mode 100644 index 0000000000..b10a2f5265 --- /dev/null +++ b/xpcom/threads/nsThreadPool.h @@ -0,0 +1,68 @@ +/* -*- 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/. */ + +#ifndef nsThreadPool_h__ +#define nsThreadPool_h__ + +#include "nsIThreadPool.h" +#include "nsIRunnable.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/AlreadyAddRefed.h" +#include "mozilla/CondVar.h" +#include "mozilla/EventQueue.h" +#include "mozilla/Mutex.h" + +class nsIThread; + +class nsThreadPool final : public mozilla::Runnable, public nsIThreadPool { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIEVENTTARGET_FULL + NS_DECL_NSITHREADPOOL + NS_DECL_NSIRUNNABLE + + nsThreadPool(); + + static void InitTLS(); + static nsThreadPool* GetCurrentThreadPool(); + + private: + ~nsThreadPool(); + + void ShutdownThread(nsIThread* aThread); + nsresult PutEvent(nsIRunnable* aEvent); + nsresult PutEvent(already_AddRefed aEvent, uint32_t aFlags); + + mozilla::Mutex mMutex; + nsCOMArray mThreads MOZ_GUARDED_BY(mMutex); + mozilla::CondVar mEventsAvailable MOZ_GUARDED_BY(mMutex); + mozilla::EventQueue mEvents MOZ_GUARDED_BY(mMutex); + uint32_t mThreadLimit MOZ_GUARDED_BY(mMutex); + uint32_t mIdleThreadLimit MOZ_GUARDED_BY(mMutex); + uint32_t mIdleThreadTimeout MOZ_GUARDED_BY(mMutex); + uint32_t mIdleCount MOZ_GUARDED_BY(mMutex); + nsIThread::QoSPriority mQoSPriority MOZ_GUARDED_BY(mMutex); + uint32_t mStackSize MOZ_GUARDED_BY(mMutex); + nsCOMPtr mListener MOZ_GUARDED_BY(mMutex); + mozilla::Atomic mShutdown; + bool mRegressiveMaxIdleTime MOZ_GUARDED_BY(mMutex); + mozilla::Atomic mIsAPoolThreadFree; + // set once before we start threads + nsCString mName MOZ_GUARDED_BY(mMutex); + nsThreadPoolNaming mThreadNaming; // all data inside this is atomic +}; + +#define NS_THREADPOOL_CID \ + { /* 547ec2a8-315e-4ec4-888e-6e4264fe90eb */ \ + 0x547ec2a8, 0x315e, 0x4ec4, { \ + 0x88, 0x8e, 0x6e, 0x42, 0x64, 0xfe, 0x90, 0xeb \ + } \ + } + +#endif // nsThreadPool_h__ diff --git a/xpcom/threads/nsThreadSyncDispatch.h b/xpcom/threads/nsThreadSyncDispatch.h new file mode 100644 index 0000000000..1673453f9d --- /dev/null +++ b/xpcom/threads/nsThreadSyncDispatch.h @@ -0,0 +1,65 @@ +/* -*- 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/. */ + +#ifndef nsThreadSyncDispatch_h_ +#define nsThreadSyncDispatch_h_ + +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/SpinEventLoopUntil.h" + +#include "nsThreadUtils.h" +#include "LeakRefPtr.h" + +class nsThreadSyncDispatch : public mozilla::Runnable { + public: + nsThreadSyncDispatch(already_AddRefed aOrigin, + already_AddRefed&& aTask) + : Runnable("nsThreadSyncDispatch"), + mOrigin(aOrigin), + mSyncTask(std::move(aTask)), + mIsPending(true) {} + + bool IsPending() { + // This is an atomic acquire on the origin thread. + return mIsPending; + } + + void SpinEventLoopUntilComplete(const nsACString& aVeryGoodReasonToDoThis) { + mozilla::SpinEventLoopUntil(aVeryGoodReasonToDoThis, + [&]() -> bool { return !IsPending(); }); + } + + private: + NS_IMETHOD Run() override { + if (nsCOMPtr task = mSyncTask.take()) { + MOZ_ASSERT(!mSyncTask); + + mozilla::DebugOnly result = task->Run(); + MOZ_ASSERT(NS_SUCCEEDED(result), "task in sync dispatch should not fail"); + + // We must release the task here to ensure that when the original + // thread is unblocked, this task has been released. + task = nullptr; + + // This is an atomic release on the target thread. + mIsPending = false; + + // unblock the origin thread + mOrigin->Dispatch(this, NS_DISPATCH_IGNORE_BLOCK_DISPATCH); + } + + return NS_OK; + } + + nsCOMPtr mOrigin; + // The task is leaked by default when Run() is not called, because + // otherwise we may release it in an incorrect thread. + mozilla::LeakRefPtr mSyncTask; + mozilla::Atomic mIsPending; +}; + +#endif // nsThreadSyncDispatch_h_ diff --git a/xpcom/threads/nsThreadUtils.cpp b/xpcom/threads/nsThreadUtils.cpp new file mode 100644 index 0000000000..6eeb3999ac --- /dev/null +++ b/xpcom/threads/nsThreadUtils.cpp @@ -0,0 +1,768 @@ +/* -*- 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 "nsThreadUtils.h" + +#include "chrome/common/ipc_message.h" // for IPC::Message +#include "LeakRefPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/Likely.h" +#include "mozilla/TimeStamp.h" +#include "nsComponentManagerUtils.h" +#include "nsExceptionHandler.h" +#include "nsIEventTarget.h" +#include "nsITimer.h" +#include "nsString.h" +#include "nsThreadSyncDispatch.h" +#include "nsTimerImpl.h" +#include "prsystem.h" + +#include "nsThreadManager.h" +#include "nsThreadPool.h" +#include "TaskController.h" + +#ifdef XP_WIN +# include +#elif defined(XP_MACOSX) +# include +#endif + +#if defined(ANDROID) +# include +#endif + +static mozilla::LazyLogModule sEventDispatchAndRunLog("events"); +#ifdef LOG1 +# undef LOG1 +#endif +#define LOG1(args) \ + MOZ_LOG(sEventDispatchAndRunLog, mozilla::LogLevel::Error, args) +#define LOG1_ENABLED() \ + MOZ_LOG_TEST(sEventDispatchAndRunLog, mozilla::LogLevel::Error) + +using namespace mozilla; + +#ifndef XPCOM_GLUE_AVOID_NSPR + +NS_IMPL_ISUPPORTS(IdlePeriod, nsIIdlePeriod) + +NS_IMETHODIMP +IdlePeriod::GetIdlePeriodHint(TimeStamp* aIdleDeadline) { + *aIdleDeadline = TimeStamp(); + return NS_OK; +} + +// NS_IMPL_NAMED_* relies on the mName field, which is not present on +// release or beta. Instead, fall back to using "Runnable" for all +// runnables. +# ifndef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMPL_ISUPPORTS(Runnable, nsIRunnable) +# else +NS_IMPL_NAMED_ADDREF(Runnable, mName) +NS_IMPL_NAMED_RELEASE(Runnable, mName) +NS_IMPL_QUERY_INTERFACE(Runnable, nsIRunnable, nsINamed) +# endif + +NS_IMETHODIMP +Runnable::Run() { + // Do nothing + return NS_OK; +} + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +Runnable::GetName(nsACString& aName) { + if (mName) { + aName.AssignASCII(mName); + } else { + aName.Truncate(); + } + return NS_OK; +} +# endif + +NS_IMPL_ISUPPORTS_INHERITED(DiscardableRunnable, Runnable, + nsIDiscardableRunnable) + +NS_IMPL_ISUPPORTS_INHERITED(CancelableRunnable, DiscardableRunnable, + nsICancelableRunnable) + +void CancelableRunnable::OnDiscard() { + // Tasks that implement Cancel() can be safely cleaned up if it turns out + // that the task will not run. + (void)NS_WARN_IF(NS_FAILED(Cancel())); +} + +NS_IMPL_ISUPPORTS_INHERITED(IdleRunnable, DiscardableRunnable, nsIIdleRunnable) + +NS_IMPL_ISUPPORTS_INHERITED(CancelableIdleRunnable, CancelableRunnable, + nsIIdleRunnable) + +NS_IMPL_ISUPPORTS_INHERITED(PrioritizableRunnable, Runnable, + nsIRunnablePriority) + +PrioritizableRunnable::PrioritizableRunnable( + already_AddRefed&& aRunnable, uint32_t aPriority) + // Real runnable name is managed by overridding the GetName function. + : Runnable("PrioritizableRunnable"), + mRunnable(std::move(aRunnable)), + mPriority(aPriority) { +# if DEBUG + nsCOMPtr runnablePrio = do_QueryInterface(mRunnable); + MOZ_ASSERT(!runnablePrio); +# endif +} + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY +NS_IMETHODIMP +PrioritizableRunnable::GetName(nsACString& aName) { + // Try to get a name from the underlying runnable. + nsCOMPtr named = do_QueryInterface(mRunnable); + if (named) { + named->GetName(aName); + } + return NS_OK; +} +# endif + +NS_IMETHODIMP +PrioritizableRunnable::Run() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + return mRunnable->Run(); +} + +NS_IMETHODIMP +PrioritizableRunnable::GetPriority(uint32_t* aPriority) { + *aPriority = mPriority; + return NS_OK; +} + +already_AddRefed mozilla::CreateRenderBlockingRunnable( + already_AddRefed&& aRunnable) { + nsCOMPtr runnable = new PrioritizableRunnable( + std::move(aRunnable), nsIRunnablePriority::PRIORITY_RENDER_BLOCKING); + return runnable.forget(); +} + +NS_IMPL_ISUPPORTS_INHERITED(PrioritizableCancelableRunnable, CancelableRunnable, + nsIRunnablePriority) + +NS_IMETHODIMP +PrioritizableCancelableRunnable::GetPriority(uint32_t* aPriority) { + *aPriority = mPriority; + return NS_OK; +} + +#endif // XPCOM_GLUE_AVOID_NSPR + +//----------------------------------------------------------------------------- + +nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult, + nsIRunnable* aInitialEvent, + nsIThreadManager::ThreadCreationOptions aOptions) { + nsCOMPtr event = aInitialEvent; + return NS_NewNamedThread(aName, aResult, event.forget(), aOptions); +} + +nsresult NS_NewNamedThread(const nsACString& aName, nsIThread** aResult, + already_AddRefed aInitialEvent, + nsIThreadManager::ThreadCreationOptions aOptions) { + nsCOMPtr event = std::move(aInitialEvent); + nsCOMPtr thread; + nsresult rv = nsThreadManager::get().nsThreadManager::NewNamedThread( + aName, aOptions, getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (event) { + rv = thread->Dispatch(event.forget(), NS_DISPATCH_IGNORE_BLOCK_DISPATCH); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + *aResult = nullptr; + thread.swap(*aResult); + return NS_OK; +} + +nsresult NS_GetCurrentThread(nsIThread** aResult) { + return nsThreadManager::get().nsThreadManager::GetCurrentThread(aResult); +} + +nsresult NS_GetMainThread(nsIThread** aResult) { + return nsThreadManager::get().nsThreadManager::GetMainThread(aResult); +} + +nsresult NS_DispatchToCurrentThread(already_AddRefed&& aEvent) { + nsresult rv; + nsCOMPtr event(aEvent); + // XXX: Consider using GetCurrentSerialEventTarget() to support TaskQueues. + nsISerialEventTarget* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } + // To keep us from leaking the runnable if dispatch method fails, + // we grab the reference on failures and release it. + nsIRunnable* temp = event.get(); + rv = thread->Dispatch(event.forget(), NS_DISPATCH_NORMAL); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Dispatch() leaked the reference to the event, but due to caller's + // assumptions, we shouldn't leak here. And given we are on the same + // thread as the dispatch target, it's mostly safe to do it here. + NS_RELEASE(temp); + } + return rv; +} + +// It is common to call NS_DispatchToCurrentThread with a newly +// allocated runnable with a refcount of zero. To keep us from leaking +// the runnable if the dispatch method fails, we take a death grip. +nsresult NS_DispatchToCurrentThread(nsIRunnable* aEvent) { + nsCOMPtr event(aEvent); + return NS_DispatchToCurrentThread(event.forget()); +} + +nsresult NS_DispatchToMainThread(already_AddRefed&& aEvent, + uint32_t aDispatchFlags) { + LeakRefPtr event(std::move(aEvent)); + nsCOMPtr thread; + nsresult rv = NS_GetMainThread(getter_AddRefs(thread)); + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_ASSERTION(false, + "Failed NS_DispatchToMainThread() in shutdown; leaking"); + // NOTE: if you stop leaking here, adjust Promise::MaybeReportRejected(), + // which assumes a leak here, or split into leaks and no-leaks versions + return rv; + } + return thread->Dispatch(event.take(), aDispatchFlags); +} + +// In the case of failure with a newly allocated runnable with a +// refcount of zero, we intentionally leak the runnable, because it is +// likely that the runnable is being dispatched to the main thread +// because it owns main thread only objects, so it is not safe to +// release them here. +nsresult NS_DispatchToMainThread(nsIRunnable* aEvent, uint32_t aDispatchFlags) { + nsCOMPtr event(aEvent); + return NS_DispatchToMainThread(event.forget(), aDispatchFlags); +} + +nsresult NS_DelayedDispatchToCurrentThread( + already_AddRefed&& aEvent, uint32_t aDelayMs) { + nsCOMPtr event(aEvent); + + // XXX: Consider using GetCurrentSerialEventTarget() to support TaskQueues. + nsISerialEventTarget* thread = NS_GetCurrentThread(); + if (!thread) { + return NS_ERROR_UNEXPECTED; + } + + return thread->DelayedDispatch(event.forget(), aDelayMs); +} + +nsresult NS_DispatchToThreadQueue(already_AddRefed&& aEvent, + nsIThread* aThread, + EventQueuePriority aQueue) { + nsresult rv; + nsCOMPtr event(aEvent); + NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG); + if (!aThread) { + return NS_ERROR_UNEXPECTED; + } + // To keep us from leaking the runnable if dispatch method fails, + // we grab the reference on failures and release it. + nsIRunnable* temp = event.get(); + rv = aThread->DispatchToQueue(event.forget(), aQueue); + if (NS_WARN_IF(NS_FAILED(rv))) { + // Dispatch() leaked the reference to the event, but due to caller's + // assumptions, we shouldn't leak here. And given we are on the same + // thread as the dispatch target, it's mostly safe to do it here. + NS_RELEASE(temp); + } + + return rv; +} + +nsresult NS_DispatchToCurrentThreadQueue(already_AddRefed&& aEvent, + EventQueuePriority aQueue) { + return NS_DispatchToThreadQueue(std::move(aEvent), NS_GetCurrentThread(), + aQueue); +} + +extern nsresult NS_DispatchToMainThreadQueue( + already_AddRefed&& aEvent, EventQueuePriority aQueue) { + nsCOMPtr mainThread; + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + if (NS_SUCCEEDED(rv)) { + return NS_DispatchToThreadQueue(std::move(aEvent), mainThread, aQueue); + } + return rv; +} + +class IdleRunnableWrapper final : public Runnable, + public nsIDiscardableRunnable, + public nsIIdleRunnable { + public: + explicit IdleRunnableWrapper(already_AddRefed&& aEvent) + : Runnable("IdleRunnableWrapper"), + mRunnable(std::move(aEvent)), + mDiscardable(do_QueryInterface(mRunnable)) {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD Run() override { + if (!mRunnable) { + return NS_OK; + } + CancelTimer(); + // Don't clear mDiscardable because that would cause QueryInterface to + // change behavior during the lifetime of an instance. + nsCOMPtr runnable = std::move(mRunnable); + return runnable->Run(); + } + + // nsIDiscardableRunnable + void OnDiscard() override { + if (!mRunnable) { + // Run() was already called from TimedOut(). + return; + } + mDiscardable->OnDiscard(); + mRunnable = nullptr; + } + + static void TimedOut(nsITimer* aTimer, void* aClosure) { + RefPtr runnable = + static_cast(aClosure); + LogRunnable::Run log(runnable); + runnable->Run(); + runnable = nullptr; + } + + void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) override { + MOZ_ASSERT(aTarget); + MOZ_ASSERT(!mTimer); + NS_NewTimerWithFuncCallback(getter_AddRefs(mTimer), TimedOut, this, aDelay, + nsITimer::TYPE_ONE_SHOT, + "IdleRunnableWrapper::SetTimer", aTarget); + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override { + aName.AssignLiteral("IdleRunnableWrapper"); + if (nsCOMPtr named = do_QueryInterface(mRunnable)) { + nsAutoCString name; + named->GetName(name); + if (!name.IsEmpty()) { + aName.AppendLiteral(" for "); + aName.Append(name); + } + } + return NS_OK; + } +#endif + + private: + ~IdleRunnableWrapper() { CancelTimer(); } + + void CancelTimer() { + if (mTimer) { + mTimer->Cancel(); + } + } + + nsCOMPtr mTimer; + nsCOMPtr mRunnable; + nsCOMPtr mDiscardable; +}; + +NS_IMPL_ADDREF_INHERITED(IdleRunnableWrapper, Runnable) +NS_IMPL_RELEASE_INHERITED(IdleRunnableWrapper, Runnable) + +NS_INTERFACE_MAP_BEGIN(IdleRunnableWrapper) + NS_INTERFACE_MAP_ENTRY(nsIIdleRunnable) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDiscardableRunnable, mDiscardable) +NS_INTERFACE_MAP_END_INHERITING(Runnable) + +extern nsresult NS_DispatchToThreadQueue(already_AddRefed&& aEvent, + uint32_t aTimeout, nsIThread* aThread, + EventQueuePriority aQueue) { + nsCOMPtr event(std::move(aEvent)); + NS_ENSURE_TRUE(event, NS_ERROR_INVALID_ARG); + MOZ_ASSERT(aQueue == EventQueuePriority::Idle || + aQueue == EventQueuePriority::DeferredTimers); + if (!aThread) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr idleEvent = do_QueryInterface(event); + + if (!idleEvent) { + idleEvent = new IdleRunnableWrapper(event.forget()); + event = do_QueryInterface(idleEvent); + MOZ_DIAGNOSTIC_ASSERT(event); + } + idleEvent->SetTimer(aTimeout, aThread); + + nsresult rv = NS_DispatchToThreadQueue(event.forget(), aThread, aQueue); + if (NS_SUCCEEDED(rv)) { + // This is intended to bind with the "DISP" log made from inside + // NS_DispatchToThreadQueue for the `event`. There is no possibly to inject + // another "DISP" for a different event on this thread. + LOG1(("TIMEOUT %u", aTimeout)); + } + + return rv; +} + +extern nsresult NS_DispatchToCurrentThreadQueue( + already_AddRefed&& aEvent, uint32_t aTimeout, + EventQueuePriority aQueue) { + return NS_DispatchToThreadQueue(std::move(aEvent), aTimeout, + NS_GetCurrentThread(), aQueue); +} + +#ifndef XPCOM_GLUE_AVOID_NSPR +nsresult NS_ProcessPendingEvents(nsIThread* aThread, PRIntervalTime aTimeout) { + nsresult rv = NS_OK; + + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return NS_ERROR_UNEXPECTED; + } + } + + PRIntervalTime start = PR_IntervalNow(); + for (;;) { + bool processedEvent; + rv = aThread->ProcessNextEvent(false, &processedEvent); + if (NS_FAILED(rv) || !processedEvent) { + break; + } + if (PR_IntervalNow() - start > aTimeout) { + break; + } + } + return rv; +} +#endif // XPCOM_GLUE_AVOID_NSPR + +inline bool hasPendingEvents(nsIThread* aThread) { + bool val; + return NS_SUCCEEDED(aThread->HasPendingEvents(&val)) && val; +} + +bool NS_HasPendingEvents(nsIThread* aThread) { + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } + } + return hasPendingEvents(aThread); +} + +bool NS_ProcessNextEvent(nsIThread* aThread, bool aMayWait) { + if (!aThread) { + aThread = NS_GetCurrentThread(); + if (NS_WARN_IF(!aThread)) { + return false; + } + } + bool val; + return NS_SUCCEEDED(aThread->ProcessNextEvent(aMayWait, &val)) && val; +} + +void NS_SetCurrentThreadName(const char* aName) { +#if defined(ANDROID) + // Workaround for Bug 1541216 - PR_SetCurrentThreadName() Fails to set the + // thread name on Android. + prctl(PR_SET_NAME, reinterpret_cast(aName)); +#else + PR_SetCurrentThreadName(aName); +#endif + if (nsThreadManager::get().IsNSThread()) { + nsThread* thread = nsThreadManager::get().GetCurrentThread(); + thread->SetThreadNameInternal(nsDependentCString(aName)); + } +} + +nsIThread* NS_GetCurrentThread() { + return nsThreadManager::get().GetCurrentThread(); +} + +nsIThread* NS_GetCurrentThreadNoCreate() { + if (nsThreadManager::get().IsNSThread()) { + return NS_GetCurrentThread(); + } + return nullptr; +} + +// nsThreadPoolNaming +nsCString nsThreadPoolNaming::GetNextThreadName(const nsACString& aPoolName) { + nsCString name(aPoolName); + name.AppendLiteral(" #"); + name.AppendInt(++mCounter, 10); // The counter is declared as atomic + return name; +} + +nsresult NS_DispatchBackgroundTask(already_AddRefed aEvent, + uint32_t aDispatchFlags) { + nsCOMPtr event(aEvent); + return nsThreadManager::get().DispatchToBackgroundThread(event, + aDispatchFlags); +} + +// nsAutoLowPriorityIO +nsAutoLowPriorityIO::nsAutoLowPriorityIO() { +#if defined(XP_WIN) + lowIOPrioritySet = + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN); +#elif defined(XP_MACOSX) + oldPriority = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD); + lowIOPrioritySet = + oldPriority != -1 && + setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_THROTTLE) != -1; +#else + lowIOPrioritySet = false; +#endif +} + +nsAutoLowPriorityIO::~nsAutoLowPriorityIO() { +#if defined(XP_WIN) + if (MOZ_LIKELY(lowIOPrioritySet)) { + // On Windows the old thread priority is automatically restored + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END); + } +#elif defined(XP_MACOSX) + if (MOZ_LIKELY(lowIOPrioritySet)) { + setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, oldPriority); + } +#endif +} + +namespace mozilla { + +nsISerialEventTarget* GetCurrentSerialEventTarget() { + if (nsISerialEventTarget* current = + SerialEventTargetGuard::GetCurrentSerialEventTarget()) { + return current; + } + + MOZ_DIAGNOSTIC_ASSERT(!nsThreadPool::GetCurrentThreadPool(), + "Call to GetCurrentSerialEventTarget() from thread " + "pool without an active TaskQueue"); + + nsCOMPtr thread; + nsresult rv = NS_GetCurrentThread(getter_AddRefs(thread)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return thread; +} + +nsISerialEventTarget* GetMainThreadSerialEventTarget() { + return static_cast(nsThreadManager::get().GetMainThreadWeak()); +} + +size_t GetNumberOfProcessors() { +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + static const PRInt32 procs = PR_GetNumberOfProcessors(); +#else + PRInt32 procs = PR_GetNumberOfProcessors(); +#endif + MOZ_ASSERT(procs > 0); + return static_cast(procs); +} + +template +void LogTaskBase::LogDispatch(T* aEvent) { + LOG1(("DISP %p", aEvent)); +} +template +void LogTaskBase::LogDispatch(T* aEvent, void* aContext) { + LOG1(("DISP %p (%p)", aEvent, aContext)); +} + +template <> +void LogTaskBase::LogDispatchWithPid(IPC::Message* aEvent, + int32_t aPid) { + if (aEvent->seqno() && aPid > 0) { + LOG1(("SEND %p %d %d", aEvent, aEvent->seqno(), aPid)); + } +} + +template +LogTaskBase::Run::Run(T* aEvent, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + // Logging address of this RAII so that we can use it to identify the DONE log + // while not keeping any ref to the event that could be invalid at the dtor + // time. + LOG1(("EXEC %p %p", aEvent, this)); +} +template +LogTaskBase::Run::Run(T* aEvent, void* aContext, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + LOG1(("EXEC %p (%p) %p", aEvent, aContext, this)); +} + +template <> +LogTaskBase::Run::Run(nsIRunnable* aEvent, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + if (!LOG1_ENABLED()) { + return; + } + + nsCOMPtr named(do_QueryInterface(aEvent)); + if (!named) { + LOG1(("EXEC %p %p", aEvent, this)); + return; + } + + nsAutoCString name; + named->GetName(name); + LOG1(("EXEC %p %p [%s]", aEvent, this, name.BeginReading())); +} + +template <> +LogTaskBase::Run::Run(Task* aTask, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + if (!LOG1_ENABLED()) { + return; + } + + nsAutoCString name; + if (!aTask->GetName(name)) { + LOG1(("EXEC %p %p", aTask, this)); + return; + } + + LOG1(("EXEC %p %p [%s]", aTask, this, name.BeginReading())); +} + +template <> +LogTaskBase::Run::Run(IPC::Message* aMessage, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + LOG1(("RECV %p %p %d [%s]", aMessage, this, aMessage->seqno(), + aMessage->name())); +} + +template <> +LogTaskBase::Run::Run(nsTimerImpl* aEvent, bool aWillRunAgain) + : mWillRunAgain(aWillRunAgain) { + // The name of the timer will be logged when running it on the target thread. + // Logging it here (on the `Timer` thread) would be redundant. + LOG1(("EXEC %p %p [nsTimerImpl]", aEvent, this)); +} + +template +LogTaskBase::Run::~Run() { + LOG1((mWillRunAgain ? "INTERRUPTED %p" : "DONE %p", this)); +} + +template class LogTaskBase; +template class LogTaskBase; +template class LogTaskBase; +template class LogTaskBase; +template class LogTaskBase; +template class LogTaskBase; +template class LogTaskBase; + +MOZ_THREAD_LOCAL(nsISerialEventTarget*) +SerialEventTargetGuard::sCurrentThreadTLS; +void SerialEventTargetGuard::InitTLS() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sCurrentThreadTLS.init()) { + MOZ_CRASH(); + } +} + +} // namespace mozilla + +bool nsIEventTarget::IsOnCurrentThread() { + if (mThread) { + return mThread == PR_GetCurrentThread(); + } + return IsOnCurrentThreadInfallible(); +} + +extern "C" { +// These functions use the C language linkage because they're exposed to Rust +// via the xpcom/rust/moz_task crate, which wraps them in safe Rust functions +// that enable Rust code to get/create threads and dispatch runnables on them. + +nsresult NS_GetCurrentThreadRust(nsIThread** aResult) { + return NS_GetCurrentThread(aResult); +} + +nsresult NS_GetMainThreadRust(nsIThread** aResult) { + return NS_GetMainThread(aResult); +} + +// NS_NewNamedThread's aStackSize parameter has the default argument +// nsIThreadManager::DEFAULT_STACK_SIZE, but we can't omit default arguments +// when calling a C++ function from Rust, and we can't access +// nsIThreadManager::DEFAULT_STACK_SIZE in Rust to pass it explicitly, +// since it is defined in a %{C++ ... %} block within nsIThreadManager.idl. +// So we indirect through this function. +nsresult NS_NewNamedThreadWithDefaultStackSize(const nsACString& aName, + nsIThread** aResult, + nsIRunnable* aEvent) { + return NS_NewNamedThread(aName, aResult, aEvent); +} + +bool NS_IsOnCurrentThread(nsIEventTarget* aTarget) { + return aTarget->IsOnCurrentThread(); +} + +nsresult NS_DispatchBackgroundTask(nsIRunnable* aEvent, + uint32_t aDispatchFlags) { + return nsThreadManager::get().DispatchToBackgroundThread(aEvent, + aDispatchFlags); +} + +nsresult NS_CreateBackgroundTaskQueue(const char* aName, + nsISerialEventTarget** aTarget) { + nsCOMPtr target = + nsThreadManager::get().CreateBackgroundTaskQueue(aName); + if (!target) { + return NS_ERROR_FAILURE; + } + + target.forget(aTarget); + return NS_OK; +} + +} // extern "C" + +nsresult NS_DispatchAndSpinEventLoopUntilComplete( + const nsACString& aVeryGoodReasonToDoThis, nsIEventTarget* aEventTarget, + already_AddRefed aEvent) { + // NOTE: Get the current thread specifically, as `SpinEventLoopUntil` can + // only spin that event target's loop. The reply will specify + // NS_DISPATCH_IGNORE_BLOCK_DISPATCH to ensure the reply is received even if + // the caller is a threadpool thread. + nsCOMPtr current = NS_GetCurrentThread(); + if (NS_WARN_IF(!current)) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr wrapper = + new nsThreadSyncDispatch(current.forget(), std::move(aEvent)); + nsresult rv = aEventTarget->Dispatch(do_AddRef(wrapper)); + if (NS_WARN_IF(NS_FAILED(rv))) { + // FIXME: Consider avoiding leaking the `nsThreadSyncDispatch` as well by + // using a fallible version of `Dispatch` once that is added. + return rv; + } + + wrapper->SpinEventLoopUntilComplete(aVeryGoodReasonToDoThis); + return NS_OK; +} diff --git a/xpcom/threads/nsThreadUtils.h b/xpcom/threads/nsThreadUtils.h new file mode 100644 index 0000000000..72041da295 --- /dev/null +++ b/xpcom/threads/nsThreadUtils.h @@ -0,0 +1,1925 @@ +/* -*- 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/. */ + +#ifndef nsThreadUtils_h__ +#define nsThreadUtils_h__ + +#include +#include +#include + +#include "MainThreadUtils.h" +#include "mozilla/EventQueue.h" +#include "mozilla/AbstractThread.h" +#include "mozilla/Atomics.h" +#include "mozilla/Likely.h" +#include "mozilla/Maybe.h" +#include "mozilla/ThreadLocal.h" +#include "mozilla/TimeStamp.h" + +#include "nsCOMPtr.h" +#include "nsICancelableRunnable.h" +#include "nsIDiscardableRunnable.h" +#include "nsIIdlePeriod.h" +#include "nsIIdleRunnable.h" +#include "nsINamed.h" +#include "nsIRunnable.h" +#include "nsIThreadManager.h" +#include "nsITimer.h" +#include "nsString.h" +#include "prinrval.h" +#include "prthread.h" + +class MessageLoop; +class nsIThread; + +//----------------------------------------------------------------------------- +// These methods are alternatives to the methods on nsIThreadManager, provided +// for convenience. + +/** + * Create a new thread, and optionally provide an initial event for the thread. + * + * @param aName + * The name of the thread. + * @param aResult + * The resulting nsIThread object. + * @param aInitialEvent + * The initial event to run on this thread. This parameter may be null. + * @param aOptions + * Options used to configure thread creation. + * Options are documented in nsIThreadManager.idl. + * + * @returns NS_ERROR_INVALID_ARG + * Indicates that the given name is not unique. + */ + +extern nsresult NS_NewNamedThread( + const nsACString& aName, nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + nsIThreadManager::ThreadCreationOptions aOptions = {}); + +extern nsresult NS_NewNamedThread( + const nsACString& aName, nsIThread** aResult, + already_AddRefed aInitialEvent, + nsIThreadManager::ThreadCreationOptions aOptions = {}); + +template +inline nsresult NS_NewNamedThread( + const char (&aName)[LEN], nsIThread** aResult, + already_AddRefed aInitialEvent, + nsIThreadManager::ThreadCreationOptions aOptions = {}) { + static_assert(LEN <= 16, "Thread name must be no more than 16 characters"); + return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult, + std::move(aInitialEvent), aOptions); +} + +template +inline nsresult NS_NewNamedThread( + const char (&aName)[LEN], nsIThread** aResult, + nsIRunnable* aInitialEvent = nullptr, + nsIThreadManager::ThreadCreationOptions aOptions = {}) { + nsCOMPtr event = aInitialEvent; + static_assert(LEN <= 16, "Thread name must be no more than 16 characters"); + return NS_NewNamedThread(nsDependentCString(aName, LEN - 1), aResult, + event.forget(), aOptions); +} + +/** + * Get a reference to the current thread, creating it if it does not exist yet. + * + * @param aResult + * The resulting nsIThread object. + */ +extern nsresult NS_GetCurrentThread(nsIThread** aResult); + +/** + * Dispatch the given event to the current thread. + * + * @param aEvent + * The event to dispatch. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + */ +extern nsresult NS_DispatchToCurrentThread(nsIRunnable* aEvent); +extern nsresult NS_DispatchToCurrentThread( + already_AddRefed&& aEvent); + +/** + * Dispatch the given event to the main thread. + * + * @param aEvent + * The event to dispatch. + * @param aDispatchFlags + * The flags to pass to the main thread's dispatch method. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + */ +extern nsresult NS_DispatchToMainThread( + nsIRunnable* aEvent, uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); +extern nsresult NS_DispatchToMainThread( + already_AddRefed&& aEvent, + uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); + +extern nsresult NS_DelayedDispatchToCurrentThread( + already_AddRefed&& aEvent, uint32_t aDelayMs); + +/** + * Dispatch the given event to the specified queue of the current thread. + * + * @param aEvent The event to dispatch. + * @param aQueue The event queue for the thread to use + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToCurrentThreadQueue( + already_AddRefed&& aEvent, mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to the specified queue of the main thread. + * + * @param aEvent The event to dispatch. + * @param aQueue The event queue for the thread to use + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToMainThreadQueue( + already_AddRefed&& aEvent, mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to an idle queue of the current thread. + * + * @param aEvent The event to dispatch. If the event implements + * nsIIdleRunnable, it will receive a call on + * nsIIdleRunnable::SetTimer when dispatched, with the value of + * aTimeout. + * + * @param aTimeout The time in milliseconds until the event should be + * moved from an idle queue to the regular queue, if it hasn't been + * executed. If aEvent is also an nsIIdleRunnable, it is expected + * that it should handle the timeout itself, after a call to + * nsIIdleRunnable::SetTimer. + * + * @param aQueue + * The event queue for the thread to use. Must be an idle queue + * (Idle or DeferredTimers) + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToCurrentThreadQueue( + already_AddRefed&& aEvent, uint32_t aTimeout, + mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to a queue of a thread. + * + * @param aEvent The event to dispatch. + * @param aThread The target thread for the dispatch. + * @param aQueue The event queue for the thread to use. + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToThreadQueue(already_AddRefed&& aEvent, + nsIThread* aThread, + mozilla::EventQueuePriority aQueue); + +/** + * Dispatch the given event to an idle queue of a thread. + * + * @param aEvent The event to dispatch. If the event implements + * nsIIdleRunnable, it will receive a call on + * nsIIdleRunnable::SetTimer when dispatched, with the value of + * aTimeout. + * + * @param aTimeout The time in milliseconds until the event should be + * moved from an idle queue to the regular queue, if it hasn't been + * executed. If aEvent is also an nsIIdleRunnable, it is expected + * that it should handle the timeout itself, after a call to + * nsIIdleRunnable::SetTimer. + * + * @param aThread The target thread for the dispatch. + * + * @param aQueue + * The event queue for the thread to use. Must be an idle queue + * (Idle or DeferredTimers) + * + * @returns NS_ERROR_INVALID_ARG + * If event is null. + * @returns NS_ERROR_UNEXPECTED + * If the thread is shutting down. + */ +extern nsresult NS_DispatchToThreadQueue(already_AddRefed&& aEvent, + uint32_t aTimeout, nsIThread* aThread, + mozilla::EventQueuePriority aQueue); + +#ifndef XPCOM_GLUE_AVOID_NSPR +/** + * Process all pending events for the given thread before returning. This + * method simply calls ProcessNextEvent on the thread while HasPendingEvents + * continues to return true and the time spent in NS_ProcessPendingEvents + * does not exceed the given timeout value. + * + * @param aThread + * The thread object for which to process pending events. If null, then + * events will be processed for the current thread. + * @param aTimeout + * The maximum number of milliseconds to spend processing pending events. + * Events are not pre-empted to honor this timeout. Rather, the timeout + * value is simply used to determine whether or not to process another event. + * Pass PR_INTERVAL_NO_TIMEOUT to specify no timeout. + */ +extern nsresult NS_ProcessPendingEvents( + nsIThread* aThread, PRIntervalTime aTimeout = PR_INTERVAL_NO_TIMEOUT); +#endif + +/** + * Shortcut for nsIThread::HasPendingEvents. + * + * It is an error to call this function when the given thread is not the + * current thread. This function will return false if called from some + * other thread. + * + * @param aThread + * The current thread or null. + * + * @returns + * A boolean value that if "true" indicates that there are pending events + * in the current thread's event queue. + */ +extern bool NS_HasPendingEvents(nsIThread* aThread = nullptr); + +/** + * Shortcut for nsIThread::ProcessNextEvent. + * + * It is an error to call this function when the given thread is not the + * current thread. This function will simply return false if called + * from some other thread. + * + * @param aThread + * The current thread or null. + * @param aMayWait + * A boolean parameter that if "true" indicates that the method may block + * the calling thread to wait for a pending event. + * + * @returns + * A boolean value that if "true" indicates that an event from the current + * thread's event queue was processed. + */ +extern bool NS_ProcessNextEvent(nsIThread* aThread = nullptr, + bool aMayWait = true); + +/** + * Returns true if we're in the compositor thread. + * + * We declare this here because the headers required to invoke + * CompositorThreadHolder::IsInCompositorThread() also pull in a bunch of system + * headers that #define various tokens in a way that can break the build. + */ +extern bool NS_IsInCompositorThread(); + +extern bool NS_IsInCanvasThreadOrWorker(); + +extern bool NS_IsInVRThread(); + +//----------------------------------------------------------------------------- +// Helpers that work with nsCOMPtr: + +inline already_AddRefed do_GetCurrentThread() { + nsIThread* thread = nullptr; + NS_GetCurrentThread(&thread); + return already_AddRefed(thread); +} + +inline already_AddRefed do_GetMainThread() { + nsIThread* thread = nullptr; + NS_GetMainThread(&thread); + return already_AddRefed(thread); +} + +//----------------------------------------------------------------------------- + +// Fast access to the current thread. Will create an nsIThread if one does not +// exist already! Do not release the returned pointer! If you want to use this +// pointer from some other thread, then you will need to AddRef it. Otherwise, +// you should only consider this pointer valid from code running on the current +// thread. +extern nsIThread* NS_GetCurrentThread(); + +// Exactly the same as NS_GetCurrentThread, except it will not create an +// nsThread if one does not exist yet. This is useful in cases where you have +// code that runs on threads that may or may not not be driven by an nsThread +// event loop, and wish to avoid inadvertently creating a superfluous nsThread. +extern nsIThread* NS_GetCurrentThreadNoCreate(); + +/** + * Set the name of the current thread. Prefer this function over + * PR_SetCurrentThreadName() if possible. The name will also be included in the + * crash report. + * + * @param aName + * Name of the thread. A C language null-terminated string. + */ +extern void NS_SetCurrentThreadName(const char* aName); + +//----------------------------------------------------------------------------- + +#ifndef XPCOM_GLUE_AVOID_NSPR + +namespace mozilla { + +// This class is designed to be subclassed. +class IdlePeriod : public nsIIdlePeriod { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIIDLEPERIOD + + IdlePeriod() = default; + + protected: + virtual ~IdlePeriod() = default; + + private: + IdlePeriod(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&) = delete; + IdlePeriod& operator=(const IdlePeriod&&) = delete; +}; + +// Cancelable runnable methods implement nsICancelableRunnable, and +// Idle and IdleWithTimer also nsIIdleRunnable. +enum class RunnableKind { Standard, Cancelable, Idle, IdleWithTimer }; + +// Implementing nsINamed on Runnable bloats vtables for the hundreds of +// Runnable subclasses that we have, so we want to avoid that overhead +// when we're not using nsINamed for anything. +# ifndef RELEASE_OR_BETA +# define MOZ_COLLECTING_RUNNABLE_TELEMETRY +# endif + +// This class is designed to be subclassed. +class Runnable : public nsIRunnable +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + , + public nsINamed +# endif +{ + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIRUNNABLE +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_DECL_NSINAMED +# endif + + Runnable() = delete; + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + explicit Runnable(const char* aName) : mName(aName) {} +# else + explicit Runnable(const char* aName) {} +# endif + + protected: + virtual ~Runnable() = default; + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + const char* mName = nullptr; +# endif + + private: + Runnable(const Runnable&) = delete; + Runnable& operator=(const Runnable&) = delete; + Runnable& operator=(const Runnable&&) = delete; +}; + +// This is a base class for tasks that might not be run, such as those that may +// be dispatched to workers. +// The owner of an event target will call either Run() or OnDiscard() +// exactly once. +// Derived classes should override Run(). An OnDiscard() override may +// provide cleanup when Run() will not be called. +class DiscardableRunnable : public Runnable, public nsIDiscardableRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + // nsIDiscardableRunnable + void OnDiscard() override {} + + DiscardableRunnable() = delete; + explicit DiscardableRunnable(const char* aName) : Runnable(aName) {} + + protected: + virtual ~DiscardableRunnable() = default; + + private: + DiscardableRunnable(const DiscardableRunnable&) = delete; + DiscardableRunnable& operator=(const DiscardableRunnable&) = delete; + DiscardableRunnable& operator=(const DiscardableRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +// Derived classes should override Run() and Cancel() to provide that +// calling Run() after Cancel() is a no-op. +class CancelableRunnable : public DiscardableRunnable, + public nsICancelableRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + // nsIDiscardableRunnable + void OnDiscard() override; + // nsICancelableRunnable + virtual nsresult Cancel() override = 0; + + CancelableRunnable() = delete; + explicit CancelableRunnable(const char* aName) : DiscardableRunnable(aName) {} + + protected: + virtual ~CancelableRunnable() = default; + + private: + CancelableRunnable(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&) = delete; + CancelableRunnable& operator=(const CancelableRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +class IdleRunnable : public DiscardableRunnable, public nsIIdleRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + + explicit IdleRunnable(const char* aName) : DiscardableRunnable(aName) {} + + protected: + virtual ~IdleRunnable() = default; + + private: + IdleRunnable(const IdleRunnable&) = delete; + IdleRunnable& operator=(const IdleRunnable&) = delete; + IdleRunnable& operator=(const IdleRunnable&&) = delete; +}; + +// This class is designed to be subclassed. +class CancelableIdleRunnable : public CancelableRunnable, + public nsIIdleRunnable { + public: + NS_DECL_ISUPPORTS_INHERITED + + CancelableIdleRunnable() : CancelableRunnable("CancelableIdleRunnable") {} + explicit CancelableIdleRunnable(const char* aName) + : CancelableRunnable(aName) {} + + protected: + virtual ~CancelableIdleRunnable() = default; + + private: + CancelableIdleRunnable(const CancelableIdleRunnable&) = delete; + CancelableIdleRunnable& operator=(const CancelableIdleRunnable&) = delete; + CancelableIdleRunnable& operator=(const CancelableIdleRunnable&&) = delete; +}; + +// This class is designed to be a wrapper of a real runnable to support event +// prioritizable. +class PrioritizableRunnable : public Runnable, public nsIRunnablePriority { + public: + PrioritizableRunnable(already_AddRefed&& aRunnable, + uint32_t aPriority); + +# ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + NS_IMETHOD GetName(nsACString& aName) override; +# endif + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLE + NS_DECL_NSIRUNNABLEPRIORITY + + protected: + virtual ~PrioritizableRunnable() = default; + + nsCOMPtr mRunnable; + uint32_t mPriority; +}; + +class PrioritizableCancelableRunnable : public CancelableRunnable, + public nsIRunnablePriority { + public: + PrioritizableCancelableRunnable(uint32_t aPriority, const char* aName) + : CancelableRunnable(aName), mPriority(aPriority) {} + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIRUNNABLEPRIORITY + + protected: + virtual ~PrioritizableCancelableRunnable() = default; + + const uint32_t mPriority; +}; + +extern already_AddRefed CreateRenderBlockingRunnable( + already_AddRefed&& aRunnable); + +namespace detail { + +// An event that can be used to call a C++11 functions or function objects, +// including lambdas. The function must have no required arguments, and must +// return void. +template +class RunnableFunction : public Runnable { + public: + template + explicit RunnableFunction(const char* aName, F&& aFunction) + : Runnable(aName), mFunction(std::forward(aFunction)) {} + + NS_IMETHOD Run() override { + static_assert(std::is_void_v, + "The lambda must return void!"); + mFunction(); + return NS_OK; + } + + private: + StoredFunction mFunction; +}; + +// Type alias for NS_NewRunnableFunction +template +using RunnableFunctionImpl = + // Make sure we store a non-reference in nsRunnableFunction. + typename detail::RunnableFunction>; +} // namespace detail + +namespace detail { + +template +struct IsRefcountedSmartPointerHelper : std::false_type {}; + +template +struct IsRefcountedSmartPointerHelper> : std::true_type {}; + +template +struct IsRefcountedSmartPointerHelper> : std::true_type {}; + +} // namespace detail + +template +struct IsRefcountedSmartPointer + : detail::IsRefcountedSmartPointerHelper> {}; + +namespace detail { + +template +struct RemoveSmartPointerHelper { + typedef T Type; +}; + +template +struct RemoveSmartPointerHelper> { + typedef Pointee Type; +}; + +template +struct RemoveSmartPointerHelper> { + typedef Pointee Type; +}; + +} // namespace detail + +template +struct RemoveSmartPointer + : detail::RemoveSmartPointerHelper> {}; + +namespace detail { + +template +struct RemoveRawOrSmartPointerHelper { + typedef T Type; +}; + +template +struct RemoveRawOrSmartPointerHelper { + typedef Pointee Type; +}; + +template +struct RemoveRawOrSmartPointerHelper> { + typedef Pointee Type; +}; + +template +struct RemoveRawOrSmartPointerHelper> { + typedef Pointee Type; +}; + +} // namespace detail + +template +struct RemoveRawOrSmartPointer + : detail::RemoveRawOrSmartPointerHelper> {}; + +} // namespace mozilla + +inline nsISupports* ToSupports(mozilla::Runnable* p) { + return static_cast(p); +} + +template +already_AddRefed NS_NewRunnableFunction( + const char* aName, Function&& aFunction) { + // We store a non-reference in RunnableFunction, but still forward aFunction + // to move if possible. + return do_AddRef(new mozilla::detail::RunnableFunctionImpl( + aName, std::forward(aFunction))); +} + +// Creates a new object implementing nsIRunnable and nsICancelableRunnable, +// which runs a given function on Run and clears the stored function object on a +// call to `Cancel` (and thus destroys all objects it holds). +template +already_AddRefed NS_NewCancelableRunnableFunction( + const char* aName, Function&& aFunc) { + class FuncCancelableRunnable final : public mozilla::CancelableRunnable { + public: + static_assert( + std::is_void_v< + decltype(std::declval>()())>); + + NS_INLINE_DECL_REFCOUNTING_INHERITED(FuncCancelableRunnable, + CancelableRunnable) + + explicit FuncCancelableRunnable(const char* aName, Function&& aFunc) + : CancelableRunnable{aName}, + mFunc{mozilla::Some(std::forward(aFunc))} {} + + NS_IMETHOD Run() override { + if (mFunc) { + (*mFunc)(); + } + + return NS_OK; + } + + nsresult Cancel() override { + mFunc.reset(); + return NS_OK; + } + + private: + ~FuncCancelableRunnable() = default; + + mozilla::Maybe> mFunc; + }; + + return mozilla::MakeAndAddRef( + aName, std::forward(aFunc)); +} + +namespace mozilla { +namespace detail { + +template +class TimerBehaviour { + public: + nsITimer* GetTimer() { return nullptr; } + void CancelTimer() {} + + protected: + ~TimerBehaviour() = default; +}; + +template <> +class TimerBehaviour { + public: + nsITimer* GetTimer() { + if (!mTimer) { + mTimer = NS_NewTimer(); + } + + return mTimer; + } + + void CancelTimer() { + if (mTimer) { + mTimer->Cancel(); + } + } + + protected: + ~TimerBehaviour() { CancelTimer(); } + + private: + nsCOMPtr mTimer; +}; + +} // namespace detail +} // namespace mozilla + +// An event that can be used to call a method on a class. The class type must +// support reference counting. This event supports Revoke for use +// with nsRevocableEventPtr. +template +class nsRunnableMethod + : public std::conditional_t< + Kind == mozilla::RunnableKind::Standard, mozilla::Runnable, + std::conditional_t>, + protected mozilla::detail::TimerBehaviour { + using BaseType = std::conditional_t< + Kind == mozilla::RunnableKind::Standard, mozilla::Runnable, + std::conditional_t>; + + public: + nsRunnableMethod(const char* aName) : BaseType(aName) {} + + virtual void Revoke() = 0; + + // These ReturnTypeEnforcer classes disallow return types that + // we know are not safe. The default ReturnTypeEnforcer compiles just fine but + // already_AddRefed will not. + template + class ReturnTypeEnforcer { + public: + typedef int ReturnTypeIsSafe; + }; + + template + class ReturnTypeEnforcer> { + // No ReturnTypeIsSafe makes this illegal! + }; + + // Make sure this return type is safe. + typedef typename ReturnTypeEnforcer::ReturnTypeIsSafe check; +}; + +template +struct nsRunnableMethodReceiver { + RefPtr mObj; + explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {} + explicit nsRunnableMethodReceiver(RefPtr&& aObj) + : mObj(std::move(aObj)) {} + ~nsRunnableMethodReceiver() { Revoke(); } + ClassType* Get() const { return mObj.get(); } + void Revoke() { mObj = nullptr; } +}; + +template +struct nsRunnableMethodReceiver { + ClassType* MOZ_NON_OWNING_REF mObj; + explicit nsRunnableMethodReceiver(ClassType* aObj) : mObj(aObj) {} + ClassType* Get() const { return mObj; } + void Revoke() { mObj = nullptr; } +}; + +static inline constexpr bool IsIdle(mozilla::RunnableKind aKind) { + return aKind == mozilla::RunnableKind::Idle || + aKind == mozilla::RunnableKind::IdleWithTimer; +} + +template +struct nsRunnableMethodTraits; + +template +struct nsRunnableMethodTraits { + typedef typename mozilla::RemoveRawOrSmartPointer::Type class_type; + static_assert(std::is_base_of::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template +struct nsRunnableMethodTraits { + typedef const typename mozilla::RemoveRawOrSmartPointer::Type + class_type; + static_assert(std::is_base_of::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +# ifdef NS_HAVE_STDCALL +template +struct nsRunnableMethodTraits { + typedef typename mozilla::RemoveRawOrSmartPointer::Type class_type; + static_assert(std::is_base_of::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template +struct nsRunnableMethodTraits { + typedef typename mozilla::RemoveRawOrSmartPointer::Type class_type; + static_assert(std::is_base_of::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template +struct nsRunnableMethodTraits { + typedef const typename mozilla::RemoveRawOrSmartPointer::Type + class_type; + static_assert(std::is_base_of::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; + +template +struct nsRunnableMethodTraits { + typedef const typename mozilla::RemoveRawOrSmartPointer::Type + class_type; + static_assert(std::is_base_of::value, + "Stored class must inherit from method's class"); + typedef R return_type; + typedef nsRunnableMethod base_type; + static const bool can_cancel = Kind == mozilla::RunnableKind::Cancelable; +}; +# endif + +// IsParameterStorageClass::value is true if T is a parameter-storage class +// that will be recognized by NS_New[NonOwning]RunnableMethodWithArg[s] to +// force a specific storage&passing strategy (instead of inferring one, +// see ParameterStorage). +// When creating a new storage class, add a specialization for it to be +// recognized. +template +struct IsParameterStorageClass : public std::false_type {}; + +// StoreXPassByY structs used to inform nsRunnableMethodArguments how to +// store arguments, and how to pass them to the target method. + +template +struct StoreCopyPassByValue { + using stored_type = std::decay_t; + typedef stored_type passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByValue(A&& a) : m(std::forward(a)) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> + : public std::true_type {}; + +template +struct StoreCopyPassByConstLRef { + using stored_type = std::decay_t; + typedef const stored_type& passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByConstLRef(A&& a) : m(std::forward(a)) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> + : public std::true_type {}; + +template +struct StoreCopyPassByLRef { + using stored_type = std::decay_t; + typedef stored_type& passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByLRef(A&& a) : m(std::forward(a)) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> : public std::true_type { +}; + +template +struct StoreCopyPassByRRef { + using stored_type = std::decay_t; + typedef stored_type&& passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByRRef(A&& a) : m(std::forward(a)) {} + passed_type PassAsParameter() { return std::move(m); } +}; +template +struct IsParameterStorageClass> : public std::true_type { +}; + +template +struct StoreRefPassByLRef { + typedef T& stored_type; + typedef T& passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreRefPassByLRef(A& a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> : public std::true_type { +}; + +template +struct StoreConstRefPassByConstLRef { + typedef const T& stored_type; + typedef const T& passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreConstRefPassByConstLRef(const A& a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> + : public std::true_type {}; + +template +struct StoreRefPtrPassByPtr { + typedef RefPtr stored_type; + typedef T* passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreRefPtrPassByPtr(A&& a) : m(std::forward(a)) {} + passed_type PassAsParameter() { return m.get(); } +}; +template +struct IsParameterStorageClass> + : public std::true_type {}; + +template +struct StorePtrPassByPtr { + typedef T* stored_type; + typedef T* passed_type; + stored_type m; + template + MOZ_IMPLICIT StorePtrPassByPtr(A a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> : public std::true_type {}; + +template +struct StoreConstPtrPassByConstPtr { + typedef const T* stored_type; + typedef const T* passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreConstPtrPassByConstPtr(A a) : m(a) {} + passed_type PassAsParameter() { return m; } +}; +template +struct IsParameterStorageClass> + : public std::true_type {}; + +template +struct StoreCopyPassByConstPtr { + typedef T stored_type; + typedef const T* passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByConstPtr(A&& a) : m(std::forward(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template +struct IsParameterStorageClass> + : public std::true_type {}; + +template +struct StoreCopyPassByPtr { + typedef T stored_type; + typedef T* passed_type; + stored_type m; + template + MOZ_IMPLICIT StoreCopyPassByPtr(A&& a) : m(std::forward(a)) {} + passed_type PassAsParameter() { return &m; } +}; +template +struct IsParameterStorageClass> : public std::true_type { +}; + +namespace detail { + +template +struct SFINAE1True : std::true_type {}; + +template +static auto HasRefCountMethodsTest(int) + -> SFINAE1True().AddRef(), + std::declval().Release())>; +template +static auto HasRefCountMethodsTest(long) -> std::false_type; + +template +struct HasRefCountMethods : decltype(HasRefCountMethodsTest(0)) {}; + +template +struct NonnsISupportsPointerStorageClass + : std::conditional< + std::is_const_v, + StoreConstPtrPassByConstPtr>, + StorePtrPassByPtr> { + using Type = typename NonnsISupportsPointerStorageClass::conditional::type; +}; + +template +struct PointerStorageClass + : std::conditional< + HasRefCountMethods::value, + StoreRefPtrPassByPtr, + typename NonnsISupportsPointerStorageClass::Type> { + using Type = typename PointerStorageClass::conditional::type; +}; + +template +struct LValueReferenceStorageClass + : std::conditional< + std::is_const_v, + StoreConstRefPassByConstLRef>, + StoreRefPassByLRef> { + using Type = typename LValueReferenceStorageClass::conditional::type; +}; + +template +struct SmartPointerStorageClass + : std::conditional< + mozilla::IsRefcountedSmartPointer::value, + StoreRefPtrPassByPtr::Type>, + StoreCopyPassByConstLRef> { + using Type = typename SmartPointerStorageClass::conditional::type; +}; + +template +struct NonLValueReferenceStorageClass + : std::conditional, + StoreCopyPassByRRef>, + typename SmartPointerStorageClass::Type> { + using Type = typename NonLValueReferenceStorageClass::conditional::type; +}; + +template +struct NonPointerStorageClass + : std::conditional, + typename LValueReferenceStorageClass< + std::remove_reference_t>::Type, + typename NonLValueReferenceStorageClass::Type> { + using Type = typename NonPointerStorageClass::conditional::type; +}; + +template +struct NonParameterStorageClass + : std::conditional< + std::is_pointer_v, + typename PointerStorageClass>::Type, + typename NonPointerStorageClass::Type> { + using Type = typename NonParameterStorageClass::conditional::type; +}; + +// Choose storage&passing strategy based on preferred storage type: +// - If IsParameterStorageClass::value is true, use as-is. +// - RC* -> StoreRefPtrPassByPtr :Store RefPtr, pass RC* +// ^^ RC quacks like a ref-counted type (i.e., has AddRef and Release methods) +// - const T* -> StoreConstPtrPassByConstPtr :Store const T*, pass const T* +// - T* -> StorePtrPassByPtr :Store T*, pass T*. +// - const T& -> StoreConstRefPassByConstLRef:Store const T&, pass const T&. +// - T& -> StoreRefPassByLRef :Store T&, pass T&. +// - T&& -> StoreCopyPassByRRef :Store T, pass std::move(T). +// - RefPtr, nsCOMPtr +// -> StoreRefPtrPassByPtr :Store RefPtr, pass T* +// - Other T -> StoreCopyPassByConstLRef :Store T, pass const T&. +// Other available explicit options: +// - StoreCopyPassByValue :Store T, pass T. +// - StoreCopyPassByLRef :Store T, pass T& (of copy!) +// - StoreCopyPassByConstPtr :Store T, pass const T* +// - StoreCopyPassByPtr :Store T, pass T* (of copy!) +// Or create your own class with PassAsParameter() method, optional +// clean-up in destructor, and with associated IsParameterStorageClass<>. +template +struct ParameterStorage + : std::conditional::value, T, + typename NonParameterStorageClass::Type> { + using Type = typename ParameterStorage::conditional::type; +}; + +template +static auto HasSetDeadlineTest(int) + -> SFINAE1True().SetDeadline( + std::declval()))>; + +template +static auto HasSetDeadlineTest(long) -> std::false_type; + +template +struct HasSetDeadline : decltype(HasSetDeadlineTest(0)) {}; + +template +std::enable_if_t<::detail::HasSetDeadline::value> SetDeadlineImpl( + T* aObj, mozilla::TimeStamp aTimeStamp) { + aObj->SetDeadline(aTimeStamp); +} + +template +std::enable_if_t::value> SetDeadlineImpl( + T* aObj, mozilla::TimeStamp aTimeStamp) {} +} /* namespace detail */ + +namespace mozilla { +namespace detail { + +// struct used to store arguments and later apply them to a method. +template +struct RunnableMethodArguments final { + std::tuple::Type...> mArguments; + template + explicit RunnableMethodArguments(As&&... aArguments) + : mArguments(std::forward(aArguments)...) {} + template + decltype(auto) apply(C* o, M m) { + return std::apply( + [&o, m](auto&&... args) { + return ((*o).*m)(args.PassAsParameter()...); + }, + mArguments); + } +}; + +template +class RunnableMethodImpl final + : public ::nsRunnableMethodTraits::base_type { + typedef typename ::nsRunnableMethodTraits + Traits; + + typedef typename Traits::class_type ClassType; + typedef typename Traits::base_type BaseType; + ::nsRunnableMethodReceiver mReceiver; + Method mMethod; + RunnableMethodArguments mArgs; + using BaseType::CancelTimer; + using BaseType::GetTimer; + + private: + virtual ~RunnableMethodImpl() { Revoke(); }; + static void TimedOut(nsITimer* aTimer, void* aClosure) { + static_assert(IsIdle(Kind), "Don't use me!"); + RefPtr r = + static_cast(aClosure); + r->SetDeadline(TimeStamp()); + r->Run(); + r->Cancel(); + } + + public: + template + explicit RunnableMethodImpl(const char* aName, ForwardedPtrType&& aObj, + Method aMethod, Args&&... aArgs) + : BaseType(aName), + mReceiver(std::forward(aObj)), + mMethod(aMethod), + mArgs(std::forward(aArgs)...) { + static_assert(sizeof...(Storages) == sizeof...(Args), + "Storages and Args should have equal sizes"); + } + + NS_IMETHOD Run() { + CancelTimer(); + + if (MOZ_LIKELY(mReceiver.Get())) { + mArgs.apply(mReceiver.Get(), mMethod); + } + + return NS_OK; + } + + nsresult Cancel() { + static_assert(Kind >= RunnableKind::Cancelable, "Don't use me!"); + Revoke(); + return NS_OK; + } + + void Revoke() { + CancelTimer(); + mReceiver.Revoke(); + } + + void SetDeadline(TimeStamp aDeadline) { + if (MOZ_LIKELY(mReceiver.Get())) { + ::detail::SetDeadlineImpl(mReceiver.Get(), aDeadline); + } + } + + void SetTimer(uint32_t aDelay, nsIEventTarget* aTarget) { + MOZ_ASSERT(aTarget); + + if (nsCOMPtr timer = GetTimer()) { + timer->Cancel(); + timer->SetTarget(aTarget); + timer->InitWithNamedFuncCallback(TimedOut, this, aDelay, + nsITimer::TYPE_ONE_SHOT, + "detail::RunnableMethodImpl::SetTimer"); + } + } +}; + +// Type aliases for NewRunnableMethod. +template +using OwningRunnableMethod = + typename ::nsRunnableMethodTraits, Method, + true, RunnableKind::Standard>::base_type; +template +using OwningRunnableMethodImpl = + RunnableMethodImpl, Method, true, + RunnableKind::Standard, Storages...>; + +// Type aliases for NewCancelableRunnableMethod. +template +using CancelableRunnableMethod = + typename ::nsRunnableMethodTraits, Method, + true, + RunnableKind::Cancelable>::base_type; +template +using CancelableRunnableMethodImpl = + RunnableMethodImpl, Method, true, + RunnableKind::Cancelable, Storages...>; + +// Type aliases for NewIdleRunnableMethod. +template +using IdleRunnableMethod = + typename ::nsRunnableMethodTraits, Method, + true, RunnableKind::Idle>::base_type; +template +using IdleRunnableMethodImpl = + RunnableMethodImpl, Method, true, + RunnableKind::Idle, Storages...>; + +// Type aliases for NewIdleRunnableMethodWithTimer. +template +using IdleRunnableMethodWithTimer = + typename ::nsRunnableMethodTraits, Method, + true, + RunnableKind::IdleWithTimer>::base_type; +template +using IdleRunnableMethodWithTimerImpl = + RunnableMethodImpl, Method, true, + RunnableKind::IdleWithTimer, Storages...>; + +// Type aliases for NewNonOwningRunnableMethod. +template +using NonOwningRunnableMethod = + typename ::nsRunnableMethodTraits, Method, + false, RunnableKind::Standard>::base_type; +template +using NonOwningRunnableMethodImpl = + RunnableMethodImpl, Method, false, + RunnableKind::Standard, Storages...>; + +// Type aliases for NonOwningCancelableRunnableMethod +template +using NonOwningCancelableRunnableMethod = + typename ::nsRunnableMethodTraits, Method, + false, + RunnableKind::Cancelable>::base_type; +template +using NonOwningCancelableRunnableMethodImpl = + RunnableMethodImpl, Method, false, + RunnableKind::Cancelable, Storages...>; + +// Type aliases for NonOwningIdleRunnableMethod +template +using NonOwningIdleRunnableMethod = + typename ::nsRunnableMethodTraits, Method, + false, RunnableKind::Idle>::base_type; +template +using NonOwningIdleRunnableMethodImpl = + RunnableMethodImpl, Method, false, + RunnableKind::Idle, Storages...>; + +// Type aliases for NewIdleRunnableMethodWithTimer. +template +using NonOwningIdleRunnableMethodWithTimer = + typename ::nsRunnableMethodTraits, Method, + false, + RunnableKind::IdleWithTimer>::base_type; +template +using NonOwningIdleRunnableMethodWithTimerImpl = + RunnableMethodImpl, Method, false, + RunnableKind::IdleWithTimer, Storages...>; + +} // namespace detail + +// NewRunnableMethod and friends +// +// Very often in Gecko, you'll find yourself in a situation where you want +// to invoke a method (with or without arguments) asynchronously. You +// could write a small helper class inheriting from nsRunnable to handle +// all these details, or you could let NewRunnableMethod take care of all +// those details for you. +// +// The simplest use of NewRunnableMethod looks like: +// +// nsCOMPtr event = +// mozilla::NewRunnableMethod("description", myObject, +// &MyClass::HandleEvent); +// NS_DispatchToCurrentThread(event); +// +// Statically enforced constraints: +// - myObject must be of (or implicitly convertible to) type MyClass +// - MyClass must define AddRef and Release methods +// +// The "description" string should specify a human-readable name for the +// runnable; the provided string is used by various introspection tools +// in the browser. +// +// The created runnable will take a strong reference to `myObject`. For +// non-refcounted objects, or refcounted objects with unusual refcounting +// requirements, and if and only if you are 110% certain that `myObject` +// will live long enough, you can use NewNonOwningRunnableMethod instead, +// which will, as its name implies, take a non-owning reference. If you +// find yourself having to use this function, you should accompany your use +// with a proof comment describing why the runnable will not lead to +// use-after-frees. +// +// (If you find yourself writing contorted code to Release() an object +// asynchronously on a different thread, you should use the +// NS_ProxyRelease function.) +// +// Invoking a method with arguments takes a little more care. The +// natural extension of the above: +// +// nsCOMPtr event = +// mozilla::NewRunnableMethod("description", myObject, +// &MyClass::HandleEvent, +// arg1, arg2, ...); +// +// can lead to security hazards (e.g. passing in raw pointers to refcounted +// objects and storing those raw pointers in the runnable). We therefore +// require you to specify the storage types used by the runnable, just as +// you would if you were writing out the class by hand: +// +// nsCOMPtr event = +// mozilla::NewRunnableMethod, nsTArray> +// ("description", myObject, &MyClass::HandleEvent, arg1, arg2); +// +// Please note that you do not have to pass the same argument type as you +// specify in the template arguments. For example, if you want to transfer +// ownership to a runnable, you can write: +// +// RefPtr ptr = ...; +// nsTArray array = ...; +// nsCOMPtr event = +// mozilla::NewRunnableMethod, nsTArray> +// ("description", myObject, &MyClass::DoSomething, +// std::move(ptr), std::move(array)); +// +// and there will be no extra AddRef/Release traffic, or copying of the array. +// +// Each type that you specify as a template argument to NewRunnableMethod +// comes with its own style of storage in the runnable and its own style +// of argument passing to the invoked method. See the comment for +// ParameterStorage above for more details. +// +// If you need to customize the storage type and/or argument passing type, +// you can write your own class to use as a template argument to +// NewRunnableMethod. If you find yourself having to do that frequently, +// please file a bug in Core::XPCOM about adding the custom type to the +// core code in this file, and/or for custom rules for ParameterStorage +// to select that strategy. +// +// For places that require you to use cancelable runnables, such as +// workers, there's also NewCancelableRunnableMethod and its non-owning +// counterpart. The runnables returned by these methods additionally +// implement nsICancelableRunnable. +// +// Finally, all of the functions discussed above have additional overloads +// that do not take a `const char*` as their first parameter; you may see +// these in older code. The `const char*` overload is preferred and +// should be used in new code exclusively. + +template +already_AddRefed> +NewRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::OwningRunnableMethodImpl( + aName, std::forward(aPtr), aMethod)); +} + +template +already_AddRefed> +NewCancelableRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::CancelableRunnableMethodImpl( + aName, std::forward(aPtr), aMethod)); +} + +template +already_AddRefed> +NewIdleRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::IdleRunnableMethodImpl( + aName, std::forward(aPtr), aMethod)); +} + +template +already_AddRefed> +NewIdleRunnableMethodWithTimer(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef(new detail::IdleRunnableMethodWithTimerImpl( + aName, std::forward(aPtr), aMethod)); +} + +template +already_AddRefed> +NewNonOwningRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod) { + return do_AddRef(new detail::NonOwningRunnableMethodImpl( + aName, std::forward(aPtr), aMethod)); +} + +template +already_AddRefed> +NewNonOwningCancelableRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef( + new detail::NonOwningCancelableRunnableMethodImpl( + aName, std::forward(aPtr), aMethod)); +} + +template +already_AddRefed> +NewNonOwningIdleRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef(new detail::NonOwningIdleRunnableMethodImpl( + aName, std::forward(aPtr), aMethod)); +} + +template +already_AddRefed> +NewNonOwningIdleRunnableMethodWithTimer(const char* aName, PtrType&& aPtr, + Method aMethod) { + return do_AddRef( + new detail::NonOwningIdleRunnableMethodWithTimerImpl( + aName, std::forward(aPtr), aMethod)); +} + +// Similar to NewRunnableMethod. Call like so: +// nsCOMPtr event = +// NewRunnableMethod(myObject, &MyClass::HandleEvent, myArg1,...); +// 'Types' are the stored type for each argument, see ParameterStorage for +// details. +template +already_AddRefed> +NewRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + " size should be equal to number of arguments"); + return do_AddRef( + new detail::OwningRunnableMethodImpl( + aName, std::forward(aPtr), aMethod, + std::forward(aArgs)...)); +} + +template +already_AddRefed> +NewNonOwningRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + " size should be equal to number of arguments"); + return do_AddRef( + new detail::NonOwningRunnableMethodImpl( + aName, std::forward(aPtr), aMethod, + std::forward(aArgs)...)); +} + +template +already_AddRefed> +NewCancelableRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + " size should be equal to number of arguments"); + return do_AddRef( + new detail::CancelableRunnableMethodImpl( + aName, std::forward(aPtr), aMethod, + std::forward(aArgs)...)); +} + +template +already_AddRefed> +NewNonOwningCancelableRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod, Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + " size should be equal to number of arguments"); + return do_AddRef( + new detail::NonOwningCancelableRunnableMethodImpl( + aName, std::forward(aPtr), aMethod, + std::forward(aArgs)...)); +} + +template +already_AddRefed> +NewIdleRunnableMethod(const char* aName, PtrType&& aPtr, Method aMethod, + Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + " size should be equal to number of arguments"); + return do_AddRef( + new detail::IdleRunnableMethodImpl( + aName, std::forward(aPtr), aMethod, + std::forward(aArgs)...)); +} + +template +already_AddRefed> +NewNonOwningIdleRunnableMethod(const char* aName, PtrType&& aPtr, + Method aMethod, Args&&... aArgs) { + static_assert(sizeof...(Storages) == sizeof...(Args), + " size should be equal to number of arguments"); + return do_AddRef( + new detail::NonOwningIdleRunnableMethodImpl( + aName, std::forward(aPtr), aMethod, + std::forward(aArgs)...)); +} + +} // namespace mozilla + +#endif // XPCOM_GLUE_AVOID_NSPR + +// This class is designed to be used when you have an event class E that has a +// pointer back to resource class R. If R goes away while E is still pending, +// then it is important to "revoke" E so that it does not try use R after R has +// been destroyed. nsRevocableEventPtr makes it easy for R to manage such +// situations: +// +// class R; +// +// class E : public mozilla::Runnable { +// public: +// void Revoke() { +// mResource = nullptr; +// } +// private: +// R *mResource; +// }; +// +// class R { +// public: +// void EventHandled() { +// mEvent.Forget(); +// } +// private: +// nsRevocableEventPtr mEvent; +// }; +// +// void R::PostEvent() { +// // Make sure any pending event is revoked. +// mEvent->Revoke(); +// +// nsCOMPtr event = new E(); +// if (NS_SUCCEEDED(NS_DispatchToCurrentThread(event))) { +// // Keep pointer to event so we can revoke it. +// mEvent = event; +// } +// } +// +// NS_IMETHODIMP E::Run() { +// if (!mResource) +// return NS_OK; +// ... +// mResource->EventHandled(); +// return NS_OK; +// } +// +template +class nsRevocableEventPtr { + public: + nsRevocableEventPtr() : mEvent(nullptr) {} + ~nsRevocableEventPtr() { Revoke(); } + + const nsRevocableEventPtr& operator=(RefPtr&& aEvent) { + if (mEvent != aEvent) { + Revoke(); + mEvent = std::move(aEvent); + } + return *this; + } + + void Revoke() { + if (mEvent) { + mEvent->Revoke(); + mEvent = nullptr; + } + } + + void Forget() { mEvent = nullptr; } + bool IsPending() { return mEvent != nullptr; } + T* get() { return mEvent; } + + private: + // Not implemented + nsRevocableEventPtr(const nsRevocableEventPtr&); + nsRevocableEventPtr& operator=(const nsRevocableEventPtr&); + + RefPtr mEvent; +}; + +template +inline already_AddRefed do_AddRef(nsRevocableEventPtr& aObj) { + return do_AddRef(aObj.get()); +} + +/** + * A simple helper to suffix thread pool name + * with incremental numbers. + */ +class nsThreadPoolNaming { + public: + nsThreadPoolNaming() = default; + + /** + * Returns a thread name as " #" and increments the counter. + */ + nsCString GetNextThreadName(const nsACString& aPoolName); + + template + nsCString GetNextThreadName(const char (&aPoolName)[LEN]) { + return GetNextThreadName(nsDependentCString(aPoolName, LEN - 1)); + } + + private: + mozilla::Atomic mCounter{0}; + + nsThreadPoolNaming(const nsThreadPoolNaming&) = delete; + void operator=(const nsThreadPoolNaming&) = delete; +}; + +/** + * Thread priority in most operating systems affect scheduling, not IO. This + * helper is used to set the current thread to low IO priority for the lifetime + * of the created object. You can only use this low priority IO setting within + * the context of the current thread. + */ +class MOZ_STACK_CLASS nsAutoLowPriorityIO { + public: + nsAutoLowPriorityIO(); + ~nsAutoLowPriorityIO(); + + private: + bool lowIOPrioritySet; +#if defined(XP_MACOSX) + int oldPriority; +#endif +}; + +void NS_SetMainThread(); + +// Used only on cooperatively scheduled "main" threads. Causes the thread to be +// considered a main thread and also causes GetCurrentVirtualThread to return +// aVirtualThread. +void NS_SetMainThread(PRThread* aVirtualThread); + +// Used only on cooperatively scheduled "main" threads. Causes the thread to no +// longer be considered a main thread. Also causes GetCurrentVirtualThread() to +// return a unique value. +void NS_UnsetMainThread(); + +/** + * Return the expiration time of the next timer to run on the current + * thread. If that expiration time is greater than aDefault, then + * return aDefault. aSearchBound specifies a maximum number of timers + * to examine to find a timer on the current thread. If no timer that + * will run on the current thread is found after examining + * aSearchBound timers, return the highest seen expiration time as a + * best effort guess. + * + * Timers with either the type nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY or + * nsITIMER::TYPE_REPEATING_SLACK_LOW_PRIORITY will be skipped when + * searching for the next expiration time. This enables timers to + * have lower priority than callbacks dispatched from + * nsIThread::IdleDispatch. + */ +extern mozilla::TimeStamp NS_GetTimerDeadlineHintOnCurrentThread( + mozilla::TimeStamp aDefault, uint32_t aSearchBound); + +/** + * Dispatches the given event to a background thread. The primary benefit of + * this API is that you do not have to manage the lifetime of your own thread + * for running your own events; the thread manager will take care of the + * background thread's lifetime. Not having to manage your own thread also + * means less resource usage, as the underlying implementation here can manage + * spinning up and shutting down threads appropriately. + * + * NOTE: there is no guarantee that events dispatched via these APIs are run + * serially, in dispatch order; several dispatched events may run in parallel. + * If you depend on serial execution of dispatched events, you should use + * NS_CreateBackgroundTaskQueue instead, and dispatch events to the returned + * event target. + */ +extern nsresult NS_DispatchBackgroundTask( + already_AddRefed aEvent, + uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); +extern "C" nsresult NS_DispatchBackgroundTask( + nsIRunnable* aEvent, uint32_t aDispatchFlags = NS_DISPATCH_NORMAL); + +/** + * Obtain a new serial event target that dispatches runnables to a background + * thread. In many cases, this is a straight replacement for creating your + * own, private thread, and is generally preferred to creating your own, + * private thread. + */ +extern "C" nsresult NS_CreateBackgroundTaskQueue( + const char* aName, nsISerialEventTarget** aTarget); + +/** + * Dispatch the given runnable to the given event target, spinning the current + * thread's event loop until the runnable has finished executing. + * + * This is roughly equivalent to the previously-supported `NS_DISPATCH_SYNC` + * flag. + */ +extern nsresult NS_DispatchAndSpinEventLoopUntilComplete( + const nsACString& aVeryGoodReasonToDoThis, nsIEventTarget* aEventTarget, + already_AddRefed aEvent); + +// Predeclaration for logging function below +namespace IPC { +class Message; +class MessageReader; +class MessageWriter; +} // namespace IPC + +class nsTimerImpl; + +namespace mozilla { + +// RAII class that will set the TLS entry to return the currently running +// nsISerialEventTarget. +// It should be used from inner event loop implementation. +class SerialEventTargetGuard { + public: + explicit SerialEventTargetGuard(nsISerialEventTarget* aThread) + : mLastCurrentThread(sCurrentThreadTLS.get()) { + Set(aThread); + } + + ~SerialEventTargetGuard() { sCurrentThreadTLS.set(mLastCurrentThread); } + + static void InitTLS(); + static nsISerialEventTarget* GetCurrentSerialEventTarget() { + return sCurrentThreadTLS.get(); + } + + protected: + friend class ::MessageLoop; + static void Set(nsISerialEventTarget* aThread) { + MOZ_ASSERT(aThread->IsOnCurrentThread()); + sCurrentThreadTLS.set(aThread); + } + + private: + static MOZ_THREAD_LOCAL(nsISerialEventTarget*) sCurrentThreadTLS; + nsISerialEventTarget* mLastCurrentThread; +}; + +// Get the serial event target corresponding to the currently executing task +// queue or thread. This method will assert if called on a thread pool without +// an active task queue. +// +// This function should generally be preferred over NS_GetCurrentThread since it +// will return a more useful answer when called from a task queue running on a +// thread pool or on a non-xpcom thread which accepts runnable dispatches. +// +// NOTE: The returned nsISerialEventTarget may not accept runnable dispatches +// (e.g. if it corresponds to a non-xpcom thread), however it may still be used +// to check if you're on the given thread/queue using IsOnCurrentThread(). + +nsISerialEventTarget* GetCurrentSerialEventTarget(); + +// Get a weak reference to a serial event target which can be used to dispatch +// runnables to the main thread. +// +// NOTE: While this is currently a weak pointer to the nsIThread* returned from +// NS_GetMainThread(), this may change in the future. + +nsISerialEventTarget* GetMainThreadSerialEventTarget(); + +// Returns the number of CPUs, like PR_GetNumberOfProcessors, except +// that it can return a cached value on platforms where sandboxing +// would prevent reading the current value (currently Linux). CPU +// hotplugging is uncommon, so this is unlikely to make a difference +// in practice. +size_t GetNumberOfProcessors(); + +/** + * A helper class to log tasks dispatch and run with "MOZ_LOG=events:1". The + * output is more machine readable and creates a link between dispatch and run. + * + * Usage example for the concrete template type nsIRunnable. + * To log a dispatch, which means putting an event to a queue: + * LogRunnable::LogDispatch(event); + * theQueue.putEvent(event); + * + * To log execution (running) of the event: + * nsCOMPtr event = theQueue.popEvent(); + * { + * LogRunnable::Run log(event); + * event->Run(); + * event = null; // to include the destructor code in the span + * } + * + * The class is a template so that we can support various specific super-types + * of tasks in the future. We can't use void* because it may cast differently + * and tracking the pointer in logs would then be impossible. + */ +template +class LogTaskBase { + public: + LogTaskBase() = delete; + + // Adds a simple log about dispatch of this runnable. + static void LogDispatch(T* aEvent); + // The `aContext` pointer adds another uniqe identifier, nothing more + static void LogDispatch(T* aEvent, void* aContext); + + // Logs dispatch of the message and along that also the PID of the target + // proccess, purposed for uniquely identifying IPC messages. + static void LogDispatchWithPid(T* aEvent, int32_t aPid); + + // This is designed to surround a call to `Run()` or any code representing + // execution of the task body. + // The constructor adds a simple log about start of the runnable execution and + // the destructor adds a log about ending the execution. + class MOZ_RAII Run { + public: + Run() = delete; + explicit Run(T* aEvent, bool aWillRunAgain = false); + explicit Run(T* aEvent, void* aContext, bool aWillRunAgain = false); + ~Run(); + + // When this is called, the log in this RAII dtor will only say + // "interrupted" expecting that the event will run again. + void WillRunAgain() { mWillRunAgain = true; } + + private: + bool mWillRunAgain = false; + }; +}; + +class MicroTaskRunnable; +class Task; // TaskController +class PresShell; +namespace dom { +class FrameRequestCallback; +} // namespace dom + +// Specialized methods must be explicitly predeclared. +template <> +LogTaskBase::Run::Run(nsIRunnable* aEvent, bool aWillRunAgain); +template <> +LogTaskBase::Run::Run(Task* aTask, bool aWillRunAgain); +template <> +void LogTaskBase::LogDispatchWithPid(IPC::Message* aEvent, + int32_t aPid); +template <> +LogTaskBase::Run::Run(IPC::Message* aMessage, bool aWillRunAgain); +template <> +LogTaskBase::Run::Run(nsTimerImpl* aEvent, bool aWillRunAgain); + +typedef LogTaskBase LogRunnable; +typedef LogTaskBase LogMicroTaskRunnable; +typedef LogTaskBase LogIPCMessage; +typedef LogTaskBase LogTimerEvent; +typedef LogTaskBase LogTask; +typedef LogTaskBase LogPresShellObserver; +typedef LogTaskBase LogFrameRequestCallback; +// If you add new types don't forget to add: +// `template class LogTaskBase;` to nsThreadUtils.cpp + +} // namespace mozilla + +#endif // nsThreadUtils_h__ diff --git a/xpcom/threads/nsTimerImpl.cpp b/xpcom/threads/nsTimerImpl.cpp new file mode 100644 index 0000000000..d143eebe86 --- /dev/null +++ b/xpcom/threads/nsTimerImpl.cpp @@ -0,0 +1,820 @@ +/* -*- 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 "nsTimerImpl.h" + +#include + +#include "TimerThread.h" +#include "mozilla/Atomics.h" +#include "mozilla/IntegerPrintfMacros.h" +#include "mozilla/Logging.h" +#include "mozilla/Mutex.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/Sprintf.h" +#include "mozilla/StaticMutex.h" +#include "nsThreadManager.h" +#include "nsThreadUtils.h" +#include "pratom.h" + +#ifdef XP_WIN +# include +# ifndef getpid +# define getpid _getpid +# endif +#else +# include +#endif + +using mozilla::Atomic; +using mozilla::LogLevel; +using mozilla::MakeRefPtr; +using mozilla::MutexAutoLock; +using mozilla::TimeDuration; +using mozilla::TimeStamp; + +// Holds the timer thread and manages all interactions with it +// under a locked mutex. This wrapper is not destroyed until after +// nsThreadManager shutdown to ensure we don't UAF during an offthread access to +// the timer thread. +class TimerThreadWrapper { + public: + constexpr TimerThreadWrapper() : mThread(nullptr){}; + ~TimerThreadWrapper() = default; + + nsresult Init(); + void Shutdown(); + + nsresult AddTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(aTimer->mMutex); + nsresult RemoveTimer(nsTimerImpl* aTimer, const MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(aTimer->mMutex); + TimeStamp FindNextFireTimeForCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound); + uint32_t AllowedEarlyFiringMicroseconds(); + nsresult GetTimers(nsTArray>& aRetVal); + + private: + static mozilla::StaticMutex sMutex; + TimerThread* mThread MOZ_GUARDED_BY(sMutex); +}; + +mozilla::StaticMutex TimerThreadWrapper::sMutex; + +nsresult TimerThreadWrapper::Init() { + mozilla::StaticMutexAutoLock lock(sMutex); + mThread = new TimerThread(); + + NS_ADDREF(mThread); + + return NS_OK; +} + +void TimerThreadWrapper::Shutdown() { + RefPtr thread; + + { + mozilla::StaticMutexAutoLock lock(sMutex); + if (!mThread) { + return; + } + thread = mThread; + } + // Shutdown calls |nsTimerImpl::Cancel| which needs to make a call into + // |RemoveTimer|. This can't be done under the lock. + thread->Shutdown(); + + { + mozilla::StaticMutexAutoLock lock(sMutex); + NS_RELEASE(mThread); + } +} + +nsresult TimerThreadWrapper::AddTimer(nsTimerImpl* aTimer, + const MutexAutoLock& aProofOfLock) { + mozilla::StaticMutexAutoLock lock(sMutex); + if (mThread) { + return mThread->AddTimer(aTimer, aProofOfLock); + } + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult TimerThreadWrapper::RemoveTimer(nsTimerImpl* aTimer, + const MutexAutoLock& aProofOfLock) { + mozilla::StaticMutexAutoLock lock(sMutex); + if (mThread) { + return mThread->RemoveTimer(aTimer, aProofOfLock); + } + return NS_ERROR_NOT_AVAILABLE; +} + +TimeStamp TimerThreadWrapper::FindNextFireTimeForCurrentThread( + TimeStamp aDefault, uint32_t aSearchBound) { + mozilla::StaticMutexAutoLock lock(sMutex); + return mThread + ? mThread->FindNextFireTimeForCurrentThread(aDefault, aSearchBound) + : TimeStamp(); +} + +uint32_t TimerThreadWrapper::AllowedEarlyFiringMicroseconds() { + mozilla::StaticMutexAutoLock lock(sMutex); + return mThread ? mThread->AllowedEarlyFiringMicroseconds() : 0; +} + +nsresult TimerThreadWrapper::GetTimers(nsTArray>& aRetVal) { + RefPtr thread; + { + mozilla::StaticMutexAutoLock lock(sMutex); + if (!mThread) { + return NS_ERROR_NOT_AVAILABLE; + } + thread = mThread; + } + return thread->GetTimers(aRetVal); +} + +static TimerThreadWrapper gThreadWrapper; + +// This module prints info about the precision of timers. +static mozilla::LazyLogModule sTimerLog("nsTimerImpl"); + +mozilla::LogModule* GetTimerLog() { return sTimerLog; } + +TimeStamp NS_GetTimerDeadlineHintOnCurrentThread(TimeStamp aDefault, + uint32_t aSearchBound) { + return gThreadWrapper.FindNextFireTimeForCurrentThread(aDefault, + aSearchBound); +} + +already_AddRefed NS_NewTimer() { return NS_NewTimer(nullptr); } + +already_AddRefed NS_NewTimer(nsIEventTarget* aTarget) { + return nsTimer::WithEventTarget(aTarget).forget(); +} + +mozilla::Result, nsresult> NS_NewTimerWithObserver( + nsIObserver* aObserver, uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + nsCOMPtr timer; + MOZ_TRY(NS_NewTimerWithObserver(getter_AddRefs(timer), aObserver, aDelay, + aType, aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithObserver(nsITimer** aTimer, nsIObserver* aObserver, + uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->Init(aObserver, aDelay, aType)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result, nsresult> NS_NewTimerWithCallback( + nsITimerCallback* aCallback, uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + nsCOMPtr timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay, + aType, aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback, + uint32_t aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitWithCallback(aCallback, aDelay, aType)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result, nsresult> NS_NewTimerWithCallback( + nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + nsCOMPtr timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), aCallback, aDelay, + aType, aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, nsITimerCallback* aCallback, + const TimeDuration& aDelay, uint32_t aType, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitHighResolutionWithCallback(aCallback, aDelay, aType)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result, nsresult> NS_NewTimerWithCallback( + std::function&& aCallback, uint32_t aDelay, uint32_t aType, + const char* aNameString, nsIEventTarget* aTarget) { + nsCOMPtr timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), std::move(aCallback), + aDelay, aType, aNameString, aTarget)); + return timer; +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, + std::function&& aCallback, + uint32_t aDelay, uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget) { + return NS_NewTimerWithCallback(aTimer, std::move(aCallback), + TimeDuration::FromMilliseconds(aDelay), aType, + aNameString, aTarget); +} + +mozilla::Result, nsresult> NS_NewTimerWithCallback( + std::function&& aCallback, const TimeDuration& aDelay, + uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) { + nsCOMPtr timer; + MOZ_TRY(NS_NewTimerWithCallback(getter_AddRefs(timer), std::move(aCallback), + aDelay, aType, aNameString, aTarget)); + return timer; +} +nsresult NS_NewTimerWithCallback(nsITimer** aTimer, + std::function&& aCallback, + const TimeDuration& aDelay, uint32_t aType, + const char* aNameString, + nsIEventTarget* aTarget) { + RefPtr timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitWithClosureCallback(std::move(aCallback), aDelay, aType, + aNameString)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result, nsresult> NS_NewTimerWithFuncCallback( + nsTimerCallbackFunc aCallback, void* aClosure, uint32_t aDelay, + uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) { + nsCOMPtr timer; + MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback, + aClosure, aDelay, aType, aNameString, + aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, uint32_t aDelay, + uint32_t aType, const char* aNameString, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitWithNamedFuncCallback(aCallback, aClosure, aDelay, aType, + aNameString)); + timer.forget(aTimer); + return NS_OK; +} + +mozilla::Result, nsresult> NS_NewTimerWithFuncCallback( + nsTimerCallbackFunc aCallback, void* aClosure, const TimeDuration& aDelay, + uint32_t aType, const char* aNameString, nsIEventTarget* aTarget) { + nsCOMPtr timer; + MOZ_TRY(NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback, + aClosure, aDelay, aType, aNameString, + aTarget)); + return std::move(timer); +} +nsresult NS_NewTimerWithFuncCallback(nsITimer** aTimer, + nsTimerCallbackFunc aCallback, + void* aClosure, const TimeDuration& aDelay, + uint32_t aType, const char* aNameString, + nsIEventTarget* aTarget) { + auto timer = nsTimer::WithEventTarget(aTarget); + + MOZ_TRY(timer->InitHighResolutionWithNamedFuncCallback( + aCallback, aClosure, aDelay, aType, aNameString)); + timer.forget(aTimer); + return NS_OK; +} + +// This module prints info about which timers are firing, which is useful for +// wakeups for the purposes of power profiling. Set the following environment +// variable before starting the browser. +// +// MOZ_LOG=TimerFirings:4 +// +// Then a line will be printed for every timer that fires. +// +// If you redirect this output to a file called "out", you can then +// post-process it with a command something like the following. +// +// cat out | grep timer | sort | uniq -c | sort -r -n +// +// This will show how often each unique line appears, with the most common ones +// first. +// +// More detailed docs are here: +// https://developer.mozilla.org/en-US/docs/Mozilla/Performance/TimerFirings_logging +// +static mozilla::LazyLogModule sTimerFiringsLog("TimerFirings"); + +static mozilla::LogModule* GetTimerFiringsLog() { return sTimerFiringsLog; } + +#include + +/* static */ +mozilla::StaticMutex nsTimerImpl::sDeltaMutex; +/* static */ +double nsTimerImpl::sDeltaSumSquared MOZ_GUARDED_BY(nsTimerImpl::sDeltaMutex) = + 0; +/* static */ +double nsTimerImpl::sDeltaSum MOZ_GUARDED_BY(nsTimerImpl::sDeltaMutex) = 0; +/* static */ +double nsTimerImpl::sDeltaNum MOZ_GUARDED_BY(nsTimerImpl::sDeltaMutex) = 0; + +static void myNS_MeanAndStdDev(double n, double sumOfValues, + double sumOfSquaredValues, double* meanResult, + double* stdDevResult) { + double mean = 0.0, var = 0.0, stdDev = 0.0; + if (n > 0.0 && sumOfValues >= 0) { + mean = sumOfValues / n; + double temp = (n * sumOfSquaredValues) - (sumOfValues * sumOfValues); + if (temp < 0.0 || n <= 1) { + var = 0.0; + } else { + var = temp / (n * (n - 1)); + } + // for some reason, Windows says sqrt(0.0) is "-1.#J" (?!) so do this: + stdDev = var != 0.0 ? sqrt(var) : 0.0; + } + *meanResult = mean; + *stdDevResult = stdDev; +} + +NS_IMPL_QUERY_INTERFACE(nsTimer, nsITimer) +NS_IMPL_ADDREF(nsTimer) + +NS_IMPL_ISUPPORTS(nsTimerManager, nsITimerManager) + +NS_IMETHODIMP nsTimerManager::GetTimers(nsTArray>& aRetVal) { + return gThreadWrapper.GetTimers(aRetVal); +} + +NS_IMETHODIMP_(MozExternalRefCountType) +nsTimer::Release(void) { + nsrefcnt count = --mRefCnt; + NS_LOG_RELEASE(this, count, "nsTimer"); + + if (count == 1) { + // Last ref, in nsTimerImpl::mITimer. Make sure the cycle is broken. + mImpl->CancelImpl(true); + } else if (count == 0) { + delete this; + } + + return count; +} + +nsTimerImpl::nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget) + : mEventTarget(aTarget), + mIsInTimerThread(false), + mType(0), + mGeneration(0), + mITimer(aTimer), + mMutex("nsTimerImpl::mMutex"), + mCallback(UnknownCallback{}), + mFiring(0) { + // XXX some code creates timers during xpcom shutdown, when threads are no + // longer available, so we cannot turn this on yet. + // MOZ_ASSERT(mEventTarget); +} + +// static +nsresult nsTimerImpl::Startup() { return gThreadWrapper.Init(); } + +void nsTimerImpl::Shutdown() { + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + mozilla::StaticMutexAutoLock lock(sDeltaMutex); + double mean = 0, stddev = 0; + myNS_MeanAndStdDev(sDeltaNum, sDeltaSum, sDeltaSumSquared, &mean, &stddev); + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("sDeltaNum = %f, sDeltaSum = %f, sDeltaSumSquared = %f\n", + sDeltaNum, sDeltaSum, sDeltaSumSquared)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("mean: %fms, stddev: %fms\n", mean, stddev)); + } + + gThreadWrapper.Shutdown(); +} + +nsresult nsTimerImpl::InitCommon(const TimeDuration& aDelay, uint32_t aType, + Callback&& newCallback, + const MutexAutoLock& aProofOfLock) { + if (!mEventTarget) { + return NS_ERROR_NOT_INITIALIZED; + } + + gThreadWrapper.RemoveTimer(this, aProofOfLock); + + // If we have an existing callback, using `swap` ensures it's destroyed after + // the mutex is unlocked in our caller. + std::swap(mCallback, newCallback); + ++mGeneration; + + mType = (uint8_t)aType; + mDelay = aDelay; + mTimeout = TimeStamp::Now() + mDelay; + + return gThreadWrapper.AddTimer(this, aProofOfLock); +} + +nsresult nsTimerImpl::InitWithNamedFuncCallback(nsTimerCallbackFunc aFunc, + void* aClosure, uint32_t aDelay, + uint32_t aType, + const char* aName) { + return InitHighResolutionWithNamedFuncCallback( + aFunc, aClosure, TimeDuration::FromMilliseconds(aDelay), aType, aName); +} + +nsresult nsTimerImpl::InitHighResolutionWithNamedFuncCallback( + nsTimerCallbackFunc aFunc, void* aClosure, const TimeDuration& aDelay, + uint32_t aType, const char* aName) { + if (NS_WARN_IF(!aFunc)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb{FuncCallback{aFunc, aClosure, aName}}; + + MutexAutoLock lock(mMutex); + return InitCommon(aDelay, aType, std::move(cb), lock); +} + +nsresult nsTimerImpl::InitWithCallback(nsITimerCallback* aCallback, + uint32_t aDelayInMs, uint32_t aType) { + return InitHighResolutionWithCallback( + aCallback, TimeDuration::FromMilliseconds(aDelayInMs), aType); +} + +nsresult nsTimerImpl::InitHighResolutionWithCallback( + nsITimerCallback* aCallback, const TimeDuration& aDelay, uint32_t aType) { + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + + // Goes out of scope after the unlock, prevents deadlock + Callback cb{nsCOMPtr{aCallback}}; + + MutexAutoLock lock(mMutex); + return InitCommon(aDelay, aType, std::move(cb), lock); +} + +nsresult nsTimerImpl::Init(nsIObserver* aObserver, uint32_t aDelayInMs, + uint32_t aType) { + if (NS_WARN_IF(!aObserver)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb{nsCOMPtr{aObserver}}; + + MutexAutoLock lock(mMutex); + return InitCommon(TimeDuration::FromMilliseconds(aDelayInMs), aType, + std::move(cb), lock); +} + +nsresult nsTimerImpl::InitWithClosureCallback( + std::function&& aCallback, const TimeDuration& aDelay, + uint32_t aType, const char* aNameString) { + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + + Callback cb{ClosureCallback{std::move(aCallback), aNameString}}; + + MutexAutoLock lock(mMutex); + return InitCommon(aDelay, aType, std::move(cb), lock); +} + +nsresult nsTimerImpl::Cancel() { + CancelImpl(false); + return NS_OK; +} + +void nsTimerImpl::CancelImpl(bool aClearITimer) { + Callback cbTrash{UnknownCallback{}}; + RefPtr timerTrash; + + { + MutexAutoLock lock(mMutex); + gThreadWrapper.RemoveTimer(this, lock); + + // The swap ensures our callback isn't dropped until after the mutex is + // unlocked. + std::swap(cbTrash, mCallback); + ++mGeneration; + + // Don't clear this if we're firing; once Fire returns, we'll get this call + // again. + if (aClearITimer && !mFiring) { + MOZ_RELEASE_ASSERT( + mITimer, + "mITimer was nulled already! " + "This indicates that someone has messed up the refcount on nsTimer!"); + timerTrash.swap(mITimer); + } + } +} + +nsresult nsTimerImpl::SetDelay(uint32_t aDelay) { + MutexAutoLock lock(mMutex); + if (GetCallback().is() && !IsRepeating()) { + // This may happen if someone tries to re-use a one-shot timer + // by re-setting delay instead of reinitializing the timer. + NS_ERROR( + "nsITimer->SetDelay() called when the " + "one-shot timer is not set up."); + return NS_ERROR_NOT_INITIALIZED; + } + + bool reAdd = false; + reAdd = NS_SUCCEEDED(gThreadWrapper.RemoveTimer(this, lock)); + + mDelay = TimeDuration::FromMilliseconds(aDelay); + mTimeout = TimeStamp::Now() + mDelay; + + if (reAdd) { + gThreadWrapper.AddTimer(this, lock); + } + + return NS_OK; +} + +nsresult nsTimerImpl::GetDelay(uint32_t* aDelay) { + MutexAutoLock lock(mMutex); + *aDelay = mDelay.ToMilliseconds(); + return NS_OK; +} + +nsresult nsTimerImpl::SetType(uint32_t aType) { + MutexAutoLock lock(mMutex); + mType = (uint8_t)aType; + // XXX if this is called, we should change the actual type.. this could effect + // repeating timers. we need to ensure in Fire() that if mType has changed + // during the callback that we don't end up with the timer in the queue twice. + return NS_OK; +} + +nsresult nsTimerImpl::GetType(uint32_t* aType) { + MutexAutoLock lock(mMutex); + *aType = mType; + return NS_OK; +} + +nsresult nsTimerImpl::GetClosure(void** aClosure) { + MutexAutoLock lock(mMutex); + if (GetCallback().is()) { + *aClosure = GetCallback().as().mClosure; + } else { + *aClosure = nullptr; + } + return NS_OK; +} + +nsresult nsTimerImpl::GetCallback(nsITimerCallback** aCallback) { + MutexAutoLock lock(mMutex); + if (GetCallback().is()) { + NS_IF_ADDREF(*aCallback = GetCallback().as()); + } else { + *aCallback = nullptr; + } + return NS_OK; +} + +nsresult nsTimerImpl::GetTarget(nsIEventTarget** aTarget) { + MutexAutoLock lock(mMutex); + NS_IF_ADDREF(*aTarget = mEventTarget); + return NS_OK; +} + +nsresult nsTimerImpl::SetTarget(nsIEventTarget* aTarget) { + MutexAutoLock lock(mMutex); + if (NS_WARN_IF(!mCallback.is())) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + if (aTarget) { + mEventTarget = aTarget; + } else { + mEventTarget = mozilla::GetCurrentSerialEventTarget(); + } + return NS_OK; +} + +nsresult nsTimerImpl::GetAllowedEarlyFiringMicroseconds(uint32_t* aValueOut) { + *aValueOut = gThreadWrapper.AllowedEarlyFiringMicroseconds(); + return NS_OK; +} + +void nsTimerImpl::Fire(int32_t aGeneration) { + uint8_t oldType; + uint32_t oldDelay; + TimeStamp oldTimeout; + Callback callbackDuringFire{UnknownCallback{}}; + nsCOMPtr timer; + + { + // Don't fire callbacks or fiddle with refcounts when the mutex is locked. + // If some other thread Cancels/Inits after this, they're just too late. + MutexAutoLock lock(mMutex); + if (aGeneration != mGeneration) { + // This timer got rescheduled or cancelled before we fired, so ignore this + // firing + return; + } + + // We modify mTimeout, so we must not be in the current TimerThread's + // mTimers list. + MOZ_ASSERT(!mIsInTimerThread); + + ++mFiring; + callbackDuringFire = mCallback; + oldType = mType; + oldDelay = mDelay.ToMilliseconds(); + oldTimeout = mTimeout; + // Ensure that the nsITimer does not unhook from the nsTimerImpl during + // Fire; this will cause null pointer crashes if the user of the timer drops + // its reference, and then uses the nsITimer* passed in the callback. + timer = mITimer; + } + + AUTO_PROFILER_LABEL("nsTimerImpl::Fire", OTHER); + + TimeStamp fireTime = TimeStamp::Now(); + if (MOZ_LOG_TEST(GetTimerLog(), LogLevel::Debug)) { + TimeDuration delta = fireTime - oldTimeout; + int32_t d = delta.ToMilliseconds(); // delta in ms + { + mozilla::StaticMutexAutoLock lock(sDeltaMutex); + sDeltaSum += abs(d); + sDeltaSumSquared += double(d) * double(d); + sDeltaNum++; + } + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] expected delay time %4ums\n", this, oldDelay)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] actual delay time %4dms\n", this, oldDelay + d)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] (mType is %d) -------\n", this, oldType)); + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] delta %4dms\n", this, d)); + } + + if (MOZ_LOG_TEST(GetTimerFiringsLog(), LogLevel::Debug)) { + LogFiring(callbackDuringFire, oldType, oldDelay); + } + + callbackDuringFire.match( + [](const UnknownCallback&) {}, + [&](const InterfaceCallback& i) { i->Notify(timer); }, + [&](const ObserverCallback& o) { + o->Observe(timer, NS_TIMER_CALLBACK_TOPIC, nullptr); + }, + [&](const FuncCallback& f) { f.mFunc(timer, f.mClosure); }, + [&](const ClosureCallback& c) { c.mFunc(timer); }); + + TimeStamp now = TimeStamp::Now(); + + MutexAutoLock lock(mMutex); + if (aGeneration == mGeneration) { + if (IsRepeating()) { + // Repeating timer has not been re-init or canceled; reschedule + if (IsSlack()) { + mTimeout = now + mDelay; + } else { + if (mDelay) { + // If we are late enough finishing the callback that we have missed + // some firings, do not attempt to play catchup, just get back on the + // cadence we're supposed to maintain. + unsigned missedFirings = + static_cast((now - mTimeout) / mDelay); + mTimeout += mDelay * (missedFirings + 1); + } else { + // Can we stop allowing repeating timers with delay 0? + mTimeout = now; + } + } + gThreadWrapper.AddTimer(this, lock); + } else { + // Non-repeating timer that has not been re-scheduled. Clear. + // XXX(nika): Other callsites seem to go to some effort to avoid + // destroying mCallback when it's held. Why not this one? + mCallback = mozilla::AsVariant(UnknownCallback{}); + } + } + + --mFiring; + + MOZ_LOG(GetTimerLog(), LogLevel::Debug, + ("[this=%p] Took %fms to fire timer callback\n", this, + (now - fireTime).ToMilliseconds())); +} + +// See the big comment above GetTimerFiringsLog() to understand this code. +void nsTimerImpl::LogFiring(const Callback& aCallback, uint8_t aType, + uint32_t aDelay) { + const char* typeStr; + switch (aType) { + case nsITimer::TYPE_ONE_SHOT: + typeStr = "ONE_SHOT "; + break; + case nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY: + typeStr = "ONE_LOW "; + break; + case nsITimer::TYPE_REPEATING_SLACK: + typeStr = "SLACK "; + break; + case nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY: + typeStr = "SLACK_LOW "; + break; + case nsITimer::TYPE_REPEATING_PRECISE: /* fall through */ + case nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP: + typeStr = "PRECISE "; + break; + default: + MOZ_CRASH("bad type"); + } + + aCallback.match( + [&](const UnknownCallback&) { + MOZ_LOG( + GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] ??? timer (%s, %5d ms)\n", getpid(), typeStr, aDelay)); + }, + [&](const InterfaceCallback& i) { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] iface timer (%s %5d ms): %p\n", getpid(), typeStr, + aDelay, i.get())); + }, + [&](const ObserverCallback& o) { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] obs timer (%s %5d ms): %p\n", getpid(), typeStr, + aDelay, o.get())); + }, + [&](const FuncCallback& f) { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] fn timer (%s %5d ms): %s\n", getpid(), typeStr, + aDelay, f.mName)); + }, + [&](const ClosureCallback& c) { + MOZ_LOG(GetTimerFiringsLog(), LogLevel::Debug, + ("[%d] closure timer (%s %5d ms): %s\n", getpid(), typeStr, + aDelay, c.mName)); + }); +} + +void nsTimerImpl::GetName(nsACString& aName, + const MutexAutoLock& aProofOfLock) { + GetCallback().match( + [&](const UnknownCallback&) { aName.AssignLiteral("Canceled_timer"); }, + [&](const InterfaceCallback& i) { + if (nsCOMPtr named = do_QueryInterface(i)) { + named->GetName(aName); + } else { + aName.AssignLiteral("Anonymous_interface_timer"); + } + }, + [&](const ObserverCallback& o) { + if (nsCOMPtr named = do_QueryInterface(o)) { + named->GetName(aName); + } else { + aName.AssignLiteral("Anonymous_observer_timer"); + } + }, + [&](const FuncCallback& f) { aName.Assign(f.mName); }, + [&](const ClosureCallback& c) { aName.Assign(c.mName); }); +} + +nsresult nsTimerImpl::GetName(nsACString& aName) { + MutexAutoLock lock(mMutex); + GetName(aName, lock); + return NS_OK; +} + +nsTimer::~nsTimer() = default; + +size_t nsTimer::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { + return aMallocSizeOf(this); +} + +/* static */ +RefPtr nsTimer::WithEventTarget(nsIEventTarget* aTarget) { + if (!aTarget) { + aTarget = mozilla::GetCurrentSerialEventTarget(); + } + return do_AddRef(new nsTimer(aTarget)); +} + +/* static */ +nsresult nsTimer::XPCOMConstructor(REFNSIID aIID, void** aResult) { + *aResult = nullptr; + auto timer = WithEventTarget(nullptr); + + return timer->QueryInterface(aIID, aResult); +} diff --git a/xpcom/threads/nsTimerImpl.h b/xpcom/threads/nsTimerImpl.h new file mode 100644 index 0000000000..49f1fd00d5 --- /dev/null +++ b/xpcom/threads/nsTimerImpl.h @@ -0,0 +1,231 @@ +/* -*- 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/. */ + +#ifndef nsTimerImpl_h___ +#define nsTimerImpl_h___ + +#include "nsITimer.h" +#include "nsIEventTarget.h" +#include "nsIObserver.h" + +#include "nsCOMPtr.h" + +#include "mozilla/Attributes.h" +#include "mozilla/Mutex.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/Variant.h" +#include "mozilla/Logging.h" + +extern mozilla::LogModule* GetTimerLog(); + +#define NS_TIMER_CID \ + { /* 5ff24248-1dd2-11b2-8427-fbab44f29bc8 */ \ + 0x5ff24248, 0x1dd2, 0x11b2, { \ + 0x84, 0x27, 0xfb, 0xab, 0x44, 0xf2, 0x9b, 0xc8 \ + } \ + } + +class nsIObserver; + +namespace mozilla { +class LogModule; +} + +// TimerThread, nsTimerEvent, and nsTimer have references to these. nsTimer has +// a separate lifecycle so we can Cancel() the underlying timer when the user of +// the nsTimer has let go of its last reference. +class nsTimerImpl { + ~nsTimerImpl() { + MOZ_ASSERT(!mIsInTimerThread); + + // The nsITimer interface requires that its users keep a reference to the + // timers they use while those timers are initialized but have not yet + // fired. If this assert ever fails, it is a bug in the code that created + // and used the timer. + // + // Further, note that this should never fail even with a misbehaving user, + // because nsTimer::Release checks for a refcount of 1 with an armed timer + // (a timer whose only reference is from the timer thread) and when it hits + // this will remove the timer from the timer thread and thus destroy the + // last reference, preventing this situation from occurring. + MOZ_ASSERT( + mCallback.is() || mEventTarget->IsOnCurrentThread(), + "Must not release mCallback off-target without canceling"); + } + + public: + typedef mozilla::TimeStamp TimeStamp; + + nsTimerImpl(nsITimer* aTimer, nsIEventTarget* aTarget); + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsTimerImpl) + NS_DECL_NON_VIRTUAL_NSITIMER + + static nsresult Startup(); + static void Shutdown(); + + void SetDelayInternal(uint32_t aDelay, TimeStamp aBase = TimeStamp::Now()); + void CancelImpl(bool aClearITimer); + + void Fire(int32_t aGeneration); + + int32_t GetGeneration() { return mGeneration; } + + struct UnknownCallback {}; + + using InterfaceCallback = nsCOMPtr; + + using ObserverCallback = nsCOMPtr; + + /// A raw function pointer and its closed-over state, along with its name for + /// logging purposes. + struct FuncCallback { + nsTimerCallbackFunc mFunc; + void* mClosure; + const char* mName; + }; + + /// A callback defined by an owned closure and its name for logging purposes. + struct ClosureCallback { + std::function mFunc; + const char* mName; + }; + + using Callback = + mozilla::Variant; + + nsresult InitCommon(const mozilla::TimeDuration& aDelay, uint32_t aType, + Callback&& newCallback, + const mozilla::MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(mMutex); + + Callback& GetCallback() MOZ_REQUIRES(mMutex) { + mMutex.AssertCurrentThreadOwns(); + return mCallback; + } + + bool IsRepeating() const { + static_assert(nsITimer::TYPE_ONE_SHOT < nsITimer::TYPE_REPEATING_SLACK, + "invalid ordering of timer types!"); + static_assert( + nsITimer::TYPE_REPEATING_SLACK < nsITimer::TYPE_REPEATING_PRECISE, + "invalid ordering of timer types!"); + static_assert(nsITimer::TYPE_REPEATING_PRECISE < + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP, + "invalid ordering of timer types!"); + return mType >= nsITimer::TYPE_REPEATING_SLACK && + mType < nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY; + } + + bool IsLowPriority() const { + return mType == nsITimer::TYPE_ONE_SHOT_LOW_PRIORITY || + mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY; + } + + bool IsSlack() const { + return mType == nsITimer::TYPE_REPEATING_SLACK || + mType == nsITimer::TYPE_REPEATING_SLACK_LOW_PRIORITY; + } + + void GetName(nsACString& aName, const mozilla::MutexAutoLock& aProofOfLock) + MOZ_REQUIRES(mMutex); + + bool IsInTimerThread() const { return mIsInTimerThread; } + void SetIsInTimerThread(bool aIsInTimerThread) { + mIsInTimerThread = aIsInTimerThread; + } + + nsCOMPtr mEventTarget; + + void LogFiring(const Callback& aCallback, uint8_t aType, uint32_t aDelay); + + nsresult InitWithClosureCallback(std::function&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, const char* aNameString); + + // Is this timer currently referenced from a TimerThread::Entry? + // Note: It is cleared before the Entry is destroyed. Take() also sets it to + // false, to indicate it's no longer in the TimerThread's list. This Take() + // call is NOT made under the nsTimerImpl's mutex (all other + // SetIsInTimerThread calls are under the mutex). However, ALL accesses to + // mIsInTimerThread are under the TimerThread's Monitor lock, so consistency + // is guaranteed by that. + bool mIsInTimerThread; + + // These members are set by the initiating thread, when the timer's type is + // changed and during the period where it fires on that thread. + uint8_t mType; + + // The generation number of this timer, re-generated each time the timer is + // initialized so one-shot timers can be canceled and re-initialized by the + // arming thread without any bad race conditions. + // Updated only after this timer has been removed from the timer thread. + int32_t mGeneration; + + mozilla::TimeDuration mDelay MOZ_GUARDED_BY(mMutex); + // Never updated while in the TimerThread's timer list. Only updated + // before adding to that list or during nsTimerImpl::Fire(), when it has + // been removed from the TimerThread's list. TimerThread can access + // mTimeout of any timer in the list safely + mozilla::TimeStamp mTimeout; + + RefPtr mITimer MOZ_GUARDED_BY(mMutex); + mozilla::Mutex mMutex; + Callback mCallback MOZ_GUARDED_BY(mMutex); + // Counter because in rare cases we can Fire reentrantly + unsigned int mFiring MOZ_GUARDED_BY(mMutex); + + static mozilla::StaticMutex sDeltaMutex; + static double sDeltaSum MOZ_GUARDED_BY(sDeltaMutex); + static double sDeltaSumSquared MOZ_GUARDED_BY(sDeltaMutex); + static double sDeltaNum MOZ_GUARDED_BY(sDeltaMutex); +}; + +class nsTimer final : public nsITimer { + explicit nsTimer(nsIEventTarget* aTarget) + : mImpl(new nsTimerImpl(this, aTarget)) {} + + virtual ~nsTimer(); + + public: + friend class TimerThread; + friend class nsTimerEvent; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_FORWARD_SAFE_NSITIMER(mImpl); + + // NOTE: This constructor is not exposed on `nsITimer` as NS_FORWARD_SAFE_ + // does not support forwarding rvalue references. + nsresult InitWithClosureCallback(std::function&& aCallback, + const mozilla::TimeDuration& aDelay, + uint32_t aType, const char* aNameString) { + return mImpl ? mImpl->InitWithClosureCallback(std::move(aCallback), aDelay, + aType, aNameString) + : NS_ERROR_NULL_POINTER; + } + + // Create a timer targeting the given target. nullptr indicates that the + // current thread should be used as the timer's target. + static RefPtr WithEventTarget(nsIEventTarget* aTarget); + + static nsresult XPCOMConstructor(REFNSIID aIID, void** aResult); + + private: + // nsTimerImpl holds a strong ref to us. When our refcount goes to 1, we will + // null this to break the cycle. + RefPtr mImpl; +}; + +class nsTimerManager final : public nsITimerManager { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSITIMERMANAGER + private: + ~nsTimerManager() = default; +}; + +#endif /* nsTimerImpl_h___ */ diff --git a/xpcom/windbgdlg/Makefile.in b/xpcom/windbgdlg/Makefile.in new file mode 100644 index 0000000000..254509ee77 --- /dev/null +++ b/xpcom/windbgdlg/Makefile.in @@ -0,0 +1,6 @@ +# +# 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/. + +MOZ_WINCONSOLE = 0 diff --git a/xpcom/windbgdlg/moz.build b/xpcom/windbgdlg/moz.build new file mode 100644 index 0000000000..7d1798b545 --- /dev/null +++ b/xpcom/windbgdlg/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SimplePrograms(["windbgdlg"]) + +OS_LIBS += [ + "advapi32", + "user32", +] diff --git a/xpcom/windbgdlg/windbgdlg.cpp b/xpcom/windbgdlg/windbgdlg.cpp new file mode 100644 index 0000000000..2bd1139b23 --- /dev/null +++ b/xpcom/windbgdlg/windbgdlg.cpp @@ -0,0 +1,113 @@ +/* -*- 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/. */ + +/* Windows only app to show a modal debug dialog - launched by nsDebug.cpp */ +#include +#include +#ifdef _MSC_VER +# include +#endif +#ifdef __MINGW32__ +/* MingW currently does not implement a wide version of the + startup routines. Workaround is to implement something like + it ourselves. See bug 472063 */ +# include +# include +int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int); + +# undef __argc +# undef __wargv + +static int __argc; +static wchar_t** __wargv; + +int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPSTR lpszCommandLine, int nCmdShow) { + LPWSTR commandLine = GetCommandLineW(); + + /* parse for __argc and __wargv for compatibility, since mingw + * doesn't claim to support it :( + */ + __wargv = CommandLineToArgvW(commandLine, &__argc); + if (!__wargv) return 127; + + /* need to strip off any leading whitespace plus the first argument + * (the executable itself) to match what should be passed to wWinMain + */ + while ((*commandLine <= L' ') && *commandLine) { + ++commandLine; + } + if (*commandLine == L'"') { + ++commandLine; + while ((*commandLine != L'"') && *commandLine) { + ++commandLine; + } + if (*commandLine) { + ++commandLine; + } + } else { + while (*commandLine > L' ') { + ++commandLine; + } + } + while ((*commandLine <= L' ') && *commandLine) { + ++commandLine; + } + + int result = wWinMain(hInstance, hPrevInstance, commandLine, nCmdShow); + LocalFree(__wargv); + return result; +} +#endif /* __MINGW32__ */ + +int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, + LPWSTR lpszCmdLine, int nCmdShow) { + /* support for auto answering based on words in the assertion. + * the assertion message is sent as a series of arguements (words) to the + * commandline. set a "word" to 0xffffffff to let the word not affect this + * code. set a "word" to 0xfffffffe to show the dialog. set a "word" to 0x5 to + * ignore (program should continue). set a "word" to 0x4 to retry (should fall + * into debugger). set a "word" to 0x3 to abort (die). + */ + DWORD regType; + DWORD regValue = -1; + DWORD regLength = sizeof regValue; + HKEY hkeyCU, hkeyLM; + RegOpenKeyExW(HKEY_CURRENT_USER, L"Software\\mozilla.org\\windbgdlg", 0, + KEY_READ, &hkeyCU); + RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\mozilla.org\\windbgdlg", 0, + KEY_READ, &hkeyLM); + for (int i = __argc - 1; regValue == (DWORD)-1 && i; --i) { + bool ok = false; + if (hkeyCU) + ok = RegQueryValueExW(hkeyCU, __wargv[i], 0, ®Type, (LPBYTE)®Value, + ®Length) == ERROR_SUCCESS; + if (!ok && hkeyLM) + ok = RegQueryValueExW(hkeyLM, __wargv[i], 0, ®Type, (LPBYTE)®Value, + ®Length) == ERROR_SUCCESS; + if (!ok) regValue = -1; + } + if (hkeyCU) RegCloseKey(hkeyCU); + if (hkeyLM) RegCloseKey(hkeyLM); + if (regValue != (DWORD)-1 && regValue != (DWORD)-2) return regValue; + static const int size = 4096; + static WCHAR msg[size]; + +#ifdef _MSC_VER + StringCchPrintfW(msg, +#else + snwprintf(msg, +#endif + size, + L"%s\n\nClick Abort to exit the Application.\n" + L"Click Retry to Debug the Application.\n" + L"Click Ignore to continue running the Application.", + lpszCmdLine); + msg[size - 1] = L'\0'; + return MessageBoxW( + nullptr, msg, L"NSGlue_Assertion", + MB_ICONSTOP | MB_SYSTEMMODAL | MB_ABORTRETRYIGNORE | MB_DEFBUTTON3); +} diff --git a/xpcom/xpcom-config.h.in b/xpcom/xpcom-config.h.in new file mode 100644 index 0000000000..9de0b4dac6 --- /dev/null +++ b/xpcom/xpcom-config.h.in @@ -0,0 +1,13 @@ +/* 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/. */ + +/* Global defines needed by xpcom clients */ + +#ifndef _XPCOM_CONFIG_H_ +#define _XPCOM_CONFIG_H_ + +/* Define to a string describing the XPCOM ABI in use */ +#undef TARGET_XPCOM_ABI + +#endif /* _XPCOM_CONFIG_H_ */ diff --git a/xpcom/xpidl/Makefile.in b/xpcom/xpidl/Makefile.in new file mode 100644 index 0000000000..2eec4cad81 --- /dev/null +++ b/xpcom/xpidl/Makefile.in @@ -0,0 +1,6 @@ +# 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/. + +export:: + $(call SUBMAKE,xpidl,$(DEPTH)/config/makefiles/xpidl) diff --git a/xpcom/xpidl/moz.build b/xpcom/xpidl/moz.build new file mode 100644 index 0000000000..568f361a54 --- /dev/null +++ b/xpcom/xpidl/moz.build @@ -0,0 +1,5 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. -- cgit v1.2.3
+ xptcall + is a library that supports both invoking methods on arbitrary xpcom + objects and implementing classes whose objects can impersonate any xpcom + interface. It does this using platform specific assembly language code. + This code needs to be ported to all platforms that want to support xptcall + (and thus mozilla). +